Repository: gleam-lang/gleam Branch: main Commit: 56c224a84420 Files: 4379 Total size: 8.4 MB Directory structure: gitextract_2kwl3uaf/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ ├── actions/ │ │ ├── build-container/ │ │ │ └── action.yml │ │ └── build-release/ │ │ └── action.yml │ ├── dependabot.yml │ ├── pull_request_template.md │ └── workflows/ │ ├── ci.yaml │ ├── release-containers.yaml │ ├── release-nightly.yaml │ └── release.yaml ├── .gitignore ├── .vscode/ │ └── settings.json ├── .well-known/ │ └── funding-manifest-urls ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.toml ├── Cross.toml ├── LICENCE ├── Makefile ├── README.md ├── RELEASE.md ├── benchmark/ │ └── list/ │ ├── .gitignore │ ├── Makefile │ ├── gleam.toml │ ├── manifest.toml │ ├── src/ │ │ └── list.gleam │ └── test/ │ ├── benchmarks.gleam │ └── list_test.gleam ├── bin/ │ └── add-nightly-suffix-to-versions.sh ├── changelog/ │ ├── v1.1.md │ ├── v1.10.md │ ├── v1.11.md │ ├── v1.12.md │ ├── v1.13.md │ ├── v1.14.md │ ├── v1.15.md │ ├── v1.2.md │ ├── v1.3.md │ ├── v1.4.md │ ├── v1.5.md │ ├── v1.6.md │ ├── v1.7.md │ ├── v1.8.md │ └── v1.9.md ├── compiler-cli/ │ ├── Cargo.toml │ ├── clippy.toml │ ├── src/ │ │ ├── add.rs │ │ ├── beam_compiler.rs │ │ ├── build.rs │ │ ├── build_lock.rs │ │ ├── cli.rs │ │ ├── compile_package.rs │ │ ├── config.rs │ │ ├── dependencies/ │ │ │ ├── dependency_manager.rs │ │ │ ├── snapshots/ │ │ │ │ ├── gleam_cli__dependencies__tests__pretty_print_major_versions_available.snap │ │ │ │ └── gleam_cli__dependencies__tests__pretty_print_version_updates.snap │ │ │ └── tests.rs │ │ ├── dependencies.rs │ │ ├── docs.rs │ │ ├── export.rs │ │ ├── fix.rs │ │ ├── format.rs │ │ ├── fs/ │ │ │ └── tests.rs │ │ ├── fs.rs │ │ ├── hex/ │ │ │ └── auth.rs │ │ ├── hex.rs │ │ ├── http.rs │ │ ├── lib.rs │ │ ├── lsp.rs │ │ ├── new/ │ │ │ ├── snapshots/ │ │ │ │ ├── gleam_cli__new__tests__new_with_default_template@src__my_project.gleam.snap │ │ │ │ ├── gleam_cli__new__tests__new_with_default_template@test__my_project_test.gleam.snap │ │ │ │ ├── gleam_cli__new__tests__new_with_javascript_template@src__my_project.gleam.snap │ │ │ │ └── gleam_cli__new__tests__new_with_javascript_template@test__my_project_test.gleam.snap │ │ │ └── tests.rs │ │ ├── new.rs │ │ ├── owner.rs │ │ ├── panic.rs │ │ ├── publish.rs │ │ ├── remove.rs │ │ ├── run.rs │ │ ├── shell.rs │ │ └── text_layout.rs │ ├── templates/ │ │ ├── erlang-shipment-entrypoint.ps1 │ │ ├── erlang-shipment-entrypoint.sh │ │ └── gleam@@compile.erl │ └── test/ │ └── hello_world/ │ ├── .gitignore │ ├── gleam.toml │ └── manifest.toml ├── compiler-core/ │ ├── Cargo.toml │ ├── clippy.toml │ ├── src/ │ │ ├── analyse/ │ │ │ ├── imports.rs │ │ │ ├── name.rs │ │ │ └── tests.rs │ │ ├── analyse.rs │ │ ├── ast/ │ │ │ ├── constant.rs │ │ │ ├── tests.rs │ │ │ ├── typed.rs │ │ │ ├── untyped.rs │ │ │ └── visit.rs │ │ ├── ast.rs │ │ ├── ast_folder.rs │ │ ├── bit_array.rs │ │ ├── build/ │ │ │ ├── elixir_libraries.rs │ │ │ ├── module_loader/ │ │ │ │ └── tests.rs │ │ │ ├── module_loader.rs │ │ │ ├── native_file_copier/ │ │ │ │ └── tests.rs │ │ │ ├── native_file_copier.rs │ │ │ ├── package_compiler/ │ │ │ │ ├── snapshots/ │ │ │ │ │ ├── gleam_core__build__package_compiler__tests__different_packages_defining_duplicate_module.snap │ │ │ │ │ └── gleam_core__build__package_compiler__tests__same_package_defining_duplicate_module.snap │ │ │ │ └── tests.rs │ │ │ ├── package_compiler.rs │ │ │ ├── package_loader/ │ │ │ │ └── tests.rs │ │ │ ├── package_loader.rs │ │ │ ├── project_compiler.rs │ │ │ ├── telemetry.rs │ │ │ └── tests.rs │ │ ├── build.rs │ │ ├── call_graph/ │ │ │ └── into_dependency_order_tests.rs │ │ ├── call_graph.rs │ │ ├── codegen.rs │ │ ├── config/ │ │ │ └── stale_package_remover.rs │ │ ├── config.rs │ │ ├── dep_tree.rs │ │ ├── dependency.rs │ │ ├── derivation_tree.rs │ │ ├── diagnostic.rs │ │ ├── docs/ │ │ │ ├── printer.rs │ │ │ ├── snapshots/ │ │ │ │ ├── gleam_core__docs__tests__canonical_link.snap │ │ │ │ ├── gleam_core__docs__tests__constructor_with_long_types_and_many_fields.snap │ │ │ │ ├── gleam_core__docs__tests__constructor_with_long_types_and_many_fields_that_need_splitting.snap │ │ │ │ ├── gleam_core__docs__tests__discarded_arguments_are_not_shown.snap │ │ │ │ ├── gleam_core__docs__tests__docs_of_a_type_constructor_are_not_used_by_the_following_function.snap │ │ │ │ ├── gleam_core__docs__tests__function_uses_reexport_of_internal_type.snap │ │ │ │ ├── gleam_core__docs__tests__function_uses_reexport_of_internal_type_in_other_module.snap │ │ │ │ ├── gleam_core__docs__tests__generated_type_variables.snap │ │ │ │ ├── gleam_core__docs__tests__generated_type_variables_do_not_take_into_account_other_definitions.snap │ │ │ │ ├── gleam_core__docs__tests__generated_type_variables_mixed_with_existing_variables.snap │ │ │ │ ├── gleam_core__docs__tests__generated_type_variables_with_existing_variables_coming_afterwards.snap │ │ │ │ ├── gleam_core__docs__tests__hello_docs.snap │ │ │ │ ├── gleam_core__docs__tests__highlight_constant_definition.snap │ │ │ │ ├── gleam_core__docs__tests__highlight_custom_type.snap │ │ │ │ ├── gleam_core__docs__tests__highlight_function_definition.snap │ │ │ │ ├── gleam_core__docs__tests__highlight_opaque_custom_type.snap │ │ │ │ ├── gleam_core__docs__tests__highlight_type_alias.snap │ │ │ │ ├── gleam_core__docs__tests__ignored_argument_is_called_arg.snap │ │ │ │ ├── gleam_core__docs__tests__internal_definitions_are_not_included.snap │ │ │ │ ├── gleam_core__docs__tests__internal_type_reexport_in_different_module.snap │ │ │ │ ├── gleam_core__docs__tests__internal_type_reexport_in_same_module.snap │ │ │ │ ├── gleam_core__docs__tests__internal_type_reexport_in_same_module_as_parameter.snap │ │ │ │ ├── gleam_core__docs__tests__internal_type_reexport_in_same_module_as_parameter_colours.snap │ │ │ │ ├── gleam_core__docs__tests__link_to_type_in_different_module.snap │ │ │ │ ├── gleam_core__docs__tests__link_to_type_in_different_module_from_nested_module.snap │ │ │ │ ├── gleam_core__docs__tests__link_to_type_in_different_module_from_nested_module_with_shared_path.snap │ │ │ │ ├── gleam_core__docs__tests__link_to_type_in_different_package.snap │ │ │ │ ├── gleam_core__docs__tests__link_to_type_in_same_module.snap │ │ │ │ ├── gleam_core__docs__tests__long_function_with_no_arguments_parentheses_are_not_split.snap │ │ │ │ ├── gleam_core__docs__tests__long_function_wrapping.snap │ │ │ │ ├── gleam_core__docs__tests__markdown_code_from_function_comment_is_trimmed.snap │ │ │ │ ├── gleam_core__docs__tests__markdown_code_from_module_comment_is_trimmed.snap │ │ │ │ ├── gleam_core__docs__tests__markdown_code_from_standalone_pages_is_not_trimmed.snap │ │ │ │ ├── gleam_core__docs__tests__no_hex_publish.snap │ │ │ │ ├── gleam_core__docs__tests__no_link_to_type_in_git_dependency.snap │ │ │ │ ├── gleam_core__docs__tests__no_link_to_type_in_path_dependency.snap │ │ │ │ ├── gleam_core__docs__tests__no_links_to_prelude_types.snap │ │ │ │ ├── gleam_core__docs__tests__output_of_search_data_json.snap │ │ │ │ ├── gleam_core__docs__tests__print_qualified_names_from_other_modules.snap │ │ │ │ ├── gleam_core__docs__tests__print_type_variables_in_function_signatures.snap │ │ │ │ ├── gleam_core__docs__tests__public_type_reexport_in_different_internal_module.snap │ │ │ │ ├── gleam_core__docs__tests__search_item_for_constant.snap │ │ │ │ ├── gleam_core__docs__tests__search_item_for_custom_type.snap │ │ │ │ ├── gleam_core__docs__tests__search_item_for_function.snap │ │ │ │ ├── gleam_core__docs__tests__search_item_for_type_alias.snap │ │ │ │ ├── gleam_core__docs__tests__tables.snap │ │ │ │ └── gleam_core__docs__tests__use_reexport_from_other_package.snap │ │ │ ├── source_links.rs │ │ │ └── tests.rs │ │ ├── docs.rs │ │ ├── encryption.rs │ │ ├── erlang/ │ │ │ ├── pattern.rs │ │ │ ├── snapshots/ │ │ │ │ ├── gleam_core__erlang__tests__allowed_string_escapes.snap │ │ │ │ ├── gleam_core__erlang__tests__binop_parens.snap │ │ │ │ ├── gleam_core__erlang__tests__bit_pattern_shadowing.snap │ │ │ │ ├── gleam_core__erlang__tests__block_assignment.snap │ │ │ │ ├── gleam_core__erlang__tests__constant_named_module_info.snap │ │ │ │ ├── gleam_core__erlang__tests__constant_named_module_info_imported.snap │ │ │ │ ├── gleam_core__erlang__tests__constant_named_module_info_imported_qualified.snap │ │ │ │ ├── gleam_core__erlang__tests__constant_named_module_info_with_function_inside.snap │ │ │ │ ├── gleam_core__erlang__tests__constant_named_module_info_with_function_inside_imported.snap │ │ │ │ ├── gleam_core__erlang__tests__constant_named_module_info_with_function_inside_imported_qualified.snap │ │ │ │ ├── gleam_core__erlang__tests__discard_in_assert.snap │ │ │ │ ├── gleam_core__erlang__tests__dynamic.snap │ │ │ │ ├── gleam_core__erlang__tests__field_access_function_call.snap │ │ │ │ ├── gleam_core__erlang__tests__field_access_function_call1.snap │ │ │ │ ├── gleam_core__erlang__tests__float_division_by_literal_non_zero.snap │ │ │ │ ├── gleam_core__erlang__tests__float_division_by_literal_zero.snap │ │ │ │ ├── gleam_core__erlang__tests__function_argument_shadowing.snap │ │ │ │ ├── gleam_core__erlang__tests__function_named_module_info.snap │ │ │ │ ├── gleam_core__erlang__tests__function_named_module_info_imported.snap │ │ │ │ ├── gleam_core__erlang__tests__function_named_module_info_imported_qualified.snap │ │ │ │ ├── gleam_core__erlang__tests__function_named_module_info_in_constant.snap │ │ │ │ ├── gleam_core__erlang__tests__function_named_module_info_in_constant_imported.snap │ │ │ │ ├── gleam_core__erlang__tests__function_named_module_info_in_constant_imported_qualified.snap │ │ │ │ ├── gleam_core__erlang__tests__guard_variable_rewriting.snap │ │ │ │ ├── gleam_core__erlang__tests__inline_const_pattern_option.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test0_1.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test0_2.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test0_3.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test1.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test10.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test11.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test12.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test13.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test16.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test17.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test18.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test19.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test1_1.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test1_2.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test1_4.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test1_5.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test1_6.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test2.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test20.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test21.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test22.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test23.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test3.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test5.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test6.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test8.snap │ │ │ │ ├── gleam_core__erlang__tests__integration_test9.snap │ │ │ │ ├── gleam_core__erlang__tests__keyword_constructors.snap │ │ │ │ ├── gleam_core__erlang__tests__keyword_constructors1.snap │ │ │ │ ├── gleam_core__erlang__tests__negation.snap │ │ │ │ ├── gleam_core__erlang__tests__negation_block.snap │ │ │ │ ├── gleam_core__erlang__tests__operator_pipe_right_hand_side.snap │ │ │ │ ├── gleam_core__erlang__tests__positive_zero.snap │ │ │ │ ├── gleam_core__erlang__tests__recursive_type.snap │ │ │ │ ├── gleam_core__erlang__tests__scientific_notation.snap │ │ │ │ ├── gleam_core__erlang__tests__tail_maybe_expr_block.snap │ │ │ │ ├── gleam_core__erlang__tests__tuple_access_in_guard.snap │ │ │ │ ├── gleam_core__erlang__tests__type_named_else.snap │ │ │ │ ├── gleam_core__erlang__tests__type_named_module_info.snap │ │ │ │ ├── gleam_core__erlang__tests__variable_name_underscores_preserved.snap │ │ │ │ └── gleam_core__erlang__tests__windows_file_escaping_bug.snap │ │ │ ├── tests/ │ │ │ │ ├── assert.rs │ │ │ │ ├── bit_arrays.rs │ │ │ │ ├── case.rs │ │ │ │ ├── conditional_compilation.rs │ │ │ │ ├── consts.rs │ │ │ │ ├── custom_types.rs │ │ │ │ ├── documentation.rs │ │ │ │ ├── echo.rs │ │ │ │ ├── external_fn.rs │ │ │ │ ├── functions.rs │ │ │ │ ├── guards.rs │ │ │ │ ├── inlining.rs │ │ │ │ ├── let_assert.rs │ │ │ │ ├── numbers.rs │ │ │ │ ├── panic.rs │ │ │ │ ├── patterns.rs │ │ │ │ ├── pipes.rs │ │ │ │ ├── prelude.rs │ │ │ │ ├── records.rs │ │ │ │ ├── reserved.rs │ │ │ │ ├── snapshots/ │ │ │ │ │ ├── gleam_core__erlang__tests__assert__assert_binary_operation.snap │ │ │ │ │ ├── gleam_core__erlang__tests__assert__assert_binary_operation2.snap │ │ │ │ │ ├── gleam_core__erlang__tests__assert__assert_binary_operation3.snap │ │ │ │ │ ├── gleam_core__erlang__tests__assert__assert_binary_operator_with_side_effects.snap │ │ │ │ │ ├── gleam_core__erlang__tests__assert__assert_binary_operator_with_side_effects2.snap │ │ │ │ │ ├── gleam_core__erlang__tests__assert__assert_function_call.snap │ │ │ │ │ ├── gleam_core__erlang__tests__assert__assert_function_call2.snap │ │ │ │ │ ├── gleam_core__erlang__tests__assert__assert_literal.snap │ │ │ │ │ ├── gleam_core__erlang__tests__assert__assert_nested_function_call.snap │ │ │ │ │ ├── gleam_core__erlang__tests__assert__assert_variable.snap │ │ │ │ │ ├── gleam_core__erlang__tests__assert__assert_with_block_message.snap │ │ │ │ │ ├── gleam_core__erlang__tests__assert__assert_with_message.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__bit_array.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__bit_array1.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__bit_array2.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__bit_array3.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__bit_array4.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__bit_array5.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__bit_array_declare_and_use_var.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__bit_array_discard.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__bit_array_discard1.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__bit_array_float.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__bit_array_literal_string_constant_is_treated_as_utf8.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__bit_array_literal_string_is_treated_as_utf8.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__bit_array_literal_string_pattern_is_treated_as_utf8.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__block_in_pattern_size.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__discard_utf8_pattern.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__non_byte_aligned_size_calculation.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__operator_in_pattern_size.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__operator_in_pattern_size2.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__operator_in_pattern_size3.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__pattern_match_utf16_codepoint_little_endian.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__pattern_match_utf32_codepoint_little_endian.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__pipe_size_segment.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__unicode_bit_array_1.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__unicode_bit_array_2.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__unicode_character_encoding_in_bit_array_pattern_segment.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__utf16_codepoint_little_endian.snap │ │ │ │ │ ├── gleam_core__erlang__tests__bit_arrays__utf32_codepoint_little_endian.snap │ │ │ │ │ ├── gleam_core__erlang__tests__case__aliased_string_prefix_pattern_referenced_in_guard.snap │ │ │ │ │ ├── gleam_core__erlang__tests__case__alternative_patter_with_string_alias.snap │ │ │ │ │ ├── gleam_core__erlang__tests__case__alternative_pattern_variable_rewriting.snap │ │ │ │ │ ├── gleam_core__erlang__tests__case__negative_zero_pattern.snap │ │ │ │ │ ├── gleam_core__erlang__tests__case__not.snap │ │ │ │ │ ├── gleam_core__erlang__tests__case__not_two.snap │ │ │ │ │ ├── gleam_core__erlang__tests__case__positive_zero_pattern.snap │ │ │ │ │ ├── gleam_core__erlang__tests__case__spread_empty_list.snap │ │ │ │ │ ├── gleam_core__erlang__tests__case__spread_empty_list_assigning.snap │ │ │ │ │ ├── gleam_core__erlang__tests__conditional_compilation__excluded_attribute_syntax.snap │ │ │ │ │ ├── gleam_core__erlang__tests__conditional_compilation__included_attribute_syntax.snap │ │ │ │ │ ├── gleam_core__erlang__tests__consts__const_generalise.snap │ │ │ │ │ ├── gleam_core__erlang__tests__consts__const_type_variable.snap │ │ │ │ │ ├── gleam_core__erlang__tests__consts__list_prepend.snap │ │ │ │ │ ├── gleam_core__erlang__tests__consts__list_prepend_from_other_module.snap │ │ │ │ │ ├── gleam_core__erlang__tests__consts__list_prepend_literal.snap │ │ │ │ │ ├── gleam_core__erlang__tests__consts__pub_const_equal_to_private_function.snap │ │ │ │ │ ├── gleam_core__erlang__tests__consts__pub_const_equal_to_record_with_nested_private_function_field.snap │ │ │ │ │ ├── gleam_core__erlang__tests__consts__pub_const_equal_to_record_with_private_function_field.snap │ │ │ │ │ ├── gleam_core__erlang__tests__consts__record_constructor.snap │ │ │ │ │ ├── gleam_core__erlang__tests__consts__record_constructor_in_tuple.snap │ │ │ │ │ ├── gleam_core__erlang__tests__consts__use_private_in_internal.snap │ │ │ │ │ ├── gleam_core__erlang__tests__consts__use_private_in_list.snap │ │ │ │ │ ├── gleam_core__erlang__tests__consts__use_private_in_tuple.snap │ │ │ │ │ ├── gleam_core__erlang__tests__consts__use_qualified_pub_const_equal_to_record_with_private_function_field.snap │ │ │ │ │ ├── gleam_core__erlang__tests__consts__use_unqualified_pub_const_equal_to_private_function.snap │ │ │ │ │ ├── gleam_core__erlang__tests__consts__use_unqualified_pub_const_equal_to_record_with_private_function_field.snap │ │ │ │ │ ├── gleam_core__erlang__tests__custom_types__annotated_external_type.snap │ │ │ │ │ ├── gleam_core__erlang__tests__custom_types__annotated_external_type_used_in_function.snap │ │ │ │ │ ├── gleam_core__erlang__tests__custom_types__phantom.snap │ │ │ │ │ ├── gleam_core__erlang__tests__custom_types__unused_opaque_constructor_is_generated_correctly.snap │ │ │ │ │ ├── gleam_core__erlang__tests__documentation__backslashes_are_escaped_in_module_comment.snap │ │ │ │ │ ├── gleam_core__erlang__tests__documentation__backslashes_in_documentation_are_escaped.snap │ │ │ │ │ ├── gleam_core__erlang__tests__documentation__double_quotes_are_escaped_in_module_comment.snap │ │ │ │ │ ├── gleam_core__erlang__tests__documentation__function_with_documentation.snap │ │ │ │ │ ├── gleam_core__erlang__tests__documentation__function_with_multiline_documentation.snap │ │ │ │ │ ├── gleam_core__erlang__tests__documentation__internal_function_has_no_documentation.snap │ │ │ │ │ ├── gleam_core__erlang__tests__documentation__multi_line_module_comment.snap │ │ │ │ │ ├── gleam_core__erlang__tests__documentation__quotes_in_documentation_are_escaped.snap │ │ │ │ │ ├── gleam_core__erlang__tests__documentation__single_line_module_comment.snap │ │ │ │ │ ├── gleam_core__erlang__tests__echo__echo_in_a_pipeline.snap │ │ │ │ │ ├── gleam_core__erlang__tests__echo__echo_in_a_pipeline_with_message.snap │ │ │ │ │ ├── gleam_core__erlang__tests__echo__echo_with_a_block.snap │ │ │ │ │ ├── gleam_core__erlang__tests__echo__echo_with_a_case_expression.snap │ │ │ │ │ ├── gleam_core__erlang__tests__echo__echo_with_a_function_call.snap │ │ │ │ │ ├── gleam_core__erlang__tests__echo__echo_with_a_function_call_and_a_message.snap │ │ │ │ │ ├── gleam_core__erlang__tests__echo__echo_with_a_panic.snap │ │ │ │ │ ├── gleam_core__erlang__tests__echo__echo_with_a_simple_expression.snap │ │ │ │ │ ├── gleam_core__erlang__tests__echo__echo_with_a_simple_expression_and_a_message.snap │ │ │ │ │ ├── gleam_core__erlang__tests__echo__echo_with_complex_expression_as_a_message.snap │ │ │ │ │ ├── gleam_core__erlang__tests__echo__multiple_echos_in_a_pipeline.snap │ │ │ │ │ ├── gleam_core__erlang__tests__echo__multiple_echos_inside_expression.snap │ │ │ │ │ ├── gleam_core__erlang__tests__echo__pipeline_printed_by_echo_is_wrapped_in_begin_end_block.snap │ │ │ │ │ ├── gleam_core__erlang__tests__echo__record_update_printed_by_echo_is_wrapped_in_begin_end_block.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__attribute_erlang.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__attribute_javascript.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__both_externals_no_valid_impl.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__discarded_arg_in_external_are_passed_correctly.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__elixir.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__erlang_and_javascript.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__hole_parameter_erlang.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__hole_parameter_javascript.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__hole_return_erlang.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__hole_return_javascript.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__inlining_external_functions_from_another_module.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__integration_test1_3.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__integration_test7.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__javascript_only.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__javascript_only_indirect.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__multiple_discarded_args_in_external_are_passed_correctly.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__multiple_discarded_args_in_external_are_passed_correctly_2.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__no_body.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__no_body_or_implementation.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__no_gleam_impl_no_annotations_function_fault_tolerance.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__no_target_supported_function_fault_tolerance.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__no_type_annotation_for_parameter.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__no_type_annotation_for_return.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__private.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__private_external_function_calls.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__private_local_function_references.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__public_elixir.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__public_local_function_calls.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__reference_to_imported_elixir_external_fn.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__unqualified_inlining_external_functions_from_another_module.snap │ │ │ │ │ ├── gleam_core__erlang__tests__external_fn__unqualified_reference_to_imported_elixir_external_fn.snap │ │ │ │ │ ├── gleam_core__erlang__tests__functions__function_as_value.snap │ │ │ │ │ ├── gleam_core__erlang__tests__functions__function_called.snap │ │ │ │ │ ├── gleam_core__erlang__tests__functions__labelled_argument_ordering.snap │ │ │ │ │ ├── gleam_core__erlang__tests__functions__nested_aliased_imported_function_as_value.snap │ │ │ │ │ ├── gleam_core__erlang__tests__functions__nested_aliased_imported_function_called.snap │ │ │ │ │ ├── gleam_core__erlang__tests__functions__nested_imported_function_as_value.snap │ │ │ │ │ ├── gleam_core__erlang__tests__functions__nested_imported_function_called.snap │ │ │ │ │ ├── gleam_core__erlang__tests__functions__nested_unqualified_imported_function_as_value.snap │ │ │ │ │ ├── gleam_core__erlang__tests__functions__nested_unqualified_imported_function_called.snap │ │ │ │ │ ├── gleam_core__erlang__tests__functions__unused_private_functions.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__clause_guards.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__clause_guards20.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__clause_guards21.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__clause_guards22.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__clause_guards23.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__clause_guards24.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__clause_guards25.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__clause_guards26.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__clause_guards27.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__clause_guards28.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__clause_guards29.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__clause_guards30.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__clause_guards31.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__clause_guards32.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__clause_guards_1.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__clause_guards_10.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__clause_guards_2.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__clause_guards_3.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__clause_guards_4.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__clause_guards_5.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__clause_guards_6.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__clause_guards_7.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__clause_guards_8.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__clause_guards_9.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__constants_in_guards.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__constants_in_guards1.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__field_access.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__module_access.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__module_list_access.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__module_nested_access.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__module_string_access.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__module_tuple_access.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__nested_record_access.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__only_guards.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__only_guards1.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__only_guards2.snap │ │ │ │ │ ├── gleam_core__erlang__tests__guards__only_guards3.snap │ │ │ │ │ ├── gleam_core__erlang__tests__inlining__blocks_get_preserved_when_needed.snap │ │ │ │ │ ├── gleam_core__erlang__tests__inlining__blocks_get_preserved_when_needed2.snap │ │ │ │ │ ├── gleam_core__erlang__tests__inlining__do_not_inline_parameters_that_have_side_effects.snap │ │ │ │ │ ├── gleam_core__erlang__tests__inlining__do_not_inline_parameters_used_more_than_once.snap │ │ │ │ │ ├── gleam_core__erlang__tests__inlining__inline_anonymous_function_call.snap │ │ │ │ │ ├── gleam_core__erlang__tests__inlining__inline_anonymous_function_in_pipe.snap │ │ │ │ │ ├── gleam_core__erlang__tests__inlining__inline_function_capture_in_pipe.snap │ │ │ │ │ ├── gleam_core__erlang__tests__inlining__inline_function_which_calls_other_function.snap │ │ │ │ │ ├── gleam_core__erlang__tests__inlining__inline_function_with_use.snap │ │ │ │ │ ├── gleam_core__erlang__tests__inlining__inline_function_with_use_and_anonymous.snap │ │ │ │ │ ├── gleam_core__erlang__tests__inlining__inline_function_with_use_becomes_tail_recursive.snap │ │ │ │ │ ├── gleam_core__erlang__tests__inlining__inline_higher_order_function.snap │ │ │ │ │ ├── gleam_core__erlang__tests__inlining__inline_higher_order_function_anonymous.snap │ │ │ │ │ ├── gleam_core__erlang__tests__inlining__inline_higher_order_function_with_capture.snap │ │ │ │ │ ├── gleam_core__erlang__tests__inlining__inline_shadowed_variable.snap │ │ │ │ │ ├── gleam_core__erlang__tests__inlining__inline_shadowed_variable_nested.snap │ │ │ │ │ ├── gleam_core__erlang__tests__inlining__inline_variable_shadowed_in_case_pattern.snap │ │ │ │ │ ├── gleam_core__erlang__tests__inlining__inline_variable_shadowing_case_pattern.snap │ │ │ │ │ ├── gleam_core__erlang__tests__inlining__inline_variable_shadowing_parameter.snap │ │ │ │ │ ├── gleam_core__erlang__tests__inlining__inlining_works_properly_with_record_updates.snap │ │ │ │ │ ├── gleam_core__erlang__tests__inlining__inlining_works_through_blocks.snap │ │ │ │ │ ├── gleam_core__erlang__tests__inlining__parameters_from_nested_functions_are_correctly_inlined.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__assignment_pattern.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__bit_array_assignment_discard.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__bit_array_assignment_float.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__bit_array_assignment_int.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__bit_array_assignment_string.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__bit_array_pattern.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__constructor_pattern.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__constructor_pattern_with_multiple_variables.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__discard_pattern.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__float_pattern.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__int_pattern.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__just_variable.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__let_assert_at_end_of_block.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__let_assert_should_not_use_redefined_variable.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__list_pattern.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__list_pattern_with_multiple_variables.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__message.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__more_than_one_var.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__one_var.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__pattern_let.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__reference_earlier_segment.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__string_pattern.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__string_prefix_pattern.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__string_prefix_pattern_with_prefix_binding.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__tuple_pattern.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__variable_message.snap │ │ │ │ │ ├── gleam_core__erlang__tests__let_assert__variable_rewrites.snap │ │ │ │ │ ├── gleam_core__erlang__tests__numbers__int_negation.snap │ │ │ │ │ ├── gleam_core__erlang__tests__numbers__numbers_with_scientific_notation.snap │ │ │ │ │ ├── gleam_core__erlang__tests__numbers__numbers_with_underscores.snap │ │ │ │ │ ├── gleam_core__erlang__tests__numbers__numbers_with_underscores1.snap │ │ │ │ │ ├── gleam_core__erlang__tests__numbers__numbers_with_underscores2.snap │ │ │ │ │ ├── gleam_core__erlang__tests__numbers__repeated_int_negation.snap │ │ │ │ │ ├── gleam_core__erlang__tests__numbers__zero_b_in_hex.snap │ │ │ │ │ ├── gleam_core__erlang__tests__panic__panic_as.snap │ │ │ │ │ ├── gleam_core__erlang__tests__panic__panic_as_function.snap │ │ │ │ │ ├── gleam_core__erlang__tests__panic__piped.snap │ │ │ │ │ ├── gleam_core__erlang__tests__panic__piped_chain.snap │ │ │ │ │ ├── gleam_core__erlang__tests__panic__plain.snap │ │ │ │ │ ├── gleam_core__erlang__tests__patterns__alternative_patterns.snap │ │ │ │ │ ├── gleam_core__erlang__tests__patterns__alternative_patterns1.snap │ │ │ │ │ ├── gleam_core__erlang__tests__patterns__alternative_patterns2.snap │ │ │ │ │ ├── gleam_core__erlang__tests__patterns__alternative_patterns3.snap │ │ │ │ │ ├── gleam_core__erlang__tests__patterns__pattern_as.snap │ │ │ │ │ ├── gleam_core__erlang__tests__patterns__string_prefix_as_pattern_with_assertion.snap │ │ │ │ │ ├── gleam_core__erlang__tests__patterns__string_prefix_as_pattern_with_list.snap │ │ │ │ │ ├── gleam_core__erlang__tests__patterns__string_prefix_as_pattern_with_multiple_subjects.snap │ │ │ │ │ ├── gleam_core__erlang__tests__patterns__string_prefix_as_pattern_with_multiple_subjects_and_guard.snap │ │ │ │ │ ├── gleam_core__erlang__tests__pipes__block_expr_into_pipe.snap │ │ │ │ │ ├── gleam_core__erlang__tests__pipes__call_pipeline_result.snap │ │ │ │ │ ├── gleam_core__erlang__tests__pipes__clever_pipe_rewriting.snap │ │ │ │ │ ├── gleam_core__erlang__tests__pipes__clever_pipe_rewriting1.snap │ │ │ │ │ ├── gleam_core__erlang__tests__pipes__multiple_pipes.snap │ │ │ │ │ ├── gleam_core__erlang__tests__pipes__pipe_in_call.snap │ │ │ │ │ ├── gleam_core__erlang__tests__pipes__pipe_in_case_subject.snap │ │ │ │ │ ├── gleam_core__erlang__tests__pipes__pipe_in_eq.snap │ │ │ │ │ ├── gleam_core__erlang__tests__pipes__pipe_in_list.snap │ │ │ │ │ ├── gleam_core__erlang__tests__pipes__pipe_in_record_update.snap │ │ │ │ │ ├── gleam_core__erlang__tests__pipes__pipe_in_tuple.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__basic.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__const_record_update_generic_respecialization.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__constant_record_update_with_unlabelled_fields.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__imported_qualified_constructor_as_fn_name_escape.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__long_definition_formatting.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__module_types.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__nested_record_update.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__nested_record_update_with_blocks.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__pipe_update_subject.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__private_unused_records.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__record_access_block.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__record_accessor_multiple_variants.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__record_accessor_multiple_variants_parameterised_types.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__record_accessor_multiple_variants_positions_other_than_first.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__record_accessor_multiple_with_first_position_different_types.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__record_accessors.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__record_constants.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__record_spread.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__record_spread1.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__record_spread2.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__record_spread3.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__record_update_with_unlabelled_fields.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__record_updates.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__record_updates1.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__record_updates2.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__record_updates3.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__record_updates4.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__reserve_words.snap │ │ │ │ │ ├── gleam_core__erlang__tests__records__type_vars.snap │ │ │ │ │ ├── gleam_core__erlang__tests__reserved__build_in_erlang_type_escaping.snap │ │ │ │ │ ├── gleam_core__erlang__tests__reserved__escape_erlang_reserved_keywords_in_type_names.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__ascii_as_unicode_escape_sequence.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__assert_const_concat.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__assert_const_concat_many_strings.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__assert_const_concat_many_strings_in_list.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__assert_const_concat_other_const_concat.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__assert_string_prefix.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__assert_string_prefix_discar.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__concat.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__concat_3_variables.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__concat_constant.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__concat_constant_fn.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__concat_function_call.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__discard_concat_rest_pattern.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__not_unicode_escape_sequence.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__not_unicode_escape_sequence2.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__pipe_concat.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__rest_variable_rewriting.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__string_of_number_concat.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__string_prefix.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__string_prefix_assignment.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__string_prefix_assignment_not_unicode_escape_sequence.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__string_prefix_assignment_with_escape_sequences.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__string_prefix_assignment_with_guard.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__string_prefix_assignment_with_multiple_subjects.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__string_prefix_not_unicode_escape_sequence.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__string_prefix_shadowing.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__string_prefix_with_escape_sequences.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__unicode1.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__unicode2.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__unicode3.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__unicode_concat1.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__unicode_concat2.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__unicode_concat3.snap │ │ │ │ │ ├── gleam_core__erlang__tests__strings__unicode_escape_sequence_6_digits.snap │ │ │ │ │ ├── gleam_core__erlang__tests__todo__named.snap │ │ │ │ │ ├── gleam_core__erlang__tests__todo__piped.snap │ │ │ │ │ ├── gleam_core__erlang__tests__todo__plain.snap │ │ │ │ │ ├── gleam_core__erlang__tests__todo__todo_as.snap │ │ │ │ │ ├── gleam_core__erlang__tests__todo__todo_as_function.snap │ │ │ │ │ ├── gleam_core__erlang__tests__type_params__custom_type_named_args_count_once.snap │ │ │ │ │ ├── gleam_core__erlang__tests__type_params__custom_type_nested_named_args_count_once.snap │ │ │ │ │ ├── gleam_core__erlang__tests__type_params__custom_type_nested_result_type_count_once.snap │ │ │ │ │ ├── gleam_core__erlang__tests__type_params__custom_type_tuple_type_params_count_twice.snap │ │ │ │ │ ├── gleam_core__erlang__tests__type_params__nested_result_type_count_once.snap │ │ │ │ │ ├── gleam_core__erlang__tests__type_params__result_type_count_once.snap │ │ │ │ │ ├── gleam_core__erlang__tests__type_params__result_type_inferred_count_once.snap │ │ │ │ │ ├── gleam_core__erlang__tests__type_params__tuple_type_params_count_twice.snap │ │ │ │ │ ├── gleam_core__erlang__tests__use___arity_1.snap │ │ │ │ │ ├── gleam_core__erlang__tests__use___arity_2.snap │ │ │ │ │ ├── gleam_core__erlang__tests__use___arity_3.snap │ │ │ │ │ ├── gleam_core__erlang__tests__use___no_callback_body.snap │ │ │ │ │ ├── gleam_core__erlang__tests__use___pipeline_that_returns_fn.snap │ │ │ │ │ ├── gleam_core__erlang__tests__variables__anon_external_fun_name_escaping.snap │ │ │ │ │ ├── gleam_core__erlang__tests__variables__blocks_are_scopes.snap │ │ │ │ │ ├── gleam_core__erlang__tests__variables__discarded.snap │ │ │ │ │ ├── gleam_core__erlang__tests__variables__module_const_vars.snap │ │ │ │ │ ├── gleam_core__erlang__tests__variables__shadow_and_call.snap │ │ │ │ │ ├── gleam_core__erlang__tests__variables__shadow_let.snap │ │ │ │ │ ├── gleam_core__erlang__tests__variables__shadow_param.snap │ │ │ │ │ └── gleam_core__erlang__tests__variables__shadow_pipe.snap │ │ │ │ ├── strings.rs │ │ │ │ ├── todo.rs │ │ │ │ ├── type_params.rs │ │ │ │ ├── use_.rs │ │ │ │ └── variables.rs │ │ │ └── tests.rs │ │ ├── erlang.rs │ │ ├── error/ │ │ │ ├── snapshots/ │ │ │ │ ├── gleam_core__error__tests__shell_program_not_found_bun_linux_other.snap │ │ │ │ ├── gleam_core__error__tests__shell_program_not_found_bun_linux_ubuntu.snap │ │ │ │ ├── gleam_core__error__tests__shell_program_not_found_bun_macos_other.snap │ │ │ │ ├── gleam_core__error__tests__shell_program_not_found_deno_linux_other.snap │ │ │ │ ├── gleam_core__error__tests__shell_program_not_found_deno_linux_ubuntu.snap │ │ │ │ ├── gleam_core__error__tests__shell_program_not_found_deno_macos_other.snap │ │ │ │ ├── gleam_core__error__tests__shell_program_not_found_elixir_linux_other.snap │ │ │ │ ├── gleam_core__error__tests__shell_program_not_found_elixir_linux_ubuntu.snap │ │ │ │ ├── gleam_core__error__tests__shell_program_not_found_elixir_macos_other.snap │ │ │ │ ├── gleam_core__error__tests__shell_program_not_found_erlc_linux_other.snap │ │ │ │ ├── gleam_core__error__tests__shell_program_not_found_erlc_linux_ubuntu.snap │ │ │ │ ├── gleam_core__error__tests__shell_program_not_found_erlc_macos_other.snap │ │ │ │ ├── gleam_core__error__tests__shell_program_not_found_git_linux_other.snap │ │ │ │ ├── gleam_core__error__tests__shell_program_not_found_git_linux_ubuntu.snap │ │ │ │ ├── gleam_core__error__tests__shell_program_not_found_git_macos_other.snap │ │ │ │ ├── gleam_core__error__tests__shell_program_not_found_node_linux_other.snap │ │ │ │ ├── gleam_core__error__tests__shell_program_not_found_node_linux_ubuntu.snap │ │ │ │ ├── gleam_core__error__tests__shell_program_not_found_node_macos_other.snap │ │ │ │ ├── gleam_core__error__tests__shell_program_not_found_rebar3_linux_other.snap │ │ │ │ ├── gleam_core__error__tests__shell_program_not_found_rebar3_linux_ubuntu.snap │ │ │ │ └── gleam_core__error__tests__shell_program_not_found_rebar3_macos_other.snap │ │ │ └── tests.rs │ │ ├── error.rs │ │ ├── exhaustiveness/ │ │ │ ├── missing_patterns.rs │ │ │ └── printer.rs │ │ ├── exhaustiveness.rs │ │ ├── fix.rs │ │ ├── format/ │ │ │ ├── tests/ │ │ │ │ ├── asignments.rs │ │ │ │ ├── binary_operators.rs │ │ │ │ ├── bit_array.rs │ │ │ │ ├── blocks.rs │ │ │ │ ├── cases.rs │ │ │ │ ├── conditional_compilation.rs │ │ │ │ ├── constant.rs │ │ │ │ ├── custom_type.rs │ │ │ │ ├── echo.rs │ │ │ │ ├── external_fn.rs │ │ │ │ ├── external_types.rs │ │ │ │ ├── function.rs │ │ │ │ ├── guards.rs │ │ │ │ ├── imports.rs │ │ │ │ ├── lists.rs │ │ │ │ ├── pipeline.rs │ │ │ │ ├── record_update.rs │ │ │ │ ├── tuple.rs │ │ │ │ └── use_.rs │ │ │ └── tests.rs │ │ ├── format.rs │ │ ├── graph.rs │ │ ├── hex.rs │ │ ├── inline.rs │ │ ├── io/ │ │ │ └── memory.rs │ │ ├── io.rs │ │ ├── javascript/ │ │ │ ├── decision.rs │ │ │ ├── expression.rs │ │ │ ├── import.rs │ │ │ ├── tests/ │ │ │ │ ├── assert.rs │ │ │ │ ├── assignments.rs │ │ │ │ ├── bit_arrays.rs │ │ │ │ ├── blocks.rs │ │ │ │ ├── bools.rs │ │ │ │ ├── case.rs │ │ │ │ ├── case_clause_guards.rs │ │ │ │ ├── consts.rs │ │ │ │ ├── custom_types.rs │ │ │ │ ├── echo.rs │ │ │ │ ├── externals.rs │ │ │ │ ├── functions.rs │ │ │ │ ├── generics.rs │ │ │ │ ├── inlining.rs │ │ │ │ ├── lists.rs │ │ │ │ ├── modules.rs │ │ │ │ ├── numbers.rs │ │ │ │ ├── panic.rs │ │ │ │ ├── prelude.rs │ │ │ │ ├── records.rs │ │ │ │ ├── recursion.rs │ │ │ │ ├── results.rs │ │ │ │ ├── snapshots/ │ │ │ │ │ ├── gleam_core__javascript__tests__assert__assert_binary_operation.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assert__assert_binary_operation2.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assert__assert_binary_operation3.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assert__assert_binary_operator_with_side_effects.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assert__assert_binary_operator_with_side_effects2.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assert__assert_function_call.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assert__assert_function_call2.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assert__assert_literal.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assert__assert_nested_function_call.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assert__assert_nil_always_throws.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assert__assert_variable.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assert__assert_with_block_message.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assert__assert_with_case_rhs.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assert__assert_with_logical_and_binary_rhs_1.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assert__assert_with_logical_and_binary_rhs_2.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assert__assert_with_logical_and_binary_rhs_3.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assert__assert_with_message.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assert__assert_with_negated_case_rhs.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assert__assert_with_pipe_on_right.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assert__prova.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__assert.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__assert1.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__assert_that_always_fails.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__assert_that_always_succeeds.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__assert_with_multiple_variants.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__case_message.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__catch_all_assert.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__constant_assignments.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__correct_variable_renaming_in_assigned_functions.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__escaped_variables_in_constants.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__keyword_assignment.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__let_assert_nested_string_prefix.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__let_assert_string_prefix.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__message.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__module_const_var.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__module_const_var1.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__nested_binding.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__rebound_argument.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__rebound_function.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__rebound_function_and_arg.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__returning_literal_subject.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__tuple_matching.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__use_discard_assignment.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__use_matching_assignment.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__variable_message.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__variable_renaming.snap │ │ │ │ │ ├── gleam_core__javascript__tests__assignments__variable_used_in_pattern_and_assignment.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__alternative_patterns_with_variable_size.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__as_module_const.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__bit_array_assignment_discard.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__bit_array_assignment_float.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__bit_array_assignment_int.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__bit_array_assignment_string.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__bit_array_dynamic_slice.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__bit_array_literal_string_constant_is_treated_as_utf8.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__bit_array_literal_string_is_treated_as_utf8.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__bit_array_literal_string_pattern_is_treated_as_utf8.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__bit_array_pattern_match_all_reachable.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__bit_array_sliced.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__bit_string.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__bit_string_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__bits.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__bits_pattern_requires_v1_9.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__bits_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__block_in_pattern_size.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_bit_array_assignment_discard.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_bit_array_assignment_float.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_bit_array_assignment_int.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_bit_array_assignment_string.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_discard_sized.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_dynamic_size_float_pattern_with_unit.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_dynamic_size_pattern_with_unit.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_empty_match.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_is_byte_aligned.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_binary_size.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_bits_with_size.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_bytes.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_bytes_with_size.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_dynamic_bits_size.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_dynamic_bytes_size.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_dynamic_size.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_dynamic_size_literal_value.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_dynamic_size_shadowed_variable.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_dynamic_size_with_other_segments.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_float.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_float_16_bit.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_float_big_endian.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_float_little_endian.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_float_sized.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_float_sized_big_endian.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_float_sized_little_endian.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_literal_aligned_float.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_literal_float.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_literal_unaligned_float.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_rest.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_rest_bits.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_rest_bits_unaligned.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_rest_bytes.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_signed.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_signed_constant_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_sized.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_sized_big_endian.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_sized_big_endian_constant_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_sized_big_endian_signed.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_sized_big_endian_signed_constant_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_sized_big_endian_unsigned.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_sized_big_endian_unsigned_constant_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_sized_constant_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_sized_little_endian.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_sized_little_endian_constant_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_sized_little_endian_signed.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_sized_little_endian_signed_constant_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_sized_little_endian_unsigned.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_sized_little_endian_unsigned_constant_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_sized_unaligned.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_sized_value.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_sized_value_constant_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_unsigned.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_match_unsigned_constant_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_pattern_with_unit.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_with_remaining_bytes_after_constant_size.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_with_remaining_bytes_after_variable_size.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__case_with_remaining_bytes_after_variable_size_2.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__const_utf16.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__const_utf32.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__discard_sized.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__dynamic_size_pattern_with_unit.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__dynamic_size_with_unit.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__empty.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__empty_match.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__explicit_sized_constant_value.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__explicit_sized_dynamic_value.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__float.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__float_big_endian.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__float_little_endian.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__float_sized.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__float_sized_big_endian.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__float_sized_little_endian.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__integer.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_binary_size.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_bits_with_size.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_bytes.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_bytes_with_size.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_case_utf8.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_case_utf8_with_escape_chars.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_dynamic_bits_size.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_dynamic_bytes_size.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_dynamic_size.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_dynamic_size_literal_value.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_dynamic_size_shadowed_variable.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_dynamic_size_with_other_segments.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_float.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_float_16_bit.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_float_big_endian.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_float_little_endian.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_float_sized.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_float_sized_big_endian.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_float_sized_little_endian.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_literal_aligned_float.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_literal_float.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_literal_unaligned_float.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_rest.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_rest_bits.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_rest_bits_unaligned.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_rest_bytes.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_signed.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_signed_constant_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_sized.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_sized_big_endian.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_sized_big_endian_constant_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_sized_big_endian_signed.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_sized_big_endian_signed_constant_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_sized_big_endian_unsigned.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_sized_big_endian_unsigned_constant_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_sized_constant_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_sized_little_endian.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_sized_little_endian_constant_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_sized_little_endian_signed.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_sized_little_endian_signed_constant_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_sized_little_endian_unsigned.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_sized_little_endian_unsigned_constant_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_sized_unaligned.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_sized_value.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_sized_value_constant_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_unsigned.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_unsigned_constant_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_utf8.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__match_utf8_with_escape_chars.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__multiple_variable_size_segments.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__negative_size_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__negative_size_pattern_2.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__non_byte_aligned_size_calculation.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__one.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__operator_in_pattern_size.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__operator_in_pattern_size2.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__operator_in_pattern_size3.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__operator_in_size_for_bit_array_segment.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__pattern_match_on_negative_size_calculation.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__pattern_match_size_arithmetic.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__pattern_match_unknown_size_and_literal_string.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__pattern_match_utf16.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__pattern_match_utf16_little_endian.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__pattern_match_utf32.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__pattern_match_utf32_little_endian.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__pattern_matching_on_32_float_minus_infinity_still_reachable.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__pattern_matching_on_32_float_minus_infinity_still_reachable_2.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__pattern_matching_on_32_float_nan_still_reachable.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__pattern_matching_on_32_float_nan_still_reachable_2.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__pattern_matching_on_32_float_plus_infinity_still_reachable.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__pattern_matching_on_32_float_plus_infinity_still_reachable_2.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__pattern_matching_on_64_float_float_is_unreachable.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__pattern_matching_on_64_float_int_is_still_reachable.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__pattern_matching_on_64_float_minus_infinity_still_reachable.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__pattern_matching_on_64_float_minus_infinity_still_reachable_2.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__pattern_matching_on_64_float_nan_still_reachable.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__pattern_matching_on_64_float_nan_still_reachable_2.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__pattern_matching_on_64_float_plus_infinity_still_reachable.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__pattern_matching_on_64_float_plus_infinity_still_reachable_2.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__pattern_with_unit.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__segments_shadowing_each_other.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__sized_big_endian_constant_value.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__sized_big_endian_dynamic_value.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__sized_bits_expression_requires_v1_9.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__sized_constant_value.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__sized_constant_value_max_size_for_compile_time_evaluation.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__sized_constant_value_negative_overflow.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__sized_constant_value_positive_overflow.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__sized_dynamic_value.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__sized_little_endian_constant_value.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__sized_little_endian_dynamic_value.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__tuple_bit_array.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__tuple_bit_array_case.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__tuple_multiple_bit_arrays.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__tuple_multiple_bit_arrays_case.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__two.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__unaligned_int_expression_requires_v1_9.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__unaligned_int_pattern_requires_v1_9.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__unit_with_bits_option.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__unit_with_bits_option_constant.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__utf16.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__utf16_codepoint.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__utf16_codepoint_little_endian.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__utf16_little_endian.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__utf32.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__utf32_codepoint.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__utf32_codepoint_little_endian.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__utf32_little_endian.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__utf8.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__utf8_codepoint.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__utf8_codepoint_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__variable.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__variable_sized.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__variable_sized_segment.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bit_arrays__with_unit.snap │ │ │ │ │ ├── gleam_core__javascript__tests__blocks__assignment_last_in_block.snap │ │ │ │ │ ├── gleam_core__javascript__tests__blocks__block.snap │ │ │ │ │ ├── gleam_core__javascript__tests__blocks__block_in_tail_position_is_not_an_iife.snap │ │ │ │ │ ├── gleam_core__javascript__tests__blocks__block_in_tail_position_shadowing_variables.snap │ │ │ │ │ ├── gleam_core__javascript__tests__blocks__block_in_tail_position_with_just_an_assignment.snap │ │ │ │ │ ├── gleam_core__javascript__tests__blocks__block_with_parenthesised_expression_returning_from_function.snap │ │ │ │ │ ├── gleam_core__javascript__tests__blocks__blocks_returning_functions.snap │ │ │ │ │ ├── gleam_core__javascript__tests__blocks__blocks_returning_use.snap │ │ │ │ │ ├── gleam_core__javascript__tests__blocks__blocks_whose_values_are_unused_do_not_generate_assignments.snap │ │ │ │ │ ├── gleam_core__javascript__tests__blocks__concat_blocks.snap │ │ │ │ │ ├── gleam_core__javascript__tests__blocks__left_operator_sequence.snap │ │ │ │ │ ├── gleam_core__javascript__tests__blocks__let_assert_message_no_lifted.snap │ │ │ │ │ ├── gleam_core__javascript__tests__blocks__let_assert_only_statement_in_block.snap │ │ │ │ │ ├── gleam_core__javascript__tests__blocks__nested_multiexpr_blocks.snap │ │ │ │ │ ├── gleam_core__javascript__tests__blocks__nested_multiexpr_blocks_with_case.snap │ │ │ │ │ ├── gleam_core__javascript__tests__blocks__nested_multiexpr_blocks_with_pipe.snap │ │ │ │ │ ├── gleam_core__javascript__tests__blocks__nested_multiexpr_non_ending_blocks.snap │ │ │ │ │ ├── gleam_core__javascript__tests__blocks__nested_simple_blocks.snap │ │ │ │ │ ├── gleam_core__javascript__tests__blocks__pattern_assignment_last_in_block.snap │ │ │ │ │ ├── gleam_core__javascript__tests__blocks__right_operator_sequence.snap │ │ │ │ │ ├── gleam_core__javascript__tests__blocks__sequences.snap │ │ │ │ │ ├── gleam_core__javascript__tests__blocks__shadowed_variable_in_nested_scope.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bools__assigning.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bools__binop_panic_left.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bools__binop_panic_right.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bools__binop_todo_left.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bools__binop_todo_right.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bools__case.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bools__constants.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bools__constants_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bools__equality.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bools__expressions.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bools__negate_panic.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bools__negate_todo.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bools__negation.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bools__negation_block.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bools__nil_case.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bools__operators.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bools__shadowed_bools_and_nil.snap │ │ │ │ │ ├── gleam_core__javascript__tests__bools__shadowed_bools_and_nil_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__assignment.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__called_case.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_branches_guards_are_wrapped_in_parentheses.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_building_list_matched_by_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_building_matched_no_variant_record.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_building_matched_no_variant_record_2.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_building_matched_no_variant_record_3.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_building_matched_no_variant_record_4.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_building_matched_string_1.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_building_matched_string_2.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_building_matched_value_alias.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_building_matched_value_alias_2.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_building_matched_value_alias_3.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_building_matched_value_wrapped_in_block.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_building_record_matched_by_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_building_record_with_labels_matched_by_pattern_1.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_building_record_with_labels_matched_by_pattern_2.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_building_record_with_labels_matched_by_pattern_3.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_building_record_with_labels_matched_by_pattern_4.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_building_record_with_labels_matched_by_pattern_5.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_building_record_with_labels_matched_by_pattern_6.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_building_record_with_select_matched_by_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_building_record_with_select_matched_by_pattern_2.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_building_record_with_select_matched_by_pattern_3.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_building_simple_value_matched_by_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_local_var_in_tuple.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_on_error.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_with_multiple_subjects_building_list_matched_by_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_with_multiple_subjects_building_record_matched_by_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_with_multiple_subjects_building_same_value_as_two_subjects_one_is_picked.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__case_with_multiple_subjects_building_simple_value_matched_by_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__deeply_nested_string_prefix_assignment.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__directly_matching_case_subject.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__duplicate_name_for_variables_used_in_guards.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__duplicate_name_for_variables_used_in_guards_shadowing_outer_name.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__following_todo.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__guard_variable_only_brought_into_scope_when_needed.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__guard_variable_only_brought_into_scope_when_needed_1.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__interfering_string_pattern_succeeds_if_succeeding.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__list_with_guard.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__list_with_guard_no_binding.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__multi_subject_catch_all.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__multi_subject_no_catch_all.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__multi_subject_or.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__multi_subject_subject_assignments.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__nested_string_prefix_assignment.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__nested_string_prefix_match.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__nested_string_prefix_match_that_would_crash_on_js.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__pattern_matching_on_aliased_result_constructor.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__pipe.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__pointless.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__preassign_assignment.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__record_update_in_pipeline_in_case_clause.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__result.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__single_clause_variables.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__single_clause_variables_assigned.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__slicing_is_handled_properly_with_multiple_branches.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__string_concatenation_in_clause_guards.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__tuple_and_guard.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case__var_true.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__alternative_patterns.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__alternative_patterns_assignment.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__alternative_patterns_guard.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__alternative_patterns_list.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__bit_array_referencing_shadowed_variable.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__bitarray_with_var.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__constant.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__constructor_function_in_guard.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__custom_type_constructor_imported_and_aliased.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__eq_complex.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__eq_scalar.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__field_access.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__float_division.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__guard_pattern_does_not_shadow_outer_scope.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__imported_aliased_ok.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__imported_ok.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__int_division.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__int_remainder.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__keyword_var.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__module_access.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__module_access_aliased.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__module_access_submodule.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__module_list_access.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__module_nested_access.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__module_string_access.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__module_tuple_access.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__nested_record_access.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__not.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__not_eq_complex.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__not_eq_scalar.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__not_two.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__operator_wrapping_left.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__operator_wrapping_right.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__rebound_var.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__referencing_pattern_var.snap │ │ │ │ │ ├── gleam_core__javascript__tests__case_clause_guards__tuple_index.snap │ │ │ │ │ ├── gleam_core__javascript__tests__consts__constant_constructor_gets_pure_annotation.snap │ │ │ │ │ ├── gleam_core__javascript__tests__consts__constant_list_with_constructors_gets_pure_annotation.snap │ │ │ │ │ ├── gleam_core__javascript__tests__consts__constant_tuple_with_constructors_gets_pure_annotation.snap │ │ │ │ │ ├── gleam_core__javascript__tests__consts__constants_get_their_own_jsdoc_comment.snap │ │ │ │ │ ├── gleam_core__javascript__tests__consts__constructor_function_in_constant.snap │ │ │ │ │ ├── gleam_core__javascript__tests__consts__custom_type_constructor_imported_and_aliased.snap │ │ │ │ │ ├── gleam_core__javascript__tests__consts__imported_aliased_ok.snap │ │ │ │ │ ├── gleam_core__javascript__tests__consts__imported_ok.snap │ │ │ │ │ ├── gleam_core__javascript__tests__consts__list_prepend.snap │ │ │ │ │ ├── gleam_core__javascript__tests__consts__list_prepend_from_other_module.snap │ │ │ │ │ ├── gleam_core__javascript__tests__consts__list_prepend_literal.snap │ │ │ │ │ ├── gleam_core__javascript__tests__consts__literal_bool_does_not_get_constant_annotation.snap │ │ │ │ │ ├── gleam_core__javascript__tests__consts__literal_float_does_not_get_constant_annotation.snap │ │ │ │ │ ├── gleam_core__javascript__tests__consts__literal_int_does_not_get_constant_annotation.snap │ │ │ │ │ ├── gleam_core__javascript__tests__consts__literal_list_does_not_get_constant_annotation.snap │ │ │ │ │ ├── gleam_core__javascript__tests__consts__literal_nil_does_not_get_constant_annotation.snap │ │ │ │ │ ├── gleam_core__javascript__tests__consts__literal_string_does_not_get_constant_annotation.snap │ │ │ │ │ ├── gleam_core__javascript__tests__consts__literal_tuple_does_not_get_constant_annotation.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__const_imported_ignoring_label.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__const_imported_multiple_fields.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__const_imported_no_label.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__const_imported_using_label.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__const_unqualified_imported_ignoring_label.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__const_unqualified_imported_multiple_fields.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__const_unqualified_imported_no_label.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__const_unqualified_imported_using_label.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__const_with_fields.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__const_with_fields_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__const_zero_arity_imported.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__const_zero_arity_imported_unqualified.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__constructor_as_value.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__constructors_get_their_own_jsdoc.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__custom_type_with_named_fields.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__destructure_custom_type_with_mixed_fields_first_unlabelled.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__destructure_custom_type_with_named_fields.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__equality_with_non_singleton_variant.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__external_annotated_type_used_in_function.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__external_annotation.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__generic_type_parameter_used_in_field.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__guard_equality_with_non_singleton_variant.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__imported_ignoring_label.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__imported_multiple_fields.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__imported_no_label.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__imported_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__imported_using_label.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__keyword_label_name.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__long_name_variant_mixed_labels_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__long_name_variant_without_labels.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__mixed_singleton_and_non_singleton.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__multiple_singleton_constructors.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__nested_pattern_with_labels.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__new_type_import_syntax.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__non_singleton_record_equality.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__opaque_types_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__qualified.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__record_access_in_guard_with_reserved_field_name.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__record_access_in_pattern_with_reserved_field_name.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__record_with_field_named_constructor.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__record_with_field_named_then.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__singleton_in_case_guard.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__singleton_record_equality.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__singleton_record_inequality.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__singleton_record_reverse_order.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__types_must_be_rendered_before_functions.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__unapplied_record_constructors_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__unnamed_fields.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__unnamed_fields_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__unqualified_constructor_as_value.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__unqualified_imported_ignoring_label.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__unqualified_imported_multiple_fields.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__unqualified_imported_no_label.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__unqualified_imported_no_label_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__unqualified_imported_using_label.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__unused_opaque_constructor_is_generated_correctly.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_aliased_clause_guard.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_aliased_expression.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_qualified_clause_guard.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_qualified_expression.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_unqualified_clause_guard.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_unqualified_expression.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__zero_arity_const.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__zero_arity_imported.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__zero_arity_imported_typscript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__zero_arity_imported_unqualified.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__zero_arity_imported_unqualified_aliased.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__zero_arity_imported_unqualified_aliased_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__zero_arity_imported_unqualified_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__custom_types__zero_arity_literal.snap │ │ │ │ │ ├── gleam_core__javascript__tests__echo__echo_evaluates_printed_value_before_message.snap │ │ │ │ │ ├── gleam_core__javascript__tests__echo__echo_in_a_pipeline.snap │ │ │ │ │ ├── gleam_core__javascript__tests__echo__echo_in_a_pipeline_with_message.snap │ │ │ │ │ ├── gleam_core__javascript__tests__echo__echo_with_a_block.snap │ │ │ │ │ ├── gleam_core__javascript__tests__echo__echo_with_a_block_as_a_message.snap │ │ │ │ │ ├── gleam_core__javascript__tests__echo__echo_with_a_case_expression.snap │ │ │ │ │ ├── gleam_core__javascript__tests__echo__echo_with_a_function_call.snap │ │ │ │ │ ├── gleam_core__javascript__tests__echo__echo_with_a_function_call_and_a_message.snap │ │ │ │ │ ├── gleam_core__javascript__tests__echo__echo_with_a_panic.snap │ │ │ │ │ ├── gleam_core__javascript__tests__echo__echo_with_a_simple_expression.snap │ │ │ │ │ ├── gleam_core__javascript__tests__echo__echo_with_a_simple_expression_and_a_message.snap │ │ │ │ │ ├── gleam_core__javascript__tests__echo__echo_with_complex_expression_as_a_message.snap │ │ │ │ │ ├── gleam_core__javascript__tests__echo__module_named_inspect.snap │ │ │ │ │ ├── gleam_core__javascript__tests__echo__multiple_echos_in_a_pipeline.snap │ │ │ │ │ ├── gleam_core__javascript__tests__echo__multiple_echos_inside_expression.snap │ │ │ │ │ ├── gleam_core__javascript__tests__externals__at_namespace_module.snap │ │ │ │ │ ├── gleam_core__javascript__tests__externals__attribute_erlang.snap │ │ │ │ │ ├── gleam_core__javascript__tests__externals__attribute_javascript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__externals__both_externals_no_valid_impl.snap │ │ │ │ │ ├── gleam_core__javascript__tests__externals__discarded_names_in_external_are_passed_correctly.snap │ │ │ │ │ ├── gleam_core__javascript__tests__externals__duplicate_import.snap │ │ │ │ │ ├── gleam_core__javascript__tests__externals__erlang_and_javascript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__externals__erlang_only.snap │ │ │ │ │ ├── gleam_core__javascript__tests__externals__external_fn_escaping.snap │ │ │ │ │ ├── gleam_core__javascript__tests__externals__external_type_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__externals__inline_function.snap │ │ │ │ │ ├── gleam_core__javascript__tests__externals__module_fn.snap │ │ │ │ │ ├── gleam_core__javascript__tests__externals__name_to_escape.snap │ │ │ │ │ ├── gleam_core__javascript__tests__externals__no_body.snap │ │ │ │ │ ├── gleam_core__javascript__tests__externals__no_module.snap │ │ │ │ │ ├── gleam_core__javascript__tests__externals__pipe_variable_shadow.snap │ │ │ │ │ ├── gleam_core__javascript__tests__externals__private_attribute_erlang.snap │ │ │ │ │ ├── gleam_core__javascript__tests__externals__private_attribute_javascript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__externals__private_erlang_and_javascript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__externals__pub_module_fn.snap │ │ │ │ │ ├── gleam_core__javascript__tests__externals__pub_module_fn_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__externals__same_module_multiple_imports.snap │ │ │ │ │ ├── gleam_core__javascript__tests__externals__same_name_external.snap │ │ │ │ │ ├── gleam_core__javascript__tests__externals__tf_type_name_usage.snap │ │ │ │ │ ├── gleam_core__javascript__tests__externals__type_.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__assert_last.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__bad_comma.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__calling_fn_literal.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__calling_functions.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__case_in_call.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__exported_functions.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__fn_return_fn_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__function_formatting.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__function_formatting1.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__function_formatting2.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__function_formatting3.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__function_formatting_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__function_formatting_typescript1.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__function_literals_get_properly_wrapped_1.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__function_literals_get_properly_wrapped_2.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__function_literals_get_properly_wrapped_3.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__immediately_invoked_function_expressions_include_statement_level.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__internal_function_gets_ignored_jsdoc.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__keyword_in_recursive_function.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__labelled_argument_ordering.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__let_last.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__module_const_fn.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__module_const_fn1.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__multiple_discard.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__no_recur_in_anon_fn.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__pipe_into_block.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__pipe_last.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__pipe_shadow_import.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__pipe_variable_rebinding.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__pipe_with_block_in_the_middle.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__public_function_gets_jsdoc.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__recursion_with_discards.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__reserved_word_argument.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__reserved_word_const.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__reserved_word_fn.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__reserved_word_imported.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__reserved_word_imported_alias.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__reserved_word_in_function_arguments.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__shadowing_current.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__star_slash_in_jsdoc.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__tail_call.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__tail_call_doesnt_clobber_tail_position_tracking.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__two_pipes_in_a_row.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__variable_rewriting_in_anon_fn_with_matching_parameter.snap │ │ │ │ │ ├── gleam_core__javascript__tests__functions__variable_rewriting_in_anon_fn_with_matching_parameter_in_case.snap │ │ │ │ │ ├── gleam_core__javascript__tests__generics__fn_generics_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__generics__record_generics_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__generics__result_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__generics__task_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__generics__tuple_generics_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__inlining__blocks_get_preserved_when_needed.snap │ │ │ │ │ ├── gleam_core__javascript__tests__inlining__blocks_get_preserved_when_needed2.snap │ │ │ │ │ ├── gleam_core__javascript__tests__inlining__do_not_inline_parameters_that_have_side_effects.snap │ │ │ │ │ ├── gleam_core__javascript__tests__inlining__do_not_inline_parameters_used_more_than_once.snap │ │ │ │ │ ├── gleam_core__javascript__tests__inlining__inline_anonymous_function_call.snap │ │ │ │ │ ├── gleam_core__javascript__tests__inlining__inline_anonymous_function_in_pipe.snap │ │ │ │ │ ├── gleam_core__javascript__tests__inlining__inline_function_capture_in_pipe.snap │ │ │ │ │ ├── gleam_core__javascript__tests__inlining__inline_function_which_calls_other_function.snap │ │ │ │ │ ├── gleam_core__javascript__tests__inlining__inline_function_with_use.snap │ │ │ │ │ ├── gleam_core__javascript__tests__inlining__inline_function_with_use_and_anonymous.snap │ │ │ │ │ ├── gleam_core__javascript__tests__inlining__inline_function_with_use_becomes_tail_recursive.snap │ │ │ │ │ ├── gleam_core__javascript__tests__inlining__inline_higher_order_function.snap │ │ │ │ │ ├── gleam_core__javascript__tests__inlining__inline_higher_order_function_anonymous.snap │ │ │ │ │ ├── gleam_core__javascript__tests__inlining__inline_higher_order_function_with_capture.snap │ │ │ │ │ ├── gleam_core__javascript__tests__inlining__inline_shadowed_variable.snap │ │ │ │ │ ├── gleam_core__javascript__tests__inlining__inline_shadowed_variable_nested.snap │ │ │ │ │ ├── gleam_core__javascript__tests__inlining__inline_variable_shadowed_in_case_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__inlining__inline_variable_shadowing_case_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__inlining__inline_variable_shadowing_parameter.snap │ │ │ │ │ ├── gleam_core__javascript__tests__inlining__inlining_works_properly_with_record_updates.snap │ │ │ │ │ ├── gleam_core__javascript__tests__inlining__inlining_works_through_blocks.snap │ │ │ │ │ ├── gleam_core__javascript__tests__inlining__parameters_from_nested_functions_are_correctly_inlined.snap │ │ │ │ │ ├── gleam_core__javascript__tests__lists__case.snap │ │ │ │ │ ├── gleam_core__javascript__tests__lists__equality.snap │ │ │ │ │ ├── gleam_core__javascript__tests__lists__list_constants.snap │ │ │ │ │ ├── gleam_core__javascript__tests__lists__list_constants_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__lists__list_destructuring.snap │ │ │ │ │ ├── gleam_core__javascript__tests__lists__list_literals.snap │ │ │ │ │ ├── gleam_core__javascript__tests__lists__long_list_literals.snap │ │ │ │ │ ├── gleam_core__javascript__tests__lists__multi_line_list_literals.snap │ │ │ │ │ ├── gleam_core__javascript__tests__lists__tight_empty_list.snap │ │ │ │ │ ├── gleam_core__javascript__tests__modules__alias_aliased_constant.snap │ │ │ │ │ ├── gleam_core__javascript__tests__modules__alias_constant.snap │ │ │ │ │ ├── gleam_core__javascript__tests__modules__alias_fn_call.snap │ │ │ │ │ ├── gleam_core__javascript__tests__modules__aliased_unqualified_fn_call.snap │ │ │ │ │ ├── gleam_core__javascript__tests__modules__constant.snap │ │ │ │ │ ├── gleam_core__javascript__tests__modules__constant_module_access_with_keyword.snap │ │ │ │ │ ├── gleam_core__javascript__tests__modules__different_package_import.snap │ │ │ │ │ ├── gleam_core__javascript__tests__modules__discarded_duplicate_import.snap │ │ │ │ │ ├── gleam_core__javascript__tests__modules__discarded_duplicate_import_with_unqualified.snap │ │ │ │ │ ├── gleam_core__javascript__tests__modules__import_with_keyword.snap │ │ │ │ │ ├── gleam_core__javascript__tests__modules__multiple_unqualified_fn_call.snap │ │ │ │ │ ├── gleam_core__javascript__tests__modules__nested_fn_call.snap │ │ │ │ │ ├── gleam_core__javascript__tests__modules__nested_module_constant.snap │ │ │ │ │ ├── gleam_core__javascript__tests__modules__nested_nested_fn_call.snap │ │ │ │ │ ├── gleam_core__javascript__tests__modules__nested_same_package.snap │ │ │ │ │ ├── gleam_core__javascript__tests__modules__renamed_module.snap │ │ │ │ │ ├── gleam_core__javascript__tests__modules__unqualified_fn_call.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__complex_division_by_non_zero_float.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__complex_division_by_non_zero_int.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__complex_remainder_by_non_zero_int.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__division_by_non_zero_float.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__division_by_non_zero_int.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__division_by_zero_float.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__division_by_zero_int.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__division_inf_by_inf_float.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__float_divide_complex_expr.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__float_equality.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__float_equality1.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__float_literals.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__float_operators.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__float_scientific_literals.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__inf_float_case_statement.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__int_divide_complex_expr.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__int_equality.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__int_equality1.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__int_literals.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__int_mod_complex_expr.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__int_negation.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__int_operators.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__int_patterns.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__many_preceeding_zeros_float.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__many_preceeding_zeros_float_const.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__many_preceeding_zeros_float_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__many_preceeding_zeros_int.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__many_preceeding_zeros_int_const.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__many_preceeding_zeros_int_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__operator_precedence.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__preceeding_zeros_float.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__preceeding_zeros_float_const.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__preceeding_zeros_float_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__preceeding_zeros_int.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__preceeding_zeros_int_const.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__preceeding_zeros_int_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__remainder.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__remainder_by_non_zero_int.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__remainder_by_zero_int.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__repeated_int_negation.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__underscore_after_binary_prefix.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__underscore_after_decimal_point.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__underscore_after_decimal_point_case_statement.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__underscore_after_hexadecimal_prefix.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__underscore_after_octal_prefix.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__underscore_after_zero.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__underscore_after_zero_after_binary_prefix.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__underscore_after_zero_after_hex_prefix.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__underscore_after_zero_after_octal_prefix.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__wide_float_div.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__zero_after_underscore_after_binary_prefix.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__zero_after_underscore_after_hex_prefix.snap │ │ │ │ │ ├── gleam_core__javascript__tests__numbers__zero_after_underscore_after_octal_prefix.snap │ │ │ │ │ ├── gleam_core__javascript__tests__panic__as_expression.snap │ │ │ │ │ ├── gleam_core__javascript__tests__panic__bare.snap │ │ │ │ │ ├── gleam_core__javascript__tests__panic__bare_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__panic__case.snap │ │ │ │ │ ├── gleam_core__javascript__tests__panic__case_message.snap │ │ │ │ │ ├── gleam_core__javascript__tests__panic__panic_as.snap │ │ │ │ │ ├── gleam_core__javascript__tests__panic__pipe.snap │ │ │ │ │ ├── gleam_core__javascript__tests__panic__sequence.snap │ │ │ │ │ ├── gleam_core__javascript__tests__prelude__qualified_error.snap │ │ │ │ │ ├── gleam_core__javascript__tests__prelude__qualified_nil.snap │ │ │ │ │ ├── gleam_core__javascript__tests__prelude__qualified_nil_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__prelude__qualified_ok.snap │ │ │ │ │ ├── gleam_core__javascript__tests__prelude__qualified_ok_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__prelude__qualified_prelude_value_does_not_conflict_with_local_value.snap │ │ │ │ │ ├── gleam_core__javascript__tests__prelude__qualified_prelude_value_does_not_conflict_with_local_value_constant.snap │ │ │ │ │ ├── gleam_core__javascript__tests__prelude__qualified_prelude_value_does_not_conflict_with_local_value_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__records__const_record_update_generic_respecialization.snap │ │ │ │ │ ├── gleam_core__javascript__tests__records__constant_record_update_with_unlabelled_fields.snap │ │ │ │ │ ├── gleam_core__javascript__tests__records__field_named_constructor_is_escaped.snap │ │ │ │ │ ├── gleam_core__javascript__tests__records__field_named_prototype_is_escaped.snap │ │ │ │ │ ├── gleam_core__javascript__tests__records__field_named_then_is_escaped.snap │ │ │ │ │ ├── gleam_core__javascript__tests__records__field_named_x0.snap │ │ │ │ │ ├── gleam_core__javascript__tests__records__record_accessor_multiple_variants.snap │ │ │ │ │ ├── gleam_core__javascript__tests__records__record_accessor_multiple_variants_parameterised_types.snap │ │ │ │ │ ├── gleam_core__javascript__tests__records__record_accessor_multiple_variants_positions_other_than_first.snap │ │ │ │ │ ├── gleam_core__javascript__tests__records__record_accessor_multiple_with_first_position_different_types.snap │ │ │ │ │ ├── gleam_core__javascript__tests__records__record_accessors.snap │ │ │ │ │ ├── gleam_core__javascript__tests__records__record_update_with_unlabelled_fields.snap │ │ │ │ │ ├── gleam_core__javascript__tests__recursion__not_tco_due_to_assignment.snap │ │ │ │ │ ├── gleam_core__javascript__tests__recursion__shadowing_so_not_recursive.snap │ │ │ │ │ ├── gleam_core__javascript__tests__recursion__tco.snap │ │ │ │ │ ├── gleam_core__javascript__tests__recursion__tco_case_block.snap │ │ │ │ │ ├── gleam_core__javascript__tests__results__aliased_error.snap │ │ │ │ │ ├── gleam_core__javascript__tests__results__aliased_error_fn.snap │ │ │ │ │ ├── gleam_core__javascript__tests__results__aliased_ok.snap │ │ │ │ │ ├── gleam_core__javascript__tests__results__aliased_ok_fn.snap │ │ │ │ │ ├── gleam_core__javascript__tests__results__error.snap │ │ │ │ │ ├── gleam_core__javascript__tests__results__error_fn.snap │ │ │ │ │ ├── gleam_core__javascript__tests__results__ok.snap │ │ │ │ │ ├── gleam_core__javascript__tests__results__ok_fn.snap │ │ │ │ │ ├── gleam_core__javascript__tests__results__qualified_error.snap │ │ │ │ │ ├── gleam_core__javascript__tests__results__qualified_error_fn.snap │ │ │ │ │ ├── gleam_core__javascript__tests__results__qualified_ok.snap │ │ │ │ │ ├── gleam_core__javascript__tests__results__qualified_ok_fn.snap │ │ │ │ │ ├── gleam_core__javascript__tests__strings__ascii_as_unicode_escape_sequence.snap │ │ │ │ │ ├── gleam_core__javascript__tests__strings__case.snap │ │ │ │ │ ├── gleam_core__javascript__tests__strings__const_concat.snap │ │ │ │ │ ├── gleam_core__javascript__tests__strings__const_concat_multiple.snap │ │ │ │ │ ├── gleam_core__javascript__tests__strings__discard_concat_rest_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__strings__equality.snap │ │ │ │ │ ├── gleam_core__javascript__tests__strings__string_concat.snap │ │ │ │ │ ├── gleam_core__javascript__tests__strings__string_literals.snap │ │ │ │ │ ├── gleam_core__javascript__tests__strings__string_patterns.snap │ │ │ │ │ ├── gleam_core__javascript__tests__strings__string_prefix.snap │ │ │ │ │ ├── gleam_core__javascript__tests__strings__string_prefix_assignment.snap │ │ │ │ │ ├── gleam_core__javascript__tests__strings__string_prefix_assignment_with_multiple_subjects.snap │ │ │ │ │ ├── gleam_core__javascript__tests__strings__string_prefix_assignment_with_utf_escape_sequence.snap │ │ │ │ │ ├── gleam_core__javascript__tests__strings__string_prefix_shadowing.snap │ │ │ │ │ ├── gleam_core__javascript__tests__strings__string_prefix_utf16.snap │ │ │ │ │ ├── gleam_core__javascript__tests__strings__unicode1.snap │ │ │ │ │ ├── gleam_core__javascript__tests__strings__unicode2.snap │ │ │ │ │ ├── gleam_core__javascript__tests__strings__unicode_escape_sequence_6_digits.snap │ │ │ │ │ ├── gleam_core__javascript__tests__todo__as_expression.snap │ │ │ │ │ ├── gleam_core__javascript__tests__todo__case_message.snap │ │ │ │ │ ├── gleam_core__javascript__tests__todo__inside_fn.snap │ │ │ │ │ ├── gleam_core__javascript__tests__todo__with_message.snap │ │ │ │ │ ├── gleam_core__javascript__tests__todo__with_message_expr.snap │ │ │ │ │ ├── gleam_core__javascript__tests__todo__without_message.snap │ │ │ │ │ ├── gleam_core__javascript__tests__todo__without_message_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__tuples__case.snap │ │ │ │ │ ├── gleam_core__javascript__tests__tuples__constant_tuples.snap │ │ │ │ │ ├── gleam_core__javascript__tests__tuples__constant_tuples1.snap │ │ │ │ │ ├── gleam_core__javascript__tests__tuples__nested_pattern.snap │ │ │ │ │ ├── gleam_core__javascript__tests__tuples__tuple.snap │ │ │ │ │ ├── gleam_core__javascript__tests__tuples__tuple1.snap │ │ │ │ │ ├── gleam_core__javascript__tests__tuples__tuple_access.snap │ │ │ │ │ ├── gleam_core__javascript__tests__tuples__tuple_formatting_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__tuples__tuple_typescript.snap │ │ │ │ │ ├── gleam_core__javascript__tests__tuples__tuple_with_block_element.snap │ │ │ │ │ ├── gleam_core__javascript__tests__tuples__tuple_with_block_element1.snap │ │ │ │ │ ├── gleam_core__javascript__tests__type_alias__import_indirect_type_alias.snap │ │ │ │ │ ├── gleam_core__javascript__tests__type_alias__private_type_in_opaque_type.snap │ │ │ │ │ ├── gleam_core__javascript__tests__type_alias__type_alias.snap │ │ │ │ │ ├── gleam_core__javascript__tests__use___arity_1.snap │ │ │ │ │ ├── gleam_core__javascript__tests__use___arity_2.snap │ │ │ │ │ ├── gleam_core__javascript__tests__use___arity_3.snap │ │ │ │ │ ├── gleam_core__javascript__tests__use___no_callback_body.snap │ │ │ │ │ └── gleam_core__javascript__tests__use___patterns.snap │ │ │ │ ├── strings.rs │ │ │ │ ├── todo.rs │ │ │ │ ├── tuples.rs │ │ │ │ ├── type_alias.rs │ │ │ │ └── use_.rs │ │ │ ├── tests.rs │ │ │ └── typescript.rs │ │ ├── javascript.rs │ │ ├── lib.rs │ │ ├── line_numbers.rs │ │ ├── manifest.rs │ │ ├── metadata/ │ │ │ └── tests.rs │ │ ├── metadata.rs │ │ ├── package_interface/ │ │ │ ├── snapshots/ │ │ │ │ ├── gleam_core__package_interface__tests__constructors_with_documentation.snap │ │ │ │ ├── gleam_core__package_interface__tests__generic_function.snap │ │ │ │ ├── gleam_core__package_interface__tests__imported_aliased_type_keeps_original_name.snap │ │ │ │ ├── gleam_core__package_interface__tests__imported_type.snap │ │ │ │ ├── gleam_core__package_interface__tests__internal_definitions_are_not_included.snap │ │ │ │ ├── gleam_core__package_interface__tests__internal_modules_are_not_exported.snap │ │ │ │ ├── gleam_core__package_interface__tests__labelled_function_parameters.snap │ │ │ │ ├── gleam_core__package_interface__tests__multiple_type_variables.snap │ │ │ │ ├── gleam_core__package_interface__tests__opaque_constructors_are_not_exposed.snap │ │ │ │ ├── gleam_core__package_interface__tests__package_documentation_is_included.snap │ │ │ │ ├── gleam_core__package_interface__tests__prelude_types.snap │ │ │ │ ├── gleam_core__package_interface__tests__private_definitions_are_not_included.snap │ │ │ │ ├── gleam_core__package_interface__tests__type_aliases.snap │ │ │ │ ├── gleam_core__package_interface__tests__type_constructors.snap │ │ │ │ └── gleam_core__package_interface__tests__type_definition.snap │ │ │ └── tests.rs │ │ ├── package_interface.rs │ │ ├── parse/ │ │ │ ├── error.rs │ │ │ ├── extra.rs │ │ │ ├── lexer.rs │ │ │ ├── snapshots/ │ │ │ │ ├── gleam_core__parse__tests__append_to_const_list.snap │ │ │ │ ├── gleam_core__parse__tests__argument_scope.snap │ │ │ │ ├── gleam_core__parse__tests__arithmetic_in_guards.snap │ │ │ │ ├── gleam_core__parse__tests__assert_statement.snap │ │ │ │ ├── gleam_core__parse__tests__assert_statement_followed_by_statement.snap │ │ │ │ ├── gleam_core__parse__tests__assert_statement_with_message.snap │ │ │ │ ├── gleam_core__parse__tests__assert_statement_without_expression.snap │ │ │ │ ├── gleam_core__parse__tests__assign_left_hand_side_of_concat_pattern.snap │ │ │ │ ├── gleam_core__parse__tests__assignment_pattern_invalid_bit_segment.snap │ │ │ │ ├── gleam_core__parse__tests__assignment_pattern_invalid_tuple.snap │ │ │ │ ├── gleam_core__parse__tests__attributes_with_improper_definition.snap │ │ │ │ ├── gleam_core__parse__tests__attributes_with_no_definition.snap │ │ │ │ ├── gleam_core__parse__tests__bare_expression.snap │ │ │ │ ├── gleam_core__parse__tests__bit_array_invalid_segment.snap │ │ │ │ ├── gleam_core__parse__tests__block_of_one.snap │ │ │ │ ├── gleam_core__parse__tests__block_of_two.snap │ │ │ │ ├── gleam_core__parse__tests__byte_order_mark.snap │ │ │ │ ├── gleam_core__parse__tests__byte_order_mark_module.snap │ │ │ │ ├── gleam_core__parse__tests__capture_with_name.snap │ │ │ │ ├── gleam_core__parse__tests__case_alternative_clause_no_subject.snap │ │ │ │ ├── gleam_core__parse__tests__case_clause_no_subject.snap │ │ │ │ ├── gleam_core__parse__tests__case_expression_without_body.snap │ │ │ │ ├── gleam_core__parse__tests__case_guard_with_empty_block.snap │ │ │ │ ├── gleam_core__parse__tests__case_guard_with_nested_blocks.snap │ │ │ │ ├── gleam_core__parse__tests__case_invalid_case_pattern.snap │ │ │ │ ├── gleam_core__parse__tests__case_invalid_expression.snap │ │ │ │ ├── gleam_core__parse__tests__case_list_pattern_after_spread.snap │ │ │ │ ├── gleam_core__parse__tests__const_invalid_bit_array_segment.snap │ │ │ │ ├── gleam_core__parse__tests__const_invalid_list.snap │ │ │ │ ├── gleam_core__parse__tests__const_invalid_record_constructor.snap │ │ │ │ ├── gleam_core__parse__tests__const_invalid_tuple.snap │ │ │ │ ├── gleam_core__parse__tests__const_record_update_all_fields.snap │ │ │ │ ├── gleam_core__parse__tests__const_record_update_basic.snap │ │ │ │ ├── gleam_core__parse__tests__const_record_update_only.snap │ │ │ │ ├── gleam_core__parse__tests__const_record_update_with_module.snap │ │ │ │ ├── gleam_core__parse__tests__const_string_concat.snap │ │ │ │ ├── gleam_core__parse__tests__const_string_concat_naked_right.snap │ │ │ │ ├── gleam_core__parse__tests__const_with_function_call.snap │ │ │ │ ├── gleam_core__parse__tests__const_with_function_call_with_args.snap │ │ │ │ ├── gleam_core__parse__tests__constant_inside_function.snap │ │ │ │ ├── gleam_core__parse__tests__correct_precedence_in_pattern_size.snap │ │ │ │ ├── gleam_core__parse__tests__deeply_nested_tuples.snap │ │ │ │ ├── gleam_core__parse__tests__deeply_nested_tuples_no_block.snap │ │ │ │ ├── gleam_core__parse__tests__deprecation_attribute_on_type_variant.snap │ │ │ │ ├── gleam_core__parse__tests__deprecation_without_message.snap │ │ │ │ ├── gleam_core__parse__tests__discard_left_hand_side_of_concat_pattern.snap │ │ │ │ ├── gleam_core__parse__tests__doesnt_issue_special_error_for_pythonic_import_if_slash.snap │ │ │ │ ├── gleam_core__parse__tests__dot_access_function_call_in_case_clause_guard.snap │ │ │ │ ├── gleam_core__parse__tests__echo_at_start_of_pipeline_wraps_the_whole_thing.snap │ │ │ │ ├── gleam_core__parse__tests__echo_cannot_have_an_expression_in_a_pipeline.snap │ │ │ │ ├── gleam_core__parse__tests__echo_followed_by_expression_ends_where_expression_ends.snap │ │ │ │ ├── gleam_core__parse__tests__echo_has_lower_precedence_than_binop.snap │ │ │ │ ├── gleam_core__parse__tests__echo_has_lower_precedence_than_pipeline.snap │ │ │ │ ├── gleam_core__parse__tests__echo_in_a_pipeline.snap │ │ │ │ ├── gleam_core__parse__tests__echo_with_assert_and_message_1.snap │ │ │ │ ├── gleam_core__parse__tests__echo_with_assert_and_message_2.snap │ │ │ │ ├── gleam_core__parse__tests__echo_with_assert_and_messages_1.snap │ │ │ │ ├── gleam_core__parse__tests__echo_with_assert_and_messages_2.snap │ │ │ │ ├── gleam_core__parse__tests__echo_with_assert_and_messages_3.snap │ │ │ │ ├── gleam_core__parse__tests__echo_with_block.snap │ │ │ │ ├── gleam_core__parse__tests__echo_with_complex_expression.snap │ │ │ │ ├── gleam_core__parse__tests__echo_with_let_assert_and_message.snap │ │ │ │ ├── gleam_core__parse__tests__echo_with_let_assert_and_messages.snap │ │ │ │ ├── gleam_core__parse__tests__echo_with_no_expressions_after_it.snap │ │ │ │ ├── gleam_core__parse__tests__echo_with_no_expressions_after_it_but_a_message.snap │ │ │ │ ├── gleam_core__parse__tests__echo_with_panic.snap │ │ │ │ ├── gleam_core__parse__tests__echo_with_panic_and_message.snap │ │ │ │ ├── gleam_core__parse__tests__echo_with_panic_and_messages.snap │ │ │ │ ├── gleam_core__parse__tests__echo_with_simple_expression_1.snap │ │ │ │ ├── gleam_core__parse__tests__echo_with_simple_expression_2.snap │ │ │ │ ├── gleam_core__parse__tests__error_message_on_variable_starting_with_underscore.snap │ │ │ │ ├── gleam_core__parse__tests__error_message_on_variable_starting_with_underscore2.snap │ │ │ │ ├── gleam_core__parse__tests__external_attribute_on_type_variant.snap │ │ │ │ ├── gleam_core__parse__tests__external_attribute_with_custom_type.snap │ │ │ │ ├── gleam_core__parse__tests__external_attribute_with_non_fn_definition.snap │ │ │ │ ├── gleam_core__parse__tests__float_empty_exponent.snap │ │ │ │ ├── gleam_core__parse__tests__function_call_in_case_clause_guard.snap │ │ │ │ ├── gleam_core__parse__tests__function_definition_angle_generics_error.snap │ │ │ │ ├── gleam_core__parse__tests__function_inside_a_type.snap │ │ │ │ ├── gleam_core__parse__tests__function_invalid_signature.snap │ │ │ │ ├── gleam_core__parse__tests__function_type_invalid_param_type.snap │ │ │ │ ├── gleam_core__parse__tests__if_like_expression.snap │ │ │ │ ├── gleam_core__parse__tests__import_type.snap │ │ │ │ ├── gleam_core__parse__tests__incomplete_function.snap │ │ │ │ ├── gleam_core__parse__tests__inner_single_quote_parses.snap │ │ │ │ ├── gleam_core__parse__tests__internal_attribute_on_type_variant.snap │ │ │ │ ├── gleam_core__parse__tests__invalid_label_shorthand.snap │ │ │ │ ├── gleam_core__parse__tests__invalid_label_shorthand_2.snap │ │ │ │ ├── gleam_core__parse__tests__invalid_label_shorthand_3.snap │ │ │ │ ├── gleam_core__parse__tests__invalid_label_shorthand_4.snap │ │ │ │ ├── gleam_core__parse__tests__invalid_label_shorthand_5.snap │ │ │ │ ├── gleam_core__parse__tests__invalid_left_paren_in_case_clause_guard.snap │ │ │ │ ├── gleam_core__parse__tests__invalid_pattern_label_shorthand.snap │ │ │ │ ├── gleam_core__parse__tests__invalid_pattern_label_shorthand_2.snap │ │ │ │ ├── gleam_core__parse__tests__invalid_pattern_label_shorthand_3.snap │ │ │ │ ├── gleam_core__parse__tests__invalid_pattern_label_shorthand_4.snap │ │ │ │ ├── gleam_core__parse__tests__invalid_pattern_label_shorthand_5.snap │ │ │ │ ├── gleam_core__parse__tests__list_spread_as_first_item_followed_by_other_items.snap │ │ │ │ ├── gleam_core__parse__tests__list_spread_followed_by_extra_item_and_another_spread.snap │ │ │ │ ├── gleam_core__parse__tests__list_spread_followed_by_extra_items.snap │ │ │ │ ├── gleam_core__parse__tests__list_spread_followed_by_other_spread.snap │ │ │ │ ├── gleam_core__parse__tests__list_spread_with_no_tail_in_the_middle_of_a_list.snap │ │ │ │ ├── gleam_core__parse__tests__missing_constructor_arguments.snap │ │ │ │ ├── gleam_core__parse__tests__missing_target.snap │ │ │ │ ├── gleam_core__parse__tests__missing_target_and_bracket.snap │ │ │ │ ├── gleam_core__parse__tests__missing_type_constructor_arguments_in_type_definition.snap │ │ │ │ ├── gleam_core__parse__tests__multiple_deprecation_attribute_on_type_variant.snap │ │ │ │ ├── gleam_core__parse__tests__multiple_deprecation_attributes.snap │ │ │ │ ├── gleam_core__parse__tests__multiple_external_for_same_project_erlang.snap │ │ │ │ ├── gleam_core__parse__tests__multiple_external_for_same_project_javascript.snap │ │ │ │ ├── gleam_core__parse__tests__multiple_internal_attributes.snap │ │ │ │ ├── gleam_core__parse__tests__multiple_unsupported_attributes_on_type_variant.snap │ │ │ │ ├── gleam_core__parse__tests__nested_block.snap │ │ │ │ ├── gleam_core__parse__tests__nested_tuple_access_after_function.snap │ │ │ │ ├── gleam_core__parse__tests__nested_tuples.snap │ │ │ │ ├── gleam_core__parse__tests__nested_tuples_no_block.snap │ │ │ │ ├── gleam_core__parse__tests__no_eq_after_binding_snapshot_1.snap │ │ │ │ ├── gleam_core__parse__tests__no_eq_after_binding_snapshot_2.snap │ │ │ │ ├── gleam_core__parse__tests__no_let_binding_snapshot_1.snap │ │ │ │ ├── gleam_core__parse__tests__no_let_binding_snapshot_2.snap │ │ │ │ ├── gleam_core__parse__tests__no_let_binding_snapshot_3.snap │ │ │ │ ├── gleam_core__parse__tests__non_module_level_function_with_a_name.snap │ │ │ │ ├── gleam_core__parse__tests__non_module_level_function_with_not_a_name.snap │ │ │ │ ├── gleam_core__parse__tests__operator_in_pattern_size.snap │ │ │ │ ├── gleam_core__parse__tests__panic_with_echo.snap │ │ │ │ ├── gleam_core__parse__tests__panic_with_echo_and_message.snap │ │ │ │ ├── gleam_core__parse__tests__prepend_no_elements_to_const_list.snap │ │ │ │ ├── gleam_core__parse__tests__prepend_to_const_list_with_multiple_spreads.snap │ │ │ │ ├── gleam_core__parse__tests__prepend_to_const_list_with_no_tail.snap │ │ │ │ ├── gleam_core__parse__tests__prepend_to_const_list_without_comma.snap │ │ │ │ ├── gleam_core__parse__tests__private_internal_const.snap │ │ │ │ ├── gleam_core__parse__tests__private_internal_function.snap │ │ │ │ ├── gleam_core__parse__tests__private_internal_type.snap │ │ │ │ ├── gleam_core__parse__tests__private_internal_type_alias.snap │ │ │ │ ├── gleam_core__parse__tests__private_opaque_type_is_parsed.snap │ │ │ │ ├── gleam_core__parse__tests__pub_function_inside_a_type.snap │ │ │ │ ├── gleam_core__parse__tests__record_access_no_label.snap │ │ │ │ ├── gleam_core__parse__tests__repeated_echos.snap │ │ │ │ ├── gleam_core__parse__tests__reserved_auto.snap │ │ │ │ ├── gleam_core__parse__tests__reserved_delegate.snap │ │ │ │ ├── gleam_core__parse__tests__reserved_derive.snap │ │ │ │ ├── gleam_core__parse__tests__reserved_echo.snap │ │ │ │ ├── gleam_core__parse__tests__reserved_else.snap │ │ │ │ ├── gleam_core__parse__tests__reserved_implement.snap │ │ │ │ ├── gleam_core__parse__tests__reserved_macro.snap │ │ │ │ ├── gleam_core__parse__tests__reserved_test.snap │ │ │ │ ├── gleam_core__parse__tests__semicolons.snap │ │ │ │ ├── gleam_core__parse__tests__special_error_for_pythonic_import.snap │ │ │ │ ├── gleam_core__parse__tests__special_error_for_pythonic_neste_import.snap │ │ │ │ ├── gleam_core__parse__tests__string_concatenation_in_case_clause_guard.snap │ │ │ │ ├── gleam_core__parse__tests__string_single_char_suggestion.snap │ │ │ │ ├── gleam_core__parse__tests__target_attribute_on_type_variant.snap │ │ │ │ ├── gleam_core__parse__tests__tuple_invalid_expr.snap │ │ │ │ ├── gleam_core__parse__tests__tuple_without_hash.snap │ │ │ │ ├── gleam_core__parse__tests__type_angle_generics_definition_error.snap │ │ │ │ ├── gleam_core__parse__tests__type_angle_generics_definition_error_fallback.snap │ │ │ │ ├── gleam_core__parse__tests__type_angle_generics_definition_with_upname_error.snap │ │ │ │ ├── gleam_core__parse__tests__type_angle_generics_usage_with_module_error.snap │ │ │ │ ├── gleam_core__parse__tests__type_angle_generics_usage_without_module_error.snap │ │ │ │ ├── gleam_core__parse__tests__type_invalid_constructor.snap │ │ │ │ ├── gleam_core__parse__tests__type_invalid_constructor_arg.snap │ │ │ │ ├── gleam_core__parse__tests__type_invalid_record.snap │ │ │ │ ├── gleam_core__parse__tests__type_invalid_record_constructor.snap │ │ │ │ ├── gleam_core__parse__tests__type_invalid_record_constructor_invalid_field_type.snap │ │ │ │ ├── gleam_core__parse__tests__type_invalid_record_constructor_without_field_type.snap │ │ │ │ ├── gleam_core__parse__tests__type_invalid_type_name.snap │ │ │ │ ├── gleam_core__parse__tests__unknown_attribute.snap │ │ │ │ ├── gleam_core__parse__tests__unknown_external_target.snap │ │ │ │ ├── gleam_core__parse__tests__unknown_target.snap │ │ │ │ ├── gleam_core__parse__tests__use_invalid_assignments.snap │ │ │ │ ├── gleam_core__parse__tests__valueless_list_spread_expression.snap │ │ │ │ ├── gleam_core__parse__tests__with_let_binding3.snap │ │ │ │ ├── gleam_core__parse__tests__with_let_binding3_and_annotation.snap │ │ │ │ ├── gleam_core__parse__tests__wrong_function_return_type_declaration_using_colon_instead_of_right_arrow.snap │ │ │ │ ├── gleam_core__parse__tests__wrong_record_access_pattern.snap │ │ │ │ └── gleam_core__parse__tests__wrong_type_of_comments_with_hash.snap │ │ │ ├── tests.rs │ │ │ └── token.rs │ │ ├── parse.rs │ │ ├── paths.rs │ │ ├── pretty/ │ │ │ └── tests.rs │ │ ├── pretty.rs │ │ ├── reference.rs │ │ ├── requirement.rs │ │ ├── snapshots/ │ │ │ ├── gleam_core__config__barebones_package_config_to_json.snap │ │ │ ├── gleam_core__config__deny_extra_deps_properties.snap │ │ │ ├── gleam_core__config__name_with_dash.snap │ │ │ ├── gleam_core__config__name_with_number_start.snap │ │ │ ├── gleam_core__config__package_config_to_json.snap │ │ │ ├── gleam_core__dependency__tests__resolution_error_message.snap │ │ │ ├── gleam_core__docs__barebones_package_config_to_json.snap │ │ │ ├── gleam_core__docs__package_config_to_json.snap │ │ │ └── gleam_core__requirement__tests__read_wrong_version.snap │ │ ├── strings.rs │ │ ├── type_/ │ │ │ ├── environment.rs │ │ │ ├── error.rs │ │ │ ├── expression.rs │ │ │ ├── fields.rs │ │ │ ├── hydrator.rs │ │ │ ├── pattern.rs │ │ │ ├── pipe.rs │ │ │ ├── prelude.rs │ │ │ ├── pretty.rs │ │ │ ├── printer.rs │ │ │ ├── snapshots/ │ │ │ │ ├── gleam_core__type___tests__const_record_update_all_fields.snap │ │ │ │ ├── gleam_core__type___tests__const_record_update_field_type_mismatch_error.snap │ │ │ │ ├── gleam_core__type___tests__const_record_update_fieldless_warning.snap │ │ │ │ ├── gleam_core__type___tests__const_record_update_non_record.snap │ │ │ │ ├── gleam_core__type___tests__const_record_update_nonexistent_field.snap │ │ │ │ ├── gleam_core__type___tests__const_record_update_type_mismatch_error.snap │ │ │ │ ├── gleam_core__type___tests__const_record_update_unlabelled_fields.snap │ │ │ │ ├── gleam_core__type___tests__const_record_update_variant_mismatch_error.snap │ │ │ │ ├── gleam_core__type___tests__const_record_update_variant_without_args.snap │ │ │ │ ├── gleam_core__type___tests__correct_type_check_for_multiple_mutually_recursive_functions.snap │ │ │ │ ├── gleam_core__type___tests__function_parameter_errors_do_not_stop_inference.snap │ │ │ │ ├── gleam_core__type___tests__generic_unlabelled_field_in_updated_const_record_wrong_type.snap │ │ │ │ ├── gleam_core__type___tests__pipe_with_annonymous_unannotated_functions_wrong_arity1.snap │ │ │ │ ├── gleam_core__type___tests__pipe_with_annonymous_unannotated_functions_wrong_arity2.snap │ │ │ │ ├── gleam_core__type___tests__pipe_with_annonymous_unannotated_functions_wrong_arity3.snap │ │ │ │ ├── gleam_core__type___tests__prepend_constant_list_wrong_element_type.snap │ │ │ │ ├── gleam_core__type___tests__prepend_constant_list_wrong_type.snap │ │ │ │ ├── gleam_core__type___tests__private_types_not_available_in_other_modules.snap │ │ │ │ ├── gleam_core__type___tests__record_update_variant_inference_fails_for_several_possible_variants.snap │ │ │ │ ├── gleam_core__type___tests__record_update_variant_inference_fails_for_several_possible_variants_on_subject_variable.snap │ │ │ │ ├── gleam_core__type___tests__string_concat_ko_1.snap │ │ │ │ ├── gleam_core__type___tests__string_concat_ko_2.snap │ │ │ │ ├── gleam_core__type___tests__type_unification_does_not_allow_different_variants_to_be_treated_as_safe.snap │ │ │ │ ├── gleam_core__type___tests__type_unification_does_not_allow_lowercase_bools_in_match_clause.snap │ │ │ │ ├── gleam_core__type___tests__type_unification_does_not_cause_false_positives_for_variant_matching.snap │ │ │ │ ├── gleam_core__type___tests__type_unification_removes_inferred_variant_in_functions.snap │ │ │ │ ├── gleam_core__type___tests__type_unification_removes_inferred_variant_in_nested_type.snap │ │ │ │ ├── gleam_core__type___tests__type_unification_removes_inferred_variant_in_tuples.snap │ │ │ │ ├── gleam_core__type___tests__unlabelled_argument_not_allowed_after_labelled_argument.snap │ │ │ │ ├── gleam_core__type___tests__variant_inference_does_not_escape_clause_scope.snap │ │ │ │ └── gleam_core__type___tests__variant_inference_on_literal_record.snap │ │ │ ├── tests/ │ │ │ │ ├── accessors.rs │ │ │ │ ├── assert.rs │ │ │ │ ├── assignments.rs │ │ │ │ ├── conditional_compilation.rs │ │ │ │ ├── custom_types.rs │ │ │ │ ├── dead_code_detection.rs │ │ │ │ ├── echo.rs │ │ │ │ ├── errors.rs │ │ │ │ ├── exhaustiveness.rs │ │ │ │ ├── externals.rs │ │ │ │ ├── functions.rs │ │ │ │ ├── guards.rs │ │ │ │ ├── imports.rs │ │ │ │ ├── let_assert.rs │ │ │ │ ├── pipes.rs │ │ │ │ ├── pretty.rs │ │ │ │ ├── snapshots/ │ │ │ │ │ ├── gleam_core__type___tests__assert__bool_literal.snap │ │ │ │ │ ├── gleam_core__type___tests__assert__comparison_on_literals.snap │ │ │ │ │ ├── gleam_core__type___tests__assert__equality_check_on_literals.snap │ │ │ │ │ ├── gleam_core__type___tests__assert__mismatched_types.snap │ │ │ │ │ ├── gleam_core__type___tests__assert__negation_of_bool_literal.snap │ │ │ │ │ ├── gleam_core__type___tests__assert__wrong_message_type.snap │ │ │ │ │ ├── gleam_core__type___tests__custom_types__conflict_with_import.snap │ │ │ │ │ ├── gleam_core__type___tests__custom_types__depreacted_type_deprecate_varient_err.snap │ │ │ │ │ ├── gleam_core__type___tests__custom_types__deprecated_all_varients_type.snap │ │ │ │ │ ├── gleam_core__type___tests__custom_types__deprecated_type.snap │ │ │ │ │ ├── gleam_core__type___tests__custom_types__deprecated_varients_type.snap │ │ │ │ │ ├── gleam_core__type___tests__custom_types__duplicate_variable_error_does_not_stop_analysis.snap │ │ │ │ │ ├── gleam_core__type___tests__custom_types__fault_tolerance.snap │ │ │ │ │ ├── gleam_core__type___tests__custom_types__pattern_match_correct_labeled_field.snap │ │ │ │ │ ├── gleam_core__type___tests__custom_types__pattern_match_correct_pos_field.snap │ │ │ │ │ ├── gleam_core__type___tests__dead_code_detection__constant_only_referenced_by_unused_constant.snap │ │ │ │ │ ├── gleam_core__type___tests__dead_code_detection__constant_only_referenced_by_unused_function.snap │ │ │ │ │ ├── gleam_core__type___tests__dead_code_detection__constructor_used_if_type_alias_shadows_it.snap │ │ │ │ │ ├── gleam_core__type___tests__dead_code_detection__imported_module_alias_only_referenced_by_unused_function.snap │ │ │ │ │ ├── gleam_core__type___tests__dead_code_detection__imported_module_alias_only_referenced_by_unused_function_with_unqualified.snap │ │ │ │ │ ├── gleam_core__type___tests__dead_code_detection__imported_module_marked_unused_when_shadowed_in_record_access.snap │ │ │ │ │ ├── gleam_core__type___tests__dead_code_detection__imported_module_only_referenced_by_unused_function.snap │ │ │ │ │ ├── gleam_core__type___tests__dead_code_detection__imported_type_and_constructor_with_same_name.snap │ │ │ │ │ ├── gleam_core__type___tests__dead_code_detection__imported_type_and_constructor_with_same_name2.snap │ │ │ │ │ ├── gleam_core__type___tests__dead_code_detection__imported_type_only_referenced_by_unused_function.snap │ │ │ │ │ ├── gleam_core__type___tests__dead_code_detection__imported_value_only_referenced_by_unused_function.snap │ │ │ │ │ ├── gleam_core__type___tests__dead_code_detection__local_variable_marked_unused_when_shadowed_in_module_access.snap │ │ │ │ │ ├── gleam_core__type___tests__dead_code_detection__private_type_alias_only_referenced_by_unused_function.snap │ │ │ │ │ ├── gleam_core__type___tests__dead_code_detection__private_type_alias_underlying_type_referenced_by_public_function.snap │ │ │ │ │ ├── gleam_core__type___tests__dead_code_detection__shadowed_imported_value_marked_unused.snap │ │ │ │ │ ├── gleam_core__type___tests__dead_code_detection__type_and_variant_unused.snap │ │ │ │ │ ├── gleam_core__type___tests__dead_code_detection__type_variant_only_referenced_by_unused_function.snap │ │ │ │ │ ├── gleam_core__type___tests__dead_code_detection__unused_mutually_recursive_functions.snap │ │ │ │ │ ├── gleam_core__type___tests__dead_code_detection__unused_recursive_function.snap │ │ │ │ │ ├── gleam_core__type___tests__dead_code_detection__unused_type_alias.snap │ │ │ │ │ ├── gleam_core__type___tests__dead_code_detection__used_shadowed_imported_value.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__access_int.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__accessor_multiple_variants_multiple_positions.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__accessor_multiple_variants_multiple_positions2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__add_f_int_float.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__add_int_float.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__add_on_strings.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__ambiguous_import_error_no_unqualified.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__ambiguous_import_error_with_unqualified.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__ambiguous_type_error.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__assigned_function_annotation.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_binary.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_bits_option_in_value.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_float.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_float_size.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_guard.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_invalid_type.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_conflicting_endianness1.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_conflicting_endianness2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_conflicting_options_bit_array.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_conflicting_options_int.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_conflicting_signedness1.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_conflicting_signedness2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_nosize.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_nosize2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_nosize3.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_size.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_size2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_aliased_variable_string.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_size_utf16.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_size_utf32.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_size_utf8.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_unit_codepoint_utf16.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_unit_codepoint_utf16_2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_unit_codepoint_utf32.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_unit_codepoint_utf32_2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_unit_codepoint_utf8.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_unit_codepoint_utf8_2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_unit_utf16.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_unit_utf32.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_unit_utf8_2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_variable_string.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_unit_no_size.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_segment_unit_unit.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_size_not_int.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_size_not_int_variable.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_using_pattern_variables.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_using_pattern_variables_from_other_bit_array.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_utf8_and_size.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_array_utf8_and_unit.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_arrays2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_arrays3.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__bit_arrays4.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case10.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case11.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case12.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case13.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case14.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case15.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case16.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case17.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case18.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case19.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case20.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case3.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case4.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case5.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case6.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case7.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case8.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case9.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case_clause_mismatch.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case_clause_pipe_diagnostic.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case_could_not_unify.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case_int_tuple_guard.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case_list_guard.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case_operator_unify_situation.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case_subject_pattern_unify.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case_subject_pattern_unify_2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case_tuple_guard.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__case_tuple_guard_2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__const_annotation_wrong.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__const_annotation_wrong_2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__const_annotation_wrong_3.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__const_annotation_wrong_4.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__const_heterogenus_list.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__const_multiple_errors_are_local_with_annotation.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__const_multiple_errors_are_local_with_inferred_value.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__const_multiple_errors_are_local_with_unbound_value.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__const_multiple_errors_invalid_annotation.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__const_multiple_errors_invalid_annotation_and_value.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__const_multiple_errors_invalid_unannotated_value.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__const_multiple_errors_invalid_value.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__const_multiple_errors_mismatched_types.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__const_string_concat_invalid_type.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__const_usage_wrong.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__constructor_that_does_not_exist_does_not_produce_error_for_labelled_args.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__correct_pipe_arity_error_location.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__custom_type_module_constants.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__do_not_suggest_ignored_variable_outside_of_current_scope.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__double_assignment_in_bit_array.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_alias_names.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_anon_function_arguments.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_const_and_function_names_const_fn.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_const_const.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_const_extfn.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_const_fn.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_const_names.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_constructors.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_constructors2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_constructors3.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_custom_type_names.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_extfn_const.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_extfn_extfn.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_extfn_fn.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_fields_in_record_update_reports_error.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_fn_const.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_fn_extfn.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_fn_fn.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_function_names.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_function_names_2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_function_names_3.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_function_names_4.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_function_names_5.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_label_shorthands_in_record_pattern.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_module_function_arguments.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_var_in_record_pattern.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_vars.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_vars_2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__duplicate_vars_3.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__echo_followed_by_invalid_message.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__echo_followed_by_no_expression.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__echo_followed_by_no_expression_10.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__echo_followed_by_no_expression_2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__echo_followed_by_no_expression_3.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__echo_followed_by_no_expression_4.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__echo_followed_by_no_expression_5.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__echo_followed_by_no_expression_6.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__echo_followed_by_no_expression_7.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__echo_followed_by_no_expression_8.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__echo_followed_by_no_expression_9.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__echo_followed_by_no_expression_and_invalid_message.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__echo_followed_by_no_expression_and_message.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__error_for_missing_type_parameters.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__expression_constructor_update.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__external_annotation_on_custom_type_with_constructors.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__extra_var_inalternative.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__extra_var_inalternative2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__extra_var_inalternative3.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__fault_tolerant_list.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__fault_tolerant_list_tail.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__fault_tolerant_negate_bool.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__fault_tolerant_negate_int.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__fault_tolerant_tuple.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__field_not_in_all_variants.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__field_not_in_any_variant.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__field_type_different_between_variants.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__float_gtf_int.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__float_operator_on_ints.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__float_operator_on_ints_2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__float_operator_on_ints_in_case_guard.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__fn0_eq_fn1.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__function_arg_and_return_annotation.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__function_return_annotation.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__function_return_annotation_mismatch_with_pipe.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__function_that_does_not_exist_does_not_produce_error_for_labelled_args.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__functions_called_outside_module.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__generic_unlabelled_field_in_updated_record_wrong_type.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__guard_float_int_eq_vars.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__guard_if_float.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__guard_int_float_eq_vars.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__guard_record_wrong_arity.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__hint_for_method_call.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__incomplete_pattern_does_not_show_structure_of_internal_type_outside_of_its_module.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__incomplete_pattern_does_not_show_structure_of_internal_type_outside_of_its_module_2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__incorrect_arity_error.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__incorrect_arity_error_2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__inexhaustive_use_reports_error.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__inferred_variant_record_update_change_type_parameter_different_branches.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__int_eq_float.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__int_float_list.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__int_gt_float.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__int_operator_on_floats.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__int_operator_on_floats_2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__int_operator_on_floats_in_case_guard.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_bit_array_pattern_discard_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_bit_array_pattern_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_case_variable_discard_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_case_variable_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_const_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_constructor_arg_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_constructor_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_constructor_pattern_discard_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_constructor_pattern_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_custom_type_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_function_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_function_type_parameter_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_list_pattern_discard_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_list_pattern_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_parameter_discard_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_parameter_discard_name2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_parameter_discard_name3.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_parameter_label.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_parameter_label2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_parameter_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_parameter_name2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_parameter_name3.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_pattern_assignment_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_pattern_label_shorthand.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_string_prefix_pattern_alias.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_string_prefix_pattern_discard_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_string_prefix_pattern_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_tuple_pattern_discard_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_tuple_pattern_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_type_alias_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_type_alias_parameter_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_type_parameter_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_use_discard_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_use_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_variable_discard_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__invalid_variable_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__leak_multiple_private_types.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__let_assert_binding_cannot_be_used_in_panic_message.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__list.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__mismatched_list_tail.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__missing_case_body.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__missing_type_constructor_arguments_in_type_annotation_1.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__missing_type_constructor_arguments_in_type_annotation_2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__missing_variable_in_alternative_pattern.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__module_arity_error.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__module_could_not_unify.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__module_could_not_unify10.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__module_could_not_unify11.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__module_could_not_unify12.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__module_could_not_unify2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__module_could_not_unify3.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__module_could_not_unify4.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__module_could_not_unify5.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__module_could_not_unify6.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__module_could_not_unify7.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__module_could_not_unify8.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__module_could_not_unify9.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__module_non_local_gaurd_var.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__module_private_type_leak_1.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__module_private_type_leak_2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__module_private_type_leak_3.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__module_private_type_leak_4.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__module_private_type_leak_5.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__module_private_type_leak_6.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__native_endianness_javascript_target.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__negate_boolean_as_integer.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__negate_float_as_integer.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__negate_string.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__negative_out_of_range_erlang_float.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__negative_out_of_range_erlang_float_in_const.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__negative_out_of_range_erlang_float_in_pattern.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__negative_size_pattern.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__no_crash_on_duplicate_definition.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__no_crash_on_duplicate_definition2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__no_crash_on_duplicate_record_fields.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__no_crash_on_record_update_when_constructor_definition_is_invalid.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__no_hint_for_non_method_call.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__no_note_about_reliable_access_if_the_accessed_type_has_a_single_variant.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__non_utf8_string_assignment.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__not_a_constructor_update.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__ok_2_args.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__out_of_range_erlang_float.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__out_of_range_erlang_float_in_const.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__out_of_range_erlang_float_in_pattern.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__pattern_with_incorrect_arity.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__pipe_arity_error.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__pipe_mismatch_error.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__pipe_value_type_mismatch_error.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__positional_argument_after_labelled.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__positional_argument_after_one_using_label_shorthand.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__private_opaque_type.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__qualified_type_invalid_operands.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__qualified_type_invalid_pipe_argument.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__qualified_type_mismatched_type_error.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__qualified_type_not_a_function.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__qualified_type_not_a_tuple.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__qualified_type_not_fn_in_use.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__qualified_type_similar_type_name.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__qualified_type_unification_error.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__qualified_type_unknown_field.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__qualified_type_use_fn_without_callback.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__record_access_on_inferred_variant_when_field_is_in_other_variants.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__record_update_compatible_fields_wrong_type.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__record_update_compatible_fields_wrong_variant.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__record_update_does_not_stop_at_first_invalid_field_1.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__record_update_does_not_stop_at_first_invalid_field_2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__record_update_does_not_stop_at_first_invalid_field_3.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__record_update_does_not_stop_at_first_invalid_field_4.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__record_update_does_not_stop_at_first_invalid_field_5.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__record_update_incompatible_but_linked_generics.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__record_update_unknown_variant.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__record_update_wrong_variant.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__record_update_wrong_variant_imported_type.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__recursive_var.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__remembering_record_field_when_type_checking_fails.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__same_imports_multiple_times.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__same_imports_multiple_times_1.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__same_imports_multiple_times_2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__same_imports_multiple_times_3.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__same_imports_multiple_times_4.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__same_imports_multiple_times_5.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__same_imports_multiple_times_6.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__same_imports_multiple_times_7.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__shadowed_fn_argument.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__shadowed_function_argument.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__shadowed_let_variable.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__shadowed_pattern_variable.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__show_only_missing_labels.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__src_importing_dev_dependency.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__subject_int_float_guard_tuple.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__suggest_unwrapping_a_result_when_types_match.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__suggest_wrapping_a_function_return_value_in_error.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__suggest_wrapping_a_function_return_value_in_ok.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__suggest_wrapping_a_use_returned_value_in_error.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__suggest_wrapping_a_use_returned_value_in_ok.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__suggest_wrapping_a_value_into_error_if_types_match.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__suggest_wrapping_a_value_into_error_if_types_match_2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__suggest_wrapping_a_value_into_ok_if_types_match.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__suggest_wrapping_a_value_into_ok_if_types_match_2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__suggest_wrapping_a_value_into_ok_if_types_match_with_block.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__suggest_wrapping_a_value_into_ok_if_types_match_with_multiline_result_in_block.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__suggest_wrapping_a_value_into_ok_with_generic_type.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__true_fn.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__tuple_2_3.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__tuple_arity.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__tuple_index_not_a_tuple.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__tuple_index_not_a_tuple_unbound.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__tuple_index_out_of_bounds.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__tuple_int.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__tuple_int_float.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__type_annotations.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__type_holes1.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__type_holes2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__type_holes3.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__type_holes4.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__type_imported_as_value.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__type_used_as_a_constructor_1.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__type_used_as_a_constructor_2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__type_used_as_a_constructor_with_more_arguments.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__type_variables_in_body.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__type_vars_must_be_declared.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unexpected_arg_with_label_shorthand.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unexpected_labelled_arg.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unexpected_labelled_arg_record_constructor.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_accessed_type.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_constructor_update.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_field.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_field_that_appears_in_a_variant_has_note.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_field_that_appears_in_an_imported_variant_has_note.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_field_that_does_not_appear_in_variant_has_no_note.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_field_update.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_field_update2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_imported_module_type.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_label.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_label_shorthand.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_module.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_module_suggest_import.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_module_suggest_typo_for_imported_module.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_module_suggest_typo_for_unimported_module.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_record_field.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_record_field_2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_type.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_type_in_alias.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_type_in_alias2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_type_var_in_alias2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_variable.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_variable_2.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_variable_3.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_variable_type.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unknown_variable_update.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__unnecessary_spread_operator.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__update_multi_variant_record.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__utf16_codepoint_javascript_target.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__utf32_codepoint_javascript_target.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__utf8_codepoint_javascript_target.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__value_imported_as_type.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__wrong_number_of_subjects.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__wrong_number_of_subjects_alternative_patterns.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__wrong_type_arg.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__wrong_type_ret.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__wrong_type_update.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__wrong_type_var.snap │ │ │ │ │ ├── gleam_core__type___tests__errors__zero_size_pattern.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__bit_array_1.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__bit_array_2.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__bit_array_bits_catches_everything.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__bit_array_bytes_needs_catch_all.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__bit_array_overlapping_patterns_are_redundant.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__bit_array_overlapping_redundant_patterns_with_variable_size.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__bit_array_overlapping_redundant_patterns_with_variable_size_2.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__bool_false.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__bool_true.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__case_error_prints_aliased_unqualified_value.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__case_error_prints_module_alias.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__case_error_prints_module_names.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__case_error_prints_module_when_aliased_and_shadowed.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__case_error_prints_module_when_shadowed.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__case_error_prints_prelude_module_unqualified.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__case_error_prints_prelude_module_when_shadowed.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__case_error_prints_unqualifed_when_aliased.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__case_error_prints_unqualified_value.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__compiler_does_not_crash_when_defining_duplicate_alternative_variables.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__compiler_does_not_crash_when_matching_on_utfcodepoint.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__correct_missing_patterns_for_opaque_type.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__correct_missing_patterns_for_opaque_type_in_definition_module.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__custom_1.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__custom_2.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__discard_2.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__discard_3.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__discard_4.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__discard_5.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__discard_6.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__duplicated_alternative_patterns.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__duplicated_pattern_in_alternative.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__duplicated_pattern_with_multiple_alternatives.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__empty_case_of_bool.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__empty_case_of_custom_type.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__empty_case_of_external.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__empty_case_of_float.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__empty_case_of_generic.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__empty_case_of_int.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__empty_case_of_list.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__empty_case_of_multi_pattern.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__empty_case_of_string.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__float_1.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__float_2.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__guard.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__guard_1.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__inexhaustive_multi_pattern.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__inexhaustive_multi_pattern2.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__inexhaustive_multi_pattern3.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__inexhaustive_multi_pattern4.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__inexhaustive_multi_pattern5.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__int_1.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__int_2.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__label_1.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__let_1.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__list_bool_1.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__list_bool_2.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__list_empty.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__list_non_empty.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__list_one.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__list_one_two.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__list_zero_one_two.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__list_zero_two_any.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__multiple_unreachable_prefix_patterns.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__multiple_unreachable_prefix_patterns_1.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__nested_type_parameter_usage.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__other_variant_unreachable_when_inferred.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__other_variant_unreachable_when_inferred2.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__redundant_1.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__redundant_2.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__redundant_3.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__redundant_4.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__redundant_5.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__redundant_float_scientific_notation.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__redundant_float_scientific_notation_and_underscore.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__redundant_float_with_different_formatting.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__redundant_float_with_no_trailing_decimal.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__redundant_float_with_underscore.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__redundant_int_with_multiple_underscores.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__redundant_int_with_underscores.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__redundant_missing_patterns.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__reference_absent_type.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__result_bool_1.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__result_bool_2.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__result_bool_3.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__result_bool_4.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__result_bool_5.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__result_bool_6.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__result_bool_7.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__result_bool_8.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__result_error.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__result_nil_error.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__result_nil_ok.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__result_ok.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__same_catch_all_bytes_are_redundant.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__string_1.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__string_2.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__string_3.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__tuple_0.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__unreachable_alternative_multi_pattern.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__unreachable_multi_pattern.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__unreachable_prefix_pattern_after_prefix.snap │ │ │ │ │ ├── gleam_core__type___tests__exhaustiveness__unreachable_string_pattern_after_prefix.snap │ │ │ │ │ ├── gleam_core__type___tests__externals__erlang_only_function_used_by_javascript_module.snap │ │ │ │ │ ├── gleam_core__type___tests__externals__erlang_only_function_with_erlang_external.snap │ │ │ │ │ ├── gleam_core__type___tests__externals__erlang_targeted_function_cant_contain_javascript_only_function.snap │ │ │ │ │ ├── gleam_core__type___tests__externals__imported_javascript_only_function.snap │ │ │ │ │ ├── gleam_core__type___tests__externals__javascript_only_constant.snap │ │ │ │ │ ├── gleam_core__type___tests__externals__javascript_only_function_used_by_erlang_module.snap │ │ │ │ │ ├── gleam_core__type___tests__externals__javascript_only_function_with_javascript_external.snap │ │ │ │ │ ├── gleam_core__type___tests__externals__javascript_targeted_function_cant_contain_erlang_only_function.snap │ │ │ │ │ ├── gleam_core__type___tests__externals__public_erlang_external.snap │ │ │ │ │ ├── gleam_core__type___tests__externals__public_javascript_external.snap │ │ │ │ │ ├── gleam_core__type___tests__externals__unsupported_target_for_unused_import.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__annotation_mismatch_function_fault_tolerance.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__bad_body_function_fault_tolerance.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__case_clause_guard_fault_tolerance.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__case_clause_pattern_fault_tolerance.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__case_clause_then_fault_tolerance.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__function_call_incorrect_arg_types_fault_tolerance.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__function_call_incorrect_arity_fault_tolerance.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__function_call_incorrect_arity_with_label_shorthand_fault_tolerance.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__function_call_incorrect_arity_with_label_shorthand_fault_tolerance2.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__function_call_incorrect_arity_with_labels_fault_tolerance.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__function_call_incorrect_arity_with_labels_fault_tolerance2.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__invalid_javascript_external_do_not_stop_analysis.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__multiple_bad_statement_assignment_fault_tolerance.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__multiple_bad_statement_assignment_with_annotation_fault_tolerance.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__multiple_bad_statement_assignment_with_annotation_fault_tolerance2.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__multiple_bad_statement_assignment_with_pattern_fault_tolerance2.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__multiple_bad_statement_expression_fault_tolerance.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__no_impl_function_fault_tolerance.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__provide_arg_type_to_fn_arg_infer_error.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__provide_arg_type_to_fn_explicit_error.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__provide_arg_type_to_fn_implicit_error.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__provide_arg_type_to_fn_not_a_tuple.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__provide_one_arg_type_to_two_args_fn.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__provide_two_args_type_to_fn_wrong_types.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__recursive_type.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__unlabelled_after_labelled.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__unlabelled_after_labelled_external.snap │ │ │ │ │ ├── gleam_core__type___tests__functions__unlabelled_after_labelled_with_type.snap │ │ │ │ │ ├── gleam_core__type___tests__guards__string_variable_access.snap │ │ │ │ │ ├── gleam_core__type___tests__imports__import_errors_do_not_block_analysis.snap │ │ │ │ │ ├── gleam_core__type___tests__imports__import_type_duplicate.snap │ │ │ │ │ ├── gleam_core__type___tests__imports__import_type_duplicate_with_as.snap │ │ │ │ │ ├── gleam_core__type___tests__imports__import_type_duplicate_with_as_multiline.snap │ │ │ │ │ ├── gleam_core__type___tests__imports__imported_constructor_instead_of_type.snap │ │ │ │ │ ├── gleam_core__type___tests__imports__module_alias_used_as_a_name.snap │ │ │ │ │ ├── gleam_core__type___tests__imports__unqualified_import_errors_do_not_block_later_unqualified.snap │ │ │ │ │ ├── gleam_core__type___tests__imports__unqualified_using_opaque_constructor.snap │ │ │ │ │ ├── gleam_core__type___tests__imports__unqualified_using_private_constructor.snap │ │ │ │ │ ├── gleam_core__type___tests__imports__unqualified_using_private_constructor_pattern.snap │ │ │ │ │ ├── gleam_core__type___tests__imports__unqualified_using_private_function.snap │ │ │ │ │ ├── gleam_core__type___tests__imports__using_opaque_constructor.snap │ │ │ │ │ ├── gleam_core__type___tests__imports__using_private_constructor.snap │ │ │ │ │ ├── gleam_core__type___tests__imports__using_private_constructor_pattern.snap │ │ │ │ │ ├── gleam_core__type___tests__imports__using_private_custom_type.snap │ │ │ │ │ ├── gleam_core__type___tests__imports__using_private_external_type.snap │ │ │ │ │ ├── gleam_core__type___tests__imports__using_private_function.snap │ │ │ │ │ ├── gleam_core__type___tests__imports__using_private_type_alias.snap │ │ │ │ │ ├── gleam_core__type___tests__imports__using_private_unqualified_custom_type.snap │ │ │ │ │ ├── gleam_core__type___tests__imports__using_private_unqualified_external_type.snap │ │ │ │ │ ├── gleam_core__type___tests__imports__using_private_unqualified_type_alias.snap │ │ │ │ │ ├── gleam_core__type___tests__let_assert__non_string_message.snap │ │ │ │ │ ├── gleam_core__type___tests__pipes__pipe_callback_wrong_arity.snap │ │ │ │ │ ├── gleam_core__type___tests__pretty__prelude_type_clash_custom_first.snap │ │ │ │ │ ├── gleam_core__type___tests__pretty__prelude_type_clash_prelude_first.snap │ │ │ │ │ ├── gleam_core__type___tests__pretty__repeated_prelude_type.snap │ │ │ │ │ ├── gleam_core__type___tests__target_implementations__function_with_no_valid_implementations.snap │ │ │ │ │ ├── gleam_core__type___tests__type_alias__alias_cycle.snap │ │ │ │ │ ├── gleam_core__type___tests__type_alias__alias_direct_cycle.snap │ │ │ │ │ ├── gleam_core__type___tests__type_alias__both_errors_are_shown.snap │ │ │ │ │ ├── gleam_core__type___tests__type_alias__conflict_with_import.snap │ │ │ │ │ ├── gleam_core__type___tests__type_alias__duplicate_parameter.snap │ │ │ │ │ ├── gleam_core__type___tests__type_alias__duplicate_variable_error_does_not_stop_analysis.snap │ │ │ │ │ ├── gleam_core__type___tests__type_alias__type_alias_error_does_not_stop_analysis.snap │ │ │ │ │ ├── gleam_core__type___tests__type_alias__unused_parameter.snap │ │ │ │ │ ├── gleam_core__type___tests__use___invalid_call_is_number.snap │ │ │ │ │ ├── gleam_core__type___tests__use___invalid_callback_type.snap │ │ │ │ │ ├── gleam_core__type___tests__use___invalid_callback_type_2.snap │ │ │ │ │ ├── gleam_core__type___tests__use___invalid_callback_type_3.snap │ │ │ │ │ ├── gleam_core__type___tests__use___invalid_callback_type_4.snap │ │ │ │ │ ├── gleam_core__type___tests__use___just_use_in_fn_body.snap │ │ │ │ │ ├── gleam_core__type___tests__use___multiple_bad_statement_use_fault_tolerance.snap │ │ │ │ │ ├── gleam_core__type___tests__use___no_callback_body.snap │ │ │ │ │ ├── gleam_core__type___tests__use___typed_pattern_wrong_type.snap │ │ │ │ │ ├── gleam_core__type___tests__use___use_with_function_that_doesnt_take_callback_as_last_arg_1.snap │ │ │ │ │ ├── gleam_core__type___tests__use___use_with_function_that_doesnt_take_callback_as_last_arg_2.snap │ │ │ │ │ ├── gleam_core__type___tests__use___use_with_function_that_doesnt_take_callback_as_last_arg_3.snap │ │ │ │ │ ├── gleam_core__type___tests__use___wrong_arity.snap │ │ │ │ │ ├── gleam_core__type___tests__use___wrong_arity_less_than_required.snap │ │ │ │ │ ├── gleam_core__type___tests__use___wrong_arity_less_than_required_2.snap │ │ │ │ │ ├── gleam_core__type___tests__use___wrong_arity_more_than_required.snap │ │ │ │ │ ├── gleam_core__type___tests__use___wrong_arity_more_than_required_2.snap │ │ │ │ │ ├── gleam_core__type___tests__use___wrong_callback_arg.snap │ │ │ │ │ ├── gleam_core__type___tests__use___wrong_callback_arg_2.snap │ │ │ │ │ ├── gleam_core__type___tests__use___wrong_callback_arg_3.snap │ │ │ │ │ ├── gleam_core__type___tests__use___wrong_callback_arg_with_wrong_annotation.snap │ │ │ │ │ ├── gleam_core__type___tests__use___wrong_callback_arity.snap │ │ │ │ │ ├── gleam_core__type___tests__use___wrong_callback_arity_2.snap │ │ │ │ │ ├── gleam_core__type___tests__use___wrong_callback_arity_3.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__aliased_module_used_by_unused_function_is_not_marked_as_unused.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__assert_on_impossible_to_reach_integer_segment.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__assert_on_inferred_variant.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__bit_array_negative_truncated_segment.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__bit_array_negative_truncated_segment_2.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__bit_array_truncated_segment.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__bit_array_truncated_segment_in_bytes.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__bit_array_truncated_segment_in_bytes_2.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__bool_assert_requires_v1_11.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__bool_literals_redundant_comparison.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__bool_literals_redundant_comparison_1.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__const_record_update_requires_v1_14_warning.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__constant_string_concatenation_requires_v1_4.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__constructing_anonymous_function_is_pure.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__deprecated_constant.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__deprecated_function.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__deprecated_imported_call_function.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__deprecated_imported_constant.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__deprecated_imported_function.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__deprecated_imported_unqualified_constant.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__deprecated_imported_unqualified_function.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__deprecated_list_append_syntax.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__deprecated_list_pattern_syntax.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__deprecated_list_pattern_syntax_1.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__deprecated_record_pattern_syntax.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__deprecated_record_pattern_syntax_with_label_shorthand.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__deprecated_record_pattern_syntax_with_no_labels.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__deprecated_target_shorthand_erlang.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__deprecated_target_shorthand_javascript.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__deprecated_type_used_as_arg.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__deprecated_type_used_as_case_clause.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__deprecated_type_used_in_alias.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__detached_doc_comment.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__different_records_0_redundant_comparison.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__different_records_1_redundant_comparison.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__different_records_2_redundant_comparison.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__different_records_3_redundant_comparison.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__doesnt_warn_twice_for_unreachable_code_if_has_already_warned_in_a_block_1.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__doesnt_warn_twice_for_unreachable_code_if_has_already_warned_in_a_block_2.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__double_unary_bool_literal.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__double_unary_bool_variable.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__double_unary_integer_literal.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__double_unary_integer_variable.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__echo_followed_by_panic.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__echo_followed_by_panicking_expression.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__empty_func_warning_test.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__empty_guard_clause.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__even_number_of_multiple_bool_negations_raise_a_single_warning.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__even_number_of_multiple_integer_negations_raise_a_single_warning.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__expression_in_expression_segment_size_requires_v1_12_warning.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__expression_in_pattern_segment_size_requires_v1_12_warning.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__external_annotation_on_custom_type_requires_v1_14.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__float_divide_in_guards_requires_v1_3.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__float_literals_redundant_comparison.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__float_literals_redundant_comparison_2.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__float_literals_redundant_comparison_3.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__float_literals_redundant_comparison_4.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__float_literals_redundant_comparison_5.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__float_literals_redundant_comparison_6.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__float_literals_redundant_comparison_7.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__float_literals_redundant_comparison_8.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__float_literals_redundant_comparison_different_repr.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__float_literals_redundant_comparison_different_repr_2.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__float_literals_redundant_comparison_infinity.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__float_literals_redundant_comparison_omitted_zero.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__float_literals_redundant_comparison_precision_loss.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__float_literals_redundant_comparison_signed_zero.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__float_minus_in_guards_requires_v1_3.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__float_multiplication_in_guards_requires_v1_3.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__float_plus_in_guards_requires_v1_3.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__function_is_impure_if_uses_todo.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__function_is_pure_on_erlang_if_external_on_js.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__function_is_pure_on_js_if_external_on_erlang.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__import_module_twice.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__importing_non_direct_dep_package.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__impossible_to_reach_integer_segment.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__impossible_to_reach_integer_segment_2.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__impossible_to_reach_integer_segment_3.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__impossible_to_reach_integer_segment_4.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__incomplete_code_block_raises_warning.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__int_divide_in_guards_requires_v1_3.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__int_literals_redundant_comparison.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__int_literals_redundant_comparison_2.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__int_literals_redundant_comparison_3.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__int_literals_redundant_comparison_4.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__int_literals_redundant_comparison_5.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__int_literals_redundant_comparison_6.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__int_literals_redundant_comparison_7.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__int_literals_redundant_comparison_8.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__int_minus_in_guards_requires_v1_3.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__int_multiplication_in_guards_requires_v1_3.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__int_plus_in_guards_requires_v1_3.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__int_remainder_in_guards_requires_v1_3.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__internal_annotation_on_constant_requires_v1_1.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__internal_annotation_on_function_requires_v1_1.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__internal_annotation_on_type_requires_v1_1.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__javascript_external_module_with_at_requires_v1_2.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__javascript_unsafe_int_binary.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__javascript_unsafe_int_decimal.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__javascript_unsafe_int_hex.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__javascript_unsafe_int_in_const.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__javascript_unsafe_int_in_const_tuple.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__javascript_unsafe_int_in_pattern.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__javascript_unsafe_int_in_tuple.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__javascript_unsafe_int_octal.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__javascript_unsafe_int_segment_in_bit_array.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__javascript_unsafe_int_segment_in_const_bit_array.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__javascript_unsafe_int_segment_size_in_bit_array.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__javascript_unsafe_int_segment_size_in_const_bit_array.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__javascript_unsafe_int_segment_size_in_pattern.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__javascript_unsafe_int_with_external_function_call.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__label_shorthand_in_call_requires_v1_4.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__label_shorthand_in_constand_requires_v1_4.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__label_shorthand_in_pattern_requires_v1_4.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__let_assert_with_message_requires_v1_7.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__list_literals_redundant_comparison.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__list_literals_redundant_comparison_2.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__list_literals_redundant_comparison_3.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__list_literals_redundant_comparison_4.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__list_literals_redundant_comparison_5.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__list_literals_redundant_comparison_7.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__missing_float_option_in_bit_array_constant_segment_requires_v1_10.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__missing_float_option_in_bit_array_pattern_segment_requires_v1_10.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__missing_float_option_in_bit_array_segment_requires_v1_10.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__missing_utf_8_option_in_bit_array_constant_segment_requires_v1_5.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__missing_utf_8_option_in_bit_array_pattern_segment_requires_v1_5.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__missing_utf_8_option_in_bit_array_segment_requires_v1_5.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__module_used_by_unused_function_is_not_marked_as_unused.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__multiple_impossible_to_reach_integer_segments.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__nested_tuple_access_requires_v1_1.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__odd_number_of_multiple_bool_negations_raise_a_single_warning_that_highlights_the_unnecessary_ones.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__odd_number_of_multiple_integer_negations_raise_a_single_warning_that_highlights_the_unnecessary_ones.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__opaque_external_type_raises_a_warning.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__panic_used_as_function.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__panic_used_as_function_2.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__panic_used_as_function_3.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__panic_used_as_function_inside_pipeline.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__pattern_matching_on_64_float_float_is_unreachable.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__pattern_matching_on_literal_empty_bit_array.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__pattern_matching_on_literal_empty_list.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__pattern_matching_on_literal_empty_tuple.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__pattern_matching_on_literal_float.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__pattern_matching_on_literal_int.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__pattern_matching_on_literal_list.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__pattern_matching_on_literal_list_with_tail.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__pattern_matching_on_literal_record.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__pattern_matching_on_literal_record_with_no_args.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__pattern_matching_on_literal_string.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__pattern_matching_on_literal_tuple.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__pattern_matching_on_multiple_literal_tuples.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__prefer_list_is_empty_over_0_eq_list_length.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__prefer_list_is_empty_over_0_lt_list_length.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__prefer_list_is_empty_over_0_not_eq_list_length.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__prefer_list_is_empty_over_list_length_eq_0.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__prefer_list_is_empty_over_list_length_eq_negative_0.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__prefer_list_is_empty_over_list_length_gt_0.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__prefer_list_is_empty_over_list_length_gt_negative_0.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__prefer_list_is_empty_over_list_length_lt_1.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__prefer_list_is_empty_over_list_length_lt_eq_0.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__prefer_list_is_empty_over_list_length_not_eq_0.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__prefer_list_is_empty_over_negative_0_eq_list_length.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__prefer_list_is_empty_over_negative_0_lt_list_length.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__pure_pipeline_raises_warning.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__pure_pipeline_with_many_steps_raises_warning.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__pure_standard_library_function.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__reachable_pattern_after_unreachable_equal_pattern.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__record_access_variant_inference_requires_v1_6.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__record_select_redundant_comparison.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__record_select_redundant_comparison_1.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__record_update_variant_inference_requires_v1_6.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__record_update_warnings_test2.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__record_update_warnings_test3.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__record_update_with_wrong_types_but_all_fields_produces_warning.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__redundant_function_capture_in_pipe_1.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__redundant_function_capture_in_pipe_2.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__redundant_function_capture_in_pipe_3.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__redundant_function_capture_in_pipe_4.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__redundant_let_assert.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__redundant_let_assert_on_custom_type.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__result_discard_warning_test.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__result_in_case_discarded.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__shadow_imported_constant.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__shadow_imported_function.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__string_literals_redundant_comparison.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__string_literals_redundant_comparison_1.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__todo_used_as_function.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__todo_used_as_function_2.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__todo_used_as_function_3.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__todo_warning_correct_location.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__todo_warning_test.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__todo_with_known_type.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unreachable_code_after_case_subject_panics_1.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unreachable_code_after_case_subject_panics_2.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unreachable_code_analysis_treats_anonymous_functions_independently_2.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unreachable_code_analysis_treats_anonymous_functions_independently_3.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unreachable_code_for_panic_as_first_pipeline_item.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unreachable_function_argument_if_panic_is_argument.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unreachable_function_call_if_panic_is_last_argument_1.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unreachable_function_call_if_panic_is_last_argument_2.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unreachable_int_pattern_with_prefix_int.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unreachable_int_pattern_with_string_of_same_value.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unreachable_string_pattern_with_different_encodings.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unreachable_use_after_panic.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unreachable_warning_1.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unreachable_warning_2.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unreachable_warning_doesnt_escape_out_of_a_block_if_panic_is_not_last.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unreachable_warning_for_panic_as_last_item_of_pipe_on_next_expression.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unreachable_warning_if_all_branches_panic.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unreachable_warning_if_all_branches_panic_2.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unreachable_warning_on_following_expression_if_panic_is_last_in_a_block.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_alias_for_duplicate_module_no_warning_for_alias_test.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_alias_warning_test.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_binary_operation_raises_a_warning.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_bit_array.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_block_wrapping_pure_expression.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_block_wrapping_pure_expressions.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_bool_negation_raises_a_warning.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_case_expression.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_destructure.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_discard_pattern.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_float.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_fn_function_call.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_function_literal_raises_a_warning.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_imported_module_warnings_test.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_imported_module_with_alias_and_unqualified_name_no_warnings_test.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_imported_module_with_alias_and_unqualified_name_warnings_test.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_imported_module_with_alias_warnings_test.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_int.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_int_negation_raises_a_warning.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_label_shorthand_pattern_arg.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_label_shorthand_pattern_arg_shadowing.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_list.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_module_select_const.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_module_select_constructor.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_module_select_constructor_call.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_module_select_function.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_module_wuth_alias_warning_test.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_pipeline_ending_with_pure_fn.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_pipeline_ending_with_variant_raises_a_warning.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_pipeline_ending_with_variant_raises_a_warning_2.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_private_const_warnings_test.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_private_fn_warnings_test.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_private_type_warnings_test.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_private_type_warnings_test3.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_private_type_warnings_test6.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_pure_function.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_pure_function_that_calls_other_pure_function.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_record_access_raises_a_warning.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_record_constructor_raises_a_warning.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_record_update_raises_a_warning.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_recursive_function_argument.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_recursive_function_argument_2.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_recursive_function_inside_anonymous_function.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_recursive_function_with_shadowing.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_string.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_tuple.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_tuple_index_raises_a_warning.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_variable_assignment_pattern.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_variable_raises_a_warning.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_variable_shadowing_test.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_variable_string_prefix_pattern.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_variable_string_prefix_pattern2.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_variable_warnings_test.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__unused_variable_warnings_test2.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__use_with_pure_fn_expression_is_marked_as_unused.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__variables_redundant_comparison.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__warning_many_at_same_time.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__warning_private_function_never_used.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__warning_variable_never_used_test.snap │ │ │ │ │ ├── gleam_core__type___tests__warnings__warnings_for_matches_on_literal_values_that_are_not_like_an_if_1.snap │ │ │ │ │ └── gleam_core__type___tests__warnings__warnings_for_matches_on_literal_values_that_are_not_like_an_if_2.snap │ │ │ │ ├── target_implementations.rs │ │ │ │ ├── type_alias.rs │ │ │ │ ├── use_.rs │ │ │ │ ├── version_inference.rs │ │ │ │ └── warnings.rs │ │ │ └── tests.rs │ │ ├── type_.rs │ │ ├── uid.rs │ │ ├── version.rs │ │ └── warning.rs │ └── templates/ │ ├── docs-css/ │ │ └── index.css │ ├── docs-js/ │ │ ├── highlightjs-gleam.js │ │ └── index.js │ ├── documentation_layout.html │ ├── documentation_module.html │ ├── documentation_page.html │ ├── echo.erl │ ├── echo.mjs │ ├── ejected.mk │ ├── gleam@@main.erl │ ├── prelude.d.mts │ └── prelude.mjs ├── compiler-wasm/ │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── lib.rs │ ├── tests.rs │ └── wasm_filesystem.rs ├── containers/ │ ├── elixir-alpine.dockerfile │ ├── elixir-slim.dockerfile │ ├── elixir.dockerfile │ ├── erlang-alpine.dockerfile │ ├── erlang-slim.dockerfile │ ├── erlang.dockerfile │ ├── node-alpine.dockerfile │ ├── node-slim.dockerfile │ ├── node.dockerfile │ └── scratch.dockerfile ├── deny.toml ├── docs/ │ ├── annoyances.md │ ├── compiler/ │ │ └── README.md │ ├── runtime-errors.md │ └── v2.md ├── gleam-bin/ │ ├── .cargo/ │ │ └── config.toml │ ├── Cargo.toml │ ├── build.rs │ └── src/ │ └── main.rs ├── hexpm/ │ ├── .github/ │ │ └── workflows/ │ │ └── ci.yml │ ├── .gitignore │ ├── CHANGELOG.md │ ├── CONTRIBUTING.md │ ├── Cargo.toml │ ├── LICENCE │ ├── README.md │ ├── build.rs │ ├── proto/ │ │ ├── names.proto │ │ ├── package.proto │ │ ├── signed.proto │ │ └── versions.proto │ ├── src/ │ │ ├── lib.rs │ │ ├── proto/ │ │ │ ├── package.rs │ │ │ ├── signed.rs │ │ │ └── versions.rs │ │ ├── proto.rs │ │ ├── tests.rs │ │ ├── version/ │ │ │ ├── lexer.rs │ │ │ ├── parser.rs │ │ │ └── tests.rs │ │ └── version.rs │ ├── test/ │ │ ├── package_exfmt │ │ ├── public_key │ │ └── versions │ └── versions ├── language-server/ │ ├── Cargo.toml │ └── src/ │ ├── code_action.rs │ ├── compiler.rs │ ├── completer.rs │ ├── edits.rs │ ├── engine.rs │ ├── feedback.rs │ ├── files.rs │ ├── lib.rs │ ├── messages.rs │ ├── progress.rs │ ├── reference.rs │ ├── rename.rs │ ├── router.rs │ ├── server.rs │ ├── signature_help.rs │ ├── tests/ │ │ ├── action.rs │ │ ├── compilation.rs │ │ ├── completion.rs │ │ ├── definition.rs │ │ ├── document_symbols.rs │ │ ├── folding_range.rs │ │ ├── hover.rs │ │ ├── reference.rs │ │ ├── rename.rs │ │ ├── router.rs │ │ ├── signature_help.rs │ │ └── snapshots/ │ │ ├── gleam_language_server__tests__action__add_annotation_triggers_on_empty_space_before_function_curly_brace.snap │ │ ├── gleam_language_server__tests__action__add_annotation_triggers_on_function_curly_brace.snap │ │ ├── gleam_language_server__tests__action__add_correct_type_annotation_for_non_variable_use.snap │ │ ├── gleam_language_server__tests__action__add_missing_patterns_adds_a_discard_for_opaque_type.snap │ │ ├── gleam_language_server__tests__action__add_missing_patterns_adds_a_discard_for_opaque_type_1.snap │ │ ├── gleam_language_server__tests__action__add_missing_patterns_adds_a_discard_for_opaque_type_2.snap │ │ ├── gleam_language_server__tests__action__add_missing_patterns_adds_patterns_for_internal_type_inside_same_module_where_it_is_defined.snap │ │ ├── gleam_language_server__tests__action__add_missing_patterns_bool.snap │ │ ├── gleam_language_server__tests__action__add_missing_patterns_custom_type.snap │ │ ├── gleam_language_server__tests__action__add_missing_patterns_infinite.snap │ │ ├── gleam_language_server__tests__action__add_missing_patterns_inline.snap │ │ ├── gleam_language_server__tests__action__add_missing_patterns_list.snap │ │ ├── gleam_language_server__tests__action__add_missing_patterns_multi.snap │ │ ├── gleam_language_server__tests__action__add_missing_patterns_multibyte_grapheme.snap │ │ ├── gleam_language_server__tests__action__add_missing_patterns_opaque_type.snap │ │ ├── gleam_language_server__tests__action__add_missing_patterns_tuple.snap │ │ ├── gleam_language_server__tests__action__add_missing_patterns_with_labels.snap │ │ ├── gleam_language_server__tests__action__add_missing_type_parameter_for_single_constructor.snap │ │ ├── gleam_language_server__tests__action__add_missing_type_parameter_preserves_comments.snap │ │ ├── gleam_language_server__tests__action__add_missing_type_parameter_sorted_alphabetically.snap │ │ ├── gleam_language_server__tests__action__add_missing_type_parameter_to_exising_parameter.snap │ │ ├── gleam_language_server__tests__action__add_multiple_annotations.snap │ │ ├── gleam_language_server__tests__action__add_omitted_labels_does_not_label_piped_argument.snap │ │ ├── gleam_language_server__tests__action__add_omitted_labels_does_not_label_use.snap │ │ ├── gleam_language_server__tests__action__add_omitted_labels_in_function_call.snap │ │ ├── gleam_language_server__tests__action__add_omitted_labels_in_function_call_uses_shorthand_syntax.snap │ │ ├── gleam_language_server__tests__action__add_omitted_labels_in_function_call_with_some_labels.snap │ │ ├── gleam_language_server__tests__action__add_omitted_labels_works_on_call_with_wrongly_placed_labels.snap │ │ ├── gleam_language_server__tests__action__add_omitted_labels_works_with_constructors_calls.snap │ │ ├── gleam_language_server__tests__action__add_omitted_labels_works_with_constructors_calls_with_some_labels.snap │ │ ├── gleam_language_server__tests__action__add_omitted_labels_works_with_constructors_calls_with_some_labels_1.snap │ │ ├── gleam_language_server__tests__action__add_omitted_labels_works_with_innermost_function_call.snap │ │ ├── gleam_language_server__tests__action__add_type_annotations_public_alias_to_internal_generic_type.snap │ │ ├── gleam_language_server__tests__action__add_type_annotations_public_alias_to_internal_type.snap │ │ ├── gleam_language_server__tests__action__add_type_annotations_public_alias_to_internal_type_aliased_module.snap │ │ ├── gleam_language_server__tests__action__add_type_annotations_uses_internal_name_for_same_package.snap │ │ ├── gleam_language_server__tests__action__adding_annotations_correctly_prints_type_variables.snap │ │ ├── gleam_language_server__tests__action__adding_annotations_prints_contextual_types.snap │ │ ├── gleam_language_server__tests__action__adding_annotations_prints_contextual_types2.snap │ │ ├── gleam_language_server__tests__action__adding_annotations_prints_contextual_types3.snap │ │ ├── gleam_language_server__tests__action__adding_annotations_prints_contextual_types4.snap │ │ ├── gleam_language_server__tests__action__adding_annotations_prints_contextual_types5.snap │ │ ├── gleam_language_server__tests__action__adding_annotations_prints_type_variable_names.snap │ │ ├── gleam_language_server__tests__action__allow_further_pattern_matching_on_asserted_list.snap │ │ ├── gleam_language_server__tests__action__allow_further_pattern_matching_on_asserted_result.snap │ │ ├── gleam_language_server__tests__action__allow_further_pattern_matching_on_let_record_destructuring.snap │ │ ├── gleam_language_server__tests__action__allow_further_pattern_matching_on_let_tuple_destructuring.snap │ │ ├── gleam_language_server__tests__action__annotate_all_top_level_definitions_constant.snap │ │ ├── gleam_language_server__tests__action__annotate_all_top_level_definitions_dont_affect_local_vars.snap │ │ ├── gleam_language_server__tests__action__annotate_all_top_level_definitions_function.snap │ │ ├── gleam_language_server__tests__action__annotate_all_top_level_definitions_partially_annotated.snap │ │ ├── gleam_language_server__tests__action__annotate_all_top_level_definitions_with_constant_and_generic_functions.snap │ │ ├── gleam_language_server__tests__action__annotate_all_top_level_definitions_with_partially_annotated_generic_function.snap │ │ ├── gleam_language_server__tests__action__annotate_all_top_level_definitions_with_two_generic_functions.snap │ │ ├── gleam_language_server__tests__action__annotate_anonymous_function.snap │ │ ├── gleam_language_server__tests__action__annotate_anonymous_function_with_annotated_return_type.snap │ │ ├── gleam_language_server__tests__action__annotate_anonymous_function_with_partially_annotated_parameters.snap │ │ ├── gleam_language_server__tests__action__annotate_constant.snap │ │ ├── gleam_language_server__tests__action__annotate_function.snap │ │ ├── gleam_language_server__tests__action__annotate_function_with_annotated_return_type.snap │ │ ├── gleam_language_server__tests__action__annotate_function_with_partially_annotated_parameters.snap │ │ ├── gleam_language_server__tests__action__annotate_local_variable.snap │ │ ├── gleam_language_server__tests__action__annotate_local_variable_let_assert.snap │ │ ├── gleam_language_server__tests__action__annotate_local_variable_with_pattern.snap │ │ ├── gleam_language_server__tests__action__annotate_local_variable_with_pattern2.snap │ │ ├── gleam_language_server__tests__action__annotate_nested_local_variable.snap │ │ ├── gleam_language_server__tests__action__annotate_use.snap │ │ ├── gleam_language_server__tests__action__annotate_use_with_partially_annotated_parameters.snap │ │ ├── gleam_language_server__tests__action__assign_unused_result.snap │ │ ├── gleam_language_server__tests__action__assign_unused_result_in_block.snap │ │ ├── gleam_language_server__tests__action__assign_unused_result_on_block_end.snap │ │ ├── gleam_language_server__tests__action__assign_unused_result_on_block_start.snap │ │ ├── gleam_language_server__tests__action__assign_unused_result_only_first_action.snap │ │ ├── gleam_language_server__tests__action__collapse_nested_case.snap │ │ ├── gleam_language_server__tests__action__collapse_nested_case_aliases_variable_if_it_is_used.snap │ │ ├── gleam_language_server__tests__action__collapse_nested_case_combines_inner_and_outer_guards.snap │ │ ├── gleam_language_server__tests__action__collapse_nested_case_combines_inner_and_outer_guards_and_adds_parentheses_when_needed.snap │ │ ├── gleam_language_server__tests__action__collapse_nested_case_combines_list_with_tail.snap │ │ ├── gleam_language_server__tests__action__collapse_nested_case_combines_list_with_unformatted_tail.snap │ │ ├── gleam_language_server__tests__action__collapse_nested_case_does_not_ignore_inner_guards.snap │ │ ├── gleam_language_server__tests__action__collapse_nested_case_does_not_ignore_outer_guards.snap │ │ ├── gleam_language_server__tests__action__collapse_nested_case_does_not_remove_labels.snap │ │ ├── gleam_language_server__tests__action__collapse_nested_case_does_not_remove_labels_with_shorthand_syntax.snap │ │ ├── gleam_language_server__tests__action__collapse_nested_case_works_with_alternative_patterns.snap │ │ ├── gleam_language_server__tests__action__collapse_nested_case_works_with_blocks.snap │ │ ├── gleam_language_server__tests__action__collapse_nested_case_works_with_patterns_defining_multiple_variables.snap │ │ ├── gleam_language_server__tests__action__convert_assert_custom_type_with_label_shorthands_to_case.snap │ │ ├── gleam_language_server__tests__action__convert_assert_result_to_case.snap │ │ ├── gleam_language_server__tests__action__convert_from_use_expression_with_empty_parens.snap │ │ ├── gleam_language_server__tests__action__convert_from_use_expression_with_multiple_patterns.snap │ │ ├── gleam_language_server__tests__action__convert_from_use_expression_with_no_parens.snap │ │ ├── gleam_language_server__tests__action__convert_from_use_expression_with_parens_and_other_args.snap │ │ ├── gleam_language_server__tests__action__convert_from_use_expression_with_single_pattern.snap │ │ ├── gleam_language_server__tests__action__convert_from_use_expression_with_type_annotations.snap │ │ ├── gleam_language_server__tests__action__convert_from_use_multiline_with_no_trailing_comma.snap │ │ ├── gleam_language_server__tests__action__convert_from_use_with_labels.snap │ │ ├── gleam_language_server__tests__action__convert_from_use_with_labels_2.snap │ │ ├── gleam_language_server__tests__action__convert_from_use_with_labels_3.snap │ │ ├── gleam_language_server__tests__action__convert_from_use_with_labels_4.snap │ │ ├── gleam_language_server__tests__action__convert_from_use_with_trailing_comma.snap │ │ ├── gleam_language_server__tests__action__convert_from_use_with_trailing_comma_2.snap │ │ ├── gleam_language_server__tests__action__convert_from_use_with_trailing_comma_and_label.snap │ │ ├── gleam_language_server__tests__action__convert_inner_let_assert_to_case.snap │ │ ├── gleam_language_server__tests__action__convert_let_assert_alias_to_case.snap │ │ ├── gleam_language_server__tests__action__convert_let_assert_bit_array_to_case.snap │ │ ├── gleam_language_server__tests__action__convert_let_assert_string_prefix_pattern_alias_to_case.snap │ │ ├── gleam_language_server__tests__action__convert_let_assert_string_prefix_to_case.snap │ │ ├── gleam_language_server__tests__action__convert_let_assert_to_case_discard.snap │ │ ├── gleam_language_server__tests__action__convert_let_assert_to_case_indented.snap │ │ ├── gleam_language_server__tests__action__convert_let_assert_to_case_multi_variables.snap │ │ ├── gleam_language_server__tests__action__convert_let_assert_to_case_no_variables.snap │ │ ├── gleam_language_server__tests__action__convert_let_assert_tuple_to_case.snap │ │ ├── gleam_language_server__tests__action__convert_let_assert_with_message_to_case.snap │ │ ├── gleam_language_server__tests__action__convert_outer_let_assert_to_case.snap │ │ ├── gleam_language_server__tests__action__convert_to_function_call_always_inlines_the_first_step.snap │ │ ├── gleam_language_server__tests__action__convert_to_function_call_works_when_piping_a_module_select.snap │ │ ├── gleam_language_server__tests__action__convert_to_function_call_works_when_piping_an_invalid_module_select.snap │ │ ├── gleam_language_server__tests__action__convert_to_function_call_works_with_argument_in_first_position.snap │ │ ├── gleam_language_server__tests__action__convert_to_function_call_works_with_argument_in_first_position_2.snap │ │ ├── gleam_language_server__tests__action__convert_to_function_call_works_with_argument_in_first_position_3.snap │ │ ├── gleam_language_server__tests__action__convert_to_function_call_works_with_argument_in_first_position_4.snap │ │ ├── gleam_language_server__tests__action__convert_to_function_call_works_with_echo.snap │ │ ├── gleam_language_server__tests__action__convert_to_function_call_works_with_function_producing_another_function.snap │ │ ├── gleam_language_server__tests__action__convert_to_function_call_works_with_hole_in_first_position.snap │ │ ├── gleam_language_server__tests__action__convert_to_function_call_works_with_hole_not_in_first_position.snap │ │ ├── gleam_language_server__tests__action__convert_to_function_call_works_with_labelled_argument.snap │ │ ├── gleam_language_server__tests__action__convert_to_function_call_works_with_labelled_argument_2.snap │ │ ├── gleam_language_server__tests__action__convert_to_pipe_on_first_step_of_pipeline.snap │ │ ├── gleam_language_server__tests__action__convert_to_pipe_pipes_the_outermost_argument.snap │ │ ├── gleam_language_server__tests__action__convert_to_pipe_when_first_arg_is_a_pipe_itself.snap │ │ ├── gleam_language_server__tests__action__convert_to_pipe_with_bool_operator_adds_braces.snap │ │ ├── gleam_language_server__tests__action__convert_to_pipe_with_comparison_adds_braces.snap │ │ ├── gleam_language_server__tests__action__convert_to_pipe_with_complex_binop_adds_braces.snap │ │ ├── gleam_language_server__tests__action__convert_to_pipe_with_function_call_on_first_argument.snap │ │ ├── gleam_language_server__tests__action__convert_to_pipe_with_function_call_on_function_name_extracts_first_argument.snap │ │ ├── gleam_language_server__tests__action__convert_to_pipe_with_function_call_on_second_argument.snap │ │ ├── gleam_language_server__tests__action__convert_to_pipe_with_function_call_with_labelled_arguments_inserts_hole.snap │ │ ├── gleam_language_server__tests__action__convert_to_pipe_with_function_call_with_labelled_arguments_inserts_hole_2.snap │ │ ├── gleam_language_server__tests__action__convert_to_pipe_with_function_call_with_shorthand_labelled_argument_inserts_hole.snap │ │ ├── gleam_language_server__tests__action__convert_to_pipe_with_function_call_with_shorthand_labelled_argument_inserts_hole_2.snap │ │ ├── gleam_language_server__tests__action__convert_to_pipe_with_function_returning_other_function.snap │ │ ├── gleam_language_server__tests__action__convert_to_pipe_with_nested_calls_picks_the_innermost_one.snap │ │ ├── gleam_language_server__tests__action__convert_to_pipe_with_string_concat_adds_braces.snap │ │ ├── gleam_language_server__tests__action__convert_to_pipe_with_sum_adds_no_braces.snap │ │ ├── gleam_language_server__tests__action__convert_to_pipe_works_in_anonymous_function_inside_a_pipeline.snap │ │ ├── gleam_language_server__tests__action__convert_to_pipe_works_in_final_step_of_a_pipeline.snap │ │ ├── gleam_language_server__tests__action__convert_to_pipe_works_inside_body_of_use.snap │ │ ├── gleam_language_server__tests__action__desugar_nested_use_expressions_picks_inner_under_cursor.snap │ │ ├── gleam_language_server__tests__action__desugar_nested_use_expressions_picks_inner_under_cursor_2.snap │ │ ├── gleam_language_server__tests__action__different_annotations_create_compatible_type_variables.snap │ │ ├── gleam_language_server__tests__action__expand_function_capture.snap │ │ ├── gleam_language_server__tests__action__expand_function_capture_2.snap │ │ ├── gleam_language_server__tests__action__expand_function_capture_does_not_shadow_variables.snap │ │ ├── gleam_language_server__tests__action__expand_function_capture_picks_a_name_based_on_the_type_of_the_hole.snap │ │ ├── gleam_language_server__tests__action__extract_anonymous_function_with_variable_capture_1.snap │ │ ├── gleam_language_server__tests__action__extract_anonymous_function_with_variable_capture_2.snap │ │ ├── gleam_language_server__tests__action__extract_anonymous_function_without_variable_capture_1.snap │ │ ├── gleam_language_server__tests__action__extract_anonymous_function_without_variable_capture_2.snap │ │ ├── gleam_language_server__tests__action__extract_block_tail_position_3.snap │ │ ├── gleam_language_server__tests__action__extract_block_tail_position_4.snap │ │ ├── gleam_language_server__tests__action__extract_constant_declaration_with_proper_indentation.snap │ │ ├── gleam_language_server__tests__action__extract_constant_doesnt_place_constant_below_documentation.snap │ │ ├── gleam_language_server__tests__action__extract_constant_doesnt_place_constant_below_large_documentation.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_call_argument_with_bit_array.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_call_argument_with_float.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_call_argument_with_int.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_call_argument_with_list.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_call_argument_with_nested_inside.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_call_argument_with_nested_outside.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_call_argument_with_string.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_call_argument_with_tuple.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_declaration_of_bin_op.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_declaration_of_bit_array.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_declaration_of_float.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_declaration_of_int.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_declaration_of_list.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_declaration_of_nested_inside.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_declaration_of_nested_outside.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_declaration_of_string.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_declaration_of_tuple.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_inside_block.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_inside_case.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_inside_use_1.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_inside_use_2.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_list_containing_constant.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_literal_within_list.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_literal_within_tuple.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_nested_inside_in_expr.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_nested_outside_in_expr.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_nil.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_non_record_variant_1.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_non_record_variant_2.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_record_variant_1.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_record_variant_2.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_return_of_float.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_return_of_int.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_return_of_list.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_return_of_nested_outside.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_return_of_string.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_return_of_tuple.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_taken_name_by_constant.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_taken_name_by_function.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_tuple_containing_constant.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_whole_declaration_of_bin_op.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_whole_declaration_of_bit_array.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_whole_declaration_of_float.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_whole_declaration_of_int.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_whole_declaration_of_list.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_whole_declaration_of_nested.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_whole_declaration_of_string.snap │ │ ├── gleam_language_server__tests__action__extract_constant_from_whole_declaration_of_tuple.snap │ │ ├── gleam_language_server__tests__action__extract_constant_in_correct_position_1.snap │ │ ├── gleam_language_server__tests__action__extract_constant_in_correct_position_2.snap │ │ ├── gleam_language_server__tests__action__extract_constant_in_correct_position_3.snap │ │ ├── gleam_language_server__tests__action__extract_function.snap │ │ ├── gleam_language_server__tests__action__extract_function_from_statements.snap │ │ ├── gleam_language_server__tests__action__extract_function_partially_selected.snap │ │ ├── gleam_language_server__tests__action__extract_function_when_multiple_names_already_in_scope.snap │ │ ├── gleam_language_server__tests__action__extract_function_when_name_already_in_scope.snap │ │ ├── gleam_language_server__tests__action__extract_function_which_use_variables_defined_in_the_extracted_span.snap │ │ ├── gleam_language_server__tests__action__extract_function_which_use_variables_shadowed_in_an_inner_scope.snap │ │ ├── gleam_language_server__tests__action__extract_function_which_uses_constant.snap │ │ ├── gleam_language_server__tests__action__extract_function_which_uses_constant_in_guard.snap │ │ ├── gleam_language_server__tests__action__extract_function_which_uses_multiple_extracted_variables.snap │ │ ├── gleam_language_server__tests__action__extract_function_which_uses_no_extracted_variables.snap │ │ ├── gleam_language_server__tests__action__extract_function_which_uses_variable_in_bit_array_pattern.snap │ │ ├── gleam_language_server__tests__action__extract_function_which_uses_variable_in_guard.snap │ │ ├── gleam_language_server__tests__action__extract_statements_in_tail_position.snap │ │ ├── gleam_language_server__tests__action__extract_unary_anonymous_function_with_variable_capture_1.snap │ │ ├── gleam_language_server__tests__action__extract_unary_anonymous_function_with_variable_capture_2.snap │ │ ├── gleam_language_server__tests__action__extract_use_in_tail_position.snap │ │ ├── gleam_language_server__tests__action__extract_use_in_tail_position_2.snap │ │ ├── gleam_language_server__tests__action__extract_variable.snap │ │ ├── gleam_language_server__tests__action__extract_variable_2.snap │ │ ├── gleam_language_server__tests__action__extract_variable_3.snap │ │ ├── gleam_language_server__tests__action__extract_variable_after_nested_anonymous_function.snap │ │ ├── gleam_language_server__tests__action__extract_variable_and_dont_shadow_existing_variable_in_argument.snap │ │ ├── gleam_language_server__tests__action__extract_variable_and_dont_shadow_existing_variable_in_operator.snap │ │ ├── gleam_language_server__tests__action__extract_variable_does_not_shadow_name_in_same_block.snap │ │ ├── gleam_language_server__tests__action__extract_variable_does_not_shadow_name_in_same_branch.snap │ │ ├── gleam_language_server__tests__action__extract_variable_does_not_shadow_names_in_anonymous_function.snap │ │ ├── gleam_language_server__tests__action__extract_variable_from_arg_in_nested_function_called_in_pipeline.snap │ │ ├── gleam_language_server__tests__action__extract_variable_from_arg_in_pipelined_call.snap │ │ ├── gleam_language_server__tests__action__extract_variable_from_arg_in_pipelined_call_of_function_to_capture.snap │ │ ├── gleam_language_server__tests__action__extract_variable_from_arg_in_pipelined_call_to_capture.snap │ │ ├── gleam_language_server__tests__action__extract_variable_from_capture_arguments_2.snap │ │ ├── gleam_language_server__tests__action__extract_variable_ignores_names_in_anonymous_functions.snap │ │ ├── gleam_language_server__tests__action__extract_variable_ignores_names_in_other_blocks.snap │ │ ├── gleam_language_server__tests__action__extract_variable_ignores_names_in_other_branches.snap │ │ ├── gleam_language_server__tests__action__extract_variable_ignores_names_in_other_branches_2.snap │ │ ├── gleam_language_server__tests__action__extract_variable_in_anonymous_fn_in_argument.snap │ │ ├── gleam_language_server__tests__action__extract_variable_in_block.snap │ │ ├── gleam_language_server__tests__action__extract_variable_in_case_branch.snap │ │ ├── gleam_language_server__tests__action__extract_variable_in_case_branch_from_second_arg.snap │ │ ├── gleam_language_server__tests__action__extract_variable_in_case_branch_using_var.snap │ │ ├── gleam_language_server__tests__action__extract_variable_in_double_nested_anonymous_function.snap │ │ ├── gleam_language_server__tests__action__extract_variable_in_multiline_case_subject_branch.snap │ │ ├── gleam_language_server__tests__action__extract_variable_in_multiline_use.snap │ │ ├── gleam_language_server__tests__action__extract_variable_in_nested_anonymous_function.snap │ │ ├── gleam_language_server__tests__action__extract_variable_in_use.snap │ │ ├── gleam_language_server__tests__action__extract_variable_inside_multiline_function_call.snap │ │ ├── gleam_language_server__tests__action__extract_variable_inside_use_body.snap │ │ ├── gleam_language_server__tests__action__extract_variable_starting_pipeline_steps.snap │ │ ├── gleam_language_server__tests__action__extract_variable_with_list_with_plural_name_does_not_add_another_s.snap │ │ ├── gleam_language_server__tests__action__fill_in_labelled_args_selects_innermost_function.snap │ │ ├── gleam_language_server__tests__action__fill_in_labelled_args_with_some_arguments_already_supplied.snap │ │ ├── gleam_language_server__tests__action__fill_in_labelled_args_with_some_arguments_already_supplied_2.snap │ │ ├── gleam_language_server__tests__action__fill_in_labelled_args_with_some_arguments_already_supplied_3.snap │ │ ├── gleam_language_server__tests__action__fill_in_labelled_args_works_with_pattern_and_no_parentheses.snap │ │ ├── gleam_language_server__tests__action__fill_in_labelled_args_works_with_pattern_and_parentheses.snap │ │ ├── gleam_language_server__tests__action__fill_in_labelled_args_works_with_pattern_and_parentheses_with_spaces.snap │ │ ├── gleam_language_server__tests__action__fill_in_labelled_args_works_with_pipes.snap │ │ ├── gleam_language_server__tests__action__fill_in_labelled_args_works_with_pipes_2.snap │ │ ├── gleam_language_server__tests__action__fill_in_labelled_args_works_with_record_constructor.snap │ │ ├── gleam_language_server__tests__action__fill_in_labelled_args_works_with_regular_function.snap │ │ ├── gleam_language_server__tests__action__fill_in_labelled_args_works_with_use.snap │ │ ├── gleam_language_server__tests__action__fill_in_labelled_args_works_with_use_2.snap │ │ ├── gleam_language_server__tests__action__fill_in_labelled_args_works_with_use_3.snap │ │ ├── gleam_language_server__tests__action__fill_labels_all_fields_have_matching_variables.snap │ │ ├── gleam_language_server__tests__action__fill_labels_falls_back_to_todo_when_type_does_not_match.snap │ │ ├── gleam_language_server__tests__action__fill_labels_generic_type_matching.snap │ │ ├── gleam_language_server__tests__action__fill_labels_ignores_underscore_prefixed_variables.snap │ │ ├── gleam_language_server__tests__action__fill_labels_ignores_variable_defined_after_call.snap │ │ ├── gleam_language_server__tests__action__fill_labels_inside_anonymous_function.snap │ │ ├── gleam_language_server__tests__action__fill_labels_inside_assignment_with_same_name_as_field.snap │ │ ├── gleam_language_server__tests__action__fill_labels_multiple_fields_some_matching.snap │ │ ├── gleam_language_server__tests__action__fill_labels_nested_pattern_constructor.snap │ │ ├── gleam_language_server__tests__action__fill_labels_pattern_constructor.snap │ │ ├── gleam_language_server__tests__action__fill_labels_pattern_constructor_let_assignment.snap │ │ ├── gleam_language_server__tests__action__fill_labels_pattern_constructor_with_some_labels.snap │ │ ├── gleam_language_server__tests__action__fill_labels_uses_function_argument_in_scope.snap │ │ ├── gleam_language_server__tests__action__fill_labels_uses_variable_in_scope_with_matching_type.snap │ │ ├── gleam_language_server__tests__action__fill_labels_variable_from_outer_scope_not_shadowed.snap │ │ ├── gleam_language_server__tests__action__fill_labels_variable_in_scope_from_case_pattern.snap │ │ ├── gleam_language_server__tests__action__fill_labels_variable_out_of_scope_in_block.snap │ │ ├── gleam_language_server__tests__action__fill_unused_fields_with_all_ignored_fields.snap │ │ ├── gleam_language_server__tests__action__fill_unused_fields_with_all_positional_fields.snap │ │ ├── gleam_language_server__tests__action__fill_unused_fields_with_ignored_fields_never_calls_a_positional_arg_as_a_labelled_one.snap │ │ ├── gleam_language_server__tests__action__fill_unused_fields_with_ignored_labelled_fields.snap │ │ ├── gleam_language_server__tests__action__fill_unused_fields_with_ignored_mixed_fields.snap │ │ ├── gleam_language_server__tests__action__fill_unused_fields_with_ignored_positional_fields.snap │ │ ├── gleam_language_server__tests__action__fix_float_operator_on_ints.snap │ │ ├── gleam_language_server__tests__action__fix_float_operator_on_ints_2.snap │ │ ├── gleam_language_server__tests__action__fix_float_operator_on_ints_3.snap │ │ ├── gleam_language_server__tests__action__fix_int_operator_on_floats.snap │ │ ├── gleam_language_server__tests__action__fix_int_operator_on_floats_2.snap │ │ ├── gleam_language_server__tests__action__fix_int_operator_on_floats_3.snap │ │ ├── gleam_language_server__tests__action__fix_plus_operator_on_strings.snap │ │ ├── gleam_language_server__tests__action__fix_truncated_segment_1.snap │ │ ├── gleam_language_server__tests__action__fix_truncated_segment_2.snap │ │ ├── gleam_language_server__tests__action__generate_dynamic_decoder.snap │ │ ├── gleam_language_server__tests__action__generate_dynamic_decoder_already_imported_module.snap │ │ ├── gleam_language_server__tests__action__generate_dynamic_decoder_complex_types.snap │ │ ├── gleam_language_server__tests__action__generate_dynamic_decoder_does_not_produce_zero_values_for_types_from_other_packages.snap │ │ ├── gleam_language_server__tests__action__generate_dynamic_decoder_for_multi_variant_type.snap │ │ ├── gleam_language_server__tests__action__generate_dynamic_decoder_for_multi_variant_type_multi_word_name.snap │ │ ├── gleam_language_server__tests__action__generate_dynamic_decoder_for_variant_with_no_fields.snap │ │ ├── gleam_language_server__tests__action__generate_dynamic_decoder_for_variants_with_mixed_fields.snap │ │ ├── gleam_language_server__tests__action__generate_dynamic_decoder_for_variants_with_no_fields.snap │ │ ├── gleam_language_server__tests__action__generate_dynamic_decoder_generates_todo_for_zero_value_when_all_constructors_fail.snap │ │ ├── gleam_language_server__tests__action__generate_dynamic_decoder_produces_zero_values_for_prelude_and_stdlib_types.snap │ │ ├── gleam_language_server__tests__action__generate_dynamic_decoder_produces_zero_values_for_user_defined_type_in_the_same_package_1.snap │ │ ├── gleam_language_server__tests__action__generate_dynamic_decoder_produces_zero_values_for_user_defined_type_in_the_same_package_2.snap │ │ ├── gleam_language_server__tests__action__generate_dynamic_decoder_produces_zero_values_for_user_defined_type_in_the_same_package_3.snap │ │ ├── gleam_language_server__tests__action__generate_dynamic_decoder_recursive_type.snap │ │ ├── gleam_language_server__tests__action__generate_dynamic_decoder_skips_over_mutually_recursive_constructors_when_generating_zero_values.snap │ │ ├── gleam_language_server__tests__action__generate_dynamic_decoder_skips_over_recursive_constructors_when_generating_zero_values.snap │ │ ├── gleam_language_server__tests__action__generate_dynamic_decoder_tuple.snap │ │ ├── gleam_language_server__tests__action__generate_dynamic_decoder_uses_decode_success_for_nil.snap │ │ ├── gleam_language_server__tests__action__generate_dynamic_decoder_uses_smallest_possible_constructor_for_zero_value.snap │ │ ├── gleam_language_server__tests__action__generate_function_arguments_with_labels_and_variables_uses_different_names.snap │ │ ├── gleam_language_server__tests__action__generate_function_arguments_with_same_name_get_renamed.snap │ │ ├── gleam_language_server__tests__action__generate_function_capture.snap │ │ ├── gleam_language_server__tests__action__generate_function_generates_argument_names_from_labels.snap │ │ ├── gleam_language_server__tests__action__generate_function_generates_argument_names_from_variables.snap │ │ ├── gleam_language_server__tests__action__generate_function_in_other_module.snap │ │ ├── gleam_language_server__tests__action__generate_function_in_other_module_correctly_appends.snap │ │ ├── gleam_language_server__tests__action__generate_function_labels_and_arguments_can_share_the_same_name.snap │ │ ├── gleam_language_server__tests__action__generate_function_picks_argument_name_based_on_record_access.snap │ │ ├── gleam_language_server__tests__action__generate_function_picks_argument_name_based_on_type.snap │ │ ├── gleam_language_server__tests__action__generate_function_takes_labels_into_account.snap │ │ ├── gleam_language_server__tests__action__generate_function_wont_generate_two_arguments_with_the_same_name_if_they_have_the_same_type.snap │ │ ├── gleam_language_server__tests__action__generate_function_works_with_constants.snap │ │ ├── gleam_language_server__tests__action__generate_function_works_with_constants_2.snap │ │ ├── gleam_language_server__tests__action__generate_function_works_with_invalid_call.snap │ │ ├── gleam_language_server__tests__action__generate_function_works_with_pipeline_steps.snap │ │ ├── gleam_language_server__tests__action__generate_function_works_with_pipeline_steps_1.snap │ │ ├── gleam_language_server__tests__action__generate_json_encoder.snap │ │ ├── gleam_language_server__tests__action__generate_json_encoder_already_imported_module.snap │ │ ├── gleam_language_server__tests__action__generate_json_encoder_complex_types.snap │ │ ├── gleam_language_server__tests__action__generate_json_encoder_for_multi_variant_type.snap │ │ ├── gleam_language_server__tests__action__generate_json_encoder_for_multi_variant_type_multi_word_name.snap │ │ ├── gleam_language_server__tests__action__generate_json_encoder_for_type_with_multiple_variants_with_no_fields.snap │ │ ├── gleam_language_server__tests__action__generate_json_encoder_for_variant_with_no_fields.snap │ │ ├── gleam_language_server__tests__action__generate_json_encoder_for_variants_with_mixed_fields.snap │ │ ├── gleam_language_server__tests__action__generate_json_encoder_list_of_tuples.snap │ │ ├── gleam_language_server__tests__action__generate_json_encoder_recursive_type.snap │ │ ├── gleam_language_server__tests__action__generate_json_encoder_tuple.snap │ │ ├── gleam_language_server__tests__action__generate_qualified_variant_in_other_module.snap │ │ ├── gleam_language_server__tests__action__generate_to_json_function_ignores_nil_and_nil_tuple_fields_with_underscore.snap │ │ ├── gleam_language_server__tests__action__generate_to_json_function_uses_json_null_for_nil.snap │ │ ├── gleam_language_server__tests__action__generate_unqualified_variant_in_other_module.snap │ │ ├── gleam_language_server__tests__action__generate_variant_from_pattern_with_fields.snap │ │ ├── gleam_language_server__tests__action__generate_variant_from_pattern_with_labelled_fields.snap │ │ ├── gleam_language_server__tests__action__generate_variant_from_pattern_with_no_fields.snap │ │ ├── gleam_language_server__tests__action__generate_variant_with_fields_in_same_module.snap │ │ ├── gleam_language_server__tests__action__generate_variant_with_labels_in_same_module.snap │ │ ├── gleam_language_server__tests__action__generate_variant_with_no_fields_in_same_module.snap │ │ ├── gleam_language_server__tests__action__generated_function_annotations_are_not_affected_by_other_functions.snap │ │ ├── gleam_language_server__tests__action__generating_function_in_other_module_uses_labels.snap │ │ ├── gleam_language_server__tests__action__generating_function_in_other_module_uses_local_names.snap │ │ ├── gleam_language_server__tests__action__import_internal_module_from_same_package.snap │ │ ├── gleam_language_server__tests__action__import_module_from_constructor.snap │ │ ├── gleam_language_server__tests__action__import_module_from_function.snap │ │ ├── gleam_language_server__tests__action__import_module_from_pattern.snap │ │ ├── gleam_language_server__tests__action__import_module_from_type.snap │ │ ├── gleam_language_server__tests__action__import_path_module_from_function.snap │ │ ├── gleam_language_server__tests__action__import_similar_module.snap │ │ ├── gleam_language_server__tests__action__inexhaustive_let_alias_to_case.snap │ │ ├── gleam_language_server__tests__action__inexhaustive_let_bit_array_to_case.snap │ │ ├── gleam_language_server__tests__action__inexhaustive_let_result_to_case.snap │ │ ├── gleam_language_server__tests__action__inexhaustive_let_string_prefix_pattern_alias_to_case.snap │ │ ├── gleam_language_server__tests__action__inexhaustive_let_string_prefix_to_case.snap │ │ ├── gleam_language_server__tests__action__inexhaustive_let_to_case_discard.snap │ │ ├── gleam_language_server__tests__action__inexhaustive_let_to_case_indented.snap │ │ ├── gleam_language_server__tests__action__inexhaustive_let_to_case_multi_variables.snap │ │ ├── gleam_language_server__tests__action__inexhaustive_let_to_case_no_variables.snap │ │ ├── gleam_language_server__tests__action__inexhaustive_let_tuple_to_case.snap │ │ ├── gleam_language_server__tests__action__inline_variable.snap │ │ ├── gleam_language_server__tests__action__inline_variable_from_definition.snap │ │ ├── gleam_language_server__tests__action__inline_variable_in_case_scope.snap │ │ ├── gleam_language_server__tests__action__inline_variable_in_nested_scope.snap │ │ ├── gleam_language_server__tests__action__inline_variable_in_record_update.snap │ │ ├── gleam_language_server__tests__action__inline_variable_label_shorthand.snap │ │ ├── gleam_language_server__tests__action__inline_variable_when_over_let_keyword.snap │ │ ├── gleam_language_server__tests__action__inline_variable_with_record_field.snap │ │ ├── gleam_language_server__tests__action__inner_inexhaustive_let_to_case.snap │ │ ├── gleam_language_server__tests__action__interpolate_string_allows_extracting_record_access_syntax.snap │ │ ├── gleam_language_server__tests__action__interpolate_string_does_not_add_empty_string_right_at_the_end.snap │ │ ├── gleam_language_server__tests__action__interpolate_string_does_not_add_empty_string_right_at_the_start.snap │ │ ├── gleam_language_server__tests__action__interpolate_string_inside_string.snap │ │ ├── gleam_language_server__tests__action__interpolating_string_as_first_pipeline_step_inserts_brackets.snap │ │ ├── gleam_language_server__tests__action__label_shorthand_action_only_applies_to_selected_args.snap │ │ ├── gleam_language_server__tests__action__label_shorthand_action_works_on_labelled_call_args.snap │ │ ├── gleam_language_server__tests__action__label_shorthand_action_works_on_labelled_constructor_call_args.snap │ │ ├── gleam_language_server__tests__action__label_shorthand_action_works_on_labelled_pattern_call_args.snap │ │ ├── gleam_language_server__tests__action__label_shorthand_action_works_on_labelled_update_call_args.snap │ │ ├── gleam_language_server__tests__action__merge_case_branch.snap │ │ ├── gleam_language_server__tests__action__merge_case_branch_can_merge_branches_defining_the_same_variables.snap │ │ ├── gleam_language_server__tests__action__merge_case_branch_can_merge_multiple_branches.snap │ │ ├── gleam_language_server__tests__action__merge_case_branch_with_complex_bodies_1.snap │ │ ├── gleam_language_server__tests__action__merge_case_branch_with_complex_bodies_2.snap │ │ ├── gleam_language_server__tests__action__merge_case_branch_with_complex_bodies_3.snap │ │ ├── gleam_language_server__tests__action__merge_case_branch_with_complex_bodies_4.snap │ │ ├── gleam_language_server__tests__action__merge_case_branch_with_todo_keeps_the_non_todo_body.snap │ │ ├── gleam_language_server__tests__action__merge_case_branch_with_todo_keeps_the_non_todo_body_1.snap │ │ ├── gleam_language_server__tests__action__merge_case_branch_with_todo_keeps_the_non_todo_body_2.snap │ │ ├── gleam_language_server__tests__action__merge_case_branch_works_with_existing_alternative_patterns.snap │ │ ├── gleam_language_server__tests__action__outer_inexhaustive_let_to_case.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_argument_adds_patterns_for_internal_type_inside_module_where_it_is_defined.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_argument_available_for_internal_type_defined_in_current_module.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_argument_generates_unique_names_even_with_labels.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_argument_multi_item_tuple.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_argument_nicely_formats_code_when_used_on_function_with_empty_body.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_argument_preserves_indentation_of_statement_following_inserted_let.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_argument_single_item_tuple.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_argument_single_unlabelled_field_is_not_numbered.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_argument_uses_case_with_multiple_constructors.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_argument_uses_label_shorthand_syntax_for_labelled_arguments.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_argument_will_use_aliased_constructor_name.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_argument_will_use_aliased_module_name.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_argument_will_use_qualified_name.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_argument_will_use_unqualified_name.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_argument_with_multiple_constructors_is_nicely_formatted_in_function_with_empty_body.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_argument_with_private_type_from_same_module.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_argument_works_on_fn_arguments.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_argument_works_on_nested_fn_arguments.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_clause_variable.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_clause_variable_nested_pattern.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_clause_variable_with_block_body.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_clause_variable_with_label.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_clause_variable_with_label_shorthand.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_let_assignment.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_let_assignment_with_multiple_constructors.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_list_tail.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_list_tail_used_in_a_branch.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_list_tail_with_shadowed_name.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_list_tail_with_strange_whitespace.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_list_variable.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_use_assignment.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_use_assignment_with_multiple_constructors.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_value_with_private_type_from_same_module.snap │ │ ├── gleam_language_server__tests__action__pattern_match_on_variable_crashes.snap │ │ ├── gleam_language_server__tests__action__qualified_aliased_to_unqualified_aliased_type.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_aliased_type.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_aliased_type_with_multiple_imports.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_basic_multiple.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_basic_record_without_argument.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_basic_type_without_argument.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_basic_with_argument.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_below_constructor.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_between_constructors.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_constant_multiple_occurrences.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_constructor_as_argument.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_constructor_complex_pattern.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_constructor_different_module_same_name_inner.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_constructor_different_module_same_name_outer.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_constructor_different_module_same_type_inner.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_constructor_different_module_same_type_outer.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_custom_type_record_declaration.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_different_constructors.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_from_constant_also_updates_functions.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_from_function_also_updates_constants.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_in_case_with_argument.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_in_case_without_argument.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_in_constant_record.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_in_constant_var.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_in_list_and_tuple.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_in_nested_constant.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_in_pattern.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_in_pattern_without_argument.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_multiple_generic_type.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_multiple_imports.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_multiple_line.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_multiple_line_aliased.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_multiple_line_bad_format_multiple_whitespace.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_multiple_line_bad_format_with_trailing_comma.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_multiple_line_bad_format_without_trailing_comma.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_nested_constructor_inner.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_nested_constructor_outer.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_nested_type_inner.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_nested_type_outer.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_type.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_when_unqualified_exists.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_with_alias.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_with_alias_multiple.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_with_comma.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_import_with_comma_pos_not_end.snap │ │ ├── gleam_language_server__tests__action__qualified_to_unqualified_record_value_constructor_module_name.snap │ │ ├── gleam_language_server__tests__action__remove_aliased_unused_value.snap │ │ ├── gleam_language_server__tests__action__remove_block_1.snap │ │ ├── gleam_language_server__tests__action__remove_block_2.snap │ │ ├── gleam_language_server__tests__action__remove_block_3.snap │ │ ├── gleam_language_server__tests__action__remove_block_triggers_on_the_innermost_selected_block.snap │ │ ├── gleam_language_server__tests__action__remove_block_unwraps_a_single_expression_in_a_binop.snap │ │ ├── gleam_language_server__tests__action__remove_echo.snap │ │ ├── gleam_language_server__tests__action__remove_echo_as_function_arg.snap │ │ ├── gleam_language_server__tests__action__remove_echo_before_pipeline.snap │ │ ├── gleam_language_server__tests__action__remove_echo_before_pipeline_selecting_step.snap │ │ ├── gleam_language_server__tests__action__remove_echo_in_pipeline_step.snap │ │ ├── gleam_language_server__tests__action__remove_echo_in_pipeline_step_with_message.snap │ │ ├── gleam_language_server__tests__action__remove_echo_in_single_line_pipeline_step.snap │ │ ├── gleam_language_server__tests__action__remove_echo_in_single_line_pipeline_step_with_message.snap │ │ ├── gleam_language_server__tests__action__remove_echo_last_in_long_pipeline_step.snap │ │ ├── gleam_language_server__tests__action__remove_echo_last_in_long_pipeline_step_with_message.snap │ │ ├── gleam_language_server__tests__action__remove_echo_last_in_short_pipeline_step.snap │ │ ├── gleam_language_server__tests__action__remove_echo_last_in_short_pipeline_step_with_message.snap │ │ ├── gleam_language_server__tests__action__remove_echo_removes_all_echos.snap │ │ ├── gleam_language_server__tests__action__remove_echo_removes_all_echos_1.snap │ │ ├── gleam_language_server__tests__action__remove_echo_removes_does_not_remove_entire_echo_statement_if_its_the_return.snap │ │ ├── gleam_language_server__tests__action__remove_echo_removes_does_not_remove_entire_echo_statement_if_its_the_return_of_a_fn.snap │ │ ├── gleam_language_server__tests__action__remove_echo_removes_entire_echo_statement_used_with_a_var.snap │ │ ├── gleam_language_server__tests__action__remove_echo_removes_entire_echo_statement_used_with_literals.snap │ │ ├── gleam_language_server__tests__action__remove_echo_removes_entire_echo_statement_used_with_literals_and_message.snap │ │ ├── gleam_language_server__tests__action__remove_echo_removes_entire_echo_statement_used_with_literals_in_a_fn.snap │ │ ├── gleam_language_server__tests__action__remove_echo_removes_multiple_entire_echo_statement_used_with_literals.snap │ │ ├── gleam_language_server__tests__action__remove_echo_removes_multiple_entire_echo_statement_used_with_literals_but_stops_at_comments.snap │ │ ├── gleam_language_server__tests__action__remove_echo_removes_multiple_entire_echo_statement_used_with_literals_in_a_fn.snap │ │ ├── gleam_language_server__tests__action__remove_echo_selecting_expression.snap │ │ ├── gleam_language_server__tests__action__remove_echo_selecting_message.snap │ │ ├── gleam_language_server__tests__action__remove_echo_with_message.snap │ │ ├── gleam_language_server__tests__action__remove_echo_with_message_and_comment.snap │ │ ├── gleam_language_server__tests__action__remove_echo_with_message_and_comment_2.snap │ │ ├── gleam_language_server__tests__action__remove_echo_with_message_and_comment_3.snap │ │ ├── gleam_language_server__tests__action__remove_echo_with_message_removes_does_not_remove_entire_echo_statement_if_its_the_return.snap │ │ ├── gleam_language_server__tests__action__remove_entire_unused_import.snap │ │ ├── gleam_language_server__tests__action__remove_multiple_redundant_tuple_with_catch_all_pattern.snap │ │ ├── gleam_language_server__tests__action__remove_multiple_unused_values.snap │ │ ├── gleam_language_server__tests__action__remove_multiple_unused_values_2.snap │ │ ├── gleam_language_server__tests__action__remove_opaque_from_private_type.snap │ │ ├── gleam_language_server__tests__action__remove_redundant_tuple_in_case_retain_extras.snap │ │ ├── gleam_language_server__tests__action__remove_redundant_tuple_in_case_subject_nested.snap │ │ ├── gleam_language_server__tests__action__remove_redundant_tuple_in_case_subject_only_safe_remove.snap │ │ ├── gleam_language_server__tests__action__remove_redundant_tuple_in_case_subject_simple.snap │ │ ├── gleam_language_server__tests__action__remove_redundant_tuple_with_catch_all_pattern.snap │ │ ├── gleam_language_server__tests__action__remove_unreachable_clauses.snap │ │ ├── gleam_language_server__tests__action__remove_unused_alias.snap │ │ ├── gleam_language_server__tests__action__remove_unused_simple.snap │ │ ├── gleam_language_server__tests__action__remove_unused_start_of_file.snap │ │ ├── gleam_language_server__tests__action__remove_unused_value.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_bit_array_pattern.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_bit_array_pattern_discard.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_case_variable.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_case_variable_discard.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_const.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_constructor.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_constructor_arg.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_constructor_pattern.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_constructor_pattern_discard.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_custom_type.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_function.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_function_type_parameter_name.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_list_pattern.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_list_pattern_discard.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_parameter.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_parameter_discard.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_parameter_discard_name2.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_parameter_discard_name3.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_parameter_label.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_parameter_label2.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_parameter_name2.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_parameter_name3.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_pattern_assignment.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_string_prefix_pattern.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_string_prefix_pattern_alias.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_string_prefix_pattern_discard.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_tuple_pattern.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_tuple_pattern_discard.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_type_alias.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_type_alias_parameter_name.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_type_parameter_name.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_use.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_use_discard.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_variable.snap │ │ ├── gleam_language_server__tests__action__rename_invalid_variable_discard.snap │ │ ├── gleam_language_server__tests__action__rename_module_for_imported.snap │ │ ├── gleam_language_server__tests__action__replace_nested_underscore_in_let_annotation.snap │ │ ├── gleam_language_server__tests__action__replace_nested_underscore_with_function_return_type.snap │ │ ├── gleam_language_server__tests__action__replace_nested_underscore_with_generic_type.snap │ │ ├── gleam_language_server__tests__action__replace_nested_underscore_with_tuple_type.snap │ │ ├── gleam_language_server__tests__action__replace_underscore_in_fn_expr_argument.snap │ │ ├── gleam_language_server__tests__action__replace_underscore_in_function_argument.snap │ │ ├── gleam_language_server__tests__action__replace_underscore_in_let_annotation.snap │ │ ├── gleam_language_server__tests__action__replace_underscore_with_function_return_type.snap │ │ ├── gleam_language_server__tests__action__replace_underscore_with_type.snap │ │ ├── gleam_language_server__tests__action__selected_statements_do_not_select_outer_block.snap │ │ ├── gleam_language_server__tests__action__split_string.snap │ │ ├── gleam_language_server__tests__action__splitting_string_as_first_pipeline_step_inserts_brackets.snap │ │ ├── gleam_language_server__tests__action__turn_call_into_use_starts_from_innermost_function.snap │ │ ├── gleam_language_server__tests__action__turn_call_into_use_with_another_use_in_the_way.snap │ │ ├── gleam_language_server__tests__action__turn_call_into_use_with_fn_with_no_args.snap │ │ ├── gleam_language_server__tests__action__turn_call_into_use_with_last_function_in_a_block.snap │ │ ├── gleam_language_server__tests__action__turn_call_into_use_with_module_function.snap │ │ ├── gleam_language_server__tests__action__turn_call_into_use_with_out_of_order_arguments.snap │ │ ├── gleam_language_server__tests__action__turn_call_into_use_with_single_line_body.snap │ │ ├── gleam_language_server__tests__action__turn_call_with_fn_with_type_annotations_into_use.snap │ │ ├── gleam_language_server__tests__action__turn_call_with_multiline_fn_into_use.snap │ │ ├── gleam_language_server__tests__action__turn_call_with_multiple_arguments_into_use.snap │ │ ├── gleam_language_server__tests__action__type_variables_are_not_duplicated_when_adding_annotations.snap │ │ ├── gleam_language_server__tests__action__type_variables_from_other_functions_do_not_change_annotations.snap │ │ ├── gleam_language_server__tests__action__type_variables_from_other_functions_do_not_change_annotations_constant.snap │ │ ├── gleam_language_server__tests__action__type_variables_in_let_bindings_are_considered_when_adding_annotations.snap │ │ ├── gleam_language_server__tests__action__unqualified_to_qualified_import_after_constructor.snap │ │ ├── gleam_language_server__tests__action__unqualified_to_qualified_import_bad_formatted_comma.snap │ │ ├── gleam_language_server__tests__action__unqualified_to_qualified_import_bad_formatted_type_constructor.snap │ │ ├── gleam_language_server__tests__action__unqualified_to_qualified_import_bad_formatted_type_constructor_with_alias.snap │ │ ├── gleam_language_server__tests__action__unqualified_to_qualified_import_between_constructors.snap │ │ ├── gleam_language_server__tests__action__unqualified_to_qualified_import_constant.snap │ │ ├── gleam_language_server__tests__action__unqualified_to_qualified_import_constructor_complex_pattern.snap │ │ ├── gleam_language_server__tests__action__unqualified_to_qualified_import_function.snap │ │ ├── gleam_language_server__tests__action__unqualified_to_qualified_import_in_constant_record.snap │ │ ├── gleam_language_server__tests__action__unqualified_to_qualified_import_in_constant_var.snap │ │ ├── gleam_language_server__tests__action__unqualified_to_qualified_import_in_list_and_tuple.snap │ │ ├── gleam_language_server__tests__action__unqualified_to_qualified_import_in_nested_constant.snap │ │ ├── gleam_language_server__tests__action__unqualified_to_qualified_import_in_pattern_matching.snap │ │ ├── gleam_language_server__tests__action__unqualified_to_qualified_import_multiple_line_aliased.snap │ │ ├── gleam_language_server__tests__action__unqualified_to_qualified_import_multiple_line_bad_format_without_trailing_comma.snap │ │ ├── gleam_language_server__tests__action__unqualified_to_qualified_import_multiple_occurrences.snap │ │ ├── gleam_language_server__tests__action__unqualified_to_qualified_import_nested_function_call.snap │ │ ├── gleam_language_server__tests__action__unqualified_to_qualified_import_record_constructor.snap │ │ ├── gleam_language_server__tests__action__unqualified_to_qualified_import_type_annotation.snap │ │ ├── gleam_language_server__tests__action__unqualified_to_qualified_import_variable_shadowing.snap │ │ ├── gleam_language_server__tests__action__unqualified_to_qualified_import_with_alias.snap │ │ ├── gleam_language_server__tests__action__unqualified_to_qualified_import_with_alias_and_module_alias.snap │ │ ├── gleam_language_server__tests__action__unqualify_already_imported_type.snap │ │ ├── gleam_language_server__tests__action__use_label_shorthand_works_for_alternative_patterns.snap │ │ ├── gleam_language_server__tests__action__use_label_shorthand_works_for_nested_calls.snap │ │ ├── gleam_language_server__tests__action__use_label_shorthand_works_for_nested_patterns.snap │ │ ├── gleam_language_server__tests__action__use_label_shorthand_works_for_nested_record_updates.snap │ │ ├── gleam_language_server__tests__action__wrap_assignment_value_in_block.snap │ │ ├── gleam_language_server__tests__action__wrap_case_assignment_of_record_access_in_block.snap │ │ ├── gleam_language_server__tests__action__wrap_case_clause_in_block.snap │ │ ├── gleam_language_server__tests__action__wrap_case_clause_inside_assignment_in_block.snap │ │ ├── gleam_language_server__tests__action__wrap_case_clause_with_guard_in_block.snap │ │ ├── gleam_language_server__tests__action__wrap_case_clause_with_multiple_patterns_in_block.snap │ │ ├── gleam_language_server__tests__action__wrap_nested_case_clause_in_block.snap │ │ ├── gleam_language_server__tests__completion__argument_shadowing.snap │ │ ├── gleam_language_server__tests__completion__argument_variable_shadowing.snap │ │ ├── gleam_language_server__tests__completion__autocomplete_doesnt_delete_the_piece_of_code_that_comes_after.snap │ │ ├── gleam_language_server__tests__completion__autocomplete_doesnt_delete_the_piece_of_code_that_comes_after_2.snap │ │ ├── gleam_language_server__tests__completion__case_subject.snap │ │ ├── gleam_language_server__tests__completion__complete_echo_keyword.snap │ │ ├── gleam_language_server__tests__completion__complete_keyword_being_typed.snap │ │ ├── gleam_language_server__tests__completion__complete_panic_keyword.snap │ │ ├── gleam_language_server__tests__completion__completion_for_partially_correct_existing_module_select.snap │ │ ├── gleam_language_server__tests__completion__completion_for_type.snap │ │ ├── gleam_language_server__tests__completion__completions_for_a_const_annotation.snap │ │ ├── gleam_language_server__tests__completion__completions_for_a_function_arg_annotation.snap │ │ ├── gleam_language_server__tests__completion__completions_for_a_function_return_annotation.snap │ │ ├── gleam_language_server__tests__completion__completions_for_a_var_annotation.snap │ │ ├── gleam_language_server__tests__completion__completions_for_an_import.snap │ │ ├── gleam_language_server__tests__completion__completions_for_an_import_from_dependency.snap │ │ ├── gleam_language_server__tests__completion__completions_for_an_import_from_dependency_with_docs.snap │ │ ├── gleam_language_server__tests__completion__completions_for_an_import_no_test.snap │ │ ├── gleam_language_server__tests__completion__completions_for_an_import_not_from_dev_dependency.snap │ │ ├── gleam_language_server__tests__completion__completions_for_an_import_not_from_dev_dependency_in_dev.snap │ │ ├── gleam_language_server__tests__completion__completions_for_an_import_not_from_dev_dependency_in_test.snap │ │ ├── gleam_language_server__tests__completion__completions_for_an_import_not_from_indirect_dependency.snap │ │ ├── gleam_language_server__tests__completion__completions_for_an_import_preceeding_whitespace.snap │ │ ├── gleam_language_server__tests__completion__completions_for_an_import_start.snap │ │ ├── gleam_language_server__tests__completion__completions_for_an_import_while_in_dev.snap │ │ ├── gleam_language_server__tests__completion__completions_for_an_import_while_in_test.snap │ │ ├── gleam_language_server__tests__completion__completions_for_an_import_with_docs.snap │ │ ├── gleam_language_server__tests__completion__completions_for_an_unqualified_import.snap │ │ ├── gleam_language_server__tests__completion__completions_for_an_unqualified_import_already_imported.snap │ │ ├── gleam_language_server__tests__completion__completions_for_an_unqualified_import_on_new_line.snap │ │ ├── gleam_language_server__tests__completion__completions_for_function_labels.snap │ │ ├── gleam_language_server__tests__completion__completions_for_imported_function_labels.snap │ │ ├── gleam_language_server__tests__completion__completions_for_imported_record_fields.snap │ │ ├── gleam_language_server__tests__completion__completions_for_imported_record_labels.snap │ │ ├── gleam_language_server__tests__completion__completions_for_internal_record_fields_inside_the_same_module.snap │ │ ├── gleam_language_server__tests__completion__completions_for_labels_in_record_update.snap.new │ │ ├── gleam_language_server__tests__completion__completions_for_outside_a_function.snap │ │ ├── gleam_language_server__tests__completion__completions_for_prelude_values.snap │ │ ├── gleam_language_server__tests__completion__completions_for_private_record_access.snap │ │ ├── gleam_language_server__tests__completion__completions_for_record_access.snap │ │ ├── gleam_language_server__tests__completion__completions_for_record_access_known_variant.snap │ │ ├── gleam_language_server__tests__completion__completions_for_record_access_unknown_variant.snap │ │ ├── gleam_language_server__tests__completion__completions_for_record_labels.snap │ │ ├── gleam_language_server__tests__completion__completions_for_type_import_completions_without_brackets.snap │ │ ├── gleam_language_server__tests__completion__constant.snap │ │ ├── gleam_language_server__tests__completion__constant_with_many_options.snap │ │ ├── gleam_language_server__tests__completion__constant_with_module_select.snap │ │ ├── gleam_language_server__tests__completion__do_not_show_completions_when_typing_a_number.snap │ │ ├── gleam_language_server__tests__completion__for_custom_type_definition.snap │ │ ├── gleam_language_server__tests__completion__for_function_arguments.snap │ │ ├── gleam_language_server__tests__completion__for_type_alias.snap │ │ ├── gleam_language_server__tests__completion__importable_adds_extra_new_line_if_import_exists_below_other_definitions.snap │ │ ├── gleam_language_server__tests__completion__importable_adds_extra_new_line_if_no_imports.snap │ │ ├── gleam_language_server__tests__completion__importable_does_not_add_extra_new_line_if_imports_exist.snap │ │ ├── gleam_language_server__tests__completion__importable_does_not_add_extra_new_line_if_newline_exists.snap │ │ ├── gleam_language_server__tests__completion__importable_module_function.snap │ │ ├── gleam_language_server__tests__completion__importable_module_function_from_deep_module.snap │ │ ├── gleam_language_server__tests__completion__importable_module_function_with_existing_imports.snap │ │ ├── gleam_language_server__tests__completion__importable_type.snap │ │ ├── gleam_language_server__tests__completion__importable_type_from_deep_module.snap │ │ ├── gleam_language_server__tests__completion__importable_type_with_existing_imports.snap │ │ ├── gleam_language_server__tests__completion__importable_type_with_existing_imports_at_top.snap │ │ ├── gleam_language_server__tests__completion__imported_module_function.snap │ │ ├── gleam_language_server__tests__completion__imported_public_enum.snap │ │ ├── gleam_language_server__tests__completion__imported_public_record.snap │ │ ├── gleam_language_server__tests__completion__imported_type.snap │ │ ├── gleam_language_server__tests__completion__imported_type_cursor_after_dot.snap │ │ ├── gleam_language_server__tests__completion__imported_type_cursor_after_dot_other_matching_modules.snap │ │ ├── gleam_language_server__tests__completion__imported_type_cursor_after_dot_other_modules.snap │ │ ├── gleam_language_server__tests__completion__imported_type_cursor_mid_phrase_other_modules.snap │ │ ├── gleam_language_server__tests__completion__imported_unqualified_module_function.snap │ │ ├── gleam_language_server__tests__completion__imported_unqualified_public_enum.snap │ │ ├── gleam_language_server__tests__completion__imported_unqualified_public_record.snap │ │ ├── gleam_language_server__tests__completion__in_custom_type_definition.snap │ │ ├── gleam_language_server__tests__completion__internal_modules_from_same_package_are_included.snap │ │ ├── gleam_language_server__tests__completion__internal_types_from_a_dependency_are_ignored.snap │ │ ├── gleam_language_server__tests__completion__internal_types_from_root_package_are_in_the_completions.snap │ │ ├── gleam_language_server__tests__completion__internal_types_from_the_same_module_are_in_the_completions.snap │ │ ├── gleam_language_server__tests__completion__internal_values_from_a_dependency_are_ignored.snap │ │ ├── gleam_language_server__tests__completion__internal_values_from_root_package_are_in_the_completions.snap │ │ ├── gleam_language_server__tests__completion__internal_values_from_the_same_module_are_in_the_completions.snap │ │ ├── gleam_language_server__tests__completion__labelled_arguments.snap │ │ ├── gleam_language_server__tests__completion__labelled_arguments_after_label.snap │ │ ├── gleam_language_server__tests__completion__labelled_arguments_from_different_module.snap │ │ ├── gleam_language_server__tests__completion__labelled_arguments_function_call.snap │ │ ├── gleam_language_server__tests__completion__labelled_arguments_with_existing_label.snap │ │ ├── gleam_language_server__tests__completion__local_private_type.snap │ │ ├── gleam_language_server__tests__completion__local_public_enum.snap │ │ ├── gleam_language_server__tests__completion__local_public_enum_with_documentation.snap │ │ ├── gleam_language_server__tests__completion__local_public_function.snap │ │ ├── gleam_language_server__tests__completion__local_public_function_with_documentation.snap │ │ ├── gleam_language_server__tests__completion__local_public_record.snap │ │ ├── gleam_language_server__tests__completion__local_public_record_with_documentation.snap │ │ ├── gleam_language_server__tests__completion__local_variable.snap │ │ ├── gleam_language_server__tests__completion__local_variable_anonymous_function.snap │ │ ├── gleam_language_server__tests__completion__local_variable_as.snap │ │ ├── gleam_language_server__tests__completion__local_variable_bit_array.snap │ │ ├── gleam_language_server__tests__completion__local_variable_case_expression.snap │ │ ├── gleam_language_server__tests__completion__local_variable_function_call.snap │ │ ├── gleam_language_server__tests__completion__local_variable_ignore_anonymous_function_args.snap │ │ ├── gleam_language_server__tests__completion__local_variable_ignore_anonymous_function_args_nested.snap │ │ ├── gleam_language_server__tests__completion__local_variable_ignore_anonymous_function_returned.snap │ │ ├── gleam_language_server__tests__completion__local_variable_ignore_within_function.snap │ │ ├── gleam_language_server__tests__completion__local_variable_ignored.snap │ │ ├── gleam_language_server__tests__completion__local_variable_inside_nested_exprs.snap │ │ ├── gleam_language_server__tests__completion__local_variable_nested_anonymous_function.snap │ │ ├── gleam_language_server__tests__completion__local_variable_pipe.snap │ │ ├── gleam_language_server__tests__completion__local_variable_pipe_with_args.snap │ │ ├── gleam_language_server__tests__completion__local_variable_string.snap │ │ ├── gleam_language_server__tests__completion__local_variable_tuple.snap │ │ ├── gleam_language_server__tests__completion__no_completions_for_imported_internal_record_fields.snap │ │ ├── gleam_language_server__tests__completion__no_label_completions_in_nested_expression.snap │ │ ├── gleam_language_server__tests__completion__no_variable_completions_after_anonymous_function_scope.snap │ │ ├── gleam_language_server__tests__completion__no_variable_completions_after_block_scope.snap │ │ ├── gleam_language_server__tests__completion__no_variable_completions_after_case_clause_scope.snap │ │ ├── gleam_language_server__tests__completion__no_variable_completions_after_case_scope.snap │ │ ├── gleam_language_server__tests__completion__no_variable_completions_before_case_clause.snap │ │ ├── gleam_language_server__tests__completion__no_variable_completions_before_declaration_in_anonymous_function.snap │ │ ├── gleam_language_server__tests__completion__no_variable_completions_before_declaration_in_block.snap │ │ ├── gleam_language_server__tests__completion__opaque_type.snap │ │ ├── gleam_language_server__tests__completion__prefer_function_which_returns_expected_generic_type.snap │ │ ├── gleam_language_server__tests__completion__prefer_function_which_returns_expected_type.snap │ │ ├── gleam_language_server__tests__completion__prefer_values_matching_expected_type.snap │ │ ├── gleam_language_server__tests__completion__private_function.snap │ │ ├── gleam_language_server__tests__completion__private_function_in_dep.snap │ │ ├── gleam_language_server__tests__completion__private_type.snap │ │ ├── gleam_language_server__tests__completion__private_type_in_dep.snap │ │ ├── gleam_language_server__tests__completion__unqualified_imported_type.snap │ │ ├── gleam_language_server__tests__completion__variable_shadowing.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_constant.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_constant_imported_record.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_constant_record.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_deep_type_in_module.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_external_module_constants.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_external_module_function_calls.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_external_module_records.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_from_alternative_pattern.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_from_anonymous_function.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_import.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_import_aliased.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_import_unqualified_type.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_import_unqualified_value.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_imported_constant.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_imported_module_constants.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_imported_module_records.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_local_variable.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_module.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_module_function_calls.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_of_external_function_in_same_module.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_of_local_variable_from_guard.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_of_module_select_from_guard.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_of_record_from_guard.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_of_record_module_select_from_guard.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_path_module_function_calls.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_record_update.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_same_module_constants.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_same_module_functions.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_same_module_records.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_type.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_type_in_module.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_type_in_path_dep.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_unqualified_function.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_unqualified_imported_module_constants.snap │ │ ├── gleam_language_server__tests__definition__goto_definition_unqualified_imported_module_records.snap │ │ ├── gleam_language_server__tests__definition__goto_type_definition_can_jump_to_all_types_in_a_function_type.snap │ │ ├── gleam_language_server__tests__definition__goto_type_definition_can_jump_to_all_types_in_a_tuple.snap │ │ ├── gleam_language_server__tests__definition__goto_type_definition_can_jump_to_multiple_types.snap │ │ ├── gleam_language_server__tests__definition__goto_type_definition_in_different_file_of_dependency.snap │ │ ├── gleam_language_server__tests__definition__goto_type_definition_in_different_file_of_same_project.snap │ │ ├── gleam_language_server__tests__definition__goto_type_definition_in_same_file.snap │ │ ├── gleam_language_server__tests__document_symbols__doc_symbols_constant.snap │ │ ├── gleam_language_server__tests__document_symbols__doc_symbols_function.snap │ │ ├── gleam_language_server__tests__document_symbols__doc_symbols_type_alias.snap │ │ ├── gleam_language_server__tests__document_symbols__doc_symbols_type_constructor_labeled_args.snap │ │ ├── gleam_language_server__tests__document_symbols__doc_symbols_type_constructor_no_args.snap │ │ ├── gleam_language_server__tests__document_symbols__doc_symbols_type_constructor_pos_and_labeled_args.snap │ │ ├── gleam_language_server__tests__document_symbols__doc_symbols_type_constructor_pos_args.snap │ │ ├── gleam_language_server__tests__document_symbols__doc_symbols_type_no_constructors.snap │ │ ├── gleam_language_server__tests__document_symbols__doc_symbols_type_no_constructors_starting_at_documentation.snap │ │ ├── gleam_language_server__tests__document_symbols__doc_symbols_type_no_constructors_starting_at_empty_doc.snap │ │ ├── gleam_language_server__tests__folding_range__folds_import_block.snap │ │ ├── gleam_language_server__tests__folding_range__folds_mixed_definitions_in_source_order.snap │ │ ├── gleam_language_server__tests__folding_range__folds_multiline_constant.snap │ │ ├── gleam_language_server__tests__folding_range__folds_multiline_custom_type.snap │ │ ├── gleam_language_server__tests__folding_range__folds_multiline_function_body.snap │ │ ├── gleam_language_server__tests__folding_range__folds_multiline_type_alias.snap │ │ ├── gleam_language_server__tests__folding_range__folds_only_multiline_functions_in_source_order.snap │ │ ├── gleam_language_server__tests__folding_range__folds_separated_import_blocks.snap │ │ ├── gleam_language_server__tests__hover__documentation_for_shared_record_field_when_variant_is_known.snap │ │ ├── gleam_language_server__tests__hover__hover_assignment_annotation.snap │ │ ├── gleam_language_server__tests__hover__hover_contextual_type.snap │ │ ├── gleam_language_server__tests__hover__hover_contextual_type_aliased.snap │ │ ├── gleam_language_server__tests__hover__hover_contextual_type_aliased_module.snap │ │ ├── gleam_language_server__tests__hover__hover_contextual_type_annotation.snap │ │ ├── gleam_language_server__tests__hover__hover_contextual_type_annotation_aliased.snap │ │ ├── gleam_language_server__tests__hover__hover_contextual_type_annotation_aliased_module.snap │ │ ├── gleam_language_server__tests__hover__hover_contextual_type_annotation_prelude.snap │ │ ├── gleam_language_server__tests__hover__hover_contextual_type_annotation_unqualified.snap │ │ ├── gleam_language_server__tests__hover__hover_contextual_type_annotation_unqualified_aliased.snap │ │ ├── gleam_language_server__tests__hover__hover_contextual_type_arg.snap │ │ ├── gleam_language_server__tests__hover__hover_contextual_type_expression.snap │ │ ├── gleam_language_server__tests__hover__hover_contextual_type_function.snap │ │ ├── gleam_language_server__tests__hover__hover_contextual_type_pattern.snap │ │ ├── gleam_language_server__tests__hover__hover_contextual_type_pattern_spread.snap │ │ ├── gleam_language_server__tests__hover__hover_contextual_type_unqualified.snap │ │ ├── gleam_language_server__tests__hover__hover_contextual_type_unqualified_aliased.snap │ │ ├── gleam_language_server__tests__hover__hover_contextual_type_unqualified_import.snap │ │ ├── gleam_language_server__tests__hover__hover_expressions_in_function_body.snap │ │ ├── gleam_language_server__tests__hover__hover_external_function_with_another_value_same_name.snap │ │ ├── gleam_language_server__tests__hover__hover_external_imported_constants.snap │ │ ├── gleam_language_server__tests__hover__hover_external_imported_ffi_renamed_function.snap │ │ ├── gleam_language_server__tests__hover__hover_external_imported_function.snap │ │ ├── gleam_language_server__tests__hover__hover_external_imported_function_nested_module.snap │ │ ├── gleam_language_server__tests__hover__hover_external_imported_function_renamed_module.snap │ │ ├── gleam_language_server__tests__hover__hover_external_imported_unqualified_constants.snap │ │ ├── gleam_language_server__tests__hover__hover_external_imported_unqualified_function.snap │ │ ├── gleam_language_server__tests__hover__hover_external_unqualified_imported_function_renamed_module.snap │ │ ├── gleam_language_server__tests__hover__hover_external_value_with_two_modules_same_name.snap │ │ ├── gleam_language_server__tests__hover__hover_for_annotation_in_use.snap │ │ ├── gleam_language_server__tests__hover__hover_for_anonymous_function_annotation.snap │ │ ├── gleam_language_server__tests__hover__hover_for_constant_bit_array.snap │ │ ├── gleam_language_server__tests__hover__hover_for_constant_bit_array_segment.snap │ │ ├── gleam_language_server__tests__hover__hover_for_constant_bit_array_segment_option.snap │ │ ├── gleam_language_server__tests__hover__hover_for_constant_float.snap │ │ ├── gleam_language_server__tests__hover__hover_for_constant_int.snap │ │ ├── gleam_language_server__tests__hover__hover_for_constant_list.snap │ │ ├── gleam_language_server__tests__hover__hover_for_constant_list_element.snap │ │ ├── gleam_language_server__tests__hover__hover_for_constant_other_constant.snap │ │ ├── gleam_language_server__tests__hover__hover_for_constant_record.snap │ │ ├── gleam_language_server__tests__hover__hover_for_constant_string.snap │ │ ├── gleam_language_server__tests__hover__hover_for_constant_string_concatenation.snap │ │ ├── gleam_language_server__tests__hover__hover_for_constant_string_concatenation_side.snap │ │ ├── gleam_language_server__tests__hover__hover_for_constant_tuple.snap │ │ ├── gleam_language_server__tests__hover__hover_for_constant_tuple_element.snap │ │ ├── gleam_language_server__tests__hover__hover_for_custom_type.snap │ │ ├── gleam_language_server__tests__hover__hover_for_invalid_record_update_1.snap │ │ ├── gleam_language_server__tests__hover__hover_for_invalid_record_update_2.snap │ │ ├── gleam_language_server__tests__hover__hover_for_invalid_record_update_3.snap │ │ ├── gleam_language_server__tests__hover__hover_for_invalid_record_update_4.snap.new │ │ ├── gleam_language_server__tests__hover__hover_for_label_in_expression.snap │ │ ├── gleam_language_server__tests__hover__hover_for_label_in_pattern.snap │ │ ├── gleam_language_server__tests__hover__hover_for_local_variable_from_guard.snap │ │ ├── gleam_language_server__tests__hover__hover_for_module_select_from_guard.snap │ │ ├── gleam_language_server__tests__hover__hover_for_module_select_pattern.snap │ │ ├── gleam_language_server__tests__hover__hover_for_nested_constant.snap │ │ ├── gleam_language_server__tests__hover__hover_for_pattern_in_use.snap │ │ ├── gleam_language_server__tests__hover__hover_for_pattern_spread_ignoring_all_fields.snap │ │ ├── gleam_language_server__tests__hover__hover_for_pattern_spread_ignoring_all_positional_fields.snap │ │ ├── gleam_language_server__tests__hover__hover_for_pattern_spread_ignoring_some_fields.snap │ │ ├── gleam_language_server__tests__hover__hover_for_record_from_guard.snap │ │ ├── gleam_language_server__tests__hover__hover_for_record_module_select_from_guard.snap │ │ ├── gleam_language_server__tests__hover__hover_for_string_prefix_pattern.snap │ │ ├── gleam_language_server__tests__hover__hover_for_string_prefix_pattern_prefix_alias.snap │ │ ├── gleam_language_server__tests__hover__hover_for_string_prefix_pattern_prefix_alias_alternative_definition.snap │ │ ├── gleam_language_server__tests__hover__hover_for_string_prefix_pattern_suffix_variable.snap │ │ ├── gleam_language_server__tests__hover__hover_for_string_prefix_pattern_suffix_variable_alternative_definition.snap │ │ ├── gleam_language_server__tests__hover__hover_for_string_prefix_pattern_suffix_variable_discard.snap │ │ ├── gleam_language_server__tests__hover__hover_function_arg_annotation_2.snap │ │ ├── gleam_language_server__tests__hover__hover_function_arg_annotation_with_documentation.snap │ │ ├── gleam_language_server__tests__hover__hover_function_argument.snap │ │ ├── gleam_language_server__tests__hover__hover_function_definition.snap │ │ ├── gleam_language_server__tests__hover__hover_function_definition_with_docs.snap │ │ ├── gleam_language_server__tests__hover__hover_function_return_annotation.snap │ │ ├── gleam_language_server__tests__hover__hover_function_return_annotation_with_tuple.snap │ │ ├── gleam_language_server__tests__hover__hover_import_unqualified_type.snap │ │ ├── gleam_language_server__tests__hover__hover_import_unqualified_value.snap │ │ ├── gleam_language_server__tests__hover__hover_import_unqualified_value_from_hex.snap │ │ ├── gleam_language_server__tests__hover__hover_imported_function.snap │ │ ├── gleam_language_server__tests__hover__hover_label_shorthand_in_call_arg.snap │ │ ├── gleam_language_server__tests__hover__hover_label_shorthand_in_pattern_call_arg.snap │ │ ├── gleam_language_server__tests__hover__hover_label_shorthand_in_pattern_call_arg_2.snap │ │ ├── gleam_language_server__tests__hover__hover_local_function.snap │ │ ├── gleam_language_server__tests__hover__hover_local_function_in_pipe.snap │ │ ├── gleam_language_server__tests__hover__hover_local_function_in_pipe_1.snap │ │ ├── gleam_language_server__tests__hover__hover_local_function_in_pipe_2.snap │ │ ├── gleam_language_server__tests__hover__hover_local_function_in_pipe_3.snap │ │ ├── gleam_language_server__tests__hover__hover_module_constant.snap │ │ ├── gleam_language_server__tests__hover__hover_module_constant_annotation.snap │ │ ├── gleam_language_server__tests__hover__hover_on_pipe_with_invalid_step.snap │ │ ├── gleam_language_server__tests__hover__hover_on_pipe_with_invalid_step_1.snap │ │ ├── gleam_language_server__tests__hover__hover_on_pipe_with_invalid_step_2.snap │ │ ├── gleam_language_server__tests__hover__hover_on_pipe_with_invalid_step_3.snap │ │ ├── gleam_language_server__tests__hover__hover_on_pipe_with_invalid_step_4.snap │ │ ├── gleam_language_server__tests__hover__hover_on_pipe_with_invalid_step_5.snap │ │ ├── gleam_language_server__tests__hover__hover_on_pipe_with_invalid_step_6.snap │ │ ├── gleam_language_server__tests__hover__hover_on_pipe_with_invalid_step_8.snap │ │ ├── gleam_language_server__tests__hover__hover_over_block_in_list_spread.snap │ │ ├── gleam_language_server__tests__hover__hover_over_imported_module.snap │ │ ├── gleam_language_server__tests__hover__hover_over_module_name.snap │ │ ├── gleam_language_server__tests__hover__hover_over_module_name_in_annotation.snap │ │ ├── gleam_language_server__tests__hover__hover_over_module_with_path.snap │ │ ├── gleam_language_server__tests__hover__hover_prelude_type.snap │ │ ├── gleam_language_server__tests__hover__hover_print_alias_when_parameters_match.snap │ │ ├── gleam_language_server__tests__hover__hover_print_aliased_imported_generic_type.snap │ │ ├── gleam_language_server__tests__hover__hover_print_imported_alias.snap │ │ ├── gleam_language_server__tests__hover__hover_print_qualified_prelude_type_when_shadowed_by_alias.snap │ │ ├── gleam_language_server__tests__hover__hover_print_qualified_prelude_type_when_shadowed_by_imported_alias.snap │ │ ├── gleam_language_server__tests__hover__hover_print_type_variable_names.snap │ │ ├── gleam_language_server__tests__hover__hover_print_unbound_type_variable_name_without_conflicts.snap │ │ ├── gleam_language_server__tests__hover__hover_print_unbound_type_variable_names.snap │ │ ├── gleam_language_server__tests__hover__hover_print_underlying_for_alias_with_parameters.snap │ │ ├── gleam_language_server__tests__hover__hover_print_underlying_for_imported_alias.snap │ │ ├── gleam_language_server__tests__hover__hover_shadowed_prelude_type.snap │ │ ├── gleam_language_server__tests__hover__hover_shadowed_prelude_type_imported.snap │ │ ├── gleam_language_server__tests__hover__hover_type_alias_annotation.snap │ │ ├── gleam_language_server__tests__hover__hover_type_constructor_annotation.snap │ │ ├── gleam_language_server__tests__hover__hover_type_constructor_with_label.snap │ │ ├── gleam_language_server__tests__hover__hover_type_constructor_with_no_fields.snap │ │ ├── gleam_language_server__tests__hover__hover_type_constructor_with_no_label.snap │ │ ├── gleam_language_server__tests__hover__hover_variable_in_use_expression.snap │ │ ├── gleam_language_server__tests__hover__hover_variable_in_use_expression_1.snap │ │ ├── gleam_language_server__tests__hover__hover_variable_in_use_expression_2.snap │ │ ├── gleam_language_server__tests__hover__hover_works_even_for_invalid_code.snap │ │ ├── gleam_language_server__tests__hover__no_documentation_for_shared_record_field.snap │ │ ├── gleam_language_server__tests__hover__no_hexdocs_link_when_hovering_over_local_module.snap │ │ ├── gleam_language_server__tests__hover__record_field_documentation.snap │ │ ├── gleam_language_server__tests__reference__references_for_aliased_const.snap │ │ ├── gleam_language_server__tests__reference__references_for_aliased_function.snap │ │ ├── gleam_language_server__tests__reference__references_for_aliased_type.snap │ │ ├── gleam_language_server__tests__reference__references_for_aliased_value.snap │ │ ├── gleam_language_server__tests__reference__references_for_constant_from_qualified_reference.snap │ │ ├── gleam_language_server__tests__reference__references_for_constant_from_unqualified_reference.snap │ │ ├── gleam_language_server__tests__reference__references_for_function_from_qualified_reference.snap │ │ ├── gleam_language_server__tests__reference__references_for_function_from_unqualified_reference.snap │ │ ├── gleam_language_server__tests__reference__references_for_local_variable.snap │ │ ├── gleam_language_server__tests__reference__references_for_local_variable_from_definition.snap │ │ ├── gleam_language_server__tests__reference__references_for_local_variable_from_guard.snap │ │ ├── gleam_language_server__tests__reference__references_for_module_select_from_guard.snap │ │ ├── gleam_language_server__tests__reference__references_for_prefix_string_alias_and_suffix_complex_guard.snap │ │ ├── gleam_language_server__tests__reference__references_for_prefix_string_alias_in_case.snap │ │ ├── gleam_language_server__tests__reference__references_for_prefix_string_alias_in_case_triggered_from_usage.snap │ │ ├── gleam_language_server__tests__reference__references_for_prefix_string_alias_in_let_assert.snap │ │ ├── gleam_language_server__tests__reference__references_for_prefix_string_alias_in_let_assert_triggered_from_usage.snap │ │ ├── gleam_language_server__tests__reference__references_for_prefix_string_alias_used_in_guard.snap │ │ ├── gleam_language_server__tests__reference__references_for_prefix_string_alias_with_alternative_definitions_in_case.snap │ │ ├── gleam_language_server__tests__reference__references_for_prefix_string_alias_with_alternative_definitions_triggered_from_second_pattern.snap │ │ ├── gleam_language_server__tests__reference__references_for_prefix_string_alias_with_alternative_definitions_triggered_from_usage.snap │ │ ├── gleam_language_server__tests__reference__references_for_prefix_string_suffix_shadowing_outer_variable.snap │ │ ├── gleam_language_server__tests__reference__references_for_prefix_string_suffix_used_in_guard.snap │ │ ├── gleam_language_server__tests__reference__references_for_prefix_string_suffix_variable_in_case.snap │ │ ├── gleam_language_server__tests__reference__references_for_prefix_string_suffix_variable_in_case_triggered_from_usage.snap │ │ ├── gleam_language_server__tests__reference__references_for_prefix_string_suffix_variable_in_let_assert.snap │ │ ├── gleam_language_server__tests__reference__references_for_prefix_string_suffix_variable_in_let_assert_triggered_from_usage.snap │ │ ├── gleam_language_server__tests__reference__references_for_prefix_string_suffix_variable_nested_in_tuple.snap │ │ ├── gleam_language_server__tests__reference__references_for_prefix_string_suffix_variable_with_alternative_definition_in_case.snap │ │ ├── gleam_language_server__tests__reference__references_for_prefix_string_suffix_variable_with_alternative_definition_triggered_from_second_pattern.snap │ │ ├── gleam_language_server__tests__reference__references_for_prefix_string_suffix_variable_with_alternative_definition_triggered_from_usage.snap │ │ ├── gleam_language_server__tests__reference__references_for_private_constant.snap │ │ ├── gleam_language_server__tests__reference__references_for_private_constant_from_reference.snap │ │ ├── gleam_language_server__tests__reference__references_for_private_function.snap │ │ ├── gleam_language_server__tests__reference__references_for_private_function_from_reference.snap │ │ ├── gleam_language_server__tests__reference__references_for_private_type.snap │ │ ├── gleam_language_server__tests__reference__references_for_private_type_from_reference.snap │ │ ├── gleam_language_server__tests__reference__references_for_private_type_variant.snap │ │ ├── gleam_language_server__tests__reference__references_for_private_type_variant_from_reference.snap │ │ ├── gleam_language_server__tests__reference__references_for_public_constant.snap │ │ ├── gleam_language_server__tests__reference__references_for_public_function.snap │ │ ├── gleam_language_server__tests__reference__references_for_public_type.snap │ │ ├── gleam_language_server__tests__reference__references_for_public_type_variant.snap │ │ ├── gleam_language_server__tests__reference__references_for_type_from_let_annotation.snap │ │ ├── gleam_language_server__tests__reference__references_for_type_from_qualified_reference.snap │ │ ├── gleam_language_server__tests__reference__references_for_type_from_unqualified_reference.snap │ │ ├── gleam_language_server__tests__reference__references_for_type_variant_from_qualified_reference.snap │ │ ├── gleam_language_server__tests__reference__references_for_type_variant_from_unqualified_reference.snap │ │ ├── gleam_language_server__tests__rename__alias_imported_module_from_guard.snap │ │ ├── gleam_language_server__tests__rename__no_rename_constant_with_invalid_name.snap │ │ ├── gleam_language_server__tests__rename__no_rename_function_with_invalid_name.snap │ │ ├── gleam_language_server__tests__rename__no_rename_invalid_name.snap │ │ ├── gleam_language_server__tests__rename__no_rename_type_variant_with_invalid_name.snap │ │ ├── gleam_language_server__tests__rename__no_rename_type_with_invalid_name.snap │ │ ├── gleam_language_server__tests__rename__reanem_module_from_import_with_unqualified_values.snap │ │ ├── gleam_language_server__tests__rename__rename_aliased_constant.snap │ │ ├── gleam_language_server__tests__rename__rename_aliased_function.snap │ │ ├── gleam_language_server__tests__rename__rename_aliased_type.snap │ │ ├── gleam_language_server__tests__rename__rename_aliased_type_variant.snap │ │ ├── gleam_language_server__tests__rename__rename_aliased_value.snap │ │ ├── gleam_language_server__tests__rename__rename_alternative_pattern.snap │ │ ├── gleam_language_server__tests__rename__rename_alternative_pattern_alias_and_variable_1.snap │ │ ├── gleam_language_server__tests__rename__rename_alternative_pattern_alias_and_variable_2.snap │ │ ├── gleam_language_server__tests__rename__rename_alternative_pattern_alias_and_variable_3.snap │ │ ├── gleam_language_server__tests__rename__rename_alternative_pattern_alias_and_variable_4.snap │ │ ├── gleam_language_server__tests__rename__rename_alternative_pattern_aliases.snap │ │ ├── gleam_language_server__tests__rename__rename_alternative_pattern_aliases_from_alternative.snap │ │ ├── gleam_language_server__tests__rename__rename_alternative_pattern_aliases_from_usage.snap │ │ ├── gleam_language_server__tests__rename__rename_alternative_pattern_from_usage.snap │ │ ├── gleam_language_server__tests__rename__rename_constant_from_definition.snap │ │ ├── gleam_language_server__tests__rename__rename_constant_from_qualified_reference.snap │ │ ├── gleam_language_server__tests__rename__rename_constant_from_reference.snap │ │ ├── gleam_language_server__tests__rename__rename_constant_from_unqualified_reference.snap │ │ ├── gleam_language_server__tests__rename__rename_constant_shadowed_by_field_access.snap │ │ ├── gleam_language_server__tests__rename__rename_constant_shadowing_module.snap │ │ ├── gleam_language_server__tests__rename__rename_custom_type_variant_pattern.snap │ │ ├── gleam_language_server__tests__rename__rename_external_function.snap │ │ ├── gleam_language_server__tests__rename__rename_external_javascript_function_with_pure_gleam_fallback.snap │ │ ├── gleam_language_server__tests__rename__rename_function_from_definition.snap │ │ ├── gleam_language_server__tests__rename__rename_function_from_qualified_reference.snap │ │ ├── gleam_language_server__tests__rename__rename_function_from_reference.snap │ │ ├── gleam_language_server__tests__rename__rename_function_from_unqualified_reference.snap │ │ ├── gleam_language_server__tests__rename__rename_function_shadowed_by_field_access.snap │ │ ├── gleam_language_server__tests__rename__rename_function_shadowing_module.snap │ │ ├── gleam_language_server__tests__rename__rename_imported_custom_type_variant_pattern.snap │ │ ├── gleam_language_server__tests__rename__rename_imported_unqualified_custom_type_variant_pattern.snap │ │ ├── gleam_language_server__tests__rename__rename_local_variable.snap │ │ ├── gleam_language_server__tests__rename__rename_local_variable_argument.snap │ │ ├── gleam_language_server__tests__rename__rename_local_variable_argument_from_definition.snap │ │ ├── gleam_language_server__tests__rename__rename_local_variable_assignment_pattern.snap │ │ ├── gleam_language_server__tests__rename__rename_local_variable_from_bit_array_pattern.snap │ │ ├── gleam_language_server__tests__rename__rename_local_variable_from_definition.snap │ │ ├── gleam_language_server__tests__rename__rename_local_variable_from_definition_assignment_pattern.snap │ │ ├── gleam_language_server__tests__rename__rename_local_variable_from_definition_nested_pattern.snap │ │ ├── gleam_language_server__tests__rename__rename_local_variable_from_guard.snap │ │ ├── gleam_language_server__tests__rename__rename_local_variable_from_label_shorthand.snap │ │ ├── gleam_language_server__tests__rename__rename_local_variable_guard_clause.snap │ │ ├── gleam_language_server__tests__rename__rename_local_variable_in_bit_array_pattern.snap │ │ ├── gleam_language_server__tests__rename__rename_local_variable_label_shorthand.snap │ │ ├── gleam_language_server__tests__rename__rename_local_variable_label_shorthand_from_definition.snap │ │ ├── gleam_language_server__tests__rename__rename_local_variable_record_access.snap │ │ ├── gleam_language_server__tests__rename__rename_local_variable_with_label_shorthand.snap │ │ ├── gleam_language_server__tests__rename__rename_module_access_in_clause_guard.snap │ │ ├── gleam_language_server__tests__rename__rename_module_from_alias_use.snap │ │ ├── gleam_language_server__tests__rename__rename_module_from_constant_in_clause_guard.snap │ │ ├── gleam_language_server__tests__rename__rename_module_from_constant_in_const.snap │ │ ├── gleam_language_server__tests__rename__rename_module_from_constant_in_expression.snap │ │ ├── gleam_language_server__tests__rename__rename_module_from_function_call.snap │ │ ├── gleam_language_server__tests__rename__rename_module_from_import.snap │ │ ├── gleam_language_server__tests__rename__rename_module_from_import_namespaced.snap │ │ ├── gleam_language_server__tests__rename__rename_module_from_import_namespaced_with_alias.snap │ │ ├── gleam_language_server__tests__rename__rename_module_from_import_namespaced_with_unqualified_value_and_alias.snap │ │ ├── gleam_language_server__tests__rename__rename_module_from_import_namespaced_with_unqualified_values.snap │ │ ├── gleam_language_server__tests__rename__rename_module_from_import_with_alias.snap │ │ ├── gleam_language_server__tests__rename__rename_module_from_import_with_alias_to_original_name.snap │ │ ├── gleam_language_server__tests__rename__rename_module_from_import_with_unqualified_value_and_alias.snap │ │ ├── gleam_language_server__tests__rename__rename_module_from_type_in_annotation.snap │ │ ├── gleam_language_server__tests__rename__rename_module_from_type_in_custom_type.snap │ │ ├── gleam_language_server__tests__rename__rename_module_from_type_in_type_alias.snap │ │ ├── gleam_language_server__tests__rename__rename_module_from_variant_in_clause_guard.snap │ │ ├── gleam_language_server__tests__rename__rename_module_from_variant_in_const.snap │ │ ├── gleam_language_server__tests__rename__rename_module_from_variant_in_expression.snap │ │ ├── gleam_language_server__tests__rename__rename_module_from_variant_in_pattern.snap │ │ ├── gleam_language_server__tests__rename__rename_module_select_from_guard.snap │ │ ├── gleam_language_server__tests__rename__rename_nested_aliased_pattern.snap │ │ ├── gleam_language_server__tests__rename__rename_prefix_string_alias_and_suffix_complex_guard.snap │ │ ├── gleam_language_server__tests__rename__rename_prefix_string_alias_in_case.snap │ │ ├── gleam_language_server__tests__rename__rename_prefix_string_alias_in_case_triggered_from_usage.snap │ │ ├── gleam_language_server__tests__rename__rename_prefix_string_alias_in_let_assert.snap │ │ ├── gleam_language_server__tests__rename__rename_prefix_string_alias_in_let_assert_triggered_from_usage.snap │ │ ├── gleam_language_server__tests__rename__rename_prefix_string_alias_used_in_guard.snap │ │ ├── gleam_language_server__tests__rename__rename_prefix_string_alias_with_alternative_definitions_in_case.snap │ │ ├── gleam_language_server__tests__rename__rename_prefix_string_alias_with_alternative_definitions_triggered_from_second_pattern.snap │ │ ├── gleam_language_server__tests__rename__rename_prefix_string_suffix_shadowing_outer_variable.snap │ │ ├── gleam_language_server__tests__rename__rename_prefix_string_suffix_used_in_guard.snap │ │ ├── gleam_language_server__tests__rename__rename_prefix_string_suffix_variable_in_case.snap │ │ ├── gleam_language_server__tests__rename__rename_prefix_string_suffix_variable_in_case_triggered_from_usage.snap │ │ ├── gleam_language_server__tests__rename__rename_prefix_string_suffix_variable_in_let_assert.snap │ │ ├── gleam_language_server__tests__rename__rename_prefix_string_suffix_variable_in_let_assert_triggered_from_usage.snap │ │ ├── gleam_language_server__tests__rename__rename_prefix_string_suffix_variable_nested_in_tuple.snap │ │ ├── gleam_language_server__tests__rename__rename_prefix_string_suffix_variable_with_alternative_definition_in_case.snap │ │ ├── gleam_language_server__tests__rename__rename_prefix_string_suffix_variable_with_alternative_definition_triggered_from_second_pattern.snap │ │ ├── gleam_language_server__tests__rename__rename_prelude_type.snap │ │ ├── gleam_language_server__tests__rename__rename_prelude_type_with_prelude_value_imported_with_trailing_comma.snap │ │ ├── gleam_language_server__tests__rename__rename_prelude_value.snap │ │ ├── gleam_language_server__tests__rename__rename_prelude_value_with_other_module_imported.snap │ │ ├── gleam_language_server__tests__rename__rename_prelude_value_with_other_prelude_value_imported.snap │ │ ├── gleam_language_server__tests__rename__rename_prelude_value_with_prelude_already_imported.snap │ │ ├── gleam_language_server__tests__rename__rename_prelude_value_with_prelude_import_with_empty_braces.snap │ │ ├── gleam_language_server__tests__rename__rename_shadowed_local_variable.snap │ │ ├── gleam_language_server__tests__rename__rename_shadowing_local_variable.snap │ │ ├── gleam_language_server__tests__rename__rename_type_from_definition.snap │ │ ├── gleam_language_server__tests__rename__rename_type_from_qualified_reference.snap │ │ ├── gleam_language_server__tests__rename__rename_type_from_reference.snap │ │ ├── gleam_language_server__tests__rename__rename_type_from_unqualified_reference.snap │ │ ├── gleam_language_server__tests__rename__rename_type_from_variant_constructor_argument.snap │ │ ├── gleam_language_server__tests__rename__rename_type_referenced_in_variant_constructor_argument.snap │ │ ├── gleam_language_server__tests__rename__rename_type_variant_from_definition.snap │ │ ├── gleam_language_server__tests__rename__rename_type_variant_from_pattern.snap │ │ ├── gleam_language_server__tests__rename__rename_type_variant_from_qualified_reference.snap │ │ ├── gleam_language_server__tests__rename__rename_type_variant_from_reference.snap │ │ ├── gleam_language_server__tests__rename__rename_type_variant_from_unqualified_reference.snap │ │ ├── gleam_language_server__tests__rename__rename_type_variant_pattern_with_arguments.snap │ │ ├── gleam_language_server__tests__rename__rename_value_in_aliased_module.snap │ │ ├── gleam_language_server__tests__rename__rename_value_in_nested_module.snap │ │ ├── gleam_language_server__tests__rename__rename_variable_used_in_record_update.snap │ │ ├── gleam_language_server__tests__rename__rename_variable_with_alternative_pattern_with_same_name.snap │ │ ├── gleam_language_server__tests__rename__rename_works_when_error_is_present.snap │ │ ├── gleam_language_server__tests__signature_help__help_for_aliased_qualified_call.snap │ │ ├── gleam_language_server__tests__signature_help__help_for_aliased_unqualified_call.snap │ │ ├── gleam_language_server__tests__signature_help__help_for_calling_local_variable_first_arg.snap │ │ ├── gleam_language_server__tests__signature_help__help_for_calling_local_variable_last_arg.snap │ │ ├── gleam_language_server__tests__signature_help__help_for_calling_local_variable_referencing_constant_referencing_function.snap │ │ ├── gleam_language_server__tests__signature_help__help_for_calling_local_variable_with_module_function.snap │ │ ├── gleam_language_server__tests__signature_help__help_for_calling_module_constant_referencing_function.snap │ │ ├── gleam_language_server__tests__signature_help__help_for_calling_module_function.snap │ │ ├── gleam_language_server__tests__signature_help__help_for_piped_function_starts_from_second_argument.snap │ │ ├── gleam_language_server__tests__signature_help__help_for_piped_imported_function_starts_from_second_argument.snap │ │ ├── gleam_language_server__tests__signature_help__help_for_qualified_call.snap │ │ ├── gleam_language_server__tests__signature_help__help_for_unqualified_call.snap │ │ ├── gleam_language_server__tests__signature_help__help_for_use_function_call_starts_from_first_argument.snap │ │ ├── gleam_language_server__tests__signature_help__help_for_use_function_call_uses_concrete_types_when_late_ubound.snap │ │ ├── gleam_language_server__tests__signature_help__help_for_use_function_call_uses_concrete_types_when_possible_or_generic_names_when_unbound.snap │ │ ├── gleam_language_server__tests__signature_help__help_for_use_function_call_uses_generic_names_when_missing_all_arguments.snap │ │ ├── gleam_language_server__tests__signature_help__help_for_use_function_call_uses_precise_types_when_missing_some_arguments.snap │ │ ├── gleam_language_server__tests__signature_help__help_for_use_function_shows_next_unlabelled_argument.snap │ │ ├── gleam_language_server__tests__signature_help__help_shows_documentation_for_imported_function.snap │ │ ├── gleam_language_server__tests__signature_help__help_shows_documentation_for_local_function.snap │ │ ├── gleam_language_server__tests__signature_help__help_shows_first_missing_labelled_argument_if_out_of_order.snap │ │ ├── gleam_language_server__tests__signature_help__help_shows_labelled_argument_after_all_unlabelled.snap │ │ ├── gleam_language_server__tests__signature_help__help_shows_labels.snap │ │ ├── gleam_language_server__tests__signature_help__help_still_shows_up_even_if_an_argument_has_the_wrong_type.snap │ │ └── gleam_language_server__tests__signature_help__help_with_labelled_constructor.snap │ └── tests.rs ├── test/ │ ├── assert/ │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── gleam.toml │ │ ├── manifest.toml │ │ ├── run_tests.sh │ │ └── src/ │ │ ├── failing1.gleam │ │ ├── failing2.gleam │ │ ├── failing3.gleam │ │ ├── failing4.gleam │ │ └── passing.gleam │ ├── compile_package0/ │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── gleam.toml │ │ ├── manifest.toml │ │ ├── src/ │ │ │ ├── one.gleam │ │ │ └── two.gleam │ │ └── test/ │ │ └── three.gleam │ ├── compile_package1/ │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── app1/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ ├── one/ │ │ │ │ └── nested.gleam │ │ │ └── one.gleam │ │ └── app2/ │ │ ├── gleam.toml │ │ └── src/ │ │ └── two.gleam │ ├── erlang_shipment_no_dev_deps/ │ │ ├── .gitignore │ │ ├── gleam.toml │ │ ├── manifest.toml │ │ ├── src/ │ │ │ └── shipment_test.gleam │ │ └── test.sh │ ├── errors/ │ │ └── type_unify_int_string/ │ │ ├── .gitignore │ │ ├── gleam.toml │ │ └── src/ │ │ └── type_unify_int_string.gleam │ ├── external_only_erlang/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── gleam.toml │ │ ├── manifest.toml │ │ ├── src/ │ │ │ ├── external_only_erlang.gleam │ │ │ └── external_only_erlang_ffi.erl │ │ └── test.sh │ ├── external_only_javascript/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── gleam.toml │ │ ├── manifest.toml │ │ ├── src/ │ │ │ ├── external_only_javascript.gleam │ │ │ └── external_only_javascript_ffi.mjs │ │ └── test.sh │ ├── hello_world/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── gleam.toml │ │ ├── manifest.toml │ │ ├── rebar.config │ │ ├── src/ │ │ │ ├── hello_world.app.src │ │ │ ├── hello_world.gleam │ │ │ ├── nest/ │ │ │ │ ├── bird!.gleam │ │ │ │ ├── bird.gleam │ │ │ │ └── bird.js │ │ │ └── other.gleam │ │ └── test/ │ │ └── hello_test.gleam │ ├── hextarball/ │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── gleam.toml │ │ ├── manifest.toml │ │ └── src/ │ │ └── hextarball.gleam │ ├── javascript_prelude/ │ │ ├── .gitignore │ │ ├── Makefile │ │ └── main.mjs │ ├── language/ │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── gleam.toml │ │ ├── manifest.toml │ │ ├── src/ │ │ │ └── language.gleam │ │ └── test/ │ │ ├── ffi.gleam │ │ ├── ffi_erlang.erl │ │ ├── ffi_javascript.mjs │ │ ├── ffi_typescript.ts │ │ ├── importable.gleam │ │ ├── language/ │ │ │ ├── alternative_pattern.gleam │ │ │ ├── anonymous_function_test.gleam │ │ │ ├── assertion_test.gleam │ │ │ ├── bit_array_dynamic_size_test.gleam │ │ │ ├── bit_array_match_test.gleam │ │ │ ├── bit_array_test.gleam │ │ │ ├── block_test.gleam │ │ │ ├── bool_negation_test.gleam │ │ │ ├── build_files_test.gleam │ │ │ ├── clause_guard_test.gleam │ │ │ ├── constant_test.gleam │ │ │ ├── directly_matching_case_subject_test.gleam │ │ │ ├── equality_test.gleam │ │ │ ├── float_test.gleam │ │ │ ├── imported_custom_type_test.gleam │ │ │ ├── importing_test.gleam │ │ │ ├── int_negation_test.gleam │ │ │ ├── int_test.gleam │ │ │ ├── list_prepend_test.gleam │ │ │ ├── mixed_arg_match_test.gleam │ │ │ ├── multiple_case_subject_test.gleam │ │ │ ├── non_utf8_string_bit_array_test.gleam │ │ │ ├── pipe_test.gleam │ │ │ ├── precedence_test.gleam │ │ │ ├── prelude_test.gleam │ │ │ ├── record_access_test.gleam │ │ │ ├── record_update_test.gleam │ │ │ ├── remainder_test.gleam │ │ │ ├── sized_bit_array_test.gleam │ │ │ ├── string_pattern_matching_test.gleam │ │ │ ├── string_test.gleam │ │ │ ├── tail_call_test.gleam │ │ │ ├── tuple_access_test.gleam │ │ │ ├── unaligned_bit_array_expression_test.gleam │ │ │ └── unaligned_bit_array_pattern_test.gleam │ │ ├── language_test.gleam │ │ ├── mod_with_numbers_0123456789.gleam │ │ ├── port.gleam │ │ ├── record_update.gleam │ │ └── shadowed_module.gleam │ ├── multi_namespace/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── gleam.toml │ │ ├── manifest.toml │ │ ├── src/ │ │ │ ├── multi_namespace.gleam │ │ │ └── second.gleam │ │ └── test.sh │ ├── multi_namespace_not_top_level/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── gleam.toml │ │ ├── src/ │ │ │ ├── module1/ │ │ │ │ └── sub.gleam │ │ │ └── module2/ │ │ │ └── sub.gleam │ │ └── test.sh │ ├── package.jsonc │ ├── project_deno/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── gleam.toml │ │ ├── manifest.toml │ │ ├── src/ │ │ │ ├── project.gleam │ │ │ └── project_ffi.mjs │ │ └── test/ │ │ └── project_test.gleam │ ├── project_erlang/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── gleam.toml │ │ ├── manifest.toml │ │ ├── priv/ │ │ │ └── hello.txt │ │ ├── src/ │ │ │ ├── elixir_file.ex │ │ │ ├── erlang_file.erl │ │ │ ├── erlang_header.hrl │ │ │ └── project.gleam │ │ └── test/ │ │ ├── elixir_test_file.ex │ │ ├── erlang_test_file.erl │ │ ├── erlang_test_header.hrl │ │ └── project_test.gleam │ ├── project_erlang_windows/ │ │ ├── .gitignore │ │ ├── gleam.toml │ │ ├── manifest.toml │ │ ├── priv/ │ │ │ └── hello.txt │ │ ├── src/ │ │ │ ├── elixir_file.ex │ │ │ ├── erlang_file.erl │ │ │ ├── erlang_header.hrl │ │ │ └── project.gleam │ │ └── test/ │ │ ├── elixir_test_file.ex │ │ ├── erlang_test_file.erl │ │ ├── erlang_test_header.hrl │ │ └── project_test.gleam │ ├── project_git_deps/ │ │ ├── .gitignore │ │ ├── gleam.toml │ │ ├── manifest.toml │ │ └── src/ │ │ └── git_deps.gleam │ ├── project_javascript/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── gleam.toml │ │ ├── manifest.toml │ │ ├── src/ │ │ │ ├── project.gleam │ │ │ └── project_ffi.mjs │ │ └── test/ │ │ └── project_test.gleam │ ├── project_path_deps/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── project_a/ │ │ │ ├── gleam.toml │ │ │ ├── manifest.toml │ │ │ ├── src/ │ │ │ │ └── project_a.gleam │ │ │ └── test/ │ │ │ └── project_a_test.gleam │ │ ├── project_b/ │ │ │ ├── gleam.toml │ │ │ ├── manifest.toml │ │ │ └── src/ │ │ │ └── project_b.gleam │ │ ├── project_c/ │ │ │ ├── gleam.toml │ │ │ ├── manifest.toml │ │ │ └── src/ │ │ │ └── project_c.gleam │ │ └── project_d/ │ │ ├── gleam.toml │ │ ├── manifest.toml │ │ ├── src/ │ │ │ └── project_d.gleam │ │ └── test/ │ │ └── project_d_test.gleam │ ├── publishing_default_main/ │ │ ├── .gitignore │ │ ├── gleam.toml │ │ ├── manifest.toml │ │ ├── src/ │ │ │ ├── default_main/ │ │ │ │ ├── one.gleam │ │ │ │ └── two.gleam │ │ │ └── default_main.gleam │ │ └── test.sh │ ├── publishing_default_readme/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── gleam.toml │ │ ├── manifest.toml │ │ ├── src/ │ │ │ └── default_readme.gleam │ │ └── test.sh │ ├── publishing_empty_readme/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── gleam.toml │ │ ├── manifest.toml │ │ ├── src/ │ │ │ └── empty_readme.gleam │ │ └── test.sh │ ├── publishing_no_readme/ │ │ ├── .gitignore │ │ ├── gleam.toml │ │ ├── manifest.toml │ │ ├── src/ │ │ │ └── no_readme.gleam │ │ └── test.sh │ ├── root_package_not_compiled_when_running_dep/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── gleam.toml │ │ ├── manifest.toml │ │ ├── src/ │ │ │ └── root_package_not_compiled_when_running_dep.gleam │ │ └── test.sh │ ├── running_modules/ │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── README.md │ │ ├── dev/ │ │ │ ├── module_dev.gleam │ │ │ ├── module_in_dev.gleam │ │ │ ├── nested/ │ │ │ │ └── module_in_dev.gleam │ │ │ └── wrong_dev_arity.gleam │ │ ├── gleam.toml │ │ ├── manifest.toml │ │ ├── run_tests.sh │ │ ├── src/ │ │ │ ├── module/ │ │ │ │ ├── no_main_function.gleam │ │ │ │ ├── sub_module.gleam │ │ │ │ └── wrong_arity.gleam │ │ │ └── module.gleam │ │ └── test/ │ │ ├── module_in_test.gleam │ │ ├── module_test.gleam │ │ ├── nested/ │ │ │ └── module_in_test.gleam │ │ └── wrong_test_arity.gleam │ ├── subdir_ffi/ │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── README.md │ │ ├── gleam.toml │ │ ├── manifest.toml │ │ ├── src/ │ │ │ ├── headers/ │ │ │ │ └── submodule_ffi_header.hrl │ │ │ ├── nested/ │ │ │ │ ├── submodule.gleam │ │ │ │ ├── submodule_ffi.erl │ │ │ │ ├── submodule_ffi.ex │ │ │ │ └── submodule_ffi.mjs │ │ │ ├── project.gleam │ │ │ ├── project_ffi.erl │ │ │ └── project_ffi.mjs │ │ └── test/ │ │ └── subdir_ffi_test.gleam │ └── unicode_path ⭐/ │ ├── .gitignore │ ├── Makefile │ ├── gleam.toml │ ├── manifest.toml │ └── src/ │ └── unicode_path.gleam ├── test-community-packages/ │ ├── .gitignore │ ├── README.md │ ├── gleam.toml │ ├── manifest.toml │ ├── src/ │ │ └── test_community_packages.gleam │ └── test/ │ └── test_community_packages_test.gleam ├── test-helpers-rs/ │ ├── Cargo.toml │ └── src/ │ └── lib.rs ├── test-output/ │ ├── Cargo.toml │ ├── cases/ │ │ ├── .gitignore │ │ ├── echo_bitarray/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── main.gleam │ │ ├── echo_bool/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── main.gleam │ │ ├── echo_charlist/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── main.gleam │ │ ├── echo_circular_reference/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ ├── main.gleam │ │ │ └── main_ffi.mjs │ │ ├── echo_custom_type/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── main.gleam │ │ ├── echo_dict/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── main.gleam │ │ ├── echo_float/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── main.gleam │ │ ├── echo_function/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── main.gleam │ │ ├── echo_importing_module_named_inspect/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ ├── inspect.gleam │ │ │ └── main.gleam │ │ ├── echo_int/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── main.gleam │ │ ├── echo_list/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── main.gleam │ │ ├── echo_nil/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── main.gleam │ │ ├── echo_non_record_atom_tag/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── main.gleam │ │ ├── echo_singleton/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ ├── main.gleam │ │ │ ├── main_ffi.mjs │ │ │ └── thing.gleam │ │ ├── echo_string/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── main.gleam │ │ ├── echo_tuple/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── main.gleam │ │ ├── echo_with_message/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── main.gleam │ │ └── linked_process_exit/ │ │ ├── gleam.toml │ │ └── src/ │ │ ├── main.gleam │ │ └── main_ffi.erl │ └── src/ │ ├── lib.rs │ ├── tests/ │ │ ├── echo.rs │ │ └── snapshots/ │ │ ├── test_output__tests__echo__echo_bool.snap │ │ ├── test_output__tests__echo__echo_charlist.snap │ │ ├── test_output__tests__echo__echo_dict.snap │ │ ├── test_output__tests__echo__echo_function.snap │ │ ├── test_output__tests__echo__echo_importing_module_named_inspect.snap │ │ ├── test_output__tests__echo__echo_int.snap │ │ ├── test_output__tests__echo__echo_list.snap │ │ ├── test_output__tests__echo__echo_nil.snap │ │ ├── test_output__tests__echo__echo_singleton.snap │ │ ├── test_output__tests__echo__echo_string.snap │ │ ├── test_output__tests__echo__echo_tuple.snap │ │ ├── test_output__tests__echo__echo_with_message.snap │ │ ├── test_output__tests__echo__erlang-echo_bitarray.snap │ │ ├── test_output__tests__echo__erlang-echo_custom_type.snap │ │ ├── test_output__tests__echo__erlang-echo_float.snap │ │ ├── test_output__tests__echo__erlang-echo_non_record_atom_tag.snap │ │ ├── test_output__tests__echo__erlang-linked_process_exit.snap │ │ ├── test_output__tests__echo__javascript-echo_bitarray.snap │ │ ├── test_output__tests__echo__javascript-echo_circular_reference.snap │ │ ├── test_output__tests__echo__javascript-echo_custom_type.snap │ │ └── test_output__tests__echo__javascript-echo_float.snap │ └── tests.rs ├── test-package-compiler/ │ ├── .gitignore │ ├── Cargo.toml │ ├── build.rs │ ├── cases/ │ │ ├── alias_unqualified_import/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ ├── one.gleam │ │ │ └── two.gleam │ │ ├── dev_importing_test/ │ │ │ ├── dev/ │ │ │ │ └── one.gleam │ │ │ ├── gleam.toml │ │ │ └── test/ │ │ │ └── two.gleam │ │ ├── duplicate_module/ │ │ │ ├── gleam.toml │ │ │ ├── src/ │ │ │ │ └── main.gleam │ │ │ └── test/ │ │ │ └── main.gleam │ │ ├── duplicate_module_dev/ │ │ │ ├── dev/ │ │ │ │ └── main.gleam │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── main.gleam │ │ ├── duplicate_module_test_dev/ │ │ │ ├── dev/ │ │ │ │ └── main.gleam │ │ │ ├── gleam.toml │ │ │ └── test/ │ │ │ └── main.gleam │ │ ├── empty_module_warning/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ ├── empty.gleam │ │ │ ├── internal.gleam │ │ │ ├── private.gleam │ │ │ └── public.gleam │ │ ├── erlang_app_generation/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── main.gleam │ │ ├── erlang_app_generation_with_argument/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── main.gleam │ │ ├── erlang_bug_752/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ ├── one.gleam │ │ │ └── two.gleam │ │ ├── erlang_empty/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── empty.gleam │ │ ├── erlang_escape_names/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ ├── one.gleam │ │ │ └── two.gleam │ │ ├── erlang_import/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ ├── one.gleam │ │ │ └── two.gleam │ │ ├── erlang_import_shadowing_prelude/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ ├── one.gleam │ │ │ └── two.gleam │ │ ├── erlang_nested/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── one/ │ │ │ └── two.gleam │ │ ├── erlang_nested_qualified_constant/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ ├── one/ │ │ │ │ └── two.gleam │ │ │ └── two.gleam │ │ ├── hello_joe/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── hello_joe.gleam │ │ ├── import_cycle/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── one.gleam │ │ ├── import_cycle_multi/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ ├── one.gleam │ │ │ ├── three.gleam │ │ │ └── two.gleam │ │ ├── import_shadowed_name_warning/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ ├── one.gleam │ │ │ └── two.gleam │ │ ├── imported_constants/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ ├── one.gleam │ │ │ └── two.gleam │ │ ├── imported_external_fns/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ ├── one.gleam │ │ │ ├── three.gleam │ │ │ └── two.gleam │ │ ├── imported_record_constructors/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ ├── one/ │ │ │ │ ├── one.gleam │ │ │ │ └── two.gleam │ │ │ └── two.gleam │ │ ├── javascript_d_ts/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── hello.gleam │ │ ├── javascript_empty/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── empty.gleam │ │ ├── javascript_import/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ ├── one/ │ │ │ │ └── two.gleam │ │ │ └── two.gleam │ │ ├── not_overwriting_erlang_module/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── app/ │ │ │ └── code.gleam │ │ ├── opaque_type_accessor/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ ├── one.gleam │ │ │ └── two.gleam │ │ ├── opaque_type_destructure/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ ├── one.gleam │ │ │ └── two.gleam │ │ ├── overwriting_erlang_module/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── code.gleam │ │ ├── src_importing_dev/ │ │ │ ├── dev/ │ │ │ │ └── two.gleam │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ └── one.gleam │ │ ├── src_importing_test/ │ │ │ ├── gleam.toml │ │ │ ├── src/ │ │ │ │ └── one.gleam │ │ │ └── test/ │ │ │ └── two.gleam │ │ ├── unknown_module_field_in_constant/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ ├── one.gleam │ │ │ └── two.gleam │ │ ├── unknown_module_field_in_expression/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ ├── one.gleam │ │ │ └── two.gleam │ │ ├── unknown_module_field_in_import/ │ │ │ ├── gleam.toml │ │ │ └── src/ │ │ │ ├── one.gleam │ │ │ └── two.gleam │ │ └── variable_or_module/ │ │ ├── gleam.toml │ │ └── src/ │ │ ├── main.gleam │ │ └── power.gleam │ └── src/ │ ├── generated_tests.rs │ ├── lib.rs │ └── snapshots/ │ ├── test_package_compiler__generated_tests__alias_unqualified_import.snap │ ├── test_package_compiler__generated_tests__dev_importing_test.snap │ ├── test_package_compiler__generated_tests__duplicate_module.snap │ ├── test_package_compiler__generated_tests__duplicate_module_dev.snap │ ├── test_package_compiler__generated_tests__duplicate_module_test_dev.snap │ ├── test_package_compiler__generated_tests__empty_module_warning.snap │ ├── test_package_compiler__generated_tests__erlang_app_generation.snap │ ├── test_package_compiler__generated_tests__erlang_app_generation_with_argument.snap │ ├── test_package_compiler__generated_tests__erlang_bug_752.snap │ ├── test_package_compiler__generated_tests__erlang_empty.snap │ ├── test_package_compiler__generated_tests__erlang_escape_names.snap │ ├── test_package_compiler__generated_tests__erlang_import.snap │ ├── test_package_compiler__generated_tests__erlang_import_shadowing_prelude.snap │ ├── test_package_compiler__generated_tests__erlang_nested.snap │ ├── test_package_compiler__generated_tests__erlang_nested_qualified_constant.snap │ ├── test_package_compiler__generated_tests__hello_joe.snap │ ├── test_package_compiler__generated_tests__import_cycle.snap │ ├── test_package_compiler__generated_tests__import_cycle_multi.snap │ ├── test_package_compiler__generated_tests__import_shadowed_name_warning.snap │ ├── test_package_compiler__generated_tests__imported_constants.snap │ ├── test_package_compiler__generated_tests__imported_external_fns.snap │ ├── test_package_compiler__generated_tests__imported_record_constructors.snap │ ├── test_package_compiler__generated_tests__javascript_d_ts.snap │ ├── test_package_compiler__generated_tests__javascript_empty.snap │ ├── test_package_compiler__generated_tests__javascript_import.snap │ ├── test_package_compiler__generated_tests__not_overwriting_erlang_module.snap │ ├── test_package_compiler__generated_tests__opaque_type_accessor.snap │ ├── test_package_compiler__generated_tests__opaque_type_destructure.snap │ ├── test_package_compiler__generated_tests__overwriting_erlang_module.snap │ ├── test_package_compiler__generated_tests__src_importing_dev.snap │ ├── test_package_compiler__generated_tests__src_importing_test.snap │ ├── test_package_compiler__generated_tests__unknown_module_field_in_constant.snap │ ├── test_package_compiler__generated_tests__unknown_module_field_in_expression.snap │ ├── test_package_compiler__generated_tests__unknown_module_field_in_import.snap │ └── test_package_compiler__generated_tests__variable_or_module.snap └── test-project-compiler/ ├── .gitignore ├── Cargo.toml ├── build.rs ├── cases/ │ ├── with_dep/ │ │ ├── gleam.toml │ │ └── src/ │ │ └── example.gleam │ └── with_dev_dep/ │ ├── gleam.toml │ └── src/ │ └── example.gleam ├── src/ │ ├── generated_tests.rs │ ├── lib.rs │ └── snapshots/ │ ├── test_project_compiler__generated_tests__with_dep_dev.snap │ ├── test_project_compiler__generated_tests__with_dep_lsp.snap │ ├── test_project_compiler__generated_tests__with_dep_prod.snap │ ├── test_project_compiler__generated_tests__with_dev_dep_dev.snap │ ├── test_project_compiler__generated_tests__with_dev_dep_lsp.snap │ └── test_project_compiler__generated_tests__with_dev_dep_prod.snap └── support/ └── package_a/ ├── gleam.toml └── src/ └── package_a.gleam ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # EditorConfig helps developers define and maintain consistent # coding styles between different editors and IDEs # editorconfig.org # top-most EditorConfig file root = true # Matches multiple files with brace expansion notation # Set default charset [*] end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 2 [*.{gleam, erl, hrl, ex, mjs, js, ts, sh}] max_line_length = 80 [*.{rs, .erl, .hrl}] indent_size = 4 [Makefile] indent_style = tab indent_size = 4 ================================================ FILE: .gitattributes ================================================ [attr]generated linguist-generated=true diff=generated # Tests: test-package-compiler/src/generated_tests.rs generated # Lock files: Cargo.lock generated ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Something isn't working title: '' labels: bug assignees: '' --- ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- ================================================ FILE: .github/actions/build-container/action.yml ================================================ name: "Build Gleam" description: "Build Gleam Container" inputs: version: description: "Build Version" required: true release-id: description: "Release ID" required: true runs: using: composite steps: - name: Download Gleam release assets uses: robinraju/release-downloader@v1 with: releaseId: "${{ inputs.release-id }}" fileName: "gleam-${{ inputs.version }}-{x86_64-unknown-linux-musl,aarch64-unknown-linux-musl}.*" - name: "Unpack release files into correct location" shell: bash run: | declare -A ARCH ARCH["amd64"]="x86_64-unknown-linux-musl" ARCH["arm64"]="aarch64-unknown-linux-musl" for SHORT in "${!ARCH[@]}"; do LONG="${ARCH[$SHORT]}" # Unpack Release tar xf "gleam-$VERSION-$LONG.tar.gz" # Move files into place mv gleam "gleam-$SHORT" # The SBoM is added to the images so that the Docker Scout Scanner is # able to find the info about the gleam binary since it was not # installed by the operating system package manager. mv "gleam-$VERSION-$LONG.tar.gz.sbom.spdx.json" "gleam-$SHORT.sbom.spdx.json" # Delete Unused Files rm -rf "gleam-$VERSION-$LONG*" done env: VERSION: "${{ inputs.version }}" - name: Authenticate with GitHub container registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ github.token }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build version and tags shell: bash id: versions run: | # Strip `v` prefix from version BARE_VERSION=$(echo "$VERSION" | sed -e 's|^v/\(.*\)|\1|') # Build version with platform PLATFORM_VERSION=$BARE_VERSION-${{ matrix.base-image }} # Build container tag TAG=ghcr.io/${{ github.repository }}:$PLATFORM_VERSION echo "platform-version=$PLATFORM_VERSION" >> $GITHUB_OUTPUT echo "container-tag=$TAG" >> $GITHUB_OUTPUT env: VERSION: "${{ inputs.version }}" - name: Build and push uses: docker/build-push-action@v6 with: context: . platforms: linux/amd64,linux/arm64 file: containers/${{ matrix.base-image }}.dockerfile push: true # Enabling `provenance` will cause the action to create SLSA build # provenance and push it alongside the tagged image. In practical terms, # we're adding info to the tag that attests to where, when, and how the # asset and image was built. # # For more info on Docker Attestations, see: # https://docs.docker.com/build/ci/github-actions/attestations/ provenance: true # Enabling `sbom` will trigger an SBoM Scan using Docker Scout: # https://docs.docker.com/scout/how-tos/view-create-sboms/ # The scan will detect any operating system packages as well as the Gleam # Build SBoM added into the Docker Container. # # Why is this helpful? # * If you build services on top of these container images, you can track # all dependencies that ship with Gleam, plus the rest of your stack in # the image. # * This makes it easier to do image-level vulnerability scans and # compliance checks. # # For more info on Docker SBoMs, see: # https://docs.docker.com/build/metadata/attestations/sbom/ sbom: true tags: ${{ steps.versions.outputs.container-tag }} labels: | org.opencontainers.image.title=gleam org.opencontainers.image.url=https://gleam.run org.opencontainers.image.source=https://github.com/gleam-lang/gleam org.opencontainers.image.version=${{ steps.versions.outputs.platform-version }} org.opencontainers.image.licenses=Apache-2.0 ================================================ FILE: .github/actions/build-release/action.yml ================================================ name: "Build Gleam" description: "Build Gleam Release" inputs: version: description: "Build Version" required: true toolchain: description: "Cargo Toolchain" required: true target: description: "Cargo Installation Target" required: true cargo-tool: description: "Cargo Tool used for Build (for example, `cross`)" required: true expected-binary-architecture: description: "Expected Binary Architecture" required: false default: "" azure-tenant-id: description: "Azure Tenant ID for Windows Code Signing" required: false azure-subscription-id: description: "Azure Subscription ID for Windows Code Signing" required: false azure-client-id: description: "Azure Client ID for Windows Code Signing" required: false azure-trusted-signing-account-name: description: "Azure Trusted Signing Account Name for Windows Code Signing" required: false azure-certificate-profile-name: description: "Azure Certificate Profile Name for Windows Code Signing" required: false outputs: archive: description: "Path to build asset" value: "${{ steps.build.outputs.archive }}" files: description: "Path to all files" value: | ${{ steps.build.outputs.archive }} ${{ steps.build.outputs.archive }}.sha256 ${{ steps.build.outputs.archive }}.sha512 ${{ steps.build.outputs.archive }}.sigstore ${{ steps.build.outputs.archive }}.sbom.spdx.json ${{ steps.build.outputs.archive }}.sbom.cyclonedx.json runs: using: "composite" steps: - name: Install Rust toolchain uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ inputs.toolchain }} target: ${{ inputs.target }} cache-key: v1-${{ inputs.target }} - name: Install Cargo SBoM shell: bash # The `cargo-sbom` version is specified in the next line. Change it to # keep it up-to-date. run: cargo install cargo-sbom@~0.9.1 - name: Build WASM release binary if: ${{ inputs.target != 'wasm32-unknown-unknown' }} uses: clechasseur/rs-cargo@v3 with: command: build args: --release --target ${{ inputs.target }} tool: ${{ inputs.cargo-tool }} - name: Install wasm-pack if: ${{ inputs.target == 'wasm32-unknown-unknown' }} shell: bash run: curl -sSL https://rustwasm.github.io/wasm-pack/installer/init.sh | sh - name: Build WASM release binary if: ${{ inputs.target == 'wasm32-unknown-unknown' }} shell: bash run: wasm-pack build --release --target web compiler-wasm - name: Verify binary architecture if: ${{ inputs.expected-binary-architecture }} shell: bash run: | BINARY_PATH="target/${{ inputs.target }}/release/gleam" if [[ "${{ inputs.target }}" == *"windows"* ]]; then BINARY_PATH="${BINARY_PATH}.exe" fi if ! file -b "$BINARY_PATH" | grep -iq "${{ inputs.expected-binary-architecture }}"; then echo "error: Architecture mismatch" echo "Expected architecture: '${{ inputs.expected-binary-architecture }}'" echo "Found binary type: '$(file -b "$BINARY_PATH")'" exit 1 fi echo "ok: Architecture match" # We use Azure Trusted Signing to sign the Windows binaries. # This is done to ensure that the binaries are trusted and can be # verified by users and systems that require signed code. # # Why is this helpful? # * It provides a way to verify the authenticity and integrity of the # binaries distributed by Gleam. # * It helps prevent tampering with the binaries, ensuring that users # can trust the code they are running. # # For more information, see: # * https://github.com/Azure/trusted-signing-action # * https://azure.microsoft.com/en-us/products/trusted-signing - name: Log in to Azure if: ${{ contains(inputs.target, '-windows-') && inputs.azure-tenant-id && inputs.azure-subscription-id && inputs.azure-client-id && inputs.azure-trusted-signing-account-name && inputs.azure-certificate-profile-name }} uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0 with: client-id: ${{ inputs.azure-client-id }} tenant-id: ${{ inputs.azure-tenant-id }} subscription-id: ${{ inputs.azure-subscription-id }} - name: Windows Code Signing if: ${{ contains(inputs.target, '-windows-') && inputs.azure-tenant-id && inputs.azure-subscription-id && inputs.azure-client-id && inputs.azure-trusted-signing-account-name && inputs.azure-certificate-profile-name }} uses: azure/trusted-signing-action@0d74250c661747df006298d0fb49944c10f16e03 # v0.5.1 with: endpoint: https://eus.codesigning.azure.net/ trusted-signing-account-name: ${{ inputs.azure-trusted-signing-account-name }} certificate-profile-name: ${{ inputs.azure-certificate-profile-name }} files: ${{ github.workspace }}\target\${{ inputs.target }}\release\gleam.exe file-digest: SHA256 timestamp-rfc3161: http://timestamp.acs.microsoft.com timestamp-digest: SHA256 - name: Build archive id: build shell: bash run: | case "$TARGET" in *windows*) ARCHIVE="gleam-$VERSION-$TARGET.zip" cp "target/$TARGET/release/gleam.exe" "gleam.exe" 7z a "$ARCHIVE" "gleam.exe" rm gleam.exe ;; wasm*) ARCHIVE="gleam-$VERSION-browser.tar.gz" tar -C compiler-wasm/pkg/ -czvf $ARCHIVE . rm -rf compiler-wasm/pkg/ ;; *) ARCHIVE="gleam-$VERSION-$TARGET.tar.gz" cp "target/$TARGET/release/gleam" "gleam" tar -czvf "$ARCHIVE" "gleam" rm gleam ;; esac echo "archive=$ARCHIVE" >> $GITHUB_OUTPUT env: TARGET: "${{ inputs.target }}" VERSION: "${{ inputs.version }}" - name: Ensure binary successfully boots if: ${{ inputs.expected-binary-architecture }} shell: bash run: | case "$TARGET" in *windows*) 7z x "$ARCHIVE" ./gleam.exe --version ;; aarch64*) echo "We cannot test an ARM binary on a AMD64 runner" ;; *) tar -xvzf "$ARCHIVE" ./gleam --version ;; esac env: TARGET: "${{ inputs.target }}" ARCHIVE: "${{ steps.build.outputs.archive }}" # By using `cargo-sbom``, we create two formats of Build SBoMs # (SPDX and CycloneDX) for the gleam build. # We store those files alongside the build artifacts on the GitHub Release # page and also use them to create Container SBoMs for Docker images. # # Why is this helpful? # * It gives us and our users complete visibility into which dependencies # and which versions are present in the build / container image. # * The SBoM can be fed into vulnerability scanners so that anyone can check # if any dependencies have known security issues. - name: Generate Build SBoM shell: bash run: | cargo-sbom \ --output-format spdx_json_2_3 \ > "$ARCHIVE.sbom.spdx.json" cargo-sbom \ --output-format cyclone_dx_json_1_4 \ > "$ARCHIVE.sbom.cyclonedx.json" env: ARCHIVE: "${{ steps.build.outputs.archive }}" - name: Hash Build Archive shell: bash run: | openssl dgst -r -sha256 -out "$ARCHIVE".sha256 "$ARCHIVE" openssl dgst -r -sha512 -out "$ARCHIVE".sha512 "$ARCHIVE" env: ARCHIVE: "${{ steps.build.outputs.archive }}" # We provide SLSA Provenance for the distribution build. This attests to # where, when, and how the asset or image was built. # # Why is this helpful? # * It provides a record of the exact Git commit (git sha) and GitHub # Actions workflow used to produce a release. # * Users or automated systems can verify that the artifact you’re # downloading was indeed built from the official Gleam repo, on a # particular date, using the correct pipeline and not tampered with later. # * The attestation is published to a transparency log for extra # verification: https://github.com/gleam-lang/gleam/attestations/ # # For more information, see: # * https://github.com/actions/attest # * https://github.com/actions/attest-sbom - name: Attest Distribution Assets with SBoM id: attest-sbom uses: actions/attest-sbom@v2 with: subject-path: | ${{ steps.build.outputs.archive }} ${{ steps.build.outputs.archive }}.sbom.spdx.json ${{ steps.build.outputs.archive }}.sbom.cyclonedx.json sbom-path: "${{ steps.build.outputs.archive }}.sbom.spdx.json" # The provenanve information is stored alongside the built artifact with # the `.sigstore` file extension. - name: "Copy SBoM provenance" id: sbom-provenance shell: bash run: | cp "$ATTESTATION" "$ARCHIVE.sigstore" env: ARCHIVE: "${{ steps.build.outputs.archive }}" ATTESTATION: "${{ steps.attest-sbom.outputs.bundle-path }}" - name: Upload artifact uses: actions/upload-artifact@v6 with: name: release-${{ matrix.target }} path: | ${{ steps.build.outputs.archive }} ${{ steps.build.outputs.archive }}.sha256 ${{ steps.build.outputs.archive }}.sha512 ${{ steps.build.outputs.archive }}.sigstore ${{ steps.build.outputs.archive }}.sbom.spdx.json ${{ steps.build.outputs.archive }}.sbom.cyclonedx.json overwrite: true ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: github-actions directory: "/" labels: [] schedule: interval: monthly open-pull-requests-limit: 10 # Rust: - package-ecosystem: "cargo" directory: "/" labels: [] schedule: interval: "monthly" - package-ecosystem: "cargo" directory: "/compiler-cli" labels: [] schedule: interval: "monthly" - package-ecosystem: "cargo" directory: "/compiler-core" labels: [] schedule: interval: "monthly" - package-ecosystem: "cargo" directory: "/compiler-wasm" labels: [] schedule: interval: "monthly" - package-ecosystem: "cargo" # Does not have any custom deps right now, # but can add them in the future: directory: "/test-package-compiler" labels: [] schedule: interval: "monthly" ================================================ FILE: .github/pull_request_template.md ================================================ - [ ] The changes in this PR have been discussed beforehand in an issue - [ ] The issue for this PR has been linked - [ ] Tests have been added for new behaviour - [ ] The changelog has been updated for any user-facing changes ================================================ FILE: .github/workflows/ci.yaml ================================================ name: ci on: pull_request: paths-ignore: - "CHANGELOG.md" - "docs/**" push: branches: - main workflow_dispatch: env: CARGO_TERM_COLOR: always RUSTFLAGS: "-D warnings" CARGO_INCREMENTAL: 0 CARGO_PROFILE_DEV_DEBUG: 0 CARGO_PROFILE_TEST_DEBUG: 0 CROSS_CONTAINER_UID: 0 permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: test: name: test runs-on: ${{ matrix.os }} timeout-minutes: 30 strategy: fail-fast: false matrix: toolchain: [stable] target: - x86_64-unknown-linux-gnu - x86_64-unknown-linux-musl - aarch64-unknown-linux-gnu - aarch64-unknown-linux-musl - x86_64-apple-darwin - x86_64-pc-windows-msvc include: - os: ubuntu-latest target: x86_64-unknown-linux-gnu binary: x86-64 cargo-tool: cargo run-integration-tests: true - os: ubuntu-latest target: x86_64-unknown-linux-musl binary: x86-64 cargo-tool: cross run-integration-tests: true - os: ubuntu-latest target: aarch64-unknown-linux-gnu binary: aarch64 cargo-tool: cross run-integration-tests: false # Cannot run aarch64 binaries on x86_64 - os: ubuntu-latest target: aarch64-unknown-linux-musl binary: aarch64 cargo-tool: cross run-integration-tests: false # Cannot run aarch64 binaries on x86_64 - os: macos-15-intel # intel target: x86_64-apple-darwin binary: x86_64 cargo-tool: cargo run-integration-tests: true - os: macos-latest # aarch64 toolchain: stable target: aarch64-apple-darwin binary: arm64 cargo-tool: cargo run-integration-tests: true - os: windows-2022 target: x86_64-pc-windows-msvc binary: x86-64 cargo-tool: cargo run-integration-tests: true steps: - name: Checkout repository uses: actions/checkout@v6 - name: Install musl-tools incl. musl-gcc uses: awalsh128/cache-apt-pkgs-action@v1 with: # musl-tools provide `musl-gcc` which is required for `ring` which is required for `rustls` et al. packages: musl-tools version: 1.1 if: ${{ matrix.target == 'x86_64-unknown-linux-musl'}} - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ matrix.toolchain }} target: ${{ matrix.target }} - name: Install Erlang uses: erlef/setup-beam@v1 with: otp-version: "28.0.1" elixir-version: "1.18" rebar3-version: "3" - name: Setup Node uses: actions/setup-node@v6 with: node-version: "20" - name: Setup Deno uses: denoland/setup-deno@v2 with: deno-version: "2.2" - name: Setup Bun uses: oven-sh/setup-bun@v2 with: bun-version: "1.2" - name: Handle Rust dependencies caching uses: Swatinem/rust-cache@v2 with: key: v1-${{ matrix.target }} - name: Install Gleam uses: clechasseur/rs-cargo@v4 with: command: install args: "--path gleam-bin --target ${{ matrix.target }} --debug --locked --force" tool: ${{ matrix.cargo-tool }} if: ${{ matrix.run-integration-tests }} - name: Verify binary architecture shell: bash run: | BINARY_PATH="${CARGO_HOME}/bin/gleam" if [[ "${{ matrix.target }}" == *"windows"* ]]; then BINARY_PATH="${BINARY_PATH}.exe" fi if ! file -b "$BINARY_PATH" | grep -q "${{ matrix.binary }}"; then echo "error: Architecture mismatch" echo "Expected architecture: '${{ matrix.binary }}'" echo "Found binary type: '$(file -b "$BINARY_PATH")'" exit 1 fi echo "ok: Architecture match" if: ${{ matrix.run-integration-tests }} - name: Run tests uses: clechasseur/rs-cargo@v4 with: command: test # We only want to run the `test-output` when running integration tests. # There's a caveat though: when `cargo-tool` is `cross` it uses a container # and would result in these integration tests failing due to not finding # the escript binary. So, in case `cargo-tool != cargo` we'll skip the # `test-output` tests as well. args: >- --workspace --target ${{ matrix.target }} ${{ ((matrix.run-integration-tests && matrix.cargo-tool == 'cargo') && ' ') || '--exclude test-output' }} tool: ${{ matrix.cargo-tool }} - name: test/project_erlang (non-windows) run: | gleam run && cd src && gleam run && cd .. gleam check gleam test && cd src && gleam test && cd .. gleam docs build working-directory: ./test/project_erlang if: ${{ runner.os != 'Windows' && matrix.run-integration-tests }} - name: test/project_erlang (windows) run: | gleam run && cd src && gleam run && cd .. gleam check gleam test && cd src && gleam test && cd .. gleam docs build working-directory: ./test/project_erlang_windows if: ${{ runner.os == 'Windows' && matrix.run-integration-tests }} - name: test/project_erlang export erlang-shipment (non-windows) run: | gleam export erlang-shipment ./build/erlang-shipment/entrypoint.sh run working-directory: ./test/project_erlang if: ${{ runner.os != 'Windows' && matrix.run-integration-tests }} - name: test/project_erlang export erlang-shipment (windows) run: | gleam export erlang-shipment .\build\erlang-shipment\entrypoint.ps1 run working-directory: ./test/project_erlang_windows if: ${{ runner.os == 'Windows' && matrix.run-integration-tests }} - name: test/project_erlang export package-interface (non-windows) run: | gleam export package-interface --out="interface.json" cat interface.json working-directory: ./test/project_erlang if: ${{ runner.os != 'Windows' && matrix.run-integration-tests }} - name: test/project_erlang export package-interface (windows) run: | gleam export package-interface --out="interface.json" cat interface.json working-directory: ./test/project_erlang_windows if: ${{ runner.os == 'Windows' && matrix.run-integration-tests }} - name: test/project_erlang export package-information run: | gleam export package-information --out="gleam.json" cat gleam.json working-directory: ./test/project_erlang if: ${{ matrix.run-integration-tests }} - name: test/external_only_javascript run: ./test.sh working-directory: ./test/external_only_javascript if: ${{ matrix.run-integration-tests }} env: GLEAM_COMMAND: gleam - name: test/external_only_erlang run: ./test.sh working-directory: ./test/external_only_erlang if: ${{ matrix.run-integration-tests }} env: GLEAM_COMMAND: gleam - name: test/root_package_not_compiled_when_running_dep run: ./test.sh working-directory: ./test/root_package_not_compiled_when_running_dep if: ${{ matrix.run-integration-tests }} env: GLEAM_COMMAND: gleam - name: test/erlang_shipment_no_dev_deps run: ./test.sh working-directory: ./test/erlang_shipment_no_dev_deps if: ${{ matrix.run-integration-tests }} env: GLEAM_COMMAND: gleam - name: test/project_javascript run: | gleam run gleam check gleam test gleam docs build working-directory: ./test/project_javascript if: ${{ matrix.run-integration-tests }} - name: test/project_path_deps run: | gleam update gleam check working-directory: ./test/project_path_deps/project_a if: ${{ matrix.run-integration-tests }} - name: test/project_git_deps run: | gleam update gleam check working-directory: ./test/project_git_deps if: ${{ matrix.run-integration-tests }} - name: Test project generation run: | gleam new lib_project cd lib_project gleam run gleam test # Test adding of deps gleam add exception # No specifier gleam add gleam_http@4 # Version specifier gleam test # Test documentation generation gleam docs build # Assert that module metadata has been written ls build/dev/erlang/lib_project/_gleam_artefacts/lib_project.cache # Assert that HTML docs and their assets have been written ls build/dev/docs/lib_project/index.html ls build/dev/docs/lib_project/lib_project.html ls build/dev/docs/lib_project/css/atom-one-light.min.css ls build/dev/docs/lib_project/css/atom-one-dark.min.css ls build/dev/docs/lib_project/css/index.css ls build/dev/docs/lib_project/js/highlight.min.js ls build/dev/docs/lib_project/js/highlightjs-gleam.js ls build/dev/docs/lib_project/js/highlightjs-erlang.min.js ls build/dev/docs/lib_project/js/highlightjs-elixir.min.js ls build/dev/docs/lib_project/js/highlightjs-javascript.min.js ls build/dev/docs/lib_project/js/highlightjs-typescript.min.js ls build/dev/docs/lib_project/js/lunr.min.js ls build/dev/docs/lib_project/js/index.js ls build/dev/docs/lib_project/fonts/karla-v23-bold-latin-ext.woff2 ls build/dev/docs/lib_project/fonts/karla-v23-bold-latin.woff2 ls build/dev/docs/lib_project/fonts/karla-v23-regular-latin-ext.woff2 ls build/dev/docs/lib_project/fonts/karla-v23-regular-latin.woff2 ls build/dev/docs/lib_project/fonts/ubuntu-mono-v15-regular-cyrillic-ext.woff2 ls build/dev/docs/lib_project/fonts/ubuntu-mono-v15-regular-cyrillic.woff2 ls build/dev/docs/lib_project/fonts/ubuntu-mono-v15-regular-greek-ext.woff2 ls build/dev/docs/lib_project/fonts/ubuntu-mono-v15-regular-greek.woff2 ls build/dev/docs/lib_project/fonts/ubuntu-mono-v15-regular-latin-ext.woff2 ls build/dev/docs/lib_project/fonts/ubuntu-mono-v15-regular-latin.woff2 if: ${{ matrix.run-integration-tests }} test-wasm: runs-on: ubuntu-latest timeout-minutes: 30 steps: - name: Checkout repository uses: actions/checkout@v6 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable with: toolchain: stable target: wasm32-unknown-unknown - uses: actions/setup-node@v6 with: node-version: "20" - name: Install wasm-pack run: | curl -sSL https://rustwasm.github.io/wasm-pack/installer/init.sh | sh - name: Run wasm tests run: wasm-pack test --node compiler-wasm rustfmt: name: rustfmt runs-on: ubuntu-latest timeout-minutes: 10 steps: - name: Checkout repository uses: actions/checkout@v6 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable with: toolchain: stable components: rustfmt - run: cargo fmt --all -- --check validate: name: validate runs-on: ubuntu-latest timeout-minutes: 10 steps: - name: Checkout repository uses: actions/checkout@v6 - name: Ensure no merge commits uses: NexusPHP/no-merge-commits@v2.2.1 if: github.event_name == 'pull_request' with: token: ${{ secrets.GITHUB_TOKEN }} - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable with: toolchain: stable - name: Install cargo-deny run: | set -e curl -L https://github.com/EmbarkStudios/cargo-deny/releases/download/0.18.6/cargo-deny-0.18.6-x86_64-unknown-linux-musl.tar.gz | tar xzf - mv cargo-deny-*-x86_64-unknown-linux-musl/cargo-deny cargo-deny echo `pwd` >> $GITHUB_PATH - name: Validate deps run: cargo deny check lint-build: name: lint-build runs-on: ubuntu-latest timeout-minutes: 10 steps: - name: Checkout repository uses: actions/checkout@v6 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable with: toolchain: stable components: clippy - name: Handle Rust dependencies caching uses: Swatinem/rust-cache@v2 with: key: v1-linux-gnu - name: Run linter run: cargo clippy --workspace - run: cargo build - name: Upload artifact (Ubuntu) uses: actions/upload-artifact@v7 with: name: gleam path: target/debug/gleam test-projects: name: test-projects needs: lint-build runs-on: ubuntu-latest timeout-minutes: 10 steps: - name: Checkout repository uses: actions/checkout@v6 - name: Install Deno uses: denoland/setup-deno@v2 with: deno-version: v2.x - name: Install Bun uses: oven-sh/setup-bun@v2 - name: Install Erlang uses: erlef/setup-beam@v1 with: otp-version: "26.1" elixir-version: "1.16.1" rebar3-version: "3" - name: Download Gleam binary from previous job uses: actions/download-artifact@v8 with: name: gleam path: ./test - name: Configure test projects to use Gleam binary run: | echo $PWD/ >> $GITHUB_PATH chmod +x ./gleam sed -i 's/cargo run --quiet --/gleam/' */Makefile sed -i 's/cargo run --/gleam/' */Makefile working-directory: ./test - name: test/language Erlang run: make clean erlang working-directory: ./test/language - name: test/language JavaScript with NodeJS run: make clean nodejs working-directory: ./test/language - name: test/language JavaScript with Deno run: make clean deno working-directory: ./test/language - name: test/language JavaScript with Bun run: make clean bun working-directory: ./test/language - name: test/compile_package0 run: make working-directory: ./test/compile_package0 - name: test/compile_package1 run: make working-directory: ./test/compile_package1 - name: Test JavaScript prelude run: make working-directory: ./test/javascript_prelude - name: Test export of hex tarball run: make test working-directory: ./test/hextarball - name: test/running_modules run: make test-all working-directory: ./test/running_modules - name: test/multi_namespace run: ./test.sh working-directory: ./test/multi_namespace - name: test/multi_namespace_not_top_level run: ./test.sh working-directory: ./test/multi_namespace_not_top_level - name: Test FFI in subdirectories run: make working-directory: ./test/subdir_ffi - name: test/unicode_path run: make working-directory: ./test/unicode_path ⭐ - name: test/assert run: make test-all working-directory: ./test/assert - name: Test publishing with default main run: ./test.sh working-directory: ./test/publishing_default_main ================================================ FILE: .github/workflows/release-containers.yaml ================================================ name: release-containers on: release: types: - "published" permissions: packages: write id-token: write attestations: write jobs: publish-container-images: name: publish-container-images runs-on: ubuntu-latest # Covered by `release-nightly.yaml` if: ${{ github.event.release.tag_name != 'nightly' }} strategy: matrix: base-image: - scratch - erlang - erlang-slim - erlang-alpine - elixir - elixir-slim - elixir-alpine - node - node-slim - node-alpine steps: - name: Checkout repository uses: actions/checkout@v6 with: sparse-checkout: | .github/actions containers - name: "Build & Push Container" uses: "./.github/actions/build-container" with: release-id: ${{ github.event.release.id }} version: ${{ github.ref_name }} ================================================ FILE: .github/workflows/release-nightly.yaml ================================================ name: release-nightly on: workflow_dispatch: schedule: - cron: "45 0 * * *" env: CARGO_TERM_COLOR: always RUSTFLAGS: "-D warnings" permissions: contents: write packages: write id-token: write attestations: write jobs: # Check if the actions already ran in the last 24 hours # * If yes: Don't run again # * If no: Clean out existing release assets prepare-nightly: runs-on: ubuntu-latest name: Prepare Nightly Release outputs: should-run: ${{ steps.should-run.outputs.should-run }} # TODO: Re-add # if: ${{ github.repository == 'gleam-lang/gleam' }} steps: - uses: actions/checkout@v6 - name: print latest_commit run: echo ${{ github.sha }} - id: should-run continue-on-error: true name: check latest commit is less than a day if: ${{ github.event_name == 'schedule' }} run: test -z $(git rev-list --after="24 hours" ${{ github.sha }}) && echo "should-run=false" >> $GITHUB_OUTPUT - name: Delete old release assets uses: mknejp/delete-release-assets@v1 if: ${{ steps.should-run != 'false' }} with: token: ${{ github.token }} tag: nightly fail-if-no-assets: false fail-if-no-release: false assets: | *.zip *.tar.gz *.sha256 *.sha512 *.sigstore *.sbom.*.json build-release: name: build-release runs-on: ${{ matrix.os }} environment: release outputs: release-id: ${{ steps.release.outputs.id }} strategy: matrix: target: - x86_64-unknown-linux-musl - aarch64-unknown-linux-musl - x86_64-apple-darwin - aarch64-apple-darwin - x86_64-pc-windows-msvc - aarch64-pc-windows-msvc toolchain: [ stable ] include: - os: ubuntu-latest target: x86_64-unknown-linux-musl expected-binary-architecture: x86-64 cargo-tool: cross - os: ubuntu-latest target: aarch64-unknown-linux-musl expected-binary-architecture: aarch64 cargo-tool: cross - os: macos-15-intel target: x86_64-apple-darwin expected-binary-architecture: x86_64 cargo-tool: cargo - os: macos-latest target: aarch64-apple-darwin expected-binary-architecture: arm64 cargo-tool: cargo - os: windows-2022 target: x86_64-pc-windows-msvc expected-binary-architecture: x86-64 cargo-tool: cargo - os: windows-11-arm target: aarch64-pc-windows-msvc expected-binary-architecture: arm64 cargo-tool: cargo - os: ubuntu-latest target: wasm32-unknown-unknown cargo-tool: wasm-pack steps: - name: Checkout repository uses: actions/checkout@v6 - name: Update versions shell: bash run: ./bin/add-nightly-suffix-to-versions.sh - name: "Build Archive" id: build uses: "./.github/actions/build-release" with: version: nightly toolchain: ${{ matrix.toolchain }} target: ${{ matrix.target }} cargo-tool: ${{ matrix.cargo-tool }} expected-binary-architecture: ${{ matrix.expected-binary-architecture }} azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }} azure-subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} azure-client-id: ${{ secrets.AZURE_CLIENT_ID }} azure-trusted-signing-account-name: ${{ vars.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }} azure-certificate-profile-name: ${{ vars.AZURE_CERTIFICATE_PROFILE_NAME }} - name: Upload release archive id: release # https://github.com/softprops/action-gh-release/issues/445 # uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@f2352b97da0095b4dbbd885a81023e3deabf4fef with: draft: false prerelease: true fail_on_unmatched_files: true files: "${{ steps.build.outputs.files }}" tag_name: nightly name: "Nightly" body: | A build that runs every night following changes to the main branch. Contains all the latest features and changes, but there is a higher chance of instability, bugs, and unfinished features. The assets are updated but the tag is not, so if you view the commit or download the source from below you will not get the correct code. build-container-images: name: build-container-images needs: [ prepare-nightly, build-release ] runs-on: ubuntu-latest if: ${{ needs.prepare-nightly.outputs.should-run != 'false' }} strategy: matrix: base-image: - scratch - erlang - erlang-slim - erlang-alpine - elixir - elixir-slim - elixir-alpine - node - node-slim - node-alpine steps: - name: Checkout repository uses: actions/checkout@v6 with: sparse-checkout: | .github/actions containers - name: "Build & Push Container" uses: "./.github/actions/build-container" with: version: nightly release-id: ${{ needs.build-release.outputs.release-id }} ================================================ FILE: .github/workflows/release.yaml ================================================ name: release on: push: tags: - "v*" env: CARGO_TERM_COLOR: always RUSTFLAGS: "-D warnings" permissions: contents: read jobs: build-release: name: build-release runs-on: ${{ matrix.os }} environment: release permissions: id-token: write attestations: write strategy: matrix: target: - x86_64-unknown-linux-musl - aarch64-unknown-linux-musl - x86_64-apple-darwin - aarch64-apple-darwin - x86_64-pc-windows-msvc - aarch64-pc-windows-msvc toolchain: [ stable ] include: - os: ubuntu-latest target: x86_64-unknown-linux-musl expected-binary-architecture: x86-64 cargo-tool: cross - os: ubuntu-latest target: aarch64-unknown-linux-musl expected-binary-architecture: aarch64 cargo-tool: cross - os: macos-15-intel target: x86_64-apple-darwin expected-binary-architecture: x86_64 cargo-tool: cargo - os: macos-latest target: aarch64-apple-darwin expected-binary-architecture: arm64 cargo-tool: cargo - os: windows-2022 target: x86_64-pc-windows-msvc expected-binary-architecture: x86-64 cargo-tool: cargo - os: windows-11-arm target: aarch64-pc-windows-msvc expected-binary-architecture: arm64 cargo-tool: cargo - os: ubuntu-latest target: wasm32-unknown-unknown cargo-tool: wasm-pack steps: - name: Checkout repository uses: actions/checkout@v6 - name: "Build Archive" id: build uses: "./.github/actions/build-release" with: version: ${{ github.ref_name }} toolchain: ${{ matrix.toolchain }} target: ${{ matrix.target }} cargo-tool: ${{ matrix.cargo-tool }} expected-binary-architecture: ${{ matrix.expected-binary-architecture }} azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }} azure-subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} azure-client-id: ${{ secrets.AZURE_CLIENT_ID }} azure-trusted-signing-account-name: ${{ vars.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }} azure-certificate-profile-name: ${{ vars.AZURE_CERTIFICATE_PROFILE_NAME }} create-release: name: create-release needs: ['build-release'] runs-on: ubuntu-latest environment: release permissions: contents: write steps: - name: Download Artifacts uses: actions/download-artifact@v8 with: pattern: release-* merge-multiple: true - name: Create release env: GITHUB_TOKEN: '${{ github.token }}' REPOSITORY: '${{ github.repository }}' TITLE: '${{ github.ref_name }}' TAG_NAME: '${{ github.ref_name }}' NOTES: '${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/CHANGELOG.md' run: | gh release create \ --repo "$REPOSITORY" \ --title "$TITLE" \ --notes "$NOTES" \ --draft \ --verify-tag \ ${{ contains(github.ref_name, '-rc') && '--prerelease' || '' }} \ "$TAG_NAME" \ gleam-* ================================================ FILE: .gitignore ================================================ **/*.rs.bk **/*.beam /target/ /tmp .idea/ .idea/* erl_crash.dump *.lock node_modules/ compiler-cli/build/ ================================================ FILE: .vscode/settings.json ================================================ { // Don't show these files in search "search.exclude": { // Test snapshots "**/*.snap": true, // Generated code "compiler-core/generated/**": true, // Images "images/**": true }, "cSpell.words": [ "camino", "cksum", "codegen", "ecow", "flate", "hardlinking", "HEXPM", "insta", "itertools", "NOCOLOUR", "reqwest", "rustfmt", "subpackage", "Telem", "Unretire", "Untar", "walkdir" ], "cSpell.language": "en,uk,en-GB,en-US" } ================================================ FILE: .well-known/funding-manifest-urls ================================================ https://gleam.run/funding.json ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## Unreleased ### Compiler - The compiler now supports list prepending in constants. For example: ```gleam pub const viviparous_mammals = ["dog", "cat", "human"] pub const all_mammals = ["platypus", "echidna", ..viviparous_mammals] ``` ([Surya Rose](https://github.com/GearsDatapacks)) - The analysis of record update expressions is now fault tolerant, meaning the compiler will no longer stop reporting errors at the first invalid field it finds. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ### Build tool - When publishing, the package manager now uses the full term instead of the shorthand "MFA" in the prompt and error message. ([Luka Ivanović](https://github.com/luka-hash)) ### Language server - The "extract variable" code action can now pick better names for variables in case branches and blocks, ignoring unrelated names of variables in other branches. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server now has a code action to replace a `_` in a type annotation with the corresponding type. For example: ```gleam pub fn load_user(id: Int) -> Result(_, Error) { // ^ // Triggering the code action here sql.find_by_id(id) |> result.map_error(CannotLoadUser) } ``` Triggering the code action over the `_` will result in the following code: ```gleam pub fn load_user(id: Int) -> Result(User, Error) { sql.find_by_id(id) |> result.map_error(CannotLoadUser) } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server now shows completions for the labelled argument of a record when writing a record update. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ### Formatter ### Bug fixes - Fixed a bug where the compiler would crash when trying to read the cache for modules containing large constants. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where `BitArray$BitArray$data` constructed a `DataView` with incorrect byte length instead of the slice's actual size, causing sliced bit arrays to include extra bytes from the underlying buffer on JavaScript. ([John Downey](https://github.com/jtdowney)) - The compiler now parses UTF-8 source files with a byte-order mark correctly, instead of raising an error. ([Lucy McPhail](https://github.com/lucymcphail)) ## v1.15.1 - 2026-03-17 ### Bug fixes - Fixed a bug where `BitArray$BitArray$data` constructed a `DataView` with offset 0 instead of the slice's actual byte offset, causing sliced bit arrays to read from the wrong position in the underlying buffer on JavaScript. ([John Downey](https://github.com/jtdowney)) - Fixed a bug where the "Add missing type parameter" code action could be triggered on types that do not exist instead of type variables. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at hello@gleam.run. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Gleam Thanks for contributing to Gleam! Before continuing please read our [code of conduct][code-of-conduct] which all contributors are expected to adhere to. [code-of-conduct]: https://github.com/gleam-lang/gleam/blob/main/CODE_OF_CONDUCT.md ## Contributing bug reports If you have found a bug in Gleam please check to see if there is an open ticket for this problem on [our GitHub issue tracker][issues]. If you cannot find an existing ticket for the bug please open a new one. [issues]: https://github.com/gleam-lang/gleam/issues A bug may be a technical problem such as a compiler crash or an incorrect return value from a library function, or a user experience issue such as unclear or absent documentation. If you are unsure if your problem is a bug please open a ticket and we will work it out together. ## Contributing code changes Before working on code it is suggested that you read the [docs/compiler/README.md](docs/compiler/README.md) file. It outlines fundamental components and design of this project. Code changes to Gleam are welcomed via the process below. 1. Find or open a GitHub issue relevant to the change you wish to make and comment saying that you wish to work on this issue. If the change introduces new functionality or behaviour this would be a good time to discuss the details of the change to ensure we are in agreement as to how the new functionality should work. 2. Update the [CHANGELOG.md](CHANGELOG.md) file with your changes. 3. Open a GitHub pull request with your changes and ensure the tests and build pass on CI. 4. A Gleam team member will review the changes and may provide feedback to work on. Depending on the change there may be multiple rounds of feedback. 5. Once the changes have been approved the code will be rebased into the `main` branch. ## Local development To run the compiler tests. This will require a recent stable version of Rust, Erlang, Elixir, NodeJS, Deno, and Bun to be installed. If you are using the Nix package manager, there's a [gleam-nix flake](https://github.com/vic/gleam-nix) you can use for running any Gleam version or quickly obtaining a development environment for Gleam. ```sh cargo test # Or if you have watchexec installed you can run them automatically # when files change make test-watch ``` To run the language integration tests. This will require a recent stable version of Rust, Erlang, and NodeJS to be installed. ```sh make language-test ``` If you don't have Rust or Cargo installed you can run the above command in a docker sandbox. Run the command below from this directory. ```sh docker run -v $(pwd):/opt/app -it -w /opt/app rust:latest bash ``` ## Rust development Here are some tips and guidelines for writing Rust code in the Gleam compiler: The `GLEAM_LOG` environment variable can be used to cause the compiler to print more information for debugging and introspection. i.e. `GLEAM_LOG=trace`. ### Clippy linter Your PR may fail on CI due to clippy errors. Clippy can be run locally like so: ```sh cargo clean -p gleam cargo clippy ``` If you have lint errors on CI but not locally upgrade your Rust version to the latest stable. ```sh rustup upgrade stable ``` ## Operating system specific code This project is used on FreeBSD, Linux, MacOS, OpenBSD, Windows, and presumably other operating systems, so there is some amount of code that needs to be different depending on which is it running on. So far this is hidden inside dependencies, with the exception of some code for working with file paths in tests and for setting file permissions, which is different on Windows. If you are working in this area then you may get a CI failure relating to this for your first attempt. If you need help resolving any issues do not hesitate to ask. ================================================ FILE: Cargo.toml ================================================ [workspace] resolver = "2" members = [ "gleam-bin", "compiler-cli", "compiler-core", "compiler-wasm", "language-server", "test-helpers-rs", "test-output", "test-package-compiler", "test-project-compiler", "language-server", "hexpm", ] # common dependencies [workspace.dependencies] # Immutable data structures im = { version = "15", features = ["serde"] } # Extra iter methods itertools = "0" # Parsing regex = "1" # Colours in terminal termcolor = "1" # Data (de)serialisation serde = { version = "1", features = ["derive", "rc"] } serde_json = "1" # toml config file parsing toml = "0.9" # Enum trait impl macros strum = { version = "0", features = ["derive"] } # Creation of tar file archives tar = "0" # gzip compression flate2 = "1" # Logging tracing = "0" # Macro to work around Rust's traits not working with async. Sigh. async-trait = "0" # HTTP types http = "1" http-serde = "2.1.1" # Async combinators for futures futures = "0" # Little helper to omit fields that cannot be debug printed debug-ignore = "1" # base encoding base16 = "0" # Language server protocol server plumbing lsp-server = "0" lsp-types = "=0.95.1" # Compact clone-on-write vector & string type ecow = "0" # Drop in replacement for std::path but with only utf-8 camino = "1" # std::error::Error definition macro thiserror = "2" # Test assertion errors with diffs pretty_assertions = "1" # Snapshot testing to make test maintenance easier insta = { version = "1", features = ["glob"] } # A transitive dependency needed to compile into wasm32-unknown-unknown # See https://docs.rs/getrandom/latest/getrandom/index.html#webassembly-support getrandom = { version = "0.2", features = ["js"] } # Non-empty vectors vec1 = "1" # Pubgrub dependency resolution algorithm pubgrub = "0.3" # Open the user's web browser opener = "0" ================================================ FILE: Cross.toml ================================================ [target.x86_64-unknown-linux-musl] image = "clux/muslrust:stable" ================================================ FILE: LICENCE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2016 - present Louis Pilfold Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: Makefile ================================================ # # Goals to be specified by user # .PHONY: help help: @cat $(MAKEFILE_LIST) | grep -E '^[a-zA-Z_-]+:.*?## .*$$' | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' .PHONY: build build: ## Build the compiler cargo build --release .PHONY: install install: ## Build the Gleam compiler and place it on PATH cd gleam-bin && cargo install --path . --force --locked .PHONY: test test: ## Run the compiler unit tests cargo test --quiet cargo clippy cd test/language && make cd test/javascript_prelude && make test cd test/project_erlang && cargo run clean && cargo run check && cargo run test cd test/project_javascript && cargo run clean && cargo run check && cargo run test cd test/project_deno && cargo run clean && cargo run check && cargo run test cd test/hextarball && make test cd test/running_modules && make test cd test/subdir_ffi && make .PHONY: insta-check-unused insta-check-unused: ## Check for unused cargo insta snapshots cargo insta test --unreferenced=warn .PHONY: insta-fix-unused insta-fix-unused: ## Remove unused cargo insta snapshots cargo insta test --unreferenced=delete .PHONY: language-test language-test: ## Run the language integration tests for all targets cd test/language && make .PHONY: language-test-watch language-test-watch: ## Run the language integration tests for all targets when files change watchexec "cd test/language && make" .PHONY: javascript-prelude-test javascript-prelude-test: ## Run the JavaScript prelude core tests cd test/javascript_prelude && make test .PHONY: javascript-prelude-test-watch javascript-prelude-test-watch: ## Run the JavaScript prelude core tests when files change watchexec "cd test/javascript_prelude && make test" .PHONY: test-watch test-watch: ## Run compiler tests when files change watchexec -e rs,toml,gleam,html "cargo test --quiet" .PHONY: export-hex-tarball-test export-hex-tarball-test: ## Run `gleam export hex-tarball` and verify it is created cd test/hextarball && make test .PHONY: benchmark benchmark: ## Run the benchmarks cd benchmark/list && make # Debug print vars with `make print-VAR_NAME` print-%: ; @echo $*=$($*) ================================================ FILE: README.md ================================================

Lucy, Gleam's mascot

GitHub release Discord chat

 
Gleam is a friendly language for building type-safe systems that scale! For more information see [the website](https://gleam.run). ## Support Gleam! Gleam is not owned by a corporation, instead it is kindly supported by its sponsors. If you like Gleam please consider [sponsoring the project or members of the core team](https://gleam.run/sponsor). Thank you so much! 💖 ================================================ FILE: RELEASE.md ================================================ # Release checklist 1. Update the version in each `Cargo.toml`. 2. Update versions in `src/new.rs` for stdlib etc if required. 3. Run `make test build`. 4. Git commit, tag, push, push tags. 5. Wait for CI release build to finish. 6. Publish release on GitHub from draft made by CI. 7. Update version in `Cargo.toml` to next-dev. 8. Update clone target version in `getting-started.md` in `website`. ================================================ FILE: benchmark/list/.gitignore ================================================ *.beam *.ez /build erl_crash.dump ================================================ FILE: benchmark/list/Makefile ================================================ .PHONY: build build: clean erlang nodejs deno bun .PHONY: clean clean: rm -rf build .PHONY: erlang erlang: @echo test/language on Erlang cargo run --quiet -- test --target erlang .PHONY: nodejs nodejs: @echo test/language on JavaScript with Node cargo run --quiet -- test --target javascript --runtime nodejs .PHONY: deno deno: @echo test/language on JavaScript with Deno cargo run --quiet -- test --target javascript --runtime deno .PHONY: bun bun: @echo test/language on JavaScript with Bun cargo run --quiet -- test --target javascript --runtime bun ================================================ FILE: benchmark/list/gleam.toml ================================================ name = "list" version = "1.0.0" description = "Benchmarks for lists" licenses = ["Apache-2.0"] [dependencies] gleam_stdlib = "~> 0.28" gleamy_bench = ">= 0.6.0 and < 1.0.0" ================================================ FILE: benchmark/list/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "gleam_stdlib", version = "0.59.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "F8FEE9B35797301994B81AF75508CF87C328FE1585558B0FFD188DC2B32EAA95" }, { name = "gleamy_bench", version = "0.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleamy_bench", source = "hex", outer_checksum = "DEF68E4B097A56781282F0F9D48371A0ABBCDDCF89CAD05B28C3BEDD6B2E8DF3" }, ] [requirements] gleam_stdlib = { version = "~> 0.28" } gleamy_bench = { version = ">= 0.6.0 and < 1.0.0" } ================================================ FILE: benchmark/list/src/list.gleam ================================================ pub fn main() { Nil } ================================================ FILE: benchmark/list/test/benchmarks.gleam ================================================ import gleamy/bench import gleam/io import gleam/list pub fn print_results(results: List(bench.BenchResults)) { results |> list.map(fn(result) { result |> bench.table([bench.IPS, bench.Min, bench.P(99)]) |> io.println() }) } ================================================ FILE: benchmark/list/test/list_test.gleam ================================================ import benchmarks.{print_results} import gleam/list import gleamy/bench pub fn main() { print_results([bench_odd_nums_between(), bench_count_ones(), bench_slice()]) } fn bench_odd_nums_between() -> bench.BenchResults { let odd_nums_between = fn(args) { let #(start, end) = args odd_nums_between(start, end, []) } bench.run( [ bench.Input("[0, 100)", #(0, 100)), bench.Input("[0, 1000)", #(0, 1000)), bench.Input("[0, 10_000)", #(0, 10_000)), bench.Input("[0, 100_000)", #(0, 100_000)), bench.Input("[0, 1_000_000)", #(0, 1_000_000)), ], [bench.Function("odd_nums_between", odd_nums_between)], [bench.Duration(1000), bench.Warmup(100)], ) } /// Returns a list of numbers from `start` up to, but not including, `end`. fn odd_nums_between(start: Int, end: Int, acc: List(Int)) -> List(Int) { case start { start if start >= end -> list.reverse(acc) _ -> { let acc = case start { n if n % 2 == 1 -> [n, ..acc] _ -> acc } odd_nums_between(start + 1, end, acc) } } } fn bench_count_ones() -> bench.BenchResults { let create_list = fn(size) { list.range(0, size) |> list.map(fn(n) { case n { _ if n % 3 == 0 -> 1 _ -> 0 } }) } let count_ones = fn(list) { count_ones(list, 0) } bench.run( [ bench.Input("100 numbers", create_list(100)), bench.Input("1000 numbers", create_list(1000)), bench.Input("10_000 numbers", create_list(10_000)), bench.Input("100_000 numbers", create_list(100_000)), bench.Input("1_000_000 numbers", create_list(1_000_000)), ], [bench.Function("count_ones", count_ones)], [bench.Duration(1000), bench.Warmup(100)], ) } /// Counts the number of ones in a list. fn count_ones(list: List(Int), count: Int) -> Int { case list { [] -> count [1, ..tail] -> count_ones(tail, count + 1) [_, ..tail] -> count_ones(tail, count) } } fn bench_slice() -> bench.BenchResults { let list = list.range(0, 1_000_000) let slice = fn(args) { let #(list, start, end) = args slice(list, start, end, []) } bench.run( [ bench.Input("[0, 1000)", #(list, 0, 1000)), bench.Input("[0, 10_000)", #(list, 0, 10_000)), bench.Input("[0, 100_000)", #(list, 0, 100_000)), bench.Input("[999_000, 1_000_000)", #(list, 999_000, 1_000_000)), bench.Input("[990_000, 1_000_000)", #(list, 990_000, 1_000_000)), bench.Input("[900_000, 1_000_000)", #(list, 900_000, 1_000_000)), bench.Input("[0, 1_000_000)", #(list, 0, 1_000_000)), ], [bench.Function("slice", slice)], [bench.Duration(1000), bench.Warmup(100)], ) } /// Slices a list. fn slice(list: List(Int), start: Int, end: Int, acc: List(Int)) -> List(Int) { case list { // If the first element is before the slice, skip it. [_, ..tail] if start > 0 -> { slice(tail, start - 1, end - 1, acc) } // If the first element is within the slice, add it to the accumulator. [head, ..tail] if end > 0 -> { slice(tail, start - 1, end - 1, [head, ..acc]) } // Otherwise, return the accumulator. _ -> list.reverse(acc) } } ================================================ FILE: bin/add-nightly-suffix-to-versions.sh ================================================ #!/bin/sh # # Add the `-nightly-YYYYMMDD` suffix the version of all Rust crates in the # workspace. Used by the nightly build. # set -e find . -name Cargo.toml -exec \ sed -i "" -e "s/^version = \"\([^\"]*\)\"$/version = \"\1-nightly-$(date +%Y%m%d)\"/" {} \; ================================================ FILE: changelog/v1.1.md ================================================ # Changelog ## v1.1.0 - 2024-04-16 ### Formatter - Fixed a bug where the first subject of a case expression clause would be indented more than necessary. ## v1.1.0-rc3 - 2024-04-12 ### Formatter - Fixed a bug where the `@internal` annotation wouldn't be displayed. - Fixed a bug where a record update's arguments would be incorrectly split on multiple lines. ## v1.1.0-rc2 - 2024-04-10 ### Compiler - Fixed a bug on the JavaScript target where variables named `debugger`, which is a JavaScript keyword, were not being renamed, leading to runtime errors. ### Formatter - Fixed a bug where comments would be moved out of an empty bit array. - Fixed a bug where the formatter could add a trailing comma inside empty bit arrays, generating invalid syntax. - Revert the warning about internal types being exposed in a package's public API. ### Build tool - Revert the change that would make the build tool refuse to publish a package that exposes an internal type in its public API. ## v1.1.0-rc1 - 2024-04-08 ### Compiler - The `@internal` attribute can now be used to annotate definitions. This will hide those definitions from the generated documentation, autocompletions and the exported module interface. - Prepending to lists in JavaScript (`[x, ..xs]` syntax) has been optimised. - Function stubs are no longer generated for functions that do not have an implementation for the current targeting being compiled for. - Fixed a bug where some functions would not result in a compile error when compiled for a target that they do not support. - Fixed a bug where sometimes a warning would not be emitted when a result is discarded. - Fixed a bug with JavaScript code generation of pattern matching guards. - URLs in error messages have been updated for the new language tour. - Improved error message when erroneously trying to append items to a list using the spread syntax (like `[..rest, last]`). - Generate [type references](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-types-) when compiling to JavaScript with TypeScript definitions enabled. - Fix a bug where JavaScript code generation would not properly return the result of nested blocks. - Fix a bug where JavaScript code generation would not properly handle functions returned by blocks. - Fix a bug where Erlang code generation would not properly handle list case patterns with no head and a spread tail. - The compiler will now raise a warning if you're pattern matching on tuple literals and suggest you use multiple subjects instead. - Fixed a bug where JavaScript code generation would incorrectly parenthesise a return statement. - Nested tuple access (`tuple.0.1`) now parses successfully. - Error messages are more clear about expecting values instead of types. - Fixed a bug where pattern matching on a string would cause the program to crash on the JavaScript target. - A warning is now emitted when defining an opaque external type. - Improve error message when using incorrect quotes (`'`) to define a string - Fixed a bug where Erlang string prefix patterns could generate invalid Erlang. - Fixed string prefix matching producing wrong results on the JavaScript target when the prefix had a Unicode codepoint escape sequence (`\u{...}`). - Improved error message for wrong patterns using constructors from other modules. - Fixed a bug on the JavaScript target where variables bound by patterns, if used within a bit array literal inside a `case` clause's guard, would be used before they were defined, leading to a runtime error when evaluating the `case` expression. - Improved error messages when failing to parse a series of things. - A warning is now raised for unused binary operations, records, record access and record updates. - Fixed a bug when using constant as the size option parameter for `BitArray` caused unknown variable exception. - Improved recommendations on error messages. - Improved error message for `BitArray` segment sizes. - A warning is now raised when an internal type is accidentally exposed in a package's public API. - Fixed the error message when using `panic` on the JavaScript target: it now correctly identifies the error variant as a `panic` instead of a `todo`. - Fixed a bug on the JavaScript target where empty lists with little space available could compile to a conversion from the array `[ , ]`, causing them to wrongly have a length of one (the array has a single `undefined` element). ### Formatter - The formatting of case expressions with multiple subjects has been improved. - Fixed a bug where the formatter would move comments from the end of bounded expressions like lists, tuples, case expressions or function calls. - Fixed a bug where a record update's arguments would not be indented correctly. - Fixed a bug where function call arguments, tuple items and list items would be needlessly indented if preceded by a comment. - Line endings other than `\n` are now handled by the formatter, preserving blank lines and converting them to `\n`. - The formatter can now format groups of imports alphabetically. - Fixed a bug where comments would be moved out of an empty list. - Fixed a bug where pipes and binary operations in function calls would be nested more than necessary. - Improved formatting of comments in binary operation chains. ### Build tool - Added support for the [Bun](https://bun.sh/) runtime when compiling to JavaScript by using `gleam run --target javascript --runtime bun` - Allow compilation of packages that require `"rebar"` using the rebar3 compiler. - A warning is now emitted if there is a `.gleam` file with a path that would be invalid as a module name. - The `~> x.y` version constraint syntax has been dropped in favour of `> x.y.z and <= xx.0.0` syntax in `gleam add` and `gleam new`, for clarity. - New projects are created with the GitHub `actions/checkout` v4 action. - Fixed a bug where bit arrays would break syntax highlighting in the generated HTML documentation. - Dependencies that use Erlang-only bit options can now compile on JavaScript, though the functions that use them will not be available for use in the root package. - The output format of the command line help messages have been changed slightly. - The command line help text now lists valid targets and runtimes. - Generated documentation no longer exposes the constructors of opaque types, no longer exposes the values of constants, and indicates which types are opaque. - Generated HTML documentation now includes a link to the package on Hex. - Terminal colors can now be forced by setting the `FORCE_COLOR` environment variable to any non-empty value. - Rust's Reqwest's `webpki-roots` are now used for TLS verification. - Update Deno config to allow passing `--location` runtime flag. - Fixed a bug with dependency resolution of exact versions of RC releases. - Fixed a bug where the documentation of a labelled record constructor could be assigned to the wrong definition in the doc site. - Fixed a bug where the code blocks in the generated documentation's site would have a wrong indentation. - Fixed a bug where the compiler would panic when it cannot get current directory. - Improved error message for non-UTF-8 paths encountered during Gleam commands. Now includes the path that caused the error for better diagnostics. - Fixed a bug where on windows local packages had absolute paths in the manifest instead of relative. - The `gleam publish` command now asks for confirmation if the package repository URL doesn't return a successful status code. - `gleam publish` can now optionally use a Hex API key to authorise publishing and retiring packages, set via the environment variable `HEXPM_API_KEY`. - `gleam publish` will now refuse to publish placeholder packages, to prevent name squatting which is against the Hex terms of service. - If a package leaks an internal type in its public API, then the build tool will now refuse to publish it to Hex. - Monospaced links in the generated documentation now have the same color as common links. - Fixed a bug where the `export package interface` command would always recompile the project ignoring the cache. ### Language Server - Update messages from the client are now batched to avoid doing excess compilation work in the language server, improving performance. This makes a large difference in low power machines where the language server could struggle to keep up with the edits from the client. - The `Compiling Gleam` message is no longer emitted each time code is compiled. This is to reduce noise in editors that show this message prominently such as Neovim. - Fixed a bug where hovering over an expression in the middle of a pipe would give the wrong node. - Go to definition now works for values in dependency Gleam modules. - Completions are now provided for module imports. ## v1.0.0 - 2024-03-04 ### Language changes - Comments have been added to the JavaScript prelude to indicate which members are in the public API and which are internal. ### Build tool - Fixed a bug where the exported package interface would not have a module's documentation. ## v1.0.0-rc2 - 2024-02-14 ### Bug fixes - Fixed a bug where the exhaustiveness checker could crash for some generic types. ### Formatter - The format used by the formatter has been improved in some niche cases. - Improved the formatting of long case guards. ## v1.0.0-rc1 - 2024-02-10 ### Language changes - Using a reserved word is now a compile error, not a warning. - Inexhaustive matches are now compile errors, not warnings. - The warning for an unused module alias now shows how to not assign a name to the module. - Type aliases with unused type parameters now emit an error. - Type definitions with duplicate type parameters now emit an error. ### Formatter - Now the formatter will nest pipelines and binary operators that are used as function arguments, list items or as tuple items. - The format function literals used as the last argument in a function call on long lines has been improved. ### Build tool - If a package contains a `todo` expression then the build tool will now refuse to publish it to Hex. - `gleam export` now takes a `package-interface` option to export a json file containing metadata about the root package. - `gleam docs build` now creates a json file containing metadata about the root package. - The order of dependencies in `manifest.toml` is now in alphabetical order. - The search bar in generated docs now has a darker background color. - The generated docs no longer shows whether an argument is discarded or not in a function signature. - It is now possible to use `gleam run -m` to run a dependency module even if that dependency uses a compile target that your project does not support. ### Bug fixes - Fixed a bug the build tool could be make to attempt to run a main function that does not support the current target in some circumstances. - Fixed a bug where the exhaustiveness checker could crash when checking nested values inserted into the parent type using type parameters. - Fixed a bug where `functionname(_name)` would incorrectly parse as a function capture instead of a syntax error. - Fixed a bug where external only functions would "successfully" compile for a target they do not support, leading to a runtime error. ## v0.34.1 - 2023-01-17 ### Build tool changes - Support has been added for using SourceHut as a repository. ### Bug fixes - Fixed a bug where long function headers with external implementations could format incorrectly. - The `@deprecated` attribute can now be used to annotate module constants. This will cause a warning to be emitted when the constant is used. ## v0.34.0 - 2023-01-16 ## v0.34.0-rc3 - 2023-01-12 ### Language changes - "echo" is now a reserved word. - A warning is no longer emitted when a function has a Gleam implementation as well as external implementations for both targets. This is because having a default Gleam implementation means the code is future-proof and continues to be cross platform even if a new target is added. ### Bug fixes - Fixed a bug where function heads would go over the line limit in the formatter. ## v0.34.0-rc2 - 2023-01-11 ### Bug fixes - Fixed a bug where `gleam run` would fail when the current directory is not the root of the project and using the JavaScript target. - Fixed a bug where the compiler would in some cases fail to error when an application uses functions that do not support the current compilation target. ## v0.34.0-rc1 - 2024-01-07 ### Language changes - Warn about function body not being used, because it already has external implementations for all targets. - It's now possible to compile a project with external functions in dependency packages that are not supported by the compilation target so long as they are not used on the current target. - The error message for when one imports a constructor instead of an homonymous type has been improved. ### Language Server Changes - Added a `View on HexDocs` link on function hover. ### Formatter - Fixed some quirk with the formatting of binary operators. - Fixed a bug where the formatter would move a function call's closed parentheses on a new line instead of splitting the function's arguments. - Now the formatter will format tuples as if they were functions, trying to first split just the last element before splitting the whole tuple. - Improved the formatting of multiline strings in string concatenation. ### Build tool changes - The `gleam new` command now accepts any existing path, as long as there are no conflicts with already existing files. Examples: `gleam new .`, `gleam new ..`, `gleam new ~/projects/test`. - The format for the README created by `gleam new` has been altered. - The `gleam.toml` created by `gleam new` now has a link to the full reference for its available options. - The `gleam` binary is now statically linked on Windows. - New projects are created requiring between versions of v0.34.0 inclusive and exclusive v2.0.0. - The `repository` section now supports additional VCS types in the form of codeberg, forgejo and gitea allowing a `user`, `repo` and additionally a `host` url. - TypeScript declaration for the prelude exports previously missing functions and classes. Additionally, swaps interfaces for classes and adds missing attributes to classes. - `gleam` commands now look in parent directories for a `gleam.toml` file. ### Bug fixes - Fixed a bug where `gleam add` would not update `manifest.toml` correctly. - Fixed a bug where `fn() { Nil }()` could generate invalid JavaScript code. - Fixed a bug where the build tool would make unnecessary calls to the Hex API when path dependencies are used. - Fixed a bug where `gleam new` would generate a gitignore with `build` rather than `/build`. - Fixed where the types of generic constants could be incorrectly inferred. - `Utf8Codepoint` has been renamed to `UtfCodepoint` in `prelude.d.mts`. - Fixed a bug where `gleam deps list` would look in filesystem root instead of the current directory. - Fixed a bug with the `isEqual` function in `prelude.js` where RegExps were being incorrectly structurally compared and being falsely reported as being equal. - JavaScript: export from `prelude.d.mts` in `gleam.d.mts` to fix the error: "Type 'Result' is not generic". - Not providing a definition after some attributes is now a parse error. ## v0.33.0 - 2023-12-18 ## v0.33.0-rc4 - 2023-12-17 - The deprecated bit array options `binary` and `bit_string` have been removed. - The deprecated ambiguous type import syntax has been removed. - The deprecated `BitString` type has been removed. - The deprecated `inspect` functions and `BitString` type has been removed from the JavaScript prelude. ## v0.33.0-rc3 - 2023-12-17 ### Formatter - The formatter now tries to split long chains of binary operations around the operator itself, rather than around other elements like lists or function calls. ### Bug fixes - Fixed a bug where string prefix aliases defined in alternative case branches would all be bound to the same constant. ## v0.33.0-rc2 - 2023-12-07 ### Language changes - The `\e` string escape sequence has been removed. Use `\u{001b}` instead. - Generated Erlang now disabled redundant case clause warnings as these are now redundant due to exhaustiveness checking. ### Bug fixes - Fixed a bug where the `\u` string escape sequence would not work with on Erlang on the right hand side of a string concatenation. ## v0.33.0-rc1 - 2023-12-06 ### Formatter - The formatter now tries to keep a function body and its arguments on a single line by first trying to split only its last argument on multiple lines. - Fixed a bug where the formatter would move comments out of blocks. - `gleam format` now ignores the Gleam build directory by default, even when not in a git repository. ### Language changes - Gleam now has full exhaustiveness checking. Exhaustiveness issues have been downgraded from errors to warnings so that existing Gleam code can be upgraded to be exhaustive without breaking existing code. In a future version they will be upgraded to errors. - The `!` operator can now be used in clause guards. - The words `auto`, `delegate`, `derive`, `else`, `implement`, `macro`, and `test` are now reserved for future use. If used they will emit a warning. In a future version this may be upgraded to an error. - The `\u{...}` syntax can be used in strings to specify unicode codepoints via a hexadecimal number with 1 to 6 digits. - The `todo as` and `panic as` syntaxes now accept an expression that evaluates to a string rather than just a string literal. ### Build tool changes - The `gleam run` and `gleam test` commands gain the `-t` flag, which is an alias of the `--target` flag. - The `gleam build`, `gleam check`, `gleam run` and `gleam test` commands now also accept `js` and `erl` as values for the `--target` flag. - The `gleam new` command now creates packages at version 1.0.0. - The `gleam publish` command now asks for confirmation if the package being published is not yet version 1.0.0. - The `gleam publish` command now asks for confirmation if the package name is one that implies the package is maintained by the Gleam core team. - The error messages shown when dependency resolution fails have been improved. ### Compiler WASM API - The WASM API for the compiler has been rewritten to be simpler. - The WASM API for the compiler now exposes warnings. ### HTML documentation generator - Searching in rendered HTML documentation now also matches words that do not start with the input but do contain it. ### Bug fixes - Fixed a bug where the JavaScript code generator could generate invalid code when pretty printing a zero arity function call when the line is over 80 columns wide. - Fixed a bug where the build directory could be left in an invalid state if there is Elixir code to compile and running on Windows without permission to create symlinks. - Fixed a bug where numbers with preceding zeros could generate incorrect JavaScript. - The Erlang code generated by the `/.` operator no longer generates a warning for the upcoming negative zero float change in Erlang OTP 27. - Fixed a bug where using only types from an aliased import, wouldn't stop the compiler from emitting an unused alias warning for that import. - Fixed a bug where the formatter would remove the ` as name` from string prefix patterns. - Fixed a bug where the formatter would misplace comments at the start of a block. - Fixed a bug where using a string prefix pattern in `let assert` would generate incorrect JavaScript. ## v0.32.4 - 2023-11-09 ### Build tool changes - The build tool now supports rebar3 and mix hex packages where the package name differs from the otp application name. ### bug fixes - Fixed a bug where invalid javascript code could be generated when a module function calls another function that was passed as an argument and the argument has the same name as the module function. - Fixed the `target` property of `gleam.toml` being ignored for local path dependencies by `gleam run -m module/name` ## v0.32.3 - 2023-11-07 ### Language changes - Imported modules can now be discarded by giving them an alias starting with `_`. ### Build tool changes - New projects are now generated with the call to `gleam format` coming last in the GitHub Actions workflow. This is so that feedback from tests is presented even if formatting is incorrect. - Added Windows support for the `gleam export erlang-shipment` command. ### Bug fixes - Fixed a bug where some nested pipelines could fail to type check. ## v0.32.2 - 2023-11-03 ### Build tool changes - New Gleam projects are created with `gleam_stdlib` v0.32 and `gleeunit` v1.0. ### Bug fixes - Fixed a bug where `gleam fix` would not produce correct results for code that shadowed a prelude name with an import of the same name but a different kind. - Fixed a bug where documentation would not publish to Hexdocs for packages with a number in the name. - Fixed a bug where aliased unqualified types and values of the same name could produce an incorrect error. ## v0.32.1 - 2023-11-02 ### Bug fixes - Fixed a bug where `gleam fix` would not produce correct results for code that shadowed a prelude name with an import of the same name but a different kind. - Fixed a bug where incorrect JavaScript could be generated due to backwards compatibility with the deprecated import syntax. ## v0.32.0 - 2023-11-01 ### Bug fixes - Fixed a bug where running `gleam fix` multiple times could produce incorrect results. ## v0.32.0-rc3 - 2023-10-26 ### Bug fixes - Fixed a bug where `gleam fix` would fail to update the deprecated type import syntax for aliased unqualified types. ## v0.32.0-rc2 - 2023-10-26 ### Bug fixes - Fixed a bug where the backward compatibility for the deprecated import syntax could result in an import error with some valid imports. ## v0.32.0-rc1 - 2023-10-25 ### Language changes - Using `import module.{TypeName}` to import a type has been deprecated, replaced by `import module.{type TypeName}`. In a future version of Gleam the old syntax will only import the value of the same name. Run `gleam fix` to update your code. - The `BitString` type has been renamed to `BitArray`. Run `gleam fix` to update your code. - The `binary` and `bit_string` bit array modifier have been deprecated in favour of `bytes` and `bits`. - The error message for when one element in a list doesn't match the others has been improved. - The error message for when the elements of a list's tail don't match the previous ones has been improved. - The error message for when one tries to access an unknown field has been improved. - The `__gleam_prelude_variant__` property has been removed from the classes defined in the JavaScript prelude. - The deprecated `todo("...")` syntax has been removed. - Module access can now be used in case clause guards. - The JS target now supports bit syntax for module constants. - The Erlang compiler will no longer emit a duplicate warning for unused functions. - The `@deprecated` attribute can now be used with type definitions. - A warning is now emitted if a module alias is unused. ### Language server changes - The language server now has a code action for removing unused items. - The language server now shows the type of variables defined using `use` on hover. ### Build tool changes - The `gleam check` command supports the `target` flag. - The `gleam fix` command updates code to use `BitArray` rather than `BitString`. - The `gleam fix` command updates code to use the new import type syntax. - `gleam fix` sets the `gleam` version constraint in `gleam.toml` to `>= 0.32.0`. - The `gleam` version constraint field in `gleam.toml` now disregards pre and build components when checking for compatibility. - The prelude is no longer rendered once per package when compiling to JavaScript, instead one copy is rendered for the entire project. If you are using the `gleam compile-package` API you now need to give a path to the prelude using the `--javascript-prelude` flag. - The `gleam export javascript-prelude` and `gleam export typescript-prelude` commands have been added to export a copy of the prelude. This command may be useful for build tools that use the compiler via the `gleam compile-package` API. - Fixed a bug where some deprecation messages would not be printed. - The content has been made wider in rendered HTML documentation. - Dependencies that can be built with both `mix` and `rebar3` are now built with `mix` if it exists on the system, and with `rebar3` if it doesn't. ### Bug fixes - "Compiling $package" is now only printed when a package has new changes to compile. - The main process started with `gleam run` no longer traps exits on Erlang. - The formatting of code in rendered HTML documentation has been improved. - The formatter no longer moves trailing comments out of custom type definitions. - Fixed a bug where some hexadecimal numbers would generate incorrect Erlang. - Fixed a bug where markdown tables would not render correctly in HTML documentation. - The float 0.0 is now rendered in Erlang as `+0.0` to silence warnings in Erlang/OTP 27. ## v0.31.0 - 2023-09-25 - New Gleam projects are created with `gleam_stdlib` v0.31, `actions/checkout` v3.\*, and `erlef/setup-beam` v1.\*. - A note is included in the generated HTML documentation if a function is deprecated. ## v0.31.0-rc1 - 2023-09-18 - The `@deprecated("...")` attribute can be used to mark a function as deprecated. This will cause a warning to be emitted when the function is used. - A warning is now emitted if a module from a transitive dependency is imported. - Record access can now be used in case clause guards. - Fixed a bug where `manifest.toml` could contain absolute paths for path dependencies. - The `as` keyword can now be used to assign the literal prefix to a variable when pattern matching on a string. - The `if` conditional compilation, `external fn`, and `external type` syntaxes have been removed. - The `description` flag for the `gleam new` command has been removed. - The highlight.js grammar included with generated HTML documentation has been updated for the latest syntax. - Packages are no longer precompiled to Erlang when publishing to Hex if the package target is set to JavaScript. - An exception is now raised if JavaScript code uses the `BitString` class constructor and passes in the incorrect argument type. - Fixed a bug where mutually recursive functions could be incorrectly inferred as having an overly general type. - Fixed a bug where recursive type constructors could incorrectly infer a type error. - Fixed a bug where some mutually recursive functions would be inferred as having too general a type. - Fixed a bug where constants where not being correctly inlined when used in the size option of a bit string pattern match. - Fixed a bug where anonymous functions could parse successfully when missing a body. - Fixed a bug where incorrect unused variable warnings could be emitted for code that doesn't type check. - Fixed a bug where packages defaulting to the JavaScript target could have modules missing from their HTML documentation when published. - Corrected some outdated links in error messages. - Hovering over a function definition will now display the function signature, or the type of the hovered argument. - Use `import type` for importing types from typescript declarations. - Use `.d.mts` extension for typescript declarations to match `.mjs`. - Prefix module names with dollar sign in typescript to avoid name collisions. ## v0.30.4 - 2023-07-26 - External implementations are always referenced directly in generated code, to avoid the overhead of an extra function call. - Fixed a bug where the compiler could infer incorrect generic type parameters when analysing a module without type annotations with self recursive functions that reference themselves multiple times. ## v0.30.3 - 2023-07-23 - Fixed a bug where JavaScript module path such as `node:fs` would be rejected. - New Gleam projects are created with `gleam_stdlib` v0.30, Erlang OTP v26.0.2, Elixir v1.15.4, actions/checkout v3.5.1, and erlef/setup-beam v1.16.0. ## v0.30.2 - 2023-07-20 - Fixed a bug where the compiler could infer incorrect generic type parameters when analysing a module without type annotations with self recursive functions. - Fixed a bug where the formatter would incorrectly format external functions by breaking the return annotation instead of the function arguments. ## v0.30.1 - 2023-07-13 - Fixed a bug where the language server could fail to import path dependencies in monorepos. ## v0.30.0 - 2023-07-12 - A warning is now emitted for the deprecated external fn syntax. ## v0.30.0-rc4 - 2023-07-10 - An error is now emitted for invalid JavaScript external implementations. - Fixed a bug where Erlang external implementations could generate invalid code. ## v0.30.0-rc3 - 2023-07-05 - Fixed a bug where `gleam fix` would fail to parse command line flags. ## v0.30.0-rc2 - 2023-07-03 - Fixed a bug where `gleam fix` would merge external functions of the same name but incompatible types. - Fixed a bug where external function arguments would incorrectly be marked as unused. ## v0.30.0-rc1 - 2023-06-29 - The new `@target(erlang)` and `@target(javascript)` attribute syntax has been added for conditional compilation. The existing `if` conditional compilation syntax has been deprecated. Run `gleam fix` to update your code. - The new `type TypeName` syntax syntax replaces the `external type TypeName` syntax. The existing external type syntax has been deprecated. Run `gleam format` to update your code. - Adding a new dependency now unlocks the target package. This helps avoid failing to find a suitable version for the package due to already being locked. - A custom message can now be specified for `panic` with `panic as "..."`. - The syntax for specifying a custom message for `todo` is now `todo as "..."`. - The Erlang error raised by `let assert` is now tagged `let_assert`. - Types named `Dynamic` are now called `dynamic_` in Erlang to avoid a clash with the new Erlang `dynamic` type introduced in OTP26. - Dependencies can now be loaded from paths using the `packagename = { path = "..." }` syntax in `gleam.toml`. - The `javascript.deno.unstable` field in `gleam.toml` can now be used to enable Deno's unstable APIs when targeting JavaScript. - Blockquotes are now styled in rendered HTML documentation. - The `gleam` property can be set in `gleam.toml` can be set to a version requirement to specify the version of Gleam required to build the project. - Type aliases can now refer to type aliases defined later in the same module. - Fixed a bug where unapplied record constructors in constant expressions would generate invalid Erlang. - Fixed a bug where the prescedence of `<>` and `|>` would clash. - Fixed a bug where `gleam docs build` would print an incorrect path upon completion. - Warnings from dependency packages are no longer surfaced in the language server. - A warning is now emitted when a Gleam file is found with an invalid name. - A warning is now emitted when using `list.length` to check for the empty list, which is slow compared to checking for equality or pattern matching (#2180). - The new `gleam remove ` can be used to remove dependencies from a Gleam project. - Fixed a bug where the formatter could crash. - Fixed a bug where invalid Erlang would be generated when piping into `panic`. - The `gleam docs build` command gains the `--open` flag to open the docs after they are generated (#2188). - Fixed a bug where type annotations for constants could not be written with type annotations. - Updated font loading in generated HTML documentation to fix an issue with fonts not loading properly in some browsers (#2209). ## v0.29.0 - 2023-05-23 - New projects now require `gleam_stdlib` v0.29. ## v0.29.0-rc2 - 2023-05-22 - The `gleam lsp` command is no longer hidden from the help output. - Fixed a bug where some language server clients would show autocompletion suggestions too eagerly. ## v0.29.0-rc1 - 2023-05-16 - The language server will now provide autocomplete suggestions for types and values either imported or defined at the top level of the current module. - Fixed a bug where record patterns using the spread operator (`..`) to discard unwanted arguments would not type check correctly when the record had no labelled fields. - Add support for using sized binary segments in pattern matches when targeting JavaScript. - A warning is now emitted for double unary negation on ints (`--`) and bools (`!!`) as this does nothing but return the original value. - Previously the build tool would discard the entire build directory when dependencies were changed. Now it will only discard the build artefacts for removed dependencies. - The errors emitted when a name is reused in a module have been made clearer. - Fixed an incorrect URL in the error message for failing to parse a let binding with a type annotation. - Fixed a bug where shadowing a prelude type name could result in incorrect errors in exhaustiveness checking. - Fixed a bug where the language server would in some scenarios not remove an error diagnostic after it becomes outdated. - Fixed a bug where the formatter would incorrectly format blocks with a comment before them that were the only argument to a function call. - Fixed a bug where the language server would not reset the build directory when it was created by a different version of Gleam. - New Gleam projects are created with `erlef/setup-beam@v1.15.4` in their GitHub actions CI configuration. - Running a module now uses the dependency's target and runtime in its `gleam.toml`. ## v0.28.3 - 2023-04-17 - Fixed a bug where the language server would show outdated error diagnostics when a new one was emitted in a different module. - Fixed a bug where the language server would attempt to analyse Gleam modules that were outside of the `src` or `test` directories. - New Gleam projects are created with `actions/checkout@v3.5.1` and `erlef/setup-beam@1.15.3` in their GitHub actions CI configuration. ## v0.28.2 - 2023-04-10 - Fixed a bug where comments above a `use` expression would be formatted incorrectly. - Fixed a bug where the formatter would fail to preserve empty lines after a block. - Fixed a bug where the formatter would fail to preserve empty lines after an anonymous function with a return annotation. ## v0.28.1 - 2023-04-05 - Fixed a bug where the language server would unset too many error diagnostics when multiple projects are open, more than one have errors, and one of them is successfully compiled. - Fixed a bug where the language server would unset error diagnostics when displaying information on hover. - Added support for type annotations in use statements. ## v0.28.0 - 2023-04-03 - New projects now require `gleam_stdlib` v0.28. ## v0.28.0-rc3 - 2023-03-31 - Fixed a bug where source links would be incorrect in HTML documentation. ## v0.28.0-rc2 - 2023-03-27 - Fixed a bug where single statement blocks inside binary operators could generate invalid JavaScript. - Fixed a bug where the formatter could incorrectly place comments. - Fixed a bug where the language server would show outdated diagnostics when a file with an error reverts to the previous valid version, causing the compiler to use the cached version of the file. ## v0.28.0-rc1 - 2023-03-26 - The language server now analyzes files on edit rather than on save, providing feedback faster. - The language server now supports editor sessions that span multiple projects. This is useful for mono-repos and projects with both a frontend and backend in Gleam. - The language server now also shows documentation on hover for expressions. - The language server now shows types and documentation on hover for patterns. - Added support for negation of integers with the new `-` unary operator. - Variable assignments are now only permitted within a function or a block, not anywhere that an expression is permitted. - The deprecated `try` expression has been removed. - The deprecated `assert ... = ...` syntax has been removed. - Semicolons are no longer whitespace. An error will be emitted if one is encountered. - Warnings are now immediately emitted rather than being buffered until the end of the compilation. - The `--warnings-as-errors` flag is now supported by `gleam build`. - Blocks are now preserved by the formatter when they only have a single expression within them. - Generated docs now export more meta data to improve the developer experience, accessibility and search engine discoverability. - Files are now only recompiled if they have changed since the last compilation, detected by file hash and modification time. Previously only the modification time was used. - Autocompletion of module imports was removed due to a buggy implementation. - Fixed a bug where the formatter would incorrectly remove `{ ... }` from bit string segment value expressions. - Fixed a bug where TypeScript type definitions files could include incorrect type names. - Fixed a bug where the compiler used VSCode specific behaviour in the language server which was incompatible with Helix. - Fixed a bug where string concatenation patterns on strings with escape characters would generate javascript code with wrong slice index. - Fixed a bug where blocks could parse incorrectly. - Allow modules to be run with the `gleam run --module` command. ## v0.27.0 - 2023-03-01 - Fixed a bug where `panic` could generate incorrect JavaScript code. - New projects now require `gleam_stdlib` v0.27. ## v0.27.0-rc1 - 2023-02-26 - The new `panic` keyword can be used to crash the program. This may be useful for situations in which a program has got into an unrecoverable invalid state. - `try` expressions are now deprecated and will be removed in a future version. - The new `gleam fix` command can be used to automatically convert `try` expressions to `use` expressions. - `let assert ... = ...` is now the syntax for assertion assignments. The `assert ... = ...` syntax is deprecated and will be removed in a future version. Run `gleam format` to automatically update your code. - `gleam export hex-tarball` can be used to create a tarball suitable for uploading to a Hex compatible package repository. - The unused private type and constructor detection has been improved. - The argument `--runtime` now accepts `nodejs` as the name for that runtime. The previous name `node` is still accepted. - Patterns can now be used in `use` expressions. - Fixed a bug where string concatenation patterns could generate javascript code with wrong slice index due to ut8/ut16 length mismatch. - The Erlang compiler will no longer emit a duplicate warning for unused variables. - Fixed a bug where typescript type definitions for types with unlabelled arguments where generated with an invalid identifier and unlabelled fields were generated with a name that didn't match the javascript implementation. - Fixed a bug in the type inferrer were unannotated functions that were used before they were defined in a module could in rare cased be inferred with a more general type than is correct. - Fixed a bug where the LSP would fail to show type information on hover for expressions after a use expression. - Fixed a bug where imported constants could generated incorrect JavaScript code. - Fixed a bug where the LSP would perform codegen for dependencies. - Fixed a bug where the LSP would compile native dependencies needlessly. - Fixed a bug where integer division with large numbers on JavaScript could produce incorrect results. - Fixed a bug where pattern matches on custom types with mixed labelled and unlabelled arguments could not be compiled when targeting JavaScript. - Fixed a bug where local variables in case guard constant expressions caused the compiler to panic. - The formatter now truncates meaningless zeroes of floats' fractional parts. - Anonymous functions may now have an empty body. The compiler will emit a warning for functions without a body, and these functions will crash at runtime if executed. - Fixed bug where raised errors on JS would have an extra stack frame recorded in them. ## v0.26.2 - 2023-02-03 - The formatter now wraps long `|` patterns in case clauses over multiple lines. - Fixed a bug where unlabelled function arguments could be declared after labelled ones. - A broken link was removed from the error messages. - Fixed a bug where using a qualified imported record constructor function as a value would produce invalid Erlang code if the name of the record variant was an Erlang reserved word. ## v0.26.1 - 2023-01-22 - New projects now require `gleeunit` v0.10. - Rebar3 dependency projects are now compiled in-place. This fixes an issue where some NIF using projects would fail to boot due to some paths not being copied to the `build` directory. - An error is now emitted if a list spread expression is written without a tail value. - An error is now emitted when a function is defined with multiple arguments with the same name. - The error message emitted when a `let` does not match all possible values has been improved. - Fixed a bug where the language server wouldn't analyse test code. - Fixed a bug where `assert` expressions can generate invalid Erlang. warning. - Fixed a bug where arguments would be passed incorrectly to Deno. - Fixed a bug where defining variables that shadow external functions could generate invalid JavaScript. ## v0.26.0 - 2023-01-19 [Release blog post](https://gleam.run/news/v0.26-incremental-compilation-and-deno/) - New projects require `gleam_stdlib` v0.26 and `gleeunit` v0.9. - Fixed a bug where JavaScript default projects would fail to publish to Hex. ## v0.26.0-rc1 - 2023-01-12 - Added support for Deno runtime for JavaScript target. - Scientific notation is now available for float literals. - The compiler now supports incremental compilation at the module level. If a module or its dependencies have not been changed then it will not be recompiled. - The format used by the formatter has been improved. - 4 digit integers are now always formatted without underscores. - Running `gleam new` will skip `git init` if the new project directory is already part of a git work tree. - Generated HTML documentation now includes all static assets, including web fonts, so that it can be accessed offline and in future once CDNs would 404. - Generated HTML documentation now supports TypeScript syntax highlighting. - New Gleam projects are created using GitHub actions erlef/setup-beam@v1.15.2. - Some modules can now be hidden from the docs by specifying a list of glob patterns in `internal_modules` in `gleam.toml`. The default value for this list is `["$package_name/internal", "$package_name/internal/*"]`. - The `gleam new` command gains the `--skip-git` flag to skip creation of `.git/*`, `.gitignore` and `.github/*` files. - The `gleam new` command gains the `--skip-github` flag to skip creation of `.github/*` files. - Fixed a bug where no error would be emitted if a `src` module imported a `test` module. - Fixed a bug where comments in list prepending expressions could be formatted incorrectly. - Fixed a bug where comments in record update expressions could be formatted incorrectly. - Fixed a bug where long `use` expressions could be formatted incorrectly. - Fixed a bug integer multiplication would overflow large integers when compiling to JavaScript. - Fixed `int` and `float` formatting in `const`s and patterns. - Fixed a bug where piping into a function capture expression with a pipe as one of the arguments would produce invalid Erlang code. - Formatter no longer removes new lines in expression blocks within case branches ## v0.25.3 - 2022-12-16 - 4 digit integers are no longer automatically formatted with underscores. ## v0.25.2 - 2022-12-16 - Updated `actions/checkout` from `actions/checkout@v3.0.0` to `@v3.2.0` for projects created via `gleam new`. - Fixed a bug where `gleam new` would set a `Rebar3` version to `25.1` instead of the latest stable `3`. - Updated following runtime versions set via `gleam new`: `Erlang/OTP` to `25.2`, and `Elixir` to `1.14.2`. - The formatter now inserts underscores into larger `Int`s and the larger integer parts of `Float`s. - Added support for top level TypeScript file inclusion in builds. - The build tool will now favour using rebar3 over Mix for packages that support both. This fixes an issue where some packages could not be compiled without Elixir installed even though it is not strictly required. ## v0.25.1 - 2022-12-11 - New Gleam projects are now configured to explicitly install rebar3 using GitHub actions erlef/setup-beam. - A better error message is now shown when attempting to use a function within a constant expression. - Changed float size limit in bit string expressions to 16, 32 or 64, when static. Also allowed dynamic size. - New Gleam projects are created using GitHub actions erlef/setup-beam@v1.15.0. - Fixed a bug where returning an anonymous function from a pipeline and calling it immediately without assigning it to a variable would produce invalid Erlang code. - Fixed a bug where the formatter would remove the braces from negating boolean expressions. ## v0.25.0 - 2022-11-24 [Release blog post](https://gleam.run/news/v0.25-introducing-use-expressions/) ## v0.25.0-rc2 - 2022-11-23 - Fixed a bug where Gleam dependency packages with a `priv` directory could fail to build. - Fixed a regression where Elixir and Erlang Markdown code blocks in generated documentation would not be highlighted. ## v0.25.0-rc1 - 2022-11-19 - Generated HTML documentation now includes the `theme-color` HTML meta tag. - The `use` expression has been introduced. This is a new syntactic sugar that permits callback using code to be written without indentation. - Nightly builds are now also published as OCI container images hosted on GitHub. - Fixed a bug where the build tool would not hook up stdin for Gleam programs it starts. - Fixed a bug where using a record constructor as a value could generate a warning in Erlang. - Fixed a bug where the build tool would use precompiled code from Hex packages rather than the latest version, which could result in incorrect external function usage in some cases. - Fixed a bug where the warning for `todo` would not print the type of the code to complete. - Fixed a bug where `try` expressions inside blocks could generate incorrect JavaScript. - Generated HTML documentation now includes all static assets (but the web fonts), so that it can be accessed offline or in far future once CDNs would 404. - New Gleam projects are created using GitHub actions erlef/setup-beam@v1.14.0 - The `javascript.typescript_declarations` field in `gleam.toml` now applies to the entire project rather than just the top level package. - The formatter now adds a `0` to floats ending with `.` (ie `1.` => `1.0`). - New projects require `gleam_stdlib` v0.25. ## 0.24.0 - 2022-10-25 [Release blog post](https://gleam.run/news/gleam-v0.24-released/) ## 0.24.0-rc4 - 2022-10-23 - Fixed a bug where the string concatenate operator could produce invalid Erlang code when working with pipe expressions. ## 0.24.0-rc3 - 2022-10-20 - Fixed a bug where the OOP method call error hint would be shown on too many errors. - Fixed a bug where the string concatenate operator could produce invalid Erlang code when working with constant values. ## 0.24.0-rc2 - 2022-10-18 - Fixed a bug where imported and qualified record constructors used in constant expressions could fail to resolve. ## 0.24.0-rc1 - 2022-10-15 - Gleam can now compile Elixir files within a project's `src` directory. - The `<>` operator can now be used for string concatenation and for string prefix pattern matching. - Fixed a bug where TypeScript definitions may have incorrect type parameters. - New projects depend on `gleam_stdlib` v0.24. - New projects' GitHub Actions config specifies Erlang/OTP 25.1 and suggest Elixir 1.14.1. - If you attempt to use the method call syntax (`thing.method()`) on a value without that field the error message will now include a hint explaining that Gleam is not object oriented and does not have methods. - Fixed a bug in the formatter where multiple line documentation comments for custom type constructor fields could be formatted incorrectly. - Fixed a bug where tail call optimisation could be incorrectly applied when compiling to JavaScript in some situations. - Fixed a bug where the remainder operator would return NaN results when the right hand side was zero when compiling to JavaScript. - Fixed a bug where Elixir dependencies would fail to compile on Windows. - Fixed a bug where images added to HTML documentation via documentation comments would not have a max width. ## v0.23.0 - 2022-09-15 [Release Blog Post](https://gleam.run/news/gleam-v0.23-released/) ## v0.23.0-rc2 - 2022-09-15 - New Gleam projects are created using GitHub actions erlef/setup-beam@v1.13.0 and actions/checkout@v3.0.0. - New Gleam projects are created using version v0.23.0 of the stdlib. - Fixed a bug where LSP hovering would fail to locate the expression. ## v0.23.0-rc1 - 2022-09-01 - Gleam can now build dependency packages that are managed using Mix. - Compiler performance has been improved by buffering disc writing and by lazily loading TLS certs. In testing this doubles performance when compiling the standard library. - The `gleam publish` command now adds the `priv` directory and any `NOTICE` file to the tarball. - The `gleam update` command can now be used to update dependency packages to their latest versions. - Module functions with empty bodies are no longer syntax errors. - The format used by the formatter has been improved. - OpenSSL swapped out for RustTLS. - Generated HTML documentation now includes a search bar. - The LSP will now provide autocompletion for imports. - A helpful error message is now returned when assignments are missing either a keyword or a value. - Qualifiers are now used when multiple types have the same name in an error message. - In JavaScript, if an object has defined an `equals` method in its prototype, Gleam will now use this method when checking for equality. - Functions can now be defined and referenced in constant expressions. - An error is now raised if the record update syntax is used with a custom type that has multiple constructors. - An error is now raised if a module is imported multiple times. - Fixed a bug where defining a type named `CustomeType` would product invalid JavaScript. - Fixed a bug where defining a variable with the same name as an unqualified import would produce invalid JavaScript. - Fixed a bug where piping to `todo` would generate invalid Erlang code. - Fixed a bug where inspecting a JavaScript object with a null prototype would crash. - Fixed a bug where the formatter could crash if source code contained 3 or more empty lines in a row. - Fixed a bug where the formatter would remove braces from blocks used as the subject of a case expression. - Fixed a bug alternative patterns with a clause containing a pipe with a pipe after the case expression could render incorrect Erlang. - Fixed a bug where formatter would strip curly braces around case guards even when they are required to specify boolean precedence. - Fixed a bug where `gleam new` would in some situations not validate the target directory correctly. - Fixed a bug where pipes inside record update subjects could generate invalid Erlang. - Fixed a bug where pipes inside record access could generate invalid Erlang. ## v0.22.1 - 2022-06-27 - The `gleam publish` confirmation prompt now accepts both "Y" and "y". - Fixed a bug where `todo` would not emit the correct line number to the LSP while. ## v0.22.0 - 2022-06-12 [Release Blog Post](https://gleam.run/news/gleam-v0.22-released/) - New projects are created with `gleam_stdlib` v0.22. ## v0.22.0-rc1 - 2022-06-12 - Fixed a bug where doc comments would dissociate from their statements when generating html documentation. - You are now allowed to use named accessors on types with multiple constructors if the accessor's name, position and type match (among the constructors) (#1610). - Added the ability to replace a release up to one hour after it is published using `gleam publish --replace`. - `gleam publish`, `gleam docs publish`, `gleam docs remove`, `gleam hex retire`, and `gleam hex unretire` now have access to environment variables for username (default key `HEXPM_USER`) and password (default key `HEXPM_PASS`) - The `gleam publish` command gains the `-y/--yes` flag to disable the "are you sure" prompt. - Clear outdated files from the build directory after compilation. - Fixed a bug where immediately calling the value that a case expression evaluates to could generate invalid JavaScript. - Fixed a bug where the default project target is set to JavaScript, but the project would run on target Erlang instead. - The compiler is now able to generate TypeScript declaration files on target JavaScript (#1563). To enable this edit `gleam.toml` like so: ```toml [javascript] typescript_declarations = true ``` - Fixed a bug where argument labels were allowed for anonymous functions. - Fixed a bug where JavaScript code could be invalid if a variable is defined inside an anonymous function with a parameter with the same name as the variable. - Fixed a bug where importing a JavaScript function named "then" could produce invalid code. - Fixed a bug where constants that reference locally defined custom types could render invalid JavaScript. - The project generator will no longer permit use of the reserved `gleam_` prefix. - Generated HTML docs easter egg updated. - `gleam export erlang-shipment` can be used to create a directory of compiled Erlang bytecode that can be used as a deployment artefact to get your application live. - `gleam format` will now preserve (up to one) empty lines between consecutive comments, as well as between comments and any following expression - The deprecated rebar3 integration has been removed. - Fixed a bug where `gleam format` would output an unwanted newline at the top of documents that only contain simple `//` comments. - No longer add `dev_dependencies` to generated `.app` Erlang files unless we're compiling the root project (#1569). - Fixed a bug where the formatter could render a syntax error with lists on long unbreakable lines. - Fixed a bug where JavaScript variable names could be incorrectly reused. - Fixed a bug where `gleam format` would remove the braces around a tuple index access when accessing a field of the returned element. - Fixed a bug case clause guards could render incorrect JavaScript if a variable name was rebinded in the clause body. - The `gleam compile-package` command no longer generates a `.app` file. This should now be done by the build tool that calls this command as it is responsible for handling dependencies. - Fixed a bug where piping a list tail would create invalid Erlang code (#1656). ## v0.21.0 - 2022-04-24 [Release Blog Post](https://gleam.run/news/v0.21-introducing-the-gleam-language-server/) - New projects are created with `gleam_stdlib` v0.21. ## v0.21.0-rc2 - 2022-04-20 - Added the ability to replace a release up to one hour after it is published using `gleam publish --replace`. - The language server will now enter a degraded mode that only performs formatting if running in a directory that is not a Gleam project with a `gleam.toml`. ## v0.21.0-rc1 - 2022-04-16 - The Gleam language server is here! This will provide IDE like features for code editors that support LSP, including but not limited to VSCode, Neovim, Emacs, Eclipse, Visual Studio, and Atom. This first version includes these features: - Project compilation. - Inline errors and warnings. - Type information on hover. - Go-to definition. - Code formatting. - Fixed a bug in generated JavaScript code where functions named `then` would cause errors when dynamically imported. - Initialize `git` repo when creating a new project. - Log messages controlled with `GLEAM_LOG` now print to standard error. - Log message colours can be disabled by setting the `GLEAM_LOG_NOCOLOUR` environment variable. - You can now specify multiple packages when using `gleam add`. - Bools can now be negated with the `!` unary operator. - If the compiler version changes we now rebuild the project from scratch on next build command to avoid issues arising from reading metadata in an old format (#1547). - Updated the "Unknown label" error message to match other error messages (#1548). - Type holes are now permitted in function arguments and return annotations (#1519). - Unused module imports now emit a warning (#1553). - The error message for failing to parse a multiline clauses without curly braces has been improved with a hint on how to fix the issue (#1555). - The error messages for when rebar3 or Erlang are missing from the machine has been improved with a tip on how to install them (#1567). - Corrected the hint given with certain int and float binary operator type errors. - Add support for `int` and `float` bit string type when compiling to JavaScript. - Add support for specifying size of integers in a bit string. Supports only exact binaries, i.e. length is a multiple of 8. - Fixed compilation of rebar3 based dependencies on Windows. ## v0.20.1 - 2022-02-24 - The type checker has been improved to enable use of the record access syntax (`record.field`) in anonymous functions passed into higher order functions without additional annotations. ## v0.20.0 - 2022-02-23 [Release Blog Post](https://gleam.run/news/gleam-v0.20-released/) - New projects are created with `gleam_stdlib` v0.20. ## v0.20.0-rc1 - 2022-02-20 - Type unification errors involving user annotated types now refer to the names specified by the user instead of internal rigid-type ids. - The build tool now validates that listed licenses are valid SPDX expressions. - A WebAssembly version of the compile is now available for use in JavaScript and other WebAssembly environments. - New projects include Hex badges and a link to Hexdocs. - Enhance type mismatch errors in the presence of try. - Enhance type mismatch error for an inconsistent try. - Enhance type mismatch error for pipe expressions to show the whole pipeline and not only its first line. - Fixed a bug where sometimes type variable could be reused result in incorrect non-deterministic type errors. - Built in support for the Mix build tool has been removed. The `mix_gleam` plugin is to be used instead. - Introduce a limited form of exhaustiveness checking for pattern matching of custom types, which only checks that all constructor tags are covered at the top level of patterns. - The `ebin` directory is now copied to the build directory for rebar3 managed dependencies if present before compilation. - The format used by the formatter has been improved. - Package names in `gleam.toml` are validated when the config is read. - The `priv` directory is linked into the build directory for Gleam projects managed by the build tool. - Fixed a bug where type errors from pipes could show incorrect information. - Fixed a bug where types could not be imported if they had the same name as a value in the prelude. ## v0.19.0 - 2022-01-12 Dedicated to the memory of Muhammad Shaheer, a good and caring man. [Release Blog Post](https://gleam.run/news/gleam-v0.19-released/) ## v0.19.0-rc4 - 2022-01-10 - New projects are created with `gleam_stdlib` v0.19 and `gleeunit` v0.6. - Fixed a bug where external functions could be specified with the wrong module name in generated Erlang when imported from a nested module in another package. - Fixed a bug where warnings wouldn't get printed. ## v0.19.0-rc3 - 2022-01-07 - Fixed a bug where precompiled packages would fail to compile due to Erlang files being compiled twice concurrently. ## v0.19.0-rc2 - 2022-01-06 - Erlang modules are now compiled in a multi-core fashion. - New projects are created with `erlef/setup-beam` v1.9.0 instead of `gleam-lang/setup-erlang` and `gleam-lang/setup-gleam`. - Fixed a bug where tail call optimisation could generate incorrect code when the function has argument names that are JavaScript keywords. - Fixed a bug where the build would continue when dependency packages failed to compile. - Fixed a bug where `include` directories would not be accessible by the Erlang compiler during Gleam compilation. ## v0.19.0-rc1 - 2022-01-03 - The build tool now supports the JavaScript target. The target can be specified in either `gleam.toml` or using the `--target` flag. - The `gleam check` command has been introduced for rapidly verifying the types of Gleam code without performing codegen. - `true` and `false` can no longer be used as pattern matching variables, to avoid accidental uses of incorrect syntax that is popular in other languages. An error will hint about using Gleam's `True` and `False` values instead. - You can now remove build artifacts using the new `gleam clean` command. - The `compile-package` can now generate `package.app` files and compile source modules to `.beam` bytecode files. - The flags that `compile-package` accepts have changed. - Published Hex packages now include precompiled Erlang files. - Erlang record headers are now written to the `include` directory within the package build directory. - The format used by the formatter has been improved. - Fixed a bug where tail recursion could sometimes generated incorrect JavaScript code. - Performance of code generators has been slightly improved. - Allow the record in a record expansion to be an expression that returns a record. - Fixed a bug where external function module names would not be escaped correctly if they contained special characters and were assigned to a variable. - A helpful error message is shown if Erlang is not installed. ## v0.18.2 - 2021-12-12 - Erlang applications are now automatically started when the VM is started by `gleam run` and `gleam test`. ## v0.18.1 - 2021-12-12 - Fixed a bug where pipe expressions in record updates and operator expressions could generate incorrect Erlang code. - The `priv` directory is now copied to the output directory for rebar3 packages prior to compilation. This is required for some packages to compile. - Fixed a bug where deps that fail to compile would be skipped when compilation would next be attempted, resulting the project being in an invalid state. ## v0.18.0 - 2021-12-06 [Release Blog Post](https://gleam.run/news/gleam-v0.18-released/) - New projects now include `gleeunit`. ## v0.18.0-rc3 - 2021-12-05 - URL format in gleam.toml is now validated. - The `gleam deps list` command has been added. - Fixed a bug where changing requirements in `gleam.toml` would not cause deps to be re-resolved. - Fixed a bug where locked deps would cause incompatible package requirements to be discarded. - Development dependencies are now included in the applications listed in the generated OTP `.app` file. - `gleam.toml` now includes an `erlang.extra_applications` key to specify extra OTP applications that need to be started. ## v0.18.0-rc2 - 2021-11-26 - Fixed a bug where OTP .app files would be generated with invalid syntax. - Removed extra whitespace from newly generated projects. ## v0.18.0-rc1 - 2021-11-25 - Gleam can now compile Gleam projects. - Gleam can now run tests with the `gleam eunit` command. - Gleam can now run programs with the `gleam run` command. - Gleam can now run an Erlang shell with the `gleam shell` command. - Gleam can now resolve package versions for a Gleam project's dependency tree. - Gleam can now download Hex packages. - Gleam can now build dependency packages that are managed using Gleam or rebar3. - Gleam is now the default build tool for new projects. - The template names for `gleam new` have been changed. - Fixed a bug where the error message for a record update with an unknown field would point to all the fields rather than the unknown one. - Improved styling for inline code in generated documentation. - New projects use v0.18 of the stdlib. ## v0.17.0 - 2021-09-20 [Release Blog Post](https://gleam.run/news/gleam-v0.17-released/) - Functions now get special handling when being printed from JavaScript. ## v0.17.0-rc2 - 2021-09-19 - Errors thrown when no case clause or assignment pattern matches the subject value now include more debugging information when targeting JavaScript. - New projects are generated using `gleam_stdlib` v0.17.1. ## v0.17.0-rc1 - 2021-09-11 - Redesigned the Gleam prelude to be a module of core classes when compiling to JavaScript. This improves the resulting generated code and makes debugging and interop easier. - Projects without rebar3 can be generated using the `gleam-lib` template. - JavaScript modules are imported using a camel case variable name to avoid name collisions with variables. - Pipelines now use assignments in the generated code in order to preserve the order of any side effects. - Fixed a bug where the compiler would crash rather than raise an error if a project contained a single module and attempted to import another. - Special variable naming has been made more consistent in rendered Erlang and JavaScript. - Conditional compilation can now be used to have different code within a module when compiling to a specific target. - Fixed a bug where `todo` caused values not to be returned in JavaScript. - Fixed a bug where multiple discarded function arguments generated invalid JavaScript. - Fixed a bug where using JavaScript reserved words as function argument names caused generated invalid JavaScript. - Fixed a bug where a case expression of just a catch-all pattern generated invalid JavaScript. - Fixed a bug where the formatter would incorrectly render extra newlines below try expressions. - Fixed a bug where tail recursive functions with arguments with the same name as JavaScript reserved words generated the wrong JavaScript. - Fixed a bug where list equality would be incorrectly reported in JavaScript. - Multiple subjects are now supported for case expressions in JavaScript. - Fixed a bug where matching using a Bool or Nil literal as the subject for a case expression would produce invalid code when compiling to JavaScript. - Unsupported feature error messages now include file path and line numbers for debugging. - Bit string literals with no segment options or just the `bit_string`, `utf8` or `utf8_codepoint` options can be constructed when compiling to JavaScript. - The format of generated JavaScript has been improved. - Fixed a bug where rendered JavaScript incorrectly incremented variables when reassigned in patterns. - Added `eval` and `arguments` to JavaScript reserved words. - Support for the deprecated `tuple(x, y, ...)` syntax has been removed in favor of the more concise (`#(x, y, ...)`). Use `gleam format` with the previous version of the compiler to auto-migrate. - New OTP projects are generated using `gleam_otp` v0.1.6. - Fixed a bug where the equality operators could return the incorrect value for records when compiling to JavaScript. - Fixed a bug where `todo` could sometimes render invalid JavaScript when used as an expression in the generated code. - An error is now emitted if the list spread syntax is used with no prepended elements `[..xs]`. - Fixed a bug where type errors inside piped expressions would be incorrectly be reported as being an incorrect usage of the pipe operator. - Gleam modules with no public exports no longer render private members in Erlang. - Fixed a bug where discard variables used in assert assignments would generate invalid Erlang code. - Fixed a bug where some expressions as case subjects would generate invalid JavaScript code. - Fixed a bug where some assignments as the final expression in a function would not return the correct value in JavaScript. - Gleam packages imported in JavaScript now have the path prefix `gleam-packages`. This can be served from your web server or aliased in your `package.json` for NodeJS projects. - Fixed a bug where the type checker would fail to generalise some type variables, causing module metadata writing to fail. - Fixed a bug where tail call optimisation when compiling to JavaScript could result in incorrect code. - Fixed a bug where variable names could be rendered incorrectly in closures. - An error is now emitted if alternative patterns fail to define all the variables defined by the first pattern. - New projects are generated using `gleam_stdlib` v0.17.0. - New projects are generated using `gleam_otp` v0.2.0. ## v0.16.1 - 2021-06-21 - Values which are being imported more than once in an unqualified fashion now cause an error to be reported. - Argument docs for custom type constructors are now rendered in the HTML documentation. - Patterns can be used with `try` expressions when compiling to JavaScript. - Types and record constructors can now be aliased with an uppercase name when imported. Aliasing them with a lowercase name is no longer permitted. - Fixed a bug where nested import paths could be rendered incorrectly in JavaScript. ## v0.16.0 - 2021-06-17 [Release Blog Post](https://gleam.run/news/gleam-v0.16-released/) ## v0.16.0-rc4 - 2021-06-17 - Fixed a bug where if a JavaScript global function was imported as an external function with the same name the generated code would diverge. ## v0.16.0-rc3 - 2021-06-17 - New projects are generated using `gleam_stdlib` v0.16.0. ## v0.16.0-rc2 - 2021-06-08 - Gleam now supports alternative patterns in case expressions for the JavaScript target. - The `gleam` prelude module can now be imported when compiling to JavaScript. - Fixed a bug where the prelude module could not be imported when using the old build compiler API. - Fixed a bug where if a JavaScript global function was imported as an external function with the same name the generated code would diverge. - Type error messages coming from pipe usage have been improved. ## v0.16.0-rc1 - 2021-06-04 - Gleam can now compile to JavaScript! Specify the `--target javascript` flag to `gleam compile-package` to use it today. - A compile time error is now raised when multiple module level constants with the same name are defined. - Fixed a bug where declaring a type constructor using reserved erlang keyword in its fields results in invalid erlang code being generated. - Fixed a bug where calling a function with discarded labelled arguments incorrectly results in a compile error. - Fixed a bug where assert statements return the wrong value. - The `gleam new` command requires a root folder param, project name is optional and if not provided the project name will be inferred from the folder name. - Generated Erlang record header files now contain Erlang type information. - New OTP application projects depend on `gleam_otp` v0.1.5. - The output of the formatter has been improved. ## v0.15.1 - 2021-05-07 - Fixed a bug where blocks that contained try expressions could be formatted incorrectly. ## v0.15.0 - 2021-05-06 [Release Blog Post](https://gleam.run/news/gleam-v0.15-released/) ## v0.15.0-rc1 - 2021-05-05 - Syntax highlighting of Gleam code in generated HTML documentation has been improved. - Fixed a bug where markdown tables in rendered HTML documentation would have the incorrect background colour on every other row. - Tuples now have a new, concise syntax variant: `#(x, y, ...)`. Existing code can be auto-migrated to the new syntax by running `gleam format`. - Fixed a bug where customt type constructors with Erlang keywords as names would generate invalid Erlang code. - Gleam now supports `\e` string escapes. - Values and types from the prelude can now be used in a qualified fashion by importing the `gleam` module. - Empty lists can now be used in constants. - Compiler performance has been improved when working with lists. - Compiler performance has been improved when working with sequences of expressions. - Assignments using `let` and `assert` are now expressions and no longer require a following expression in their containing block. They are now themselves expressions. - Fixed a bug where tuple indexing could incorrectly claim a tuple is not of type tuple in some circumstances. - Glean `new` command now checks if target folder exists, if so it returns an error. - A compile time error is now raised if a module is defined with the name `gleam`. - A compile time error is now raised if a module is defined with the a keyword in the name. - New projects are generated using `gleam_stdlib` v0.15.0. - New projects are generated at v0.1.0. ## v0.14.4 - 2021-03-27 - The Gleam compiler has been updated to compile with the new Rust v1.51.0. - New project's `gleam.toml` has a comment that shows how to add a `repository` field. - New projects no longer include a licence field in `src/$APP.app.src` by default. ## v0.14.3 - 2021-03-20 - Added an error hint when joining string using the `+` or `+.` operator. - New projects are created with `setup-erlang` v1.1.2 and Erlang/OTP v23.2. - Fixed a bug where the compiler would be unable to locate an imported module if a value from a nested module is used in a qualified fashion. ## v0.14.2 - 2021-03-02 - Project names can now contain numbers. ## v0.14.1 - 2021-02-27 - The error message for binary operators has been given more detail and hints. - Fixed a bug where alternative patterns would incorrectly report unused variables. - Fixed a bug where private types shadowed shadowed by values would incorrectly report unused variables. ## v0.14.0 - 2021-02-18 [Release Blog Post](https://gleam.run/news/gleam-v0.14-released/) ## v0.14.0-rc2 - 2021-02-18 - New projects are created with `gleam_stdlib` v0.14.0. ## v0.14.0-rc1 - 2021-02-14 - Gleam now generates Erlang typespecs. - New projects no longer include a licence file by default. - New projects can be created using the new `escript` template to generate a command line tool style program. - A warning is emitted when a literal value is constructed but not used. - Automatically generate a link to repository in docs if available. - Code in HTML documentation is has highlighted syntax. - Gleam now only supports `\r`, `\n`, `\t`, `\"`, and `\\` string escapes. - A set of OCI container images are built automatically for each release. - New compile time checks for invalid bit string literals and patterns have been added. - The error messages for syntax errors in names have been improved. - Fixed a bug where the repo URL would render incorrectly in HTML docs. - Fixed a bug where piping a block can render invalid Erlang. - New compile time warnings on unused types, functions and variables. - The runtime error emitted by the `todo` keyword now carries additional information. - The runtime error emitted by the `assert` keyword now carries additional information. - Fixed a bug where bit string patterns would not correctly unify with the subject being pattern matches on. - Documentation dark mode. - Fixed a bug where some app.src properties were incorrectly named. - `--warnings-as-errors` flag added to `gleam build` command. ## v0.13.2 - 2021-01-14 - `ring` dep upgraded to enable compilation on Apple M1 ARM processors. ## v0.13.1 - 2021-01-13 - Fix off-by-one error in message messages. ## v0.13.0 - 2021-01-13 [Release Blog Post](https://gleam.run/news/gleam-v0.13-released/) - New Gleam projects use stdlib v0.13.0. ## v0.13.0-rc2 - 2021-01-12 - The `version` property in `gleam.toml` is now optional again. ## v0.13.0-rc1 - 2021-01-09 - Variable names now only have 1st letter capitalized when converted to erlang. - Records defined in other modules can now be used in module constants. - Documentation can link from functions, types & constants to their source code definitions on popular project hosting sites. - Documentation hosted on HexDocs now has a version selector. - Fixed a bug where the `app` project template rendered invalid code. - Newly generated projects use stdlib v0.12.0. - Named subexpressions in patterns now render correct Erlang. - The anonymous function syntax now successfully parses with whitespace between `fn` and `(`. - Fixed a bug where the formatter would incorrectly remove blocks around some binary operators. - Constants can now be defined after they are used in functions - The parser has been rewritten from scratch, dramatically improving error messages and compilation times. - `1-1` and `a-1` are now parsed as `1 - 1` and `a - 1` - Further information has been added to the error messages when a function returns the wrong type. - Further information has been added to the error messages when case clauses return different types. - Fixed a bug where imported record constructors without labels used as an anonymous function generates incorrect Erlang. ## v0.12.1 - 2020-11-15 - The compiler can now discriminate between record access and module access for shadowed names - The `new` command will no longer permit projects to be made with names that clash with Erlang standard library modules. - The formatter now correctly treats lines of only whitespace as empty. - The styling of tables in rendered HTML documentation has been improved. - Rendered HTML documentation has regained its max-width styling. ## v0.12.0 - 2020-10-31 [Release Blog Post](https://gleam.run/news/gleam-v0.12-and-gleam-otp-v0.1-released/) ## v0.12.0-rc4 - 2020-10-31 - The rendered module documentation sidebar can now scroll independently to the page. - Application projects now have the correct `mod` value in the generated `.app.src`. - Records without fields can now be used in module constants. - New application projects are now created used Gleam's type safe OTP pulled from Hex. ## v0.12.0-rc3 - 2020-10-24 ## v0.12.0-rc2 - 2020-10-24 ## v0.12.0-rc1 - 2020-10-24 - The utf8, utf16, and utf32 type specifiers are now only available in bit string construction, matching must be done with the codepoint versions. - Functions may now be called before they are defined in a module. This enabled mutually recursive functions! - Discarded variable names may now include numbers. - Fixed a bug where discarded variables might generate incorrect Erlang. - Added support tuple access in clause guards. - New projects are created with version 1.0.2 of the setup-gleam GitHub action. - New application projects are now created used Gleam's type safe OTP. - Comments are now correctly handled on platforms that use \r\n line endings, such as Windows. ## v0.11.2 - 2020-09-01 - Fixed a bug where an imported constructor would emit an unused constructor warning when only used in pattern matching. ## v0.11.1 - 2020-08-31 - The formatter style has been improved to render function type arguments on a single line when possible, even if the return type will not fit on a single line. - The format for printed types in error messages has been improved. - Fixed a bug where the formatter would strip a constructor pattern spread when no fields are given. - Fixed a bug where assigning the result of a block to a variable would generate incorrect Erlang. - The formatter style has been improved for function calls that take a single block as an argument. - Reserved words are no longer incorrectly permitted as project names. ## v0.11.0 - 2020-08-28 [Release Blog Post](https://lpil.uk/blog/gleam-v0.11-released/) ## v0.11.0-rc3 - 2020-08-27 - Bit strings now support non-literal strings as segment values. - Fixed a bug where Erlang variables could be generated with incorrect names when defining an anonymous function. ## v0.11.0-rc2 - 2020-08-24 - The formatter style has been improved to render some single argument calls in a more compact style. ## v0.11.0-rc1 - 2020-08-22 - Field access now works before the custom type is defined. - The error message returned by the compiler when the user tries to use unknown labelled arguments now handles multiple labels at once, and does not suggest labels they have already supplied. - The formatter style has been improved to use a trailing comma on imports broken over multiple lines. - The formatter style has been improved to wrap lists and bit strings over as few lines as possible if the elements are Ints, Floats, or Strings. - The formatter style has been improved to preserve comments on labelled call arguments. - The formatter style has been improved to preserve empty lines in assignments. - The performance of the formatter has been improved. - Records can be updated using the spread syntax. A warning is emitted if no fields are updated when using this syntax. - Fixed a bug where type parameters can leak between different type definitions in a module. - Markdown tables, footnotes, strikethroughs, and tasklists are now supported in documentation. - Fixed a bug where generic types may be incorrectly unified. - Ints and floats can now be written with underscores for clarity. - The warning for a `todo` now includes the required type of the not-yet-implemented expression. - Holes can be used in type annotations to specify part of a type, leaving the rest for inference. - The incorrect arity error now prints any missing labelled arguments. - Fixed a bug where Erlang variables could be generated with incorrect names when directly calling an anonymous function. - A warning is emitted when a type is imported or created but not used. - Fixed a bug where Erlang variables names could clash when rebinding variables while similarly named variables ending in a number are in scope. - Fixed a bug in the pretty printer which prevented the formatter from rendering sub-expressions in a single line when later code would not fit on the same line. - The formatter style has been improved to render some single argument calls in a more compact style. - Gleam now supports hex, octal, and binary literals. - Rebar3 hex packages now include `gleam.toml` and `gen`. - Newly generated projects use stdlib v0.11.0. ## v0.10.1 - 2020-07-15 - Fixed a bug where the compiler failed to return an error when type checking a tuple with the wrong arity in a pattern. - The error message for a duplicate module member now shows the location of both definitions. - Fix compiler bug where labelled arguments were being reordered incorrectly. ## v0.10.0 - 2020-07-01 [Release Blog Post](https://lpil.uk/blog/gleam-v0.10-released/) - Newly generated projects use stdlib v0.10.1. - Fixed a bug where discards inside bit string patterns generated invalid code. ## v0.10.0-rc2 - 2020-06-30 - Fixed a bug where variables names would be incorrectly generated when using alternative patterns. ## v0.10.0-rc1 - 2020-06-29 - Single letter module names are now permitted. - Added support for bit string syntax. - Support for the deprecated list prepend syntax has been removed. - Added module level constants that are inlined at compile time. - Public module level constants generate documentation. - The formatter style has been improved to wrap and sort imports. - The formatter now permits comments at the end of module function bodies. - The formatter now skips files that match patterns defined in ignore files such as .gitignore and .ignore. - Error message diagnostic code previews for type errors when using the the pipe operator have been made more accurate. - Added support for list literals in clause guards. - Fixed bug when reassigning a variable inside a case clause with alternative patterns. - Todos can now take an optional label. ## v0.9.1 - 2020-06-12 - Fixed a bug where binary operators may lose required `{ }`s when formatted. ## v0.9.0 - 2020-06-01 [Release Blog Post](https://lpil.uk/blog/gleam-v0.9-released/) - Newly generated projects use stdlib v0.9.0. - Additional information is printed to the console when generating HTML documentation from Gleam code. - Fixed a bug where blocks on either side of a binary operator would be rendered without `{ }`. ## v0.9.0-rc1 - 2020-05-26 - The formatter style has been improved. - Numbers are now permitted in module names. - Emitted Erlang code correctly adds parentheses around binary subexpressions to preserve precedence. - Record names and fields are now escaped in `.hrl` files if they conflict with Erlang reserved words - Annotations are now supported on `let` and `assert` expressions - Formatter now accepts comments for the fields of a custom type's constructors - Added opaque custom types, which have constructors that cannot be accessed from outside their own modules. - Additional (arbitrary) markdown documentation pages can now be added and built with `docs build`. - Fix code generation when calling functions returned through either record or tuple access - Add lookup for Gleam source code in Mix's `deps` directory. - Newly generated Gleam projects use the GitHub action `gleam-lang/setup-erlang` v1.1.0. - Added support for custom type record literals in guards. - Type variables are now correctly preserved within nested scopes. ## v0.8.1 - 2020-05-19 - The formatter now correctly handles unicode comments. ## v0.8.0 - 2020-05-07 [Release Blog Post](https://lpil.uk/blog/gleam-v0.8-released/) - The `docs build`, `docs publish`, and `docs remove` commands can be used to compile HTML documentation locally, publish them to HexDocs, and remove them from HexDocs respectively. - Type error reporting has been improved when using the pipe operator. - Newly generated projects use stdlib v0.8.0. - The compiler can now emit warnings. Currently there are warnings for using the old '|' syntax in lists and for todos. - Will give a clearer error when a function given as an argument to another function doesn't match the type of the parameter. - Fixed bug where imported type constructors had the incorrect arity. - Fixed bug where a doing an unqualified import of a type constructor and giving it an alias would use the wrong name if it contained any values. - Fixed a bug trying to access an imported constructor which contained values. - Fixed a compiler crash that occurred when trying to unify a tuple with something other than another tuple or a variable. - Added support for tuple literals in guards. ## v0.8.0-rc1 - 2020-04-28 - Strings are now encoded as utf8 binaries in the generated Erlang. - HTML documentation can now be generated from Gleam code by running `gleam build --doc`. - Gleam code can be formatted using the `gleam format` command. - The pipe operator `|>` will now attempt to insert the left hand side as the first argument to the right hand side if the right hand side is a call, removing the need for function capture boilerplate. - A `record.label` syntax can now be used to access the fields of a custom type that have a single record variant. - Anonymous functions can now have return type annotations. - There is a `todo` keyword for type checking functions that have not yet been implemented. - Tuples can be indexed into using the `var.1` syntax. - `>`, `>=`, `<`, and `<=` operators are now supported in case clause guards and can be used to check the ordering of integers. - `>.`, `>=.`, `<.`, and `<=.` operators are now supported in case clause guards and can be used to check the ordering of floats. - The list prepend syntax is now `[x, ..y]`. The old `[x | y]` syntax is deprecated but will continue to work for now. The formatter will rewrite the old syntax to the new. - Add new assert syntax for binding variables `assert Ok(x) = result`. In the future this will allow you to use a pattern that does not match all values. - Added support for int and float literals in guards. - Color codes are now only emitted in error output for interactive terminal sessions. - Added a new `..` syntax for discarding the remaining fields of a record. - Using the same variable name multiple times in the same pattern will now raise an error. - Discard can now be omitted in list tails in patterns, ie `[x, ..]` is the same as `[x, .._]`. The former is the preferred version and is emitted by the formatter. ## v0.7.1 - 2020-03-03 - Projects generated with `gleam new` use `stdlib` version 0.7.0. ## v0.7.0 - 2020-03-01 [Release Blog Post](https://lpil.uk/blog/gleam-v0.7-released/) ## v0.7.0-rc1 - 2020-02-28 - Type aliases can be defined to give concise names to frequently used types. - Case expression clauses may have guards which can be used to require equality between specified variables in order for the clause to match. - Case expression clauses may have alternative patterns, enabling one clause to match for multiple different possible patterns. - Types may now be used before they are defined within their defining module. - Fixed a bug where import paths would not be correctly resolved on Windows. - Added job to create precompiled binary for 64-bit Windows when releasing. - `gleam new` now creates a project that uses `actions/checkout@v2.0.0` in its GitHub actions workflow. - Labelled argument in functions may now be discarded by prefixing the name with an underscore, like unlabelled arguments. - Sub-patterns can have names assigned to them within a pattern using the `as` keyword. - The format of compiler error messages printed to the console has been improved by upgrading to a newer version of the codespan-reporting library. - Type variables in the given and expected types will now be printed with the same name in type error messages if they are equivalent. - A friendly error message is rendered when a case expression clause has the incorrect number of patterns for the subjects. - A friendly error message is rendered when a .gleam file cannot be read. - A friendly error message is rendered when the `gleam new` command fails to write the new project to the file system. - A friendly error message is rendered when there is a cycle formed by module imports. - Top level types are now printed in error messages for type parameter mismatches. - The `gen` directory is now deleted before each compilation. - `gleam new` now includes installation instructions for Hex packages in the generated README. - `gleam new` now accepts a `--description` flag for including a description of the project in the README and `.app.src` file. - Fixed a bug where variable names would be incorrectly generated in some situations when variable names are reused during and after a case expression. - Performance of the Erlang code generator has been improved by removing some vector allocations. - An error is emitted when multiple types with the same name are defined in or imported into a module. ## v0.6.0 - 2019-12-25 🎄 [Release Blog Post](https://lpil.uk/blog/gleam-v0.6-released/) - Function capture syntax now supports labelled arguments. ## v0.6.0-rc1 - 2019-12-23 - Syntax for defining structs and enums have been unified into a singular custom type definition statement. Instances of these custom types are called records. - Anonymous structs have been renamed tuples. - Values and types can be given a new name when imported in the unqualified fashion using the `import mod.{value as name}` syntax. - An error will be emitted if multiple values constructors are defined with the same name in a module. ## v0.5.1 - 2019-12-23 - Fixed a bug where invalid Erlang would be generated when using a local private function as a value. ## v0.5.0 - 2019-12-16 [Release Blog Post](https://lpil.uk/blog/gleam-v0.5-released/) - Enum constructor arguments can now be labelled, allowing arguments to be given by name at the call site. - An Erlang header file with a record definition is generated for each Gleam struct defined. - `gleam new` creates a project at v1.0.0. - Function calls are now properly escaped when the function name conflicts with an Erlang keyword. - References to unqualified imported functions now generate correct Erlang code. - Fixed a bug where variable rebinding would generate incorrect code in some case expressions. - Fixed a bug where variable rebinding of function arguments would generate incorrect code. ## v0.5.0-rc1 - 2019-11-26 - Function arguments can be labelled, allowing arguments to be given by name at the call site. - `case` expressions now accept multiple subjects, enabling pattern matching on multiple values simultaneously. - Values and types can be imported from modules and references in an unqualified fashion. - Named structs now have their name as the first element in the generated Erlang code. This enabled easier use from Erlang by defining records for them, as well as slightly clearer printf debugging. - Anonymous structs have been introduced, serving as a quick and generic alternative to declared structs and as a format for interop with Erlang tuples. - `gleam new` now accepts a `--template` flag to generate different styles of project. An OTP application template has been added alongside the existing OTP library template. - `gleam new` now creates configuration for GitHub Actions, making Gleam projects ready for continuous integration out of the box. - The syntax for defining enums, case expressions, and blocks has been changed to a syntax closer to that found in the C family of languages. - The source code preview for functions that return a type incompatible with the functions annotations has been improved to be more precise. - A helpful error message is rendered if an enum field contains a generic type that has not been declared. - A bug has been fixed in which type mismatch errors originating from pattern matching would sometimes display the incorrect expected type. ## v0.4.2 - 2019-10-22 - Fixed a crash when an incorrect number of labelled struct arguments are given. - Fixed a struct labelled argument being incorrect reported as already given. ## v0.4.1 - 2019-09-29 - Struct types with parameterised fields are now registered with the correct number of type parameters. ## v0.4.0 - 2019-09-19 [Release Blog Post](https://lpil.uk/blog/gleam-v0.4-released/) - The struct data type has be introduced. Structs are pre-declared user defined data types with named fields and constant access time. - The map and tuple data types has been removed, replaced by the struct data type. - The generated code no longer contains export statements if no functions are exported from a module. - Comparison operators have been specialised to operate only on Ints. - The `>.` `>=.` `<.` and `<=.` comparison operators have been added for comparing Floats. - It is now an error to export an enum which has a constructor that takes a private type as an argument. - The error messages for defining multiple modules with the same name and for importing test modules into application code have been improved. - Numbers are now permitted in type names and constructors. - The `Nil` constructor will no longer erroneously be of type `Int`. ## v0.3.0 - 2019-08-08 [Release Blog Post](https://lpil.uk/blog/gleam-v0.3-released/) - New project structure can be generated with the `gleam new` command. - Functions can be annotated with their argument and return types. This may be used to restrict the function to a less general type than inferred by the compiler, or purely for documentation purposes. - External function names and their target functions are now escaped in the generated code if they collide with Erlang keywords such as `catch` or `or`. - Type error arising from the arguments of function calls have more accurate error diagnostics. - Precompiled Gleam binaries are now available on the GitHub release page. - Precompiled Docker images containing the Gleam binary are now available on DockerHub. - The formatting of the Erlang code rendered by the compiler has been altered to improve legibility. - A helpful error message is now rendered if the shorthand anonymous function syntax is used with too many underscores. - A helpful error message is now rendered when attempting to import an unknown module. ## v0.2.0 - 2019-06-25 - Modules can now live within namespaces such as `my_app/user/profile`. - The name of the variable created can be specified when importing a module using the `import my_mod as name` syntax. - Function names and atoms are now escaped in the generated code if they collide with Erlang keywords such as `catch` or `or`. - There is a shorthand syntax for prepending multiple elements to a list. `[1, 2, 3 | my_list]` ## v0.1.2 - 2019-05-12 - Types containing more than 26 type variables will no longer render with invalid type variable names. - Types in error messages no longer have extra indentation that increases as the type gets larger. - There is a new type `Nil` which is occupied by a single value (`Nil`). This type is used to represent the absence of a value and is commonly used with `Result` to model a value that is either present (`Ok(value)`) or absent (`Error(Nil)`). - Zero arity enum constructors now generate the correct Erlang when used in modules other than the one they are defined in. ## v0.1.1 - 2019-04-28 - Error messages now display the path of the file containing the problem. - Maps and modules with erroneous extra fields now have a custom error message. - Rows with tails that are unbound type variables are now correctly unified in the type system. This fixes a bug in which maps and modules may sometimes fail to type check when there is no error. ## v0.1.0 - 2019-04-15 [Release Blog Post](https://lpil.uk/blog/hello-gleam/) - Initial release! ================================================ FILE: changelog/v1.10.md ================================================ # Changelog ## v1.10.0 - 2025-04-14 ### Bug fixes - Fixed a bug where the code action to unqualify types and values would add an unqualified import even if it was already imported. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where numbers starting with `0x_`, `0o_` and `0b_` would cause a syntax error when compiling to JavaScript. ([Surya Rose](https://github.com/GearsDatapacks)) ## v1.10.0-rc1 - 2025-04-05 ### Compiler - On the JavaScript target, bit arrays can now use the `unit` option to control the units of the `size` option. ([Surya Rose](https://github.com/GearsDatapacks)) - The compiler can now tell if string branches are unreachable. For example, the following code: ```gleam case a_string { "Hello, " <> name -> name "Hello, Jak" -> "Jak" _ -> "Stranger" } ``` Will raise the following warning: ``` warning: Unreachable case clause ┌─ /src/greet.gleam:7:5 │ 7 │ "Hello, Jak" -> "Jak" │ ^^^^^^^^^^^^^^^^^^^^^ This case clause cannot be reached as a previous clause matches the same values. Hint: It can be safely removed. ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - On the JavaScript target, blocks and various other expressions no longer compile to immediately invoked function expressions. ([Surya Rose](https://github.com/GearsDatapacks)) - On the JavaScript target, bit arrays can now use 16-bit floats in expressions and patterns. ([Richard Viney](https://github.com/richard-viney)) - Improved the error message for unknown and missing target names in the `@target` attribute. ([Alexander Keleschovsky](https://github.com/AlecGhost)) - The compiler now uses a call graph for detecting unused types and values. This means that among other things, it can now detect unused recursive functions. For example: ```gleam // warning: unused fn some_recursive_function() { some_recursive_function() } ``` ([Surya Rose](https://github.com/GearsDatapacks)) - The compiler now emits a warning when using `let assert` to assert a value whose variant has already been inferred. For example: ```gleam // warning: This will always crash let assert Ok(_) = Error("Some error") ``` ([Surya Rose](https://github.com/GearsDatapacks)) - It is now possible to omit the `:float` option for literal floats used in a `BitArray` segment. ```gleam <<1.11>> ``` Is the same as: ```gleam <<1.11:float>> ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Compilation of binary operators is now fault tolerant and won't stop at the first type error. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The compiler now provides a better error message when using the wrong operator to try and join two strings together. For example: ```txt error: Type mismatch ┌─ /src/wibble.gleam:2:13 │ 2 │ "Hello, " + "Lucy" │ ^ Use <> instead The + operator can only be used on Ints. To join two strings together you can use the <> operator. ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The compiler now provides a better error message when using an Int operator on Float values, suggesting the correct replacement. For example: ```txt error: Type mismatch ┌─ /Users/giacomocavalieri/Desktop/prova/src/prova.gleam:2:7 │ 2 │ 1.0 + 2.0 │ ^ Use +. instead The + operator can only be used on Ints. ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The compiler now provides a better error message when using a Float operator on Int values, suggesting the correct replacement. For example: ```txt error: Type mismatch ┌─ /Users/giacomocavalieri/Desktop/prova/src/prova.gleam:2:5 │ 2 │ 1 >. 2 │ ^^ Use > instead The >. operator can only be used on Floats. ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The compiler no longer shows errors for a function's labels if the called function itself doesn't exist. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ### Build tool - Include a type annotation for the `main` function generated by `gleam new`. ([Drew Olson](https://github.com/drewolson)) - Two entry point scripts are now always generated by `gleam export erlang-shipment`: - `entrypoint.sh` for POSIX Shell - `entrypoint.ps1` for PowerShell ([Greg Burri](https://github.com/ummon)) - The `gleam export` command now takes a `package-information` option to export the project's `gleam.toml` as a JSON file. ([Rodrigo Álvarez](https://github.com/Papipo)) - Improved the error message when failing to encrypt or decrypt a local Hex API key. ([Samuel Cristobal](https://github.com/scristobal)) - The `HEXPM_USER` and `HEXPM_PASS` environment variables when running `gleam publish` have been deprecated in favour of `HEXPM_API_KEY`. ([Samuel Cristobal](https://github.com/scristobal)) - The "functions" and "constants" sections of generated HTML documentation have been merged into one "values" section. ([Sam Zanca](https://github.com/metruzanca)) ### Language server - The language server now allows renaming of functions, constants, custom type variants and custom types across modules. For example: ```gleam // wibble.gleam pub fn wibble() { wibble() //^ Trigger rename } // wobble.gleam import wibble pub fn main() { wibble.wibble() } ``` Becomes: ```gleam // wibble.gleam pub fn wobble() { wobble() } // wobble.gleam import wibble pub fn main() { wibble.wobble() } ``` ([Surya Rose](https://github.com/GearsDatapacks)) - The language server can now offer a code action to replace a `..` in a pattern with all the fields that are being ignored. For example triggering the code action on this spread: ```gleam pub type Pokemon { Pokemon(id: Int, name: String, moves: List(String)) } pub fn main() { let Pokemon(..) = todo // ^ If you put your cursor here } ``` Would generate the following code: ```gleam pub type Pokemon { Pokemon(id: Int, name: String, moves: List(String)) } pub fn main() { let Pokemon(id:, name:, moves:) = todo } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The function generated by the "Generate JSON encoder" code action has been slightly modified so that it will now fail to compile if the type has new fields added, ensuring the programmer remembers to re-run the code action. For example, for this type: ```gleam type Person { Person(name: String, age: Int) } ``` The following code used to be generated: ```gleam fn encode_person(person: Person) -> json.Json { json.object([ #("name", json.string(person.name)), #("age", json.int(person.age)), ]) } ``` But now, this code is generated: ```gleam fn encode_person(person: Person) -> json.Json { let Person(name:, age:) = person json.object([ #("name", json.string(name)), #("age", json.int(age)), ]) } ``` ([Surya Rose](https://github.com/GearsDatapacks)) - The language server now supports finding references to values and types, both within a module and across multiple modules. ([Surya Rose](https://github.com/GearsDatapacks)) - The language server now offers a code action to remove all `echo`s in a module. For example: ```gleam pub fn main() { [1, 2, 3] |> echo // ^^^^ If you put your cursor over here |> list.filter(int.is_even) |> echo } ``` Triggering the code action would remove all the `echo` pipeline steps: ```gleam pub fn main() { [1, 2, 3] |> list.filter(int.is_even) } ``` This also works with all the `echo`s used before an expression: ```gleam pub fn main() { echo 1 + 2 //^^^^^^^^^^ If hovering anywhere over here } ``` Triggering the code action would remove the `echo`: ```gleam pub fn main() { 1 + 2 } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server now offers a code action to replace a Float operator used on Int values with the correct operator. For example: ```gleam pub fn main() { 11 +. 1 //^^^^^^^ When hovering anywhere over here } ``` Triggering the code action would fix the compilation error by using the correct Int operator: ```gleam pub fn main() { 11 + 1 } ``` This also works the other way around: ```gleam pub fn main() { 1.1 + 10.0 //^^^^^^^^^^ If hovering anywhere over here } ``` Triggering the code action would replace the wrong operator with the correct equivalent Float operator: ```gleam pub fn main() { 1.1 +. 10.0 } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - If there's a compilation error because two strings are being joined with the wrong `+` operator (instead of using `<>`), the language server now offers a code action to fix the error automatically. For example: ```gleam pub fn main() { "Hello, " + "Jak" //^^^^^^^^^^^^^^^^^ When hovering anywhere over here } ``` Triggering the code action would fix the compilation error by using the correct `<>` operator instead of `+`: ```gleam pub fn main() { "Hello, " <> "Jak" } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server now offers the option to lift expressions into consts. For example, in two uses of the code action: ```gleam pub fn main() { [#("a", 0), #("b", 1), #("a", 2)] |> key_filter("a") } ``` Becomes: ```gleam const values = [#("a", 0), #("b", 1), #("a", 2)] const string = "a" pub fn main() { values |> key_filter(string) } ``` ([Matias Carlander](https://github.com/matiascr)) - The language server will now only offer the code action to generate a JSON encoder if the `gleam_json` package is installed as a dependency. ([Surya Rose](https://github.com/GearsDatapacks)) - The language server will offer to wrap assignment or case clause values in blocks. Useful when adding more expressions to an existing case clause or variable assignment. ```gleam pub fn f(pokemon_type: PokemonType) { case pokemon_type { Water -> soak() // ^^^^^^ selecting the right-hand side of the `->` in a clause Fire -> burn() } } ``` Becomes ```gleam pub fn f(pokemon_type: PokemonType) { case pokemon_type { Water -> { soak() } Fire -> burn() } } ``` ([Matias Carlander](https://github.com/matiascr)) - The "Generate function" code action now uses labels or variable names to improve the names of generated arguments. For example: ```gleam pub fn main() { let language = English greet(language, name: "Louis") } ``` Will generate the following function: ```gleam pub fn greet(language: String, name name: String) -> a { todo } ``` ([Surya Rose](https://github.com/GearsDatapacks)) - The "Rewrite from `use`" code action now only triggers if the cursor is on the first line of the `use` expression to rewrite. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ### Formatter ### Container images - Container images now contain Software Bill of Materials (SBoM) and SLSA Provenance information. ([Jonatan Männchen](https://github.com/maennchen)) ### Bug fixes - Fixed a bug where tuples with atoms in the first position could be incorrectly formatted by `echo`. ([Louis Pilfold](https://github.com/lpil)) - Fixed a bug where unlabelled arguments would be allowed after labelled arguments in variant constructor definitions. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where using the "Convert to pipe" code action on a function whose first argument is itself a pipe would result in invalid code. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where using the "Convert to pipe" code action on a function or record capture produces invalid code. ([Matias Carlander](https://github.com/matiascr)) - Fixed a bug where a temporarily moved or removed file does not get recompiled, even though its dependencies changed in the meanwhile. ([Sakari Bergen](http://github.com/sbergen)) - Fixed a bug where the "Inline variable" code action would not work properly if used inside a record update. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where variant inference wouldn't work on `let assert` assignments. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the build tool could fail to lock the build directory but not report an error. ([Louis Pilfold](https://github.com/lpil)) - Fixed a bug where the language server would be too eager to recompile modules when it could use the cache from previous compilations. ([Louis Pilfold](https://github.com/lpil)) - Fixed a bug where `let assert` would not assert that the given value matched the pattern if it was the only expression inside a block. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the code generated for `echo` on JavaScript could have name collisions if there are functions called `console` or `process`, or custom type variants called `Object` or `Deno` defined in the module. ([Louis Pilfold](https://github.com/lpil)) - Fixed a bug where the language server would stop working if the build directory was deleted e.g. as a result of `gleam clean`. ([Sakari Bergen](https://github.com/sbergen)) - Fixed a bug where the "Rewrite to pipe" code action could generate invalid code when the piped argument was a binary operation. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where prelude types and values would be suggested in autocomplete when part of a module select. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the check for multiple top-level modules when publishing would incorrectly print a warning. ([Surya Rose](https://github.com/GearsDatapacks)) ## v1.9.1 - 2025-03-10 ### Formatter - Improved the formatting of pipelines printed with `echo`. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ### Bug fixes - Fixed a bug where `echo` used before a pipeline would generate invalid code for the Erlang target. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ================================================ FILE: changelog/v1.11.md ================================================ # Changelog ## v1.11.0 - 2025-06-02 ### Bug fixes - Fixed a bug where using a pipe operator on the right-hand side of an `assert` statement would generate invalid code on the JavaScript target. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the build tool would try to run a module whose main function is private. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the compiler would sometimes warn that an assertion was unnecessary because it only asserted literal values, when that was not the case. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where using the "generate function" code action on a function capture would generate an argument named `_capture`, using the internal variable names of the compiler. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the language server would provide completions inside a constant string. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a grammatical error in the bit array truncation warning message. ([Louis Pilfold](https://github.com/lpil)) ## v1.11.0-rc2 - 2025-05-29 ### Compiler - The format of the `assert` and `let assert` location information has been improved, and the file path of the source module has been added. ([Louis Pilfold](https://github.com/lpil)) ### Language server - When using the "remove `echo`" code action, the language server will also remove any literal expression being printed by `echo` statements. For example ```gleam pub fn main() { echo "Before" do_complex_stuff() echo "After" do_something_else() } ``` Will become: ```gleam pub fn main() { do_complex_stuff() do_something_else() } ``` Making it easier to get rid of debug printing messages once they're no longer needed. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ### Bug fixes - Fixed a bug where type constructors with many fields would not be formatted properly in the generated documentation. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where fields name `x0` in records could cause invalid code to be generated on the JavaScript target. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where exceptions on JavaScript could be sometimes missing the function name metadata. ([Louis Pilfold](https://github.com/lpil)) - Fixed a bug where the missing patterns shown for an inexhaustive `case` expression would include constructors of opaque types which were not available in the current module, leading to invalid code. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the language server would offer completions for local variables where the variables were not in scope, leading to invalid code being produced if the completion was followed. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the compiler would crash when compiling `let assert` statements which contained a bit array pattern inside a tuple pattern on the JavaScript target. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where a zero-length segment of a bit array would never match in a case expression on the JavaScript target. ([Sakari Bergen](https://github.com/sbergen)) ## v1.11.0-rc1 - 2025-05-15 ### Compiler - The compiler can now tell if some branches with bit array patterns are unreachable. For example, the following code: ```gleam case payload { <> -> first_byte <<1, _:bits>> -> 1 _ -> 0 } ``` Will raise the following warning: ``` warning: Unreachable case clause ┌─ /src/bit_array.gleam:4:5 │ 4 │ <<1, _:bits>> -> 1 │ ^^^^^^^^^^^^^^^^^^ This case clause cannot be reached as a previous clause matches the same values. Hint: It can be safely removed. ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The code generated for pattern matching on the JavaScript target has been improved to be more efficient and perform as little checks as possible. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The compiler now raises a warning when it can tell that an int segment with a literal value is going to be truncated. For example, if you wrote this: ```gleam <<258>> ``` The compiler will now warn you: ```txt warning: Truncated bit array segment ┌─ /src/main.gleam:4:5 │ 4 │ <<258>> │ ^^^ You can safely replace this with 2 This segment is 1 byte long, but 258 doesn't fit in that many bytes. It would be truncated by taking its first byte, resulting in the value 2. ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The compiler will now include labels in the error message when a `case` expression is inexhaustive. For example, this code: ```gleam pub type Person { Person(name: String, age: Int) } pub fn classify(person: Person) { case person { Person(name: "John", age: 27) -> todo Person(name: _, age: 42) -> todo } } ``` Will produces this error: ``` error: Inexhaustive patterns ┌─ /src/main.gleam:6:3 │ 6 │ ╭ case person { 7 │ │ Person(name: "John", age: 27) -> todo 8 │ │ Person(name: _, age: 42) -> todo 9 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Person(name:, age:) ``` ([Surya Rose](https://github.com/GearsDatapacks)) - The analysis of lists, tuples, negation operators, `panic`, `echo` and `todo` is now fault tolerant, meaning that the compiler will not stop reporting errors as soon as it finds one. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The error message for types used with the wrong number of arguments has been improved. For example, this piece of code: ```gleam type Wibble(a) type Wobble { Wobble(Wibble) } ``` Produces the following error: ```txt error: Incorrect arity ┌─ /src/one/two.gleam:5:10 │ 5 │ Wobble(Wibble) │ ^^^^^^ Expected 1 type argument, got 0 `Wibble` requires 1 type argument but none where provided. ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - You can now use the `assert` keyword by itself to test a boolean expression. If the expression evaluates to `False` at runtime, the `assert` statement will cause the program to panic, with information about the expression that was asserted. For example: ```gleam pub fn ok_error_test() { assert result.is_ok(Ok(10)) assert result.is_error(Error("Some error")) assert Ok(1) != Error(1) assert result.is_error(Ok(42)) // panic: Assertion failed } ``` A custom panic message can also be provided in order to add extra information: ```gleam pub fn identity_test() { assert function.identity(True) as "Identity of True should never be False" } ``` ([Surya Rose](https://github.com/GearsDatapacks)) - The compiler will now emit a warning when the return value of a call to a function without side effects is unused. For example the following code: ```gleam pub fn main() { add(1, 2) add(3, 4) } ``` Will produce the following warning: ``` warning: Unused value ┌─ /src/main.gleam:4:3 │ 4 │ add(1, 2) │ ^^^^^^^^^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ``` ([Surya Rose](https://github.com/GearsDatapacks)) - The compiler will now generate more efficient code for `let assert` on the Erlang target. ([Surya Rose](https://github.com/GearsDatapacks)) - The compiler will now reject bit array segment patterns whose constant size is zero or negative. Previously a zero or negative sized segment would crash the compiler. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The compiler will not generate needless code for unused pattern variables. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Function parameters are now fault tolerant, meaning that type-checking can continue to occur if a function references invalid type in its signature. ([Surya Rose](https://github.com/GearsDatapacks)) - The compiler is now fault tolerant when analysing patterns for custom types, meaning it won't stop at the first error it encounters (for example if a constructor is wrong). ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - On the JavaScript target, bit arrays now support UTf-16 and UTF-32 string segments. ([Surya Rose](https://github.com/GearsDatapacks)) - When an import with unqualified types and values is unused the compiler will now raise a single warning for the entire import rather than warning for each individual item. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ### Build tool - The build tool now supports placing modules in a directory called `dev`, which like `test`, is only for development code. ([Surya Rose](https://github.com/GearsDatapacks)) - There is now a new CLI command, `gleam dev`, which runs the `$PACKAGE_dev` module, for running development entrypoints. ([Surya Rose](https://github.com/GearsDatapacks)) - Updated the Erlang shipment POSIX entrypoint script to add an exec statement so the Erlang process replaces the shell's process and can receive signals when deployed. ([Christopher De Vries](https://github.com/devries)) - The build tool now provides additional information when printing warnings for deprecated environment variables. ([Surya Rose](https://github.com/GearsDatapacks)) - When generating documentation, the build tool now prints type variable using the same names as were used in the code for function signatures. For example, this code: ```gleam pub fn from_list(entries: List(#(key, value))) -> Dict(key, value) { ... } ``` Previously would have been printed as: ```gleam pub fn from_list(entries: List(#(a, b))) -> Dict(a, b) ``` But now is rendered as the following: ```gleam pub fn from_list(entries: List(#(key, value))) -> Dict(key, value) ``` ([Surya Rose](https://github.com/GearsDatapacks)) - When generating documentation, types from other modules are now rendered with their module qualifiers. Hovering over them shows the module name. For example, this code: ```gleam import gleam/dynamic/decode pub fn something_decoder() -> decode.Decoder(Something) { ... } ``` Will now generate the following documentation: ```gleam pub fn something_decoder() -> decode.Decoder(Something) ``` And hovering over the `decode.Decoder` text will show the following: ```txt gleam/dynamic/decode.{type Decoder} ``` ([Surya Rose](https://github.com/GearsDatapacks)) - When generating documentation, types in rendered documentation code will now link to their corresponding documentation pages. ([Surya Rose](https://github.com/GearsDatapacks)) - `gleam add` will now emit an error if you try to add a package as a dependency of itself. ([Louis Pilfold](https://github.com/lpil)) - The build tool will now emit an error when compiling a package if the package has a Gleam and Erlang file which would collide. ([Surya Rose](https://github.com/GearsDatapacks)) ### Language server - The code action to add missing labels to functions now also works in patterns: ```gleam pub type Person { Person(name: String, age: Int, job: String) } pub fn age(person: Person) { let Person(age:) = person age } ``` Becomes: ```gleam pub type Person { Person(name: String, age: Int, job: String) } pub fn age(person: Person) { let Person(age:, name:, job:) = person age } ``` ([Surya Rose](https://github.com/GearsDatapacks)) - The JSON encoding function that the language server code action generates is now named `$TYPENAME_to_json` instead of `encode_$TYPENAME`. This is to remove ambiguity with functions that encode to other formats, and to make the function easier to discover by searching. ([Louis Pilfold](https://github.com/lpil)) - The code action to add missing patterns to a `case` expression now includes labels in the generated patterns. For example: ```gleam pub type Person { Person(name: String, age: Int) } pub fn classify(person: Person) { case person { Person(name: "John", age: 27) -> todo Person(name: _, age: 42) -> todo } } ``` Will now become: ```gleam pub type Person { Person(name: String, age: Int) } pub fn classify(person: Person) { case person { Person(name: "John", age: 27) -> todo Person(name: _, age: 42) -> todo Person(name:, age:) -> todo } } ``` ([Surya Rose](https://github.com/GearsDatapacks)) - The language server now provides hover, autocomplete and goto definition for constant definitions. ([Surya Rose](https://github.com/GearsDatapacks)) - The "generate function" code action can now choose better names based on the labels and variables used. For example if I write the following code: ```gleam pub fn main() -> List(Int) { let list = [1, 2, 3] let number = 1 remove(each: number, in: list) //^^^^ This function doesn't exist yet! } ``` And ask the language server to generate the missing function, the generated code will now look like this: ```gleam fn remove(each number: Int, in list: List(Int)) -> List(Int) { todo } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server now provides autocomplete suggestions for labels after part of the label has already been typed. For example, in this code: ```gleam pub type Person { Person(name: String, number: Int) } pub fn main() { Person(n|) } ``` The language server will provide `name:` and `number:` as autocomplete suggestions. ([Surya Rose](https://github.com/GearsDatapacks)) - The language server now provides a code action to automatically generate a new variant from incorrect code: ```gleam pub type Msg { ServerSentResponse(Json) } pub fn view() -> Element(Msg) { div([], [ button([on_click(UserPressedButton)], [text("Press me!")]) // ^^^^^^^^^^^^^^^^^ This doesn't exist yet! ]) } ``` Triggering the code action on the `UserPressedButton` will add it to the `Msg` type: ```gleam pub type Msg { ServerSentResponse(Json) UserPressedButton } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server can now remove unused imported values and types: ```gleam import a_module.{type Unused, unused, used} pub fn main() { used } ``` Triggering the code action will remove all unused types and values: ```gleam import a_module.{used} pub fn main() { used } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ### Formatter - Improved the formatting of `echo` when followed by long binary expressions. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ### Installation - Windows ARM64 pre-built binaries are now provided. ([Jonatan Männchen](https://github.com/maennchen)) ### Bug fixes - Fixed a bug where `case` expressions in custom panic messages would compile to invalid syntax on the JavaScript target. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where an underscore after a zero in a number would compile to invalid syntax on the JavaScript target. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the "generate function" code action could generate invalid code when the same variable was passed as an argument twice. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where replacing a Hex dependency with a Git dependency of the same name would cause the build tool to fail. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where updating the remote URL of a Git dependency would fail to update the remote in the local dependency, causing a caching issue. ([Surya Rose](https://github.com/GearsDatapacks)) - Fix slightly wrong error message for missing main function in test module. ([Samuel Cristobal](https://github.com/scristobal)) - Fixed a bug where the compiler would not properly warn for unreachable patterns in a `case` expression when the clause matched on multiple alternative patterns. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the language server would generate invalid code for the "fill in missing labels" code action. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where referencing an earlier segment of the bit array in a bit array pattern in a `let assert` assignment would generate invalid code on the Erlang target. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed instances where the "Extract variable" code action would produce invalid code, most noticeable in code inside `case` clauses and `use` expressions. ([Matias Carlander](https://github.com/matiascr)) - Fixed a bug where the compiler would crash when type-checking code containing an assignment pattern inside a bit-array pattern. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where using the pipe operator in the `size` option of a bit array segment would generate invalid code on the Erlang target. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the language server would generate invalid code for the "convert to use" code action, when used on a function call with labelled arguments. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where a reference to an imported external function with a non-alphanumeric module name would compile to invalid syntax on the Erlang target. ([Mathieu Darse](https://github.com/mdarse)) - Fixed a bug where the compiler would not correctly check the size of bit arrays when doing bit array pattern matching on the JavaScript target. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where using the pipe operator inside a record update would cause the compiler to generate invalid code on the JavaScript target. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where some code actions would produce invalid code when a file contained characters that were represented with more than one byte in UTF-8. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where LSP ranges would be incorrect when a file contained characters that were represented with more than one byte in UTF-8. ([Surya Rose](https://github.com/GearsDatapacks)) - Only print the user-friendly LSP message when stdin is a terminal to avoid emitting redundant logs. ([Ariel Parker](https://github.com/arielherself)) - Fixed a bug where the language server would wrongly override local variables if they were shadowing an unqualified module function. ([Samuel Cristobal](https://github.com/scristobal)) - Fixed a bug where renaming a local variable which is used in combination with label shorthand syntax would produce invalid code. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the stack would overflow on Windows when type-checking multiple nested `use` expressions. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where using a variable from a separate pattern inside a bit array pattern would be allowed but generate invalid Erlang code. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the compiler would crash if duplicate variables were defined in alternative patterns. ([Surya Rose](https://github.com/GearsDatapacks)) ================================================ FILE: changelog/v1.12.md ================================================ # Changelog ## v1.12.0 - 2025-08-05 ### Bug fixes - Corrected an error message that used incorrect terminology. ([Louis Pilfold](https://github.com/lpil)) ## v1.12.0-rc3 - 2025-07-31 ### Bug fixes - Fixed a bug where using `echo` in a module with a function named `process` would result in a runtime error on JavaScript. ([Peter Saxton](https://github.com/CrowdHailer)) ## v1.12.0-rc2 - 2025-07-24 ### Formatter - The formatter now allows more control over how bit arrays are split. By adding a trailing comma at the end of a bit array that can fit on a single line, the bit array will be split on multiple lines: ```gleam pub fn dgram() -> BitArray { <> } ``` Will be formatted as: ```gleam pub fn dgram() -> BitArray { << ip_version:4, header_length:4, service_type:8, >> } ``` By removing the trailing comma, the formatter will try and fit the bit array on a single line again: ```gleam pub fn dgram() -> BitArray { << ip_version:4, header_length:4, service_type:8 >> } ``` Will be formatted back to a single line: ```gleam pub fn dgram() -> BitArray { <> } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The formatter now allows more control over how bit arrays are formatted. If a bit array is split with multiple segments on the same line, removing the trailing comma will make sure the formatter keeps each segment on its own line: ```gleam pub fn dgram() -> BitArray { << "This bit array was formatted", "keeping segments on the same line", "notice how the formatting changes by removing the trailing comma ->", >> } ``` Is formatted as: ```gleam pub fn dgram() -> BitArray { << "This bit array was formatted", "keeping segments on the same line", "notice how the formatting changes by removing the trailing comma ->" >> } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ### Bug fixes - Fixed a bug where the formatter would move a comment before `assert` to be after it. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the message following an `echo`, `panic`, `todo`, `assert`, or `let assert` would not be formatted properly when preceded by a comment. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the compiler would generate invalid code for an `assert` using pipes on the JavaScript target. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ## v1.12.0-rc1 - 2025-07-18 ### Compiler - It is now possible to add a custom message to be printed by `echo`, making it easier to include additional context to be printed at runtime: ```gleam pub fn main() { echo 11 as "lucky number" } ``` Will output to stderr: ```txt /src/module.gleam:2 lucky number 11 ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Generated JavaScript functions, constants, and custom type constructors now include any doc comment as a JSDoc comment, making it easier to use the generated code and browse its documentation from JavaScript. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The code generated for a `case` expression on the JavaScript target is now reduced in size in many cases. ([Surya Rose](https://github.com/GearsDatapacks)) - The code generators now perform usage-based dead code elimination. Unused definitions are not longer generated. ([Louis Pilfold](https://github.com/lpil)) - `echo` now has better support for character lists, JavaScript errors, and JavaScript circular references. ([Louis Pilfold](https://github.com/lpil)) - The look of errors and warnings has been improved. Additional labels providing context for the error message are no longer highlighted with the same style as the source of the problem. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Gleam will now emit a helpful message when attempting to import modules using `.` instead of `/`. ```txt error: Syntax error ┌─ /src/parse/error.gleam:1:11 │ 1 │ import one.two.three │ ^ I was expecting either `/` or `.{` here. Perhaps you meant one of: import one/two import one.{item} ``` ([Zij-IT](https://github.com/zij-it)) - The compiler now emits a warning when a top-level constant or function declaration shadows an imported name in the current module. ([Aayush Tripathi](https://github.com/aayush-tripathi)) - The compiler can now tell when an unknown variable might be referring to an ignored variable and provide an helpful error message highlighting it. For example, this piece of code: ```gleam pub fn go() { let _x = 1 x + 1 } ``` Now results in the following error: ``` error: Unknown variable ┌─ /src/one/two.gleam:4:3 │ 3 │ let _x = 1 │ -- This value is discarded 4 │ x + 1 │ ^ So it is not in scope here. Hint: Change `_x` to `x` or reference another variable ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The code generated for pattern matching has been optimised on the JavaScript target to reuse the matched variables when safe to do so. Take the following snippet of Gleam code: ```gleam pub fn find_book() { case ask_for_isbn() { Ok(isbn) -> load_book(isbn) Error(Nil) -> Error(Nil) } } ``` Notice how in the `Error` case we're returning exactly the same value that is being matched on! Now the compiler will generate the following JavaScript code instead of allocating a new `Error` variant entirely: ```js export function find_book() { let result = ask_for_isbn(); if (result instanceof Ok) { let isbn = result[0]; return load_book(isbn); } else { // Previously this would have been: `return new Error(undefined);`! return result; } } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The compiler now raises a warning when performing a redundant comparison that it can tell is always going to succeed or fail. For example, this piece of code: ```gleam pub fn find_line(lines) { list.find(lines, fn(x) { x == x }) } ``` Would result in the following warning: ``` warning: Redundant comparison ┌─ /src/warning.gleam:2:17 │ 1 │ list.find(lines, fn(x) { x == x }) │ ^^^^^^ This is always `True` This comparison is redundant since it always succeeds. ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Attempting to use the list prefix syntax with two lists will now show a helpful error message. For example, this snippet of code: ```gleam pub fn main() -> Nil { let xs = [1, 2, 3] let ys = [5, 6, 7] [1, ..xs, ..ys] } ``` Would result in the following error: ``` error: Syntax error ┌─ /src/parse/error.gleam:5:13 │ 5 │ [1, ..xs, ..ys] │ -- ^^ I wasn't expecting a second list here │ │ │ You're using a list here Lists are immutable and singly-linked, so to join two or more lists all the elements of the lists would need to be copied into a new list. This would be slow, so there is no built-in syntax for it. ``` ([Carl Bordum Hansen](https://github.com/carlbordum)) and ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The error message one gets when calling a function with the wrong number of arguments has been improved and now only suggests the relevant missing labels. For example, this piece of code: ```gleam pub type Pokemon { Pokemon(id: Int, name: String, moves: List(String)) } pub fn best_pokemon() { Pokemon(198, name: "murkrow") } ``` Would result in the following error, suggesting the missing labels: ```txt error: Incorrect arity ┌─ /src/main.gleam:6:3 │ 6 │ Pokemon(198, name: "murkrow") │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Expected 3 arguments, got 2 This call accepts these additional labelled arguments: - moves ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Code generators now reuse existing variables when possible for the record update syntax, reducing the size of the generated code and number of variables defined for both Erlang and JavaScript. ```gleam pub fn main() -> Nil { let trainer = Trainer(name: "Ash", badges: 0) battle(Trainer(..trainer, badges: 1)) } ``` Previously this Gleam code would generate this Erlang code: ```erlang -spec main() -> nil. main() -> Trainer = {trainer, 0, <<"Ash"/utf8>>}, battle( begin _record = Trainer, {trainer, 1, erlang:element(3, _record)} end ). ``` Now this code will be generated instead: ```erlang -spec main() -> nil. main() -> Trainer = {trainer, 0, <<"Ash"/utf8>>}, battle({trainer, 1, erlang:element(3, Trainer)}). ``` ([Louis Pilfold](https://github.com/lpil)) - The compiler now allows using bit array options to specify endianness when constructing or pattern matching on UTF codepoints in bit arrays. ([Surya Rose](https://github.com/GearsDatapacks)) - Calculations are now allowed in the size options of bit array patterns. For example, the following code is now valid: ```gleam let assert <> = some_bit_array ``` ([Surya Rose](https://github.com/GearsDatapacks)) - On the Erlang target each generated module enables inlining from the Erlang compiler. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The code generated for division and modulo operators on the JavaScript target has been improved to avoid performing needless checks. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ### Build tool - `gleam update`, `gleam deps update`, and `gleam deps download` will now print a message when there are new major versions of packages available. For example: ```txt $ gleam update Resolving versions The following dependencies have new major versions available: gleam_http 1.7.0 -> 4.0.0 gleam_json 1.0.1 -> 3.0.1 lustre 3.1.4 -> 5.1.1 ``` ([Amjad Mohamed](https://github.com/andho)) - The documentation generator now strips trailing slashes from Gitea/Forgejo hosts so sidebar "Repository" and "View Source" links never include `//`, and single-line "View Source" anchors emit `#Lx` instead of `#Lx-x`. ([Aayush Tripathi](https://github.com/aayush-tripathi)) - The build tool can now compile packages that will have already booted the Erlang compiler application instead of failing. ([Louis Pilfold](https://github.com/lpil)) - `gleam deps list` now uses a tab rather than a space as a separator. ([Louis Pilfold](https://github.com/lpil)) - The build tool now also supports `.cjs` files placed in the `src`, `dev` or `test` directories. ([yoshi](https://github.com/yoshi-monster)) - The build tool now produces better error messages when version resolution fails. For example: ``` $ gleam add wisp@1 Resolving versions error: Dependency resolution failed There's no compatible version of `gleam_otp`: - You require wisp >= 1.0.0 and < 2.0.0 - wisp requires mist >= 1.2.0 and < 5.0.0 - mist requires gleam_otp >= 0.9.0 and < 1.0.0 - You require lustre >= 5.2.1 and < 6.0.0 - lustre requires gleam_otp >= 1.0.0 and < 2.0.0 There's no compatible version of `gleam_json`: - You require wisp >= 1.0.0 and < 2.0.0 - wisp requires gleam_json >= 3.0.0 and < 4.0.0 - You require gleam_json >= 2.3.0 and < 3.0.0 ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The `repository` section in `gleam.toml` now allows specifying the `tag-prefix` property, which is prepended to the default tag. This makes it possible to have multiple packages with different versions in the same repository (together with `path`), without breaking links to source code in documentation. ([Sakari Bergen](https://github.com/sbergen)) ### Language server - It is now possible to use the "Pattern match on variable" code action on variables on the left hand side of a `use`. For example: ```gleam pub type User { User(id: Int, name: String) } pub fn main() { use user <- result.try(load_user()) // ^^^^ Triggering the code action here todo } ``` Would result in the following code: ```gleam pub type User { User(id: Int, name: String) } pub fn main() { use user <- result.try(load_user()) let User(id:, name:) = user todo } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The "generate function" and "generate variant" code actions are now quickfixes, allowing them to be more easily applied to code which is producing an error. ([Surya Rose](https://github.com/GearsDatapacks)) - The language server now offers a code action to remove needless blocks wrapping a single expression. For example, in this code snippet: ```gleam case greeting { User(name:) -> { "Hello, " <> name } // ^^^^^^^^^^^^^^^^^^^^^ Triggering the code action // with the cursor over this block. Anonymous -> "Hello, stranger!" } ``` Would be turned into: ```gleam case greeting { User(name:) -> "Hello, " <> name Anonymous -> "Hello, stranger!" } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - It is now possible to trigger the "Add type annotation" code action anywhere between the start of a function definition and the start of its body. For example the action can trigger here while it previously wouldn't: ```gleam pub fn my_lucky_number() { // ^^ The action can trigger here as well! 11 } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ### Formatter - The formatter now allows more control over how lists are split. By adding a trailing comma at the end of a list that can fit on a single line, the list will be split on multiple lines: ```gleam pub fn my_favourite_pokemon() -> List(String) { ["natu", "chimecho", "milotic",] } ``` Will be formatted as: ```gleam pub fn my_favourite_pokemon() -> List(String) { [ "natu", "chimecho", "milotic", ] } ``` By removing the trailing comma, the formatter will try and fit the list on a single line again: ```gleam pub fn my_favourite_pokemon() -> List(String) { [ "natu", "chimecho", "milotic" ] } ``` Will be formatted back to a single line: ```gleam pub fn my_favourite_pokemon() -> List(String) { ["natu", "chimecho", "milotic"] } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The formatter now allows more control over how lists are formatted. If a list is split with multiple elements on the same line, removing the trailing comma will make sure the formatter keeps each item on its own line: ```gleam pub fn my_favourite_pokemon() -> List(String) { [ "This list was formatted", "keeping multiple elements on the same line", "notice how the formatting changes by removing the trailing comma ->" ] } ``` Is formatted as: ```gleam pub fn my_favourite_pokemon() -> List(String) { [ "This list was formatted", "keeping multiple elements on the same line", "notice how the formatting changes by removing the trailing comma ->", ] } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The formatter no longer removes empty lines between list items. In case an empty line is added between list items they will all be split on multiple lines. For example: ```gleam pub fn main() { [ "natu", "xatu", "chimeco" ] } ``` Is formatted as: ```gleam pub fn main() { [ "natu", "xatu", "chimeco", ] } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ### Bug fixes - Fixed a bug where the language server would not show type-related code actions for record fields in custom type definitions. ([cysabi](https://github.com/cysabi)) - Fixed a bug where the "Inline variable" code action would be offered for function parameters and other invalid cases. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the "Inline variable" code action would not be applied correctly to variables using label shorthand syntax. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the compiler would emit the same error twice for patterns with the wrong number of labels. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the language server would generate invalid code when the "Extract variable" code action was used on a `use` expression. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the compiler would crash when using the `utf8_codepoint` bit array segment on the JavaScript target. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where `==` and `!=` would return incorrect output for some JavaScript objects. ([Louis Pilfold](https://github.com/lpil)) - Fixed a bug where specific combinations of options in bit array segments would not be allowed on the JavaScript target. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where invalid code would be generated for `let assert` in some cases on the JavaScript target. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where using the prelude `Ok` and `Error` values in a qualified fashion could cause a conflict with user-defined `Ok` and `Error` values when generating code on the JavaScript target. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the "Import module" code action would suggest importing internal modules from other packages. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the language server would not rename variables defined in alternative patterns. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where trying to rename a type or value from the Gleam prelude would result in invalid code. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the generated documentation would not be formatted properly. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the language server wouldn't allow you to jump to the definition of a record from a record update expression. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the language server would allow using the "extract variable" code action on variables used in record updates. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where a record pattern with a spread `..` would not be formatted properly. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where fields of custom types named `prototype` would not be properly escaped on the JavaScript target. ([Surya Rose](https://github.com/GearsDatapacks)) ## v1.11.1 - 2025-06-05 ### Compiler - The displaying of internal types in HTML documentation has been improved. ([Louis Pilfold](https://github.com/lpil)) - A warning is now emitted when the same module is imported multiple times into the same module with different aliases. ([Louis Pilfold](https://github.com/lpil)) ### Bug fixes - Fixed a bug where a bit array segment matching on a floating point number would match with `NaN` or `Infinity` on the JavaScript target. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ================================================ FILE: changelog/v1.13.md ================================================ # Changelog ## v1.13.0 - 2025-10-19 ## v1.13.0-rc2 - 2025-10-06 ### Bug fixes - Fixed a bug where the "Extract function" code action would not properly extract a `use` expression. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the "Extract function" code action would generate a function with the wrong type when used on a use expression. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the error message for inexhaustive patterns could show incorrect extra patterns in addition to the correct missing patterns. ([Adi Salimgereyev](https://github.com/abs0luty)) - Fixed a bug where triggering the "Generate function" code action to generate a function in a different module could cause the generated function to appear in the middle of an existing function, resulting in invalid code. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the "turn into pipe" code action would not trigger inside the final step of a pipeline. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the "pattern match on variable" code action would crash when used on a variable followed by a case expression. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ## v1.13.0-rc1 - 2025-09-29 ### Compiler - The compiler now applies an optimisation known as "interference based pruning" when compiling bit array pattern matching where matches are performed at the start of bit arrays. This optimisation drastically reduces compile times, memory usage and the compiled code size, removing many redundant checks. It is particularly important for network protocol applications where it is typical to match on some fixed patterns at the start of the bitarray. For example: ```gleam pub fn parser_headers(headers: BitArray, bytes: Int) -> Headers { case headers { <<"CONTENT_LENGTH" as header, 0, value:size(bytes), 0, rest:bytes>> | <<"QUERY_STRING" as header, 0, value:size(bytes), 0, rest:bytes>> | <<"REQUEST_URI" as header, 0, value:size(bytes), 0, rest:bytes>> // ... | <<"REDIRECT_STATUS" as header, 0, value:size(bytes), 0, rest:bytes>> | <<"SCRIPT_NAME" as header, 0, value:size(bytes), 0, rest:bytes>> -> [#(header, value), ..parse_headers(rest)] } } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The compiler now raises a warning for unreachable clauses that are matching on bit array segments that could never match. Consider this example: ```gleam pub fn get_payload(packet: BitArray) -> Result(BitArray, Nil) { case packet { <<200, payload:bytes>> -> Ok(payload) <<404, _:bits>> -> Error(Nil) _ -> Ok(packet) } } ``` There's a subtle bug here. The second clause can never match since it's impossible for the first byte of the bit array to have the value `404`. The new error explains this nicely: ```txt warning: Unreachable pattern ┌─ /src.gleam:4:5 │ 4 │ <<404, _:bits>> -> Error(Nil) │ ^^^^^^^^^^^^^^^ │ │ │ A 1 byte unsigned integer will never match this value This pattern cannot be reached as it contains segments that will never match. Hint: It can be safely removed. ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The compiler now raises a warning when a function's argument is only passed along in a recursive call but not actually used for anything. For example: ```gleam import gleam/io pub fn greet(x, times) { case times { 0 -> Nil _ -> { io.println("Hello, Joe!") greet(x, times - 1) } } } ``` In this piece of code the `x` argument is actually never used, and the compiler will raise the following warning: ```txt warning: Unused function argument ┌─ /Users/giacomocavalieri/Desktop/prova/src/prova.gleam:3:14 │ 3 │ pub fn greet(x, times) { │ ^ This argument is unused This argument is passed to the function when recursing, but it's never used for anything. ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The compiler now emits a better error message for private types marked as opaque. For example, the following piece of code: ```gleam opaque type Wibble { Wobble } ``` Would result in the following error: ``` error: Private opaque type ┌─ /src/one/two.gleam:2:1 │ 2 │ opaque type Wibble { │ ^^^^^^ You can safely remove this. Only a public type can be opaque. ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The parsing of opaque private types is now fault tolerant: having a private opaque type in a module no longer stops the compiler from highlighting other errors. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The compiler now emits a single warning for multiple negations in a row, while previously it would emit multiple comments highlighting increasingly longer spans. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Redundant `_ as x` patterns are now deprecated in favour of `x`. ([eutampieri](https://github.com/eutampieri)) - The compiler will now raise warning for inefficient use of `list.length()` when trying to check is list empty via `0 < list.length(list)` or `list.length(list) > 0` as well as in other cases. For example, the following code: ```gleam import gleam/list pub fn main() { let numbers = [1, 46] let _ = 0 < list.length(numbers) let _ = list.length(numbers) > 0 } ``` Would result in following warnings: ``` warning: Inefficient use of `list.length` ┌─ /data/data/com.termux/files/home/test_gleam/src/test_gleam.gleam:5:13 │ 5 │ let _ = 0 < list.length(numbers) │ ^^^^^^^^^^^^^^^^^^^^^^^^ The `list.length` function has to iterate across the whole list to calculate the length, which is wasteful if you only need to know if the list is empty or not. Hint: You can use `the_list != []` instead. warning: Inefficient use of `list.length` ┌─ /data/data/com.termux/files/home/test_gleam/src/test_gleam.gleam:6:13 │ 6 │ let _ = list.length(numbers) > 0 │ ^^^^^^^^^^^^^^^^^^^^^^^^ The `list.length` function has to iterate across the whole list to calculate the length, which is wasteful if you only need to know if the list is empty or not. Hint: You can use `the_list != []` instead. ``` ([Andrey Kozhev](https://github.com/ankddev)) - The compiler now provides an improved error message for when trying to define a constant inside a function. For example, the following code: ```gleam pub fn deep_thought() -> Int { const the_answer = 42 the_answer } ``` Will produce this error message: ```txt error: Syntax error ┌─ /src/file.gleam:2:3 │ 3 │ const the_answer = 43 │ ^^^^^ Constants are not allowed inside functions All variables are immutable in Gleam, so constants inside functions are not necessary. Hint: Either move this into the global scope or use `let` binding instead. ``` ([Surya Rose](https://github.com/GearsDatapacks)) - The code generated for blocks on the JavaScript target has been improved and is now smaller in certain cases. ([Surya Rose](https://github.com/GearsDatapacks)) - Writing a type name followed by `()` now emits an error during analysis rather than parsing, so it no longer stops the compiler from reporting errors further in the code. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The compiler now shows a specific syntax error when trying to use an angle-bracket syntax for generic types or function definitions: ```txt error: Syntax error ┌─ /src/parse/error.gleam:2:12 │ 2 │ type Either { │ ^ I was expecting `(` here. Type parameters use lowercase names and are surrounded by parentheses. type Either(a, b) { See: https://tour.gleam.run/data-types/generic-custom-types/ ``` ([Aaron Christiansen](https://github.com/AaronC81)) - Fault tolerance for analysis of labeled fields in variant patterns has been improved. ([sobolevn](https://github.com/sobolevn)) - Compiler now adds a hint when `#`-styled comments are used. This code: ```gleam fn some() { let a = 1 # let b = 2 } ``` Now produces: ```txt error: Syntax error ┌─ /src/main.gleam:3:5 │ 3 │ # let b = 2 │ ^^^ I was not expecting this Found the keyword `let`, expected one of: - `(` Hint: Maybe you meant to create a comment? Comments in Gleam start with `//`, not `#` ``` ([sobolevn](https://github.com/sobolevn)) - The `erlang.application_start_argument` parameter has been added to `gleam.toml`. This is a string containing an Erlang term that will be written into the package's Erlang `.app` file if `erlang.application_start_module` has been set, replacing the default argument of `[]`. ([Louis Pilfold](https://github.com/lpil)) - Generated code for the JavaScript target now includes a public API which can be used for FFI interacting with Gleam custom types. For example, if you have this Gleam code: ```gleam pub type Person { Teacher(name: String, subject: String) Student(name: String, age: Int) } ``` You can use the new API to use the `Person` type in FFI code: ```javascript import {...} from "./person.mjs"; // Constructing custom types let teacher = Person$Teacher("Joe Armstrong", "Computer Science"); let student = Person$Student("Louis Pilfold"); let randomPerson = Math.random() > 0.5 ? teacher : student; // Checking variants let randomIsTeacher = Person$isTeacher(randomPerson); // Getting fields let teacherSubject = Person$Teacher$subject(teacher); // The `name` field is shared so can be accessed from either variant let personName = Person$name(randomPerson); ``` ([Surya Rose](https://github.com/GearsDatapacks)) ### Build tool - New projects are generated using OTP28 on GitHub Actions. ([Louis Pilfold](https://github.com/lpil)) - The build tool now has a new `hex owner transfer` subcommand to transfer ownership of existing packages. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - `gleam add` now adds `dependencies` and `dev_dependencies` as tables instead of inline tables if they are missing. ([Andrey Kozhev](https://github.com/ankddev)) - After dependency resolution the build tool will now print all packages added and removed, and any versions changed. ([Louis Pilfold](https://github.com/lpil)) - `gleam publish` now blocks publishing packages that contain the default main function to prevent accidental publishing of unmodified template code. ([Joohoon Cha](https://github.com/jcha0713)) - When generating documentation, the build tool will now print the names of public type aliases instead of internal type names when annotating functions and types. For example, for the following code: ```gleam import my_package/internal pub type ExternalAlias = internal.InternalRepresentation pub fn do_thing() -> ExternalAlias { ... } ``` This is what the build tool used to generate: ```gleam pub fn do_thing() -> @internal InternalRepresentation ``` Whereas now it will not use the internal name, and instead produce: ```gleam pub fn do_thing() -> ExternalAlias ``` ([Surya Rose](https://github.com/GearsDatapacks)) - Support has been added for using Tangled as a repository. ([Naomi Roberts](https://github.com/naomieow)) ### Language server - The language server now offers a code action to remove all the unreachable clauses in a case expression. For example: ```gleam pub fn main() { case find_user() { Ok(user) -> todo Ok(Admin) -> todo // ^^^^^^^^^ This clause is unreachable Ok(User) -> todo // ^^^^^^^^ This clause is unreachable Error(_) -> todo } } ``` Hovering over one of the unreachable clauses and triggering the code action would remove all the unreachable clauses: ```gleam pub fn main() { case find_user() { Ok(user) -> todo Error(_) -> todo } } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The "pattern match on variable" can now be triggered on lists. For example: ```gleam pub fn is_empty(list: List(a)) -> Bool { // ^^^^ Triggering the action here } ``` Triggering the action on the `list` argument would result in the following code: ```gleam pub fn is_empty(list: List(a)) -> Bool { case list { [] -> todo [first, ..rest] -> todo } } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The "pattern match on variable" code action can now be triggered on variables introduced by other patterns. For example: ```gleam pub fn main() { let User(name:, role:) = find_user("lucy") // ^^^^ Triggering the action here } ``` Triggering the action on another variable like `role` would result in the following code: ```gleam pub fn main() { let User(name:, role:) = find_user("lucy") case role { Admin -> todo Member -> todo } } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The "pattern match on variable" code action can now be triggered on variables in case expressions. For example: ```gleam pub fn main() { case find_user() { Ok(user) -> todo Error(_) -> todo } } ``` Triggering the action on the `user` variable would result in the following code: ```gleam pub fn main() { case find_user() { Ok(Admin) -> todo Ok(Member) -> todo Error(_) -> todo } } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server now offers a quick fix to remove `opaque` from a private type: ```gleam opaque type Wibble { // ^^^ This is an error! Wobble } ``` If you hover over the type and trigger the quick fix, the language server will automatically remove the `opaque` keyword: ```gleam type Wibble { Wobble } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server now offers a code action to add the omitted labels in a call. For example: ```gleam pub type User { User(first_name: String, last_name: String, likes: List(String)) } pub fn main() { let first_name = "Giacomo" User(first_name, "Cavalieri", ["gleam"]) //^^^^ Triggering the code action here } ``` Triggering the code action on the `User` constructor will result in the following code: ```gleam pub type User { User(first_name: String, last_name: String, likes: List(String)) } pub fn main() { let first_name = "Giacomo" User(first_name:, last_name: "Cavalieri", likes: ["gleam"]) } ``` - The "inline variable" code action is now only suggested when hovering over the relevant variable. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - When hovering over a record field in a record access expression, the language sever will now show the documentation for that field, if present. ([Surya Rose](https://github.com/GearsDatapacks)) - Renaming a variable from a label shorthand (`name:`) no longer includes the colon in the rename dialog (`name:` -> `name`) ([fruno](https://github.com/frunobulax-the-poodle)) - The language server now offers a code action to collapse nested case expressions. Take this example: ```gleam case user { User(role: Admin, name:) -> // Here the only thing we're doing is pattern matching on the // `name` variable we've just defined in the outer pattern. case name { "Joe" -> "Hello, Joe!" _ -> "Hello, stranger" } _ -> "You're not an admin!" } ``` We could simplify this case expression and reduce nesting like so: ```gleam case user { User(role: Admin, name: "Joe") -> "Hello, Joe!" User(role: Admin, name: _) -> "Hello, stranger" _ -> "You're not an admin!" } ``` Now, if you hover over that pattern, the language server will offer the "collapse nested case" action that will simplify your code like shown in the example above. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The "Generate function" code action now allows generating function in other modules. For example, given the following code: ```gleam // maths.gleam pub fn add(a: Int, b: Int) -> Int { a + b } // main.gleam import maths pub fn main() -> Int { echo maths.add(1, 2) echo maths.subtract(from: 2, subtract: 1) // ^ Trigger the "Generate function" code action here } ``` The language sever will edit the `maths.gleam` file: ```gleam pub fn add(a: Int, b: Int) -> Int { a + b } pub fn subtract(from from: Int, subtract subtract: Int) -> Int { todo } ``` ([Surya Rose](https://github.com/GearsDatapacks)) - The "Add type annotations" and "Generate function" code actions now ignore type variables defined in other functions, improving the generated code. For example: ```gleam fn something(a: a, b: b, c: c) -> d { todo } fn pair(a, b) { #(a, b) } ``` Previously, when triggering the "Add type annotations" code action on the `pair` function, the language server would have generated: ```gleam fn pair(a: e, b: f) -> #(e, f) { #(a, b) } ``` However in 1.13, it will now generate: ```gleam fn pair(a: a, b: b) -> #(a, b) { #(a, b) } ``` ([Surya Rose](https://github.com/GearsDatapacks)) - You can now go to definition, rename, etc. from alternative patterns! ```gleam case wibble { Wibble | Wobble -> 0 // ^- Previously you could not trigger actions from here } ``` ([fruno](https://github.com/fruno-bulax)) - When showing types of values on hover, or adding type annotations, the language server will now prefer public type aliases to internal types. For example, if the "Add type annotations" code action was triggered on the following code: ```gleam import lustre/html import lustre/element import lustre/attribute pub fn make_link(attribute, element) { html.a([attribute], [elements]) } ``` Previously, the following code would have been generated: ```gleam pub fn make_link( attribute: vattr.Attribute, element: vdom.Element(a) ) -> vdom.Element(a) { html.a([attribute], [elements]) } ``` Which references internal types which should not be imported by the user. However, now the language server will produce the following: ```gleam pub fn make_link( attribute: attribute.Attribute, element: element.Element(a) ) -> element.Element(a) { html.a([attribute], [elements]) } ``` ([Surya Rose](https://github.com/GearsDatapacks)) - The language server now offers the `convert to case` code action only if a single `let assert` expression is selected. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server now offers an "Extract function" code action to extract a selected piece of code into a separate function. For example: ```gleam const head_byte_count = 256 pub fn get_head_of_file() { let assert Ok(contents) = read_file() case contents { //^ Select from here <> -> Ok(head) _ -> Error(Nil) } //^ Until here } ``` Would become: ```gleam const head_byte_count = 256 pub fn get_head_of_file() { let assert Ok(contents) = read_file() function(contents) } fn function(contents: BitArray) -> Result(BitArray, Nil) { case contents { <> -> Ok(head) _ -> Error(Nil) } } ``` You can then use language server renaming to choose an appropriate name for the new function. ([Surya Rose](https://github.com/GearsDatapacks)) ### Formatter - The formatter now removes needless multiple negations that are safe to remove. For example, this snippet of code: ```gleam pub fn useless_negations() { let lucky_number = --11 let lucy_is_a_star = !!!False } ``` Is rewritten as: ```gleam pub fn useless_negations() { let lucky_number = 11 let lucy_is_a_star = !False } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Redundant `_ as x` patterns are rewritten to `x`. ([eutampieri](https://github.com/eutampieri)) - The formatter no longer removes blocks from case clause guards. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The formatter now properly formats tuple return annotation with comments. ([Andrey Kozhev](https://github.com/ankddev)) ### Bug fixes - Fixed a bug where literals using `\u{XXXX}` syntax in bit array pattern segments were not translated to Erlang's `\x{XXXX}` syntax correctly. ([Benjamin Peinhardt](https://github.com/bcpeinhardt)) - Fixed a bug where `echo` could crash on JavaScript if the module contains record variants with the same name as some built-in JavaScript objects. ([Louis Pilfold](https://github.com/lpil)) - Fixed a bug where the compiler would highlight an entire double negation expression as safe to remove, instead of just highlighting the double negation. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the compiler would crash if there was an invalid version requirement in a project's `gleam.toml`. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the compiler would suggest a discouraged project name as an alternative to the reserved `gleam` name. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where Forgejo source URLs in the HTML documentation could be incorrectly structured. ([Louis Pilfold](https://github.com/lpil)) - The compiler now emits an error when a module in the `src` directory imports a dev dependency, while previously it would incorrectly let these dependencies to be imported. ([Surya Rose](https://github.com/GearsDatapacks)) - Erroneous extra fields in `gleam.toml` dependency specifications will no longer be siltently ignored. An error is now returned highlighting the problem instead. ([Louis Pilfold](https://github.com/lpil)) - Fixed a bug where renaming a constant which is referenced in another module inside a guard would generate invalid code. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where `echo .. as ..` message will be omitted in browser target. ([Andrey Kozhev](https://github.com/ankddev)) - Fixed a bug where renaming a variable used in a record update would produce invalid code in certain situations. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where adding `echo` to the subject of a `case` expression would prevent variant inference from working correctly. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the compiler would suggest to use a discarded value defined in a different function. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the formatter would format a panic message adding more nesting than necessary. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the language server wouldn't offer the "unqualify" code action if used on a type alias. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the language server would fail to rename an external function with no body. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the compiler allowed to write a guard with an empty clause. ([Tristan-Mihai Radulescu](https://github.com/Courtcircuits)) - Fixed a bug where switching from a hex dependency to a git dependency would result in an error from the compiler. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the compiler would reference a redeclared variable in a let assert message, instead of the original variable, on the Erlang target. ([Danielle Maywood](https://github.com/DanielleMaywood)) - Fixed a bug where the compiler would report an imported module as unused if it were used by private functions only. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the "Extract variable" code action would shadow existing variables, constants and function names. ([Matias Carlander](https://github.com/matiascr)) - Fixed a bug where the language server would not fill in the missing labels of a pattern correctly, generating invalid code. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where invalid code was being generated when using the "Extract variable" code action inside an anonymous function. ([Matias Carlander](https://github.com/matiascr)) - Fixed a bug where running `gleam update` would not properly update git dependencies unless `gleam clean` was run first. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the compiler would produce wrong JavaScript code for binary pattern matching expressions using literal strings and byte segments. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the build tool would crash when trying to add transitive dependency. ([Andrey Kozhev](https://github.com/ankddev)) ================================================ FILE: changelog/v1.14.md ================================================ # Changelog ## v1.14.0 - 2025-12-25 🎁 ### Bug fixes - Fixed a bug where using bit array segments in guard clauses could cause incorrect code to be generated on the JavaScript target. ([Surya Rose](https://github.com/GearsDatapacks)) ## v1.14.0-rc3 - 2025-12-21 ### Bug fixes - Fixed a bug where checking for equality with a variant with no fields using qualified syntax would generate invalid code on the JavaScript target. ([Surya Rose](https://github.com/GearsDatapacks)) ## v1.14.0-rc2 - 2025-12-19 ### Bug fixes - Fixed a bug where the formatter would remove `@external` attributes from custom types. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where updating records with unlabelled fields would result in invalid code. ([Surya Rose](https://github.com/GearsDatapacks)) ## v1.14.0-rc1 - 2025-12-15 ### Compiler - The output of `echo` when printing atoms has been updated to use `atom.create("...")` instead of `atom.create_from_string("...")`. ([Patrick Dewey](https://github.com/ptdewey)) - Patterns aliasing a string prefix have been optimised to generate faster code on the Erlang target. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Type inference for constants is now fault tolerant, meaning the compiler won't stop at the first error as it is typing constants. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Analysis is now fault tolerant in the presence of errors in field definitions of custom type variants. ([Adi Salimgereyev](https://github.com/abs0luty)) - The compiler now emits a warning when a module contains no public definitions and prevents publishing packages with empty modules to Hex. ([Vitor Souza](https://github.com/vit0rr)) - The `@external` annotation is now supported for external types. It allows users to point an external type definition to a specific Erlang or TypeScript type. For example, the `dict.Dict` type from the standard library can now be written as the following: ```gleam @external(erlang, "erlang", "map") @external(javascript, "../dict.d.mts", "Dict") pub type Dict(key, value) ``` ([Surya Rose](https://github.com/GearsDatapacks)) - When matching the wrong number of subjects, the compiler now pinpoints the error location instead of marking the entire branch. ```gleam case wibble { 0, _ -> 1 ^^^^ Expected 1 pattern, got 2 0 | -> 1 ^ I was expecting a pattern after this } ``` ([fruno](https://github.com/fruno-bulax/)) - Missing patterns in error messages and the "Add missing patterns" code action are no longer sorted lexicographically. Instead, they now consider the order in which variants were defined. As programmers often group "related" variants together, this should mean less reshuffling after inserting missing patterns! ([fruno](https://github.com/fruno-bulax/)) - The performance of `==` and `!=` has been improved for fieldless custom type variants when compiling to JavaScript. This was done by generating comparison code specific to the custom type rather than using the generic equality check code. ([Nafi](https://github.com/re-masashi)) - The lowercase bool pattern error is no longer a syntax error, but instead a part of the analysis step. This allows the entire module to be analyzed, rather than stopping at the syntax error. ([mxtthias](https://github.com/mxtthias)) - Exhaustiveness checks for ints and floats now correctly handle unreachable cases in which the numbers contain underscores (i.e. `10` and `1_0`). Float exhaustiveness checks also now correctly identify unreachable cases containing scientific notation or trailing zeros (i.e. `100` and `1e2`). ([ptdewey](https://github.com/ptdewey)) - The compiler now emits a warning when a doc comment is not attached to a definition due to a regular comment in between. For example, in the following code: ```gleam /// This documentation is not attached // This is not a doc comment /// This is actual documentation pub fn wibble() { todo } ``` Will now produce the following warning: ```txt warning: Detached doc comment ┌─ src/main.gleam:1:4 │ 1 │ /// This documentation is not attached │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This is not attached to a definition This doc comment is followed by a regular comment so it is not attached to any definition. Hint: Move the comment above the doc comment ``` ([Surya Rose](https://github.com/GearsDatapacks)) - The [interference-based pruning](https://gleam.run/news/formalising-external-apis/#Improved-bit-array-exhaustiveness-checking) from 1.13 has been extended to int segments! Aside from the various performance improvements, this allows the compiler to mark more branches as unreachable. ```gleam case bits { <<"a">> -> 0 <<97>> -> 1 // ^- This branch is unreachable because it's equal to "a". <<0b1:1, _:1>> -> 2 <<0b11:2>> -> 3 // ^- This branch is unreachable because the branch before it already covers it. _ -> 99 } ``` ([fruno](https://github.com/fruno-bulax/)) - Comparison of record constructors with non-zero arity always produces `False`, because under the hood during code generation they become anonymous functions: ```gleam pub type Wibble { Wobble(String) } pub fn main() { echo Wobble == Wobble // False } ``` Previously compiler produced false-positive redundant comparison warning, which is now removed: ([Adi Salimgereyev](https://github.com/abs0luty)) - Record update syntax can now be used in constant definitions. For example: ```gleam pub const base_http_config = HttpConfig( host: "0.0.0.0", port: 8080, use_tls: False, log_level: Info, ) pub const dev_http_config = HttpConfig( ..base_http_config, port: 4000, log_level: Debug, ) pub const prod_http_config = HttpConfig( ..base_http_config, port: 80, use_tls: True, log_level: Warn, ) ``` ([Adi Salimgereyev](https://github.com/abs0luty)) ### Build tool - The help text displayed by `gleam dev --help`, `gleam test --help`, and `gleam run --help` has been improved: now each one states which function it's going to run. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The `--invert` and `--package` options of `gleam deps tree` are now mutually exclusive; if both options are given the command will fail. Previously, `--invert` would be silently ignored if given together with `--package`. ([Evan Silberman](https://github.com/silby)) - Updated to use the latest Elixir API, so a warning would not be shown when compiling Elixir file in a Gleam project. ([Andrey Kozhev](https://github.com/ankddev)) - The build tool now has a new `gleam deps outdated` command that shows outdated versions for dependencies. For example: ```sh $ gleam deps outdated Package Current Latest ------- ------- ------ wibble 1.4.0 1.4.1 wobble 1.0.1 2.3.0 ``` ([Vladislav Shakitskiy](https://github.com/vshakitskiy)) - The format used for `gleam deps list` and the notice of available major version upgrades has been improved. ([Louis Pilfold](https://github.com/lpil)) - `gleam new` now creates the project directory using the confirmed project name when a suggested rename is accepted. ([Adi Salimgereyev](https://github.com/abs0luty)) - The build tool now provides better error message when trying to build Git dependencies without Git installed. Previously, it would show this error: ```txt error: Shell command failure There was a problem when running the shell command `git`. The error from the shell command library was: Could not find the stdio stream ``` Now it will show: ```txt error: Program not found The program `git` was not found. Is it installed? Documentation for installing Git can be viewed here: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git ``` ([Andrey Kozhev](https://github.com/ankddev)) ### Language server - The language server can now offer a code action to merge case clauses with the same body. For example: ```gleam case user { Admin(name:, ..) -> todo //^^^^^^^^^^^^^^^^^^^^^^^^ Guest(name:, ..) -> todo //^^^^^^^^^^^^^^^^ Selecting these two branches you can // trigger the "Merge case branches" code action _ -> todo } ``` Triggering the code action would result in the following code: ```gleam case user { Admin(name:, ..) | Guest(name:, ..) -> todo _ -> todo } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The "generate function" code action can now pick better names for arguments that use the record access syntax. For example: ```gleam pub type User { User(id: Int, name: String) } pub fn go(user: User) { authenticate(user.id, user.name) todo } ``` Having the language server generate the missing `authenticate` function will produce the following code: ```gleam pub fn authenticate(id: Int, name: String) { todo } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The "inline variable" code action can now trigger when used over the `let` keyword of a variable to inline. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The "add omitted labels" code action can now be used in function calls where some of the labels have been provided already. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The "generate function" code action can now trigger when used over constant values as well. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Grouping of related diagnostics should now work across more editors. Warnings will display together with their hints and you no longer have "go to next diagnostic" twice in a row. Zedlings rejoice! ([fruno](https://github.com/fruno-bulax/)) - The "pattern match on variable" code action can now pick better names when used on tuples. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - When renaming, if the new name is invalid, the language server will produce an error message instead of silently doing nothing. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - When providing autocomplete suggestions, the language server will now prioritise values which match the expected type of the value being completed. ([Surya Rose](https://github.com/GearsDatapacks)) - The language server now offers code action to add type annotations to all functions and constants. For example, ```gleam pub const answer = 42 pub fn add(x, y) { x + y } pub fn add_one(thing) { // ^ Triggering "Annotate all top level definitions" code action here let result = add(thing, 1) result } ``` Triggering the "Annotate all top level definitions" code action over the name of function `add_one` would result in following code: ```gleam pub const answer: Int = 42 pub fn add(x: Int, y: Int) -> Int { x + y } pub fn add_one(thing: Int) -> Int { let result = add(thing, 1) result } ``` ([Andrey Kozhev](https://github.com/ankddev)) - Qualify and unqualify code actions can now trigger when used over constant values as well. ([Vladislav Shakitskiy](https://github.com/vshakitskiy)) ### Formatter ### Bug fixes - Fixed two bugs that made gleam not update the manifest correctly, causing it to hit hex for version resolution on every operation and quickly reach request limits in large projects. ([fruno](https://github.com/fruno-bulax/)) - Fixed a bug where renaming a variable from an alternative pattern would not rename all its occurrences. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The compiler now reports an error for literal floats that are outside the floating point representable range on both targets. Previously it would only do that when compiling on the Erlang target. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a typo in the error message emitted when trying to run a module that does not have a main function. ([Louis Pilfold](https://github.com/lpil)) - Fixed a bug where the "Generate function" code action would be incorrectly offered when calling a function unsupported by the current target, leading to invalid code if the code action was accepted. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the formatter would not remove the right number of double negations from literal integers. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a typo for the "Invalid number of patterns" error. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a stack overflow when type checking some case expressions with thousands of branches. ([fruno](https://github.com/fruno-bulax/)) - The "add omitted label" code action no longer adds labels to arguments being piped in or the callbacks of `use`. ([fruno](https://github.com/fruno-bulax)) - Fixed a bug that caused the compiler to incorrectly optimise away runtime size checks in bit array patterns on the javascript target if they used calculations in the size of a segment (`_:size(wibble - wobble)`). ([fruno](https://github.com/fruno-bulax/)) - Add a missing BitArray constructor return type in the prelude's TypeScript definitions. ([Richard Viney](https://github.com/richard-viney)) - Fixed a bug where the BEAM would be shut down abruptly once the program had successfully finished running. ([Louis Pilfold](https://github.com/lpil)) - Fixed a bug where the "pattern match on variable" code action would generate invalid code when applied on a list's tail. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the "pattern match on variable" code action would generate invalid patterns by repeating a variable name already used in the same pattern. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the "generate function" code action would pop up for variants. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where useless comparison warnings for floats compared literal strings, claiming for example that `1.0 == 1.` was always false. ([fruno](https://github.com/fruno-bulax/)) - Fixed a bug where pattern variables in case clause guards would incorrectly shadow outer scope variables in other branches when compiling to JavaScript. ([Elias Haider](https://github.com/EliasDerHai)) - Fix invalid TypeScript definition being generated for variant constructors with long names that take no arguments. ([Richard Viney](https://github.com/richard-viney)) - Fixed a bug where the formatter would remove the `@deprecated` attribute from constants. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where invalid code would be generated on the JavaScript target in cases where an underscore followed the decimal point in a float literal. ([Patrick Dewey](https://github.com/ptdewey)) - Typos in the error message shown when trying to install a non-existent package have been fixed. ([Ioan Clarke](https://github.com/ioanclarke)) - Fixed a bug where the compiler would generate invalid Erlang and TypeScript code for unused opaque types referencing private types. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the type checker would allow invalid programs when a large group of functions were all mutually recursive. ([Surya Rose](https://github.com/GearsDatapacks)) - The compiler now provides a clearer error message when a function's return type is mistakenly declared using `:` instead of `->`. ([Gurvir Singh](https://github.com/baraich)) - Fixed a bug where the data generated for searching documentation was in the wrong format, preventing it from being used by Hexdocs search. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the "collapse nested case" code action would produce invalid code on a list tail pattern. ([Matias Carlander](https://github.com/matiascr)) - Fixed two bugs that made gleam not update the manifest correctly, causing it to hit hex for version resolution on every operation and quickly reach request limits in large projects. ([fruno](https://github.com/fruno-bulax/)) ================================================ FILE: changelog/v1.15.md ================================================ # Changelog ## v1.15.0 - 2026-03-16 - Fixed a bug where the language server wouldn't show the correct hover for some patterns. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ## v1.15.0-rc2 - 2026-03-16 ### Bug fixes - Marked the `bitSize` and `bitOffset` fields of `BitArray$BitArray` TypeScript definition as optional. ([acandoo](https://github.com/acandoo)) - Fixed invalid JavaScript code generation when ints or floats had prefixed `0`s before and after an `_` (e.g. `000_001`). ([Gavin Morrow](https://github.com/gavinmorrow)) ## v1.15.0-rc1 - 2026-03-04 ### Compiler - The compiler now reports an error when int and float binary operators are used incorrectly in case expression guards. ([Adi Salimgereyev](https://github.com/abs0luty)) - The compiler now supports string concatenation in clause guards: ```gleam case message { #(version, action) if version <> ":" <> action == "v1:delete" -> handle_delete() _ -> ignore() } ``` ([Adi Salimgereyev](https://github.com/abs0luty)) - Improved the code generated on the Erlang target when dividing a `Float` number by the literal number `0.0`. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The compiler no longer shows the structure of internal types when displaying an "Inexhaustive patterns" error, making it harder to inadvertently rely on internal implementation details. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The JavaScript prelude TypeScript API now contains the `BitArray$isBitArray` and `BitArray$BitArray$data` functions. ([Louis Pilfold](https://github.com/lpil)) - The type-checking JavaScript functions for Gleam data structure now use the `value is TypeName` TypeScript return type rather than `boolean`. ([Louis Pilfold](https://github.com/lpil)) - The compiler now shows better error message on passing unexpected labeled arguments by taking into account, whether it's a function or a constructor. ([Andrey Kozhev](https://github.com/ankddev)) ### Build tool - Upgraded `actions/checkout` from v4 to v6 in the GitHub Actions workflow used by `gleam new`. ([Christian Widlund](https://github.com/chrillep)) - When adding a package that does not exist on Hex, the message is a bit friendlier. ([Ameen Radwan](https://github.com/Acepie)) - The `gleam.toml` format is now consistent. The two sausage-case fields (`dev-dependencies` and `tag-prefix`) have been replaced by snake_case versions. Files using the old names will continue to work. ([Louis Pilfold](https://github.com/lpil)) - The password used for encrypting new local Hex API keys must now be at least 8 characters in length. ([Louis Pilfold](https://github.com/lpil)) - The `gleam help add`, `gleam help deps`, and `gleam help docs` commands have been improved with much more detailed documentation output. ([Louis Pilfold](https://github.com/lpil)) - When attempting to publish a package on Hex with an already taken name, the message is clearer. ([vyacheslavhere](https://github.com/vyacheslavhere)) - The build tool will now refuse to publish any package that has the default README generated by the `gleam new` command. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The build tool now uses OAuth and time-based one-time-passwords for authentication with Hex, improving security. Any legacy API tokens previously stored will be revoked after authenticating using OAuth. ([Louis Pilfold](https://github.com/lpil)) - The build tool will now refuse to publish any package that has no README, or an empty README. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ### Language server - The language server now allows extracting the start of a pipeline into a variable. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server now shows hover information when hovering over custom type definitions. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server now shows hover information when hovering over a custom type's constructors. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server is now smarter when producing autocompletions. Imagine you're updating your code to fully qualify the uses of the `Json` type: ```gleam pub fn payload() -> js|Json // ^ typing the module name ``` Accepting the `json.Json` completion will now produce the correct `json.Json` annotation rather than generating invalid code: `json.JsonJson`. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server now suggests completions for keywords like `echo`, `panic`, and `todo`. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The "Add missing patterns" code action will insert a catch all pattern for internal types, making it harder to inadvertently rely on internal implementation details. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server will no longer show completions for the fields of internal types outside the module they're defined in, making it harder to inadvertently rely on internal implementation details. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server now suggests a quick-fix code action for when a custom type definition uses a type parameter in its variants that have not been declared in its header. ([Andi Pabst](https://github.com/andipabst)) - The `Extract function` code action now provides more ergonomic/idiomatic refactorings when used on anonymous functions. ([Hari Mohan](https://github.com/seafoamteal)) - It's now possible to find references and rename variables in string prefix patterns. ```gleam case wibble { "1" as digit <> rest -> digit <> rest // ^^^^^ ^^^^ // You can now trigger "Find references" and "Rename" from here } ``` ([Igor Castejón](https://github.com/IgorCastejon)) - The language server now offers a rename for module when the cursor is placed over its import statement or when placed on its name or alias somewhere. For example, ```gleam import lustre/element import lustre/element/html import lustre/event fn view(model: Int) -> element.Element(Msg) { // ^ Renaming module to `el` here let count = int.to_string(model) html.div([], [ html.button([event.on_click(Incr)], [element.text(" + ")]), html.p([], [element.text(count)]), html.button([event.on_click(Decr)], [element.text(" - ")]), ]) } ``` Using renaming when hovering on module name will would result in the following code: ```gleam import lustre/element as el import lustre/element/html import lustre/event fn view(model: Int) -> el.Element(Msg) { let count = int.to_string(model) html.div([], [ html.button([event.on_click(Incr)], [el.text(" + ")]), html.p([], [el.text(count)]), html.button([event.on_click(Decr)], [el.text(" - ")]), ]) } ``` ([Vladislav Shakitskiy](https://github.com/vshakitskiy)) - The "Fill labels" code action now uses variables from scope when they match the label name and expected type, using shorthand syntax (`name:`) instead of `name: todo`. ([Vladislav Shakitskiy](https://github.com/vshakitskiy)) - The "Interpolate String" code action now lets the user "cut out" any portion of a string, regardless of whether it is a valid Gleam identifier or not. ([Hari Mohan](https://github.com/seafoamteal)) - When interpolating an expression at the very start or the very end of a string, redundant empty strings are no longer added before/after the interpolated expression. ([Hari Mohan](https://github.com/seafoamteal)) - The language server now performs best-effort zero value generation for `decode.failure` when using the `Generate Dynamic Decoder` code action. ([Hari Mohan](https://github.com/seafoamteal)) - The `Generate Dynamic Decoder` and `Generate To-JSON Function` code action now generate a decoder and an encoder, respectively, for `Nil` values. ([Hari Mohan](https://github.com/seafoamteal)) - The language server now supports renaming, go to definition, hover, and finding references from expressions in case clause guards. ([Surya Rose](https://github.com/GearsDatapacks)) - The language server now supports `textDocument/foldingRange`, enabling folding for contiguous import blocks and multiline top-level definitions such as function bodies, custom types, constants, and type aliases. For example, this import block: ```gleam import gleam/int import gleam/list import gleam/string ``` can now be folded in the editor to: ```gleam import gleam/int ... ``` ([Aayush Tripathi](https://github.com/aayush-tripathi)) - The function signature helper now displays original function definition generic names when arguments are unbound. For example, in this code: ```gleam pub fn wibble(x: something, y: fn() -> something, z: anything) { Nil } pub fn main() { wibble( ) // ↑ } ``` will show a signature help ```gleam wibble(something, fn() -> something, anything) ``` instead of ```gleam wibble(a, fn() -> a, b) -> Nil ``` ([Samuel Cristobal](https://github.com/scristobal)) ### Formatter - The formatter no longer wraps multiple tuple or field access into a block. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ### Bug fixes - The compiler now emits correctly-scoped JavaScript code for `case` expressions whose subjects directly match one of the branches. ([Justin Lubin](https://github.com/justinlubin)) - Fixed a bug where some bit array patterns would erroneously be marked as unreachable. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the Gleam standard library's `dict.each` function would incorrectly be assumed to be pure. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The compiler now correctly tracks the minimum required version for constant record updates to be `>= 1.14.0`. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The compiler now correctly tracks the minimum required version for expressions in `BitArray`s' `size` option to be `>= 1.12.0`. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the formatter would not properly format some function calls if the last argument was followed by a trailing comment. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the compiler would generate invalid code on the JavaScript target when using a `case` expression as the right hand side of an equality check in an `assert`. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the compiler would generate invalid code on the JavaScript target for some `case` expressions using clause guards. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The formatter no longer stack overflows trying to format lists with many items. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the formatter would not be able to consistently format a constant list with an `@internal` attribute. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The "convert to pipe" code action now works on nested function calls as well, rather than always being applied to the outermost one. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the compiler would not detect and reject duplicate modules in a project's dependencies. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server no longer shows completions when typing a number. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server no longer recommends the deprecated `@target` attribute. ([Hari Mohan](https://github.com/seafoamteal)) - The compiler no longer crashes when trying to pattern match on a `UtfCodepoint`. ([Hari Mohan](https://github.com/seafoamteal)) - Fixed a bug that would result in not being able to rename an aliased pattern. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed JavaScript codegen bug for `assert` when using `&&` with comparison operators on the right side. ([vyacheslavhere](https://github.com/vyacheslavhere)) - Added an error message when attempting to update packages that are not dependencies of the project, instead of failing silently. ([Etienne Boutet](https://github.com/EtienneBoutet), [Vladislav Shakitskiy](https://github.com/vshakitskiy)) - The build tool now doesn't perform code generation when exporting package interface. ([Andrey Kozhev](https://github.com/ankddev)) - The "Extract constant" code action now correctly places new constant when function has documentation. For example, ```gleam /// Wibble does some wobbling pub fn wibble() { let x = "wobble" // ^ Trigger "Extract constant" here x } ``` Previously, it would incorrectly place it below doc comment: ```gleam /// Wibble does some wobbling const x = "wobble" pub fn wibble() { x } ``` Now it will correctly place constant above doc comment: ```gleam const x = "wobble" /// Wibble does some wobbling pub fn wibble() { x } ``` ([Andrey Kozhev](https://github.com/ankddev)) - Fixed a bug where renaming would not work properly if there was an error in target file. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where generics in custom types would not be properly generated when emitting TypeScript declarations. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where dev dependencies would be compiled and included in production builds such as `gleam export erlang-shipment`. ([John Downey](https://github.com/jtdowney)) - Fixed a bug where the package cache would not properly be reset when a version of a package was replaced on Hex. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a crash (`bad_generator`) that could occur when a linked OTP process exits with a non-standard exit reason. ([John Downey](https://github.com/jtdowney)) - Fixed a bug where diagnostic about incorrect `size` and `unit` options would use incorrect error location. ([Andrey Kozhev](https://github.com/ankddev)) - `gleam add` now adds correct constraints for pre-release versions. ([Andrey Kozhev](https://github.com/ankddev)) - Fixed a bug where changes to a path dependency's own dependencies were not detected when rebuilding the root project, causing the compiler to report errors about missing modules. ([daniellionel01](https://github.com/daniellionel01)) - Fixed a bug where the compiler would crash when type-checking record updates if the constructor definition has a positional field defined after labelled fields. ([Hari Mohan](https://gituhub.com/seafoamteal)) ================================================ FILE: changelog/v1.2.md ================================================ # Changelog ## v1.2.0 - 2024-05-27 ## v1.2.0-rc2 - 2024-05-27 ### Bug fixes - Fixed a bug where the formatter would incorrectly move comments at the start of an anonymous function to the end of the arguments. ([Ameen Radwan](https://github.com/Acepie)) - Fixed a bug where the formatter would not indent a multiline function used in a pipeline. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the compiler would raise a warning for matching on a literal value if the case expression is used just for its guards. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where not all the analysis errors would be presented to the programmer. ([Louis Pilfold](https://github.com/lpil)) ## v1.2.0-rc1 - 2024-05-23 ### Build tool - A helpful error message is now shown if the `manifest.toml` file has been edited to be invalid in some way. ``` error: Corrupt manifest.toml The `manifest.toml` file is corrupt. Hint: Please run `gleam update` to fix it. ``` ([zahash](https://github.com/zahash)) - The error message shown when unable to find package versions that satisfy all the version constraints specified for a project's dependencies has been greatly improved. ``` error: Dependency resolution failed An error occurred while determining what dependency packages and versions should be downloaded. The error from the version resolver library was: Unable to find compatible versions for the version constraints in your gleam.toml. The conflicting packages are: - hellogleam - lustre_dev_tools - glint ``` ([zahash](https://github.com/zahash)) - A link to the package on Hex is no longer auto-added to the HTML documentation when building them locally. It is still added when publishing to Hex. ([Pi-Cla](https://github.com/Pi-Cla)) - An error is now emitted when compiling to Erlang and there is a Gleam module that would overwrite a built-in Erlang/OTP module, causing cryptic errors and crashes. ([Louis Pilfold](https://github.com/lpil)) ``` error: Erlang module name collision The module `src/code.gleam` compiles to an Erlang module named `code`. By default Erlang includes a module with the same name so if we were to compile and load your module it would overwrite the Erlang one, potentially causing confusing errors and crashes. Hint: Rename this module and try again. ``` - New subcommand `gleam hex revert` added. - You can specify the options like this: `gleam hex revert --package gling --version 1.2.3` - A new package can be reverted or updated within 24 hours of it's initial publish, a new version of an existing package can be reverted or updated within one hour. - You could already update packages even before this release by running: `gleam publish` again. ([Pi-Cla](https://github.com/Pi-Cla)) - When the user tries to replace a release without the `--replace` flag the error message now mentions the lack of a `--replace` flag. ``` error: Version already published Version v1.0.0 has already been published. This release has been recently published so you can replace it or you can publish it using a different version number Hint: Please add the --replace flag if you want to replace the release. ``` ([Pi-Cla](https://github.com/Pi-Cla)) ### Compiler - The compiler will now raise a warning for `let assert` assignments where the assertion is redundant. ``` warning: Redundant assertion ┌─ /home/lucy/src/app/src/app.gleam:4:7 │ 4 │ let assert x = get_name() │ ^^^^^^ You can remove this This assertion is redundant since the pattern covers all possibilities. ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Empty case expressions are no longer parse errors and will instead be exhaustiveness errors. ([Race Williams](https://github.com/raquentin)) - Improve error message if importing type using the value import syntax or vice versa. ``` error: Unknown module field ┌─ /src/one/two.gleam:1:19 │ 1 │ import gleam/one.{One} │ ^^^ Did you mean `type One`? `One` is only a type, it cannot be imported as a value. ``` ``` error: Unknown module type ┌─ /src/one/two.gleam:1:19 │ 1 │ import gleam/two.{type Two} │ ^^^^^^^^ Did you mean `Two`? `Two` is only a value, it cannot be imported as a type. ``` ([Pi-Cla](https://github.com/Pi-Cla/)) - The compiler will now raise a warning when you try to use `todo` or `panic` as if they were functions: this could previously lead to a confusing behaviour since one might expect the arguments to be printed in the error message. The error message now suggests the correct way to add an error message to `todo` and `panic`. ``` warning: Todo used as a function ┌─ /src/warning/wrn.gleam:2:16 │ 2 │ todo(1) │ ^ `todo` is not a function and will crash before it can do anything with this argument. Hint: if you want to display an error message you should write `todo as "my error message"` See: https://tour.gleam.run/advanced-features/todo/ ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Improve error message when something that is not a function appears on the right hand side of `<-` in a `use` expression. ```txt error: Type mismatch ┌─ /src/one/two.gleam:2:8 │ 2 │ use <- 123 │ ^^^ In a use expression, there should be a function on the right hand side of `<-`, but this value has type: Int See: https://tour.gleam.run/advanced-features/use/ ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Improve error message when a function with the wrong number of arguments appears on the right hand side of `<-` in a `use` expression. ```txt error: Incorrect arity ┌─ /src/one/two.gleam:3:8 │ 3 │ use <- func │ ^^^^ Expected no arguments, got 1 The function on the right of `<-` here takes no arguments. But it has to take at least one argument, a callback function. See: https://tour.gleam.run/advanced-features/use/ ``` ```txt error: Incorrect arity ┌─ /src/one/two.gleam:3:8 │ 3 │ use <- f(1, 2) │ ^^^^^^^ Expected 2 arguments, got 3 The function on the right of `<-` here takes 2 arguments. All the arguments have already been supplied, so it cannot take the `use` callback function as a final argument. See: https://tour.gleam.run/advanced-features/use/ ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Improve error message when the callback function of a `use` expression returns a value with the wrong type. Now the error will point precisely to the last statement and not complain about the whole block saying it has the wrong function type. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The compiler will now raise a warning when pattern matching on a literal value like a list, a tuple, integers, strings, etc. ``` warning: Redundant list ┌─ /src/warning/wrn.gleam:2:14 │ 2 │ case [1, 2] { │ ^^^^^^ You can remove this list wrapper Instead of building a list and matching on it, you can match on its contents directly. A case expression can take multiple subjects separated by commas like this: case one_subject, another_subject { _, _ -> todo } See: https://tour.gleam.run/flow-control/multiple-subjects/ ``` ``` warning: Match on a literal value ┌─ /src/warning/wrn.gleam:4:8 │ 4 │ case 1 { │ ^ There's no need to pattern match on this value Matching on a literal value is redundant since you can already tell which branch is going to match with this value. ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The compiler will now continue module analysis when there are errors in top level definitions. This means that when these errors occur the compiler will continue analysing the rest of the code to find other errors and type information. When using the build tool this means that the programmer will be shown multiple error messages when there are multiple problems in a module. When using the language server multiple error diagnostics will be shown, and the compiler will get updated type information about the code even when there are errors. This should improve the accuracy of feedback and suggestions from the language server as its information about the code will be more up-to-date. ([Ameen Radwan](https://github.com/Acepie)) and ([Louis Pilfold](https://github.com/Acepie)) - An informative error message is now emitted when attempting to use a function from another module in a constant expression. Previously this would result in a cryptic parse error. ``` error: Syntax error ┌─ /src/parse/error.gleam:3:18 │ 3 │ const wib: Int = wibble(1, "wobble") │ ^^^^^^^ Functions can only be called within other functions ``` ([Nino Annighoefer](https://github.com/nino)) - The compiler will now provide more helpful error messages when triple equals are used instead of double equals. ``` error: Syntax error ┌─ /src/parse/error.gleam:4:37 │ 4 │ [1,2,3] |> list.filter(fn (a) { a === 3 }) │ ^^^ Did you mean `==`? Gleam uses `==` to check for equality between two values. See: https://tour.gleam.run/basics/equality ``` ([Rabin Gaire](https://github.com/rabingaire)) - The compiler will now raise a warning for unreachable code that comes after a panicking expression. ``` pub fn main() { panic "unreachable!" } ``` ``` warning: Unreachable code ┌─ /src/warning/wrn.gleam:3:11 │ 3 │ "unreachable!" │ ^^^^^^^^^^^^^^ This code is unreachable because it comes after a `panic`. ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - JavaScript external module names may now include the character `@`. ([Louis Pilfold](https://github.com/lpil)) ### Formatter - Redundant alias names for imported modules are now removed. ```gleam import gleam/result as result ``` is formatted to ```gleam import gleam/result ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Comments are no longer moved out of constant lists, constant tuples and empty tuples. You can now write this: ```gleam const values = [ // This is a comment! 1, 2, 3 // Another comment... 11, // And a final one. ] ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Comments at the end of an anonymous function are no longer moved out of it. You can now write this: ```gleam fn() { todo // A comment here! } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Pipes can now be placed on a single line if they are short enough: ```gleam [1, 2, 3] |> list.map(int.to_string) |> string.join(with: "\n") ``` In addition you can also force the formatter to break a pipe on multiple lines by manually breaking it. This: ```gleam [1, 2, 3] // By putting a newline here I'm telling the formatter to split the pipeline |> list.map(int.to_string) |> string.join(with: "\n") ``` Will turn into this: ```gleam [1, 2, 3] |> list.map(int.to_string) |> string.join(with: "\n") ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Comments appearing after arguments are no longer moved to a different place. You can now write all of those: ```gleam type Record { Record( field: String, // comment_line_1: String, // comment_line_2: String, ) } ``` ```gleam pub fn main() { fn( a, // A comment 2 ) { 1 } } ``` ```gleam fn main() { let triple = Triple(1, 2, 3) let Triple( a, .., // comment ) = triple a } ``` ```gleam type Record { Record( // comment_line_1: String, // comment_line_2: String, ) } ``` ([Mateusz Ledwoń](https://github.com/Axot017)) ### Language Server - The code action to remove unused imports now removes the entire line is removed if it would otherwise be left blank. ([Milco Kats](https://github.com/katsmil)) - Hover for type annotations is now separate from the thing being annotated. ([Ameen Radwan](https://github.com/Acepie)) - Go to definition now works for direct type annotations. ([Ameen Radwan](https://github.com/Acepie)) - Go to definition now works for import statements. ([Ameen Radwan](https://github.com/Acepie)) - Hover now works for unqualified imports. ([Ameen Radwan](https://github.com/Acepie)) - The language server now detects when the `gleam.toml` config file has changed even if the client does not support watching files. This means that changes to the default target, new dependencies, and other configuration will be automatically detected. ([Louis Pilfold](https://github.com/lpil)) - Completions are now provided for values and types for use in unqualified imports. ([Ameen Radwan](https://github.com/Acepie)) - The character `.` is now advertised as a completion trigger character. ([Louis Pilfold](https://github.com/lpil)) - A new code action has been added to remove redundant tuples around case expression subjects and patterns when possible. ([Nicky Lim](https://github.com/nicklimmm)) ``` case #(x, y) { #(1, 2) -> 0 #(_, _) -> 1 } ``` Is rewritten to: ``` case x, y { 1, 2 -> 0 _, _ -> 1 } ``` - The language server will now register information about code even when there was a type error or similar. This means that the language server will be able to produce some up-to-date information about the project, even when errors are present. This should greatly improve the experience using the language server. ([Louis Pilfold](https://github.com/lpil)) ### Bug Fixes - Fixed [RUSTSEC-2021-0145](https://rustsec.org/advisories/RUSTSEC-2021-0145) by using Rust's `std::io::IsTerminal` instead of the `atty` library. ([Pi-Cla](https://github.com/Pi-Cla)) - Fixed the generated `mod` property in the Erlang application file when using the `application_start_module` property in `gleam.toml`. ([Alex Manning](https://github.com/rawhat)) - Fixed a confusing error message when using some reserved keywords. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed variables in constant expressions not being escaped correctly when exporting to JavaScript. ([PgBiel](https://github.com/PgBiel)) - Fixed a typo when attempting to publish a package with non-Hex dependencies ([inoas](https://github.com/inoas)) - Fixed import completions not appearing in some editors due to the range being longer than the line. ([Ameen Radwan](https://github.com/Acepie)) - Fixed a bug where TypeScript definitions files would use `null` instead of `undefined`. ([Louis Pilfold](https://github.com/lpil)) - Fixed a bug where unreachable infinite cases would not be detected when after a discard or variable pattern. ([Ameen Radwan](https://github.com/Acepie)) and ([Pi-Cla](https://github.com/Pi-Cla)) - Fixed a bug where module imports in guard clauses would not be generated correctly for js target. ([Ameen Radwan](https://github.com/Acepie)) - Fixed a bug where formatting constant lists of tuples would force the tuples to be broken across multiple lines, even when they could fit on a single line. ([Isaac Harris-Holt](https://github.com/isaacharrisholt)) - Fixed a bug where floating points in scientific notation with no trailing zeros would generate invalid Erlang code. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where having utf8 symbols in `gleam.toml`'s description value would result in an HTTP 500 error when running `gleam publish`. ([inoas](https://github.com/inoas)) - Unicode `\u{}` syntax in bit_array string segments now produce valid Erlang unicode characters ([Pi-Cla](https://github.com/Pi-Cla)) - Fixed a bug where using a constant defined in another module that referenced a private function could generate invalid code on the Erlang target. ([Shayan Javani](https://github.com/massivefermion)) - Fixed a bug where the language server would dynamically request the client to watch files even when the client has stated it does not support that. ([Louis Pilfold](https://github.com/lpil)) - Fixed a bug where local path dependencies could be mishandled on Windows. ([Francisco Montanez](https://github.com/Francisco-Montanez)) - Fixed a bug where adding a comment to a case clause would cause it to break on multiple lines. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where pattern matching on a string prefix containing an escape code could generate incorrect Erlang code. ([Nashwan Azhari](https://github.com/aznashwan)) - Fixed a bug where the formatter would produce uneven indentation within multi-line comments at the bottom of case blocks. ([Race Williams](https://github.com/raquentin)) ================================================ FILE: changelog/v1.3.md ================================================ # Changelog ## v1.3.0 - 2024-07-09 ## v1.3.0-rc3 - 2024-07-08 - Fixed a bug where not all pure function calls in constant definitions would be annotated as `@__PURE__` when compiling to JavaScript. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ## v1.3.0-rc2 - 2024-07-06 ### Formatter - Fixed a bug when multiple subjects in a case would be split even if not necessary. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ## v1.3.0-rc1 - 2024-06-30 ### Build tool - `gleam remove` will now present an error if a package being removed does not exist as a dependency in the project. ([Changfeng Lou](https://github.com/hnlcf)) - `gleam add` now takes an optional package version specifier, separated by a `@`, that resolves as follows: ```sh gleam add lustre@1.2.3 # "1.2.3" gleam add lustre@1.2 # ">= 1.2.0 and < 2.0.0" gleam add lustre@1 # ">= 1.0.0 and < 2.0.0" ``` ([Rahul D. Ghosal](https://github.com/rdghosal)) ### Compiler - Added more an informative error message for when attempting to use the `..` syntax to append to a list rather than prepend. ``` error: Syntax error ┌─ /src/parse/error.gleam:4:14 │ 4 │ [..rest, last] -> 1 │ ^^^^^^ I wasn't expecting elements after this Lists are immutable and singly-linked, so to match on the end of a list would require the whole list to be traversed. This would be slow, so there is no built-in syntax for it. Pattern match on the start of the list instead. ``` ([Antonio Iaccarino](https://github.com/eingin)) - The compiler now emits a warning for redundant function captures in a pipeline: ``` warning: Redundant function capture ┌─ /src/warning/wrn.gleam:5:17 │ 5 │ 1 |> wibble(_, 2) |> wibble(2) │ ^ You can safely remove this This function capture is redundant since the value is already piped as the first argument of this call. See: https://tour.gleam.run/functions/pipelines/ ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The syntax `[a..b]` is now deprecated in favour of the `[a, ..b]` syntax. This was to avoid it being mistaken for a range syntax, which Gleam does not have. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Functions etc named `maybe` are now escaped in generated Erlang as it is now a reserved word in Erlang/OTP 27. ([Jake Barszcz](https://github.com/barszcz)) - Functions, types and constructors named `maybe` and `else` are now escaped in generated Erlang to avoid clashing with Erlang's keywords. ([Jake Barszcz](https://github.com/barszcz)) and ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Non byte aligned arrays that use literals for size are now marked as an Unsupported feature for Javascript since they would already cause a runtime error on Javascript. This means if you compile specifically for Javascript you will now receive this error: ``` error: Unsupported feature for compilation target ┌─ /src/test/gleam_test.gleam:6:5 │ 6 │ <<1:size(5)>> │ ^^^^^^^^^ Non byte aligned array is not supported for JavaScript compilation. ``` Else any functions which rely on this will not be compiled into Javascript. ([Pi-Cla](https://github.com/Pi-Cla)) - Compilation fault tolerance is now at a statement level instead of a function level. This means that the compiler will attempt to infer the rest of the statements in a function when there is an error in a previous one. ([Ameen Radwan](https://github.com/Acepie)) - The JavaScript prelude is no-longer rewritten each time a project is compiled to JavaScript. ([Ofek Doitch](https://github.com/ofekd)) - Compiler now supports arithmetic operations in guards. ([Danielle Maywood](https://github.com/DanielleMaywood)) - Import cycles now show the location where the import occur. ([Ameen Radwan](https://github.com/Acepie)) - Error messages resulting from unexpected tokens now include information on the found token's type. This updated message explains how the lexer handled the token, so as to guide the user towards providing correct syntax. Following is an example, where the error message indicates that the name of the provided field conflicts with a keyword: ``` 3 │ A(type: String) │ ^^^^ I was not expecting this Found the keyword `type`, expected one of: - `)` - a constructor argument name ``` ([Rahul D. Ghosal](https://github.com/rdghosal)) - When compiling to JavaScript constants will now be annotated as `@__PURE__`. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ### Formatter ### Language Server - The language server will now suggest the "Remove redundant tuple" action even if the case expression contains some catch all patterns: ``` case #(a, b) { #(1, 2) -> todo _ -> todo } ``` Becomes: ``` case a, b { 1, 2 -> todo _, _ -> todo } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - LSP can now suggest completions for values and types from importable modules and adds the import to the top of the file. ([Ameen Radwan](https://github.com/Acepie)) - Diagnostics with extra labels now show the diagnostic in all locations including across multiple files. ([Ameen Radwan](https://github.com/Acepie)) - LSP completions now use the "text_edit" language server API resulting in better/more accurate insertions. ([Ameen Radwan](https://github.com/Acepie)) - Completions are no longer provided inside comments. ([Nicky Lim](https://github.com/nicklimmm)) - The language server will now show all the ignored fields when hovering over `..` in a record pattern. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ### Bug Fixes - Fixed a bug where the compiler would output a confusing error message when trying to use the spread syntax to append to a list. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the formatter would strip empty lines out of the body of an anonymous function passed as an argument. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the compiler would crash when a type was defined with the same name as an imported type. ([Gears](https://github.com/gearsdatapacks)) - Fixed a bug where a horizontal scrollbar would appear on code blocks in built documentation when they contained lines 79 or 80 characters long. ([Richard Viney](https://github.com/richard-viney)) - Fixed a bug where importing a record constructor in an unqualified fashion and aliasing it and then using it in a constant expression would generate invalid JavaScript. ([Louis Pilfold](https://github.com/lpil)) - Fixed a bug where the compiler would crash because types weren't registered if they referenced a non-existent type. ([Gears](https://github.com/gearsdatapacks)) - Fixed a bug where the compiler would generate invalid Erlang when pattern matching on strings with an `as` pattern. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the compiler would warn that a module alias was unused when it was only used in case patterns. ([Michael Jones](https://github.com/michaeljones)) ## v1.2.1 - 2024-05-30 ### Bug Fixes - Fixed a bug where the compiler could fail to detect modules that would clash with Erlang modules. ([Louis Pilfold](https://github.com/lpil)) - Fixed a bug where dependency version resolution could crash for certain release candidate versions. ([Marshall Bowers](https://github.com/maxdeviant)) - Fixed a bug where trailing comments would be moved out of a bit array. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ================================================ FILE: changelog/v1.4.md ================================================ # Changelog ## v1.4.0 - 2024-08-02 ### Bug Fixes - Fixed a bug where pipe function arity errors could have an incorrect error message. ([sobolevn](https://github.com/sobolevn)) - Fixed a bug where the case of type parameters would not be checked. ([Surya Rose](https://github.com/gearsdatapacks)) - Fixed a bug where the language server would still show completions when inside a comment. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ## v1.4.0-rc1 - 2024-07-29 ### Build tool - `gleam docs build` now takes an optional `--target` flag to specify the target platform for the generated documentation. ([Jiangda Wang](https://github.com/frank-iii)) - Warnings are now emitted each time the project is built, even if the module the warnings originated from were loaded from the cache rather than recompiling. ([Louis Pilfold](https://github.com/lpil)) ### Compiler - Labelled arguments can now use the label shorthand syntax. This means that when you're passing a variable as a labelled argument and it happens to have the same name as the label, you can omit the variable name: ```gleam pub fn date(day day: Int, month month: Month, year year: Year) -> Date { todo } pub fn main() { let day = 11 let month = October let year = 1998 date(year:, month:, day:) // This is the same as writing // date(year: year, month: month, day: day) } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Labelled pattern variables can now use the label shorthand syntax. This means that when you're pattern matching on a record constructor and binding its labelled fields to variables that happen to have the same name, you can omit the variable name: ```gleam pub type Date Date(day: Int, month: Month, year: Year) } pub fn main() { case Date(11, October, 1998) { Date(year:, month:, day:) -> todo // This is the same as writing // Date(year: year, month: month, day: day) -> todo } } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The warning for the deprecated `[..]` pattern has been improved. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Record accessors are now fault tolerant. This means an invalid label can be properly detected and won't invalidate the rest of the expression. ([Ameen Radwan](https://github.com/Acepie)) - Erlang type spec generation has been improved to avoid new warnings emitted in OTP27. ([Damir Vandic](https://github.com/dvic)) - Error messages for invalid record constructors now contain a restructured example of what the user likely intended. This is especially helpful for users coming from other languages, like Rust or Go. For example, provided a User type: ```gleam pub type User { name: String } ``` The compiler errors with the following message: ``` error: Syntax error ┌─ /src/parse/error.gleam:3:5 │ 3 │ name: String, │ ^^^^ I was not expecting this Each custom type variant must have a constructor: pub type User { User( name: String, ) } ``` ([Rahul D. Ghosal](https://github.com/rdghosal)) - The `<>` string concatenation operator can now be used in constant expressions. ([Thomas](https://github.com/DeviousStoat)) - Function calls are now fault tolerant. This means that errors in the function call arguments won't stop the rest of the call from being analysed. ([Ameen Radwan](https://github.com/Acepie)) - The error message presented when a function is called in a guard has been improved. ([Thomas](https://github.com/DeviousStoat)) - Case expressions are now fault tolerant. This means an subject, pattern, guard, or then body can be properly detected and won't invalidate the rest of the expression. ([Ameen Radwan](https://github.com/Acepie)) - Documentation comments that come before a regular comment are no longer clumped together with the documentation of the following definition. Now commenting out a definition won't result in its documentation merging with the following one's. ```gleam /// This doc comment will be ignored! // a commented definition // fn wibble() {} /// Wibble's documentation. fn wibble() { todo } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The `little` and `big` endianness options, the `signed` and `unsigned` integer options, and sized floats (32-bit and 64-bit), can now be used in bit array expressions and patterns on the JavaScript target. ([Richard Viney](https://github.com/richard-viney)) - The `utf8` option can now be used with constant strings in bit array patterns on the JavaScript target. ([Richard Viney](https://github.com/richard-viney)) ### Formatter - The formatter will no longer move a documentation comment below a regular comment following it. This snippet of code is left as it is by the formatter: ```gleam /// This doc comment will be ignored! // a commented definition // fn wibble() {} /// Wibble's documentation. fn wibble() { todo } ``` While previously all documentation comments would be merged together into one, ignoring the regular comment separating them: ```gleam // a commented definition // fn wibble() {} /// This doc comment will be ignored! /// Wibble's documentation. fn wibble() { todo } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ### Language Server - The language server can now show completions for fields if a record access is being attempted. ([Ameen Radwan](https://github.com/Acepie)) - The language server will now insert a blank line before the first statement when inserting a new import and there are no other imports at the top of the module. ([Zhomart Mukhamejanov](https://github.com/Zhomart)) - The language server now suggests a code a action to rename variables, types and functions when they don't match the Gleam naming requirements: ```gleam let myNumber = 10 ``` Becomes: ```gleam let my_number = 10 ``` ([Surya Rose](https://github.com/gearsdatapacks)) - The language server can now suggest a code action to convert `let assert` into a case expression: ```gleam let assert Ok(value) = get_result() ``` Becomes: ```gleam let value = case get_result() { Ok(value) -> value _ -> panic } ``` ([Surya Rose](https://github.com/gearsdatapacks)) - The language server can now show signature help when writing functions. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server now supports listing document symbols, such as functions and constants, for the current Gleam file. ([PgBiel](https://github.com/PgBiel)) - The language server can now suggest a code action to automatically use shorthand labels where possible: ```gleam case date { Day(day: day, month: month, year: year) -> todo } ``` Becomes: ```gleam case date { Day(day:, month:, year:) -> todo } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server can now show completions for labels when writing a function call or record construction. ([Ameen Radwan](https://github.com/Acepie)) - The language server can now suggest a code action to fill in the labels of a function call: ```gleam pub type Date { Date(year: Int, month: Int, day: Int) } pub fn main() { Date() } ``` Becomes: ```gleam pub type Date { Date(year: Int, month: Int, day: Int) } pub fn main() { Date(year: todo, month: todo, day: todo) } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Completions are now sorted by priority based on why the completion is in the list. This means that more specific completions like labels and local definitions will be shown before more broad completions like functions from a not yet imported module. ([Ameen Radwan](https://github.com/Acepie)) ### Bug Fixes - Functions, types and constructors named `module_info` are now escaped in generated Erlang code to avoid conflicts with the builtin `module_info/0` and `module_info/1` functions. ([Juraj Petráš](https://github.com/Hackder)) - Fixed formatting of comments at the start of a case branch. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where a private type could be leaked from an internal module. ([Ameen Radwan](https://github.com/Acepie)) - Fixed a bug where certain binops would not wrap their arguments properly thus generating invalid JavaScript. ([Ameen Radwan](https://github.com/Acepie)) - Fixed formatting of function definitions marked as `@internal` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where importing a record constructor in an unqualified fashion and aliasing it and then using it in a case guard expression would generate invalid JavaScript. ([PgBiel](https://github.com/PgBiel)) ## v1.3.2 - 2024-07-11 ### Language Server - The language server no longer shows completions when inside a literal string. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ### Bug Fixes - Fixed a bug where the compiler would report errors for duplicate `@external` attributes with inconsistent spans between Erlang and JavaScript. ([Connor Szczepaniak](https://github.com/cszczepaniak)) - Fixed a bug where `gleam add` would fail to parse version specifiers correctly. ([Louis Pilfold](https://github.com/lpil)) - Fixed a bug where single clause case expressions could generate JavaScript code with incorrectly rewritten JavaScript variable names. ([Louis Pilfold](https://github.com/lpil)) ## v1.3.1 - 2024-07-10 ### Bug Fixes - Fixes a bug with import cycle detection when there is more than 2 imports in the cycle. ([Ameen Radwan](https://github.com/Acepie)) ================================================ FILE: changelog/v1.5.md ================================================ # Changelog ## v1.5.0 - 2024-09-19 ## v1.5.0-rc2 - 2024-09-18 ### Bug Fixes - Fixed a bug where the formatter would not format nested tuple access properly. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where multi-variant custom type accessors wouldn't be properly detected. ([Louis Pilfold](https://github.com/lpil)) ## v1.5.0-rc1 - 2024-09-14 ### Build tool - The `--no-print-progress` flag has been added to prevent the build tool from printing messages as the project is built. ([Ankit Goel](https://github.com/crazymerlyn)) - The compiler is now able to run a dependency's module using `gleam run -m` even when there's compilation errors in your own project's code. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - HTML docs: make module names in sidebar wrap before a / when possible ([Jiangda Wang](https://github.com/frank-iii)) - The printing of runtime errors has been improved, including those from linked processes. ([Louis Pilfold](https://github.com/lpil)) - OTP application trees are now shut down gracefully when `main` exits. ([Louis Pilfold](https://github.com/lpil)) - The `gleam fix` command can now update a project's `gleam` version constraint to make sure it respects the inferred minimum required version. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The build tool now refuses to publish a project where the `gleam` version constraint would include a compiler version that doesn't support the features used by the package. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - If a project doesn't specify a `gleam` version constraint, the build tool will automatically infer it and add it to the project's `gleam.toml` before publishing it. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ### Compiler - Compiler progress is now printed to stderr, instead of stdout. ([Victor Kobinski](https://github.com/vkobinski)) - It is now possible to omit the `:utf8` option for literal strings used in a `BitArray` segment. ```gleam <<"Hello", " ", "world">> ``` Is the same as: ```gleam <<"Hello":utf8, " ":utf8, "world":utf8>> ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - In inexhaustive pattern match errors the missing variants are now printed using the correct syntax for the module the error is emitted in, rather than the module it was defined in. For example, if you had this code: ```gleam import gleam/option pub fn main() { let an_option = option.Some("wibble!") case an_option { option.None -> "missing" } } ``` The error message would show the qualified `option.Some(_)` as the missing pattern: ```txt error: Inexhaustive patterns ┌─ /Users/giacomocavalieri/Desktop/prova/src/prova.gleam:5:3 │ 5 │ ╭ case an_option { 6 │ │ option.None -> "missing" 7 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: option.Some(_) ``` ([Surya Rose](https://github.com/gearsdatapacks)) - Anonymous functions that are immediately called with a record or a tuple as an argument are now inferred correctly without the need to add type annotations. For example you can now write: ```gleam fn(x) { x.0 }(#(1, 2)) // ^ you no longer need to annotate this! ``` ([sobolevn](https://github.com/sobolevn)) - Anonymous functions that are being piped a record or a tuple as an argument are now inferred correctly without the need to add type annotations. For example you can now write: ```gleam pub type User { User(name: String) } pub fn main() { User("Giacomo") |> fn(user) { user.name } // ^^^^ you no longer need to annotate this! |> io.debug } ``` ([sobolevn](https://github.com/sobolevn)) - The record pattern matching syntax `Record(a ..)` is now deprecated in favour of the `Record(a, ..)` syntax. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Adds a better error message when module names are used as values. For example the following code: ```gleam import gleam/list pub fn main() { list } ``` Results in the error: ```txt error: Module `list` used as a value ┌─ /Users/giacomocavalieri/Desktop/prova/src/prova.gleam:4:3 │ 4 │ list │ ^^^^ Modules are not values, so you cannot assign them to variables, pass them to functions, or anything else that you would do with a value. ``` ([sobolevn](https://github.com/sobolevn)) - An helpful error message has been added when the programmer attempts to write a function within a custom type definition, likely trying to declare an OOP class. For example: ```gleam pub type User { User(name: String) fn greet(user: User) -> String { "hello " <> user.name } } ``` Now results in the following error: ```txt error: Syntax error ┌─ /Users/giacomocavalieri/Desktop/prova/src/prova.gleam:8:3 │ 8 │ fn greet(user: User) -> String { │ ^^ I was not expecting this Found the keyword `fn`, expected one of: - `}` - a record constructor Hint: Gleam is not an object oriented programming language so functions are declared separately from types. ``` ([sobolevn](https://github.com/sobolevn)) - The compiler now gives a hint to import a module when accessing modules that aren't imported. It only suggests a module if it exports a type/value with the same name as what the user was trying to access: ```gleam pub fn main() { io.println("Hello, world!") } ``` Produces the following error: ``` error: Unknown module ┌─ /src/file.gleam:2:3 │ 2 │ io.println("Hello, world!") │ ^^ No module has been found with the name `io`. Hint: Did you mean to import `gleam/io`? ``` This code, however, produces no hint: ```gleam pub fn main() { io.non_existent() } ``` ([Surya Rose](https://github.com/gearsdatapacks)) - The compiler now provides improved suggestions in the error for an inexhaustive case expression. The following code: ```gleam let a = True case a {} ``` Now produces this error: ``` error: Inexhaustive patterns ┌─ /src/file.gleam:3:3 │ 3 │ case a {} │ ^^^^^^^^^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: False True ``` Whereas before, it would suggest `_` as the only missing pattern. ([Surya Rose](https://github.com/GearsDatapacks)) - Improve error message for using `@external` with unknown target ([Jiangda Wang](https://github.com/frank-iii)) - Improved error title when using an unknown module value. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The compiler now shows an helpful error message if you try writing an `if` expression instead of a case. For example, this code: ```gleam pub fn main() { let a = if wibble { 1 } } ``` Results in the following error: ```txt error: Syntax error ┌─ /src/parse/error.gleam:3:11 │ 3 │ let a = if wibble { │ ^^ Gleam doesn't have if expressions If you want to write a conditional expression you can use a `case`: case condition { True -> todo False -> todo } See: https://tour.gleam.run/flow-control/case-expressions/ ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The compiler can now infer the minimum Gleam version needed for your code to compile and emits a warning if the project's `gleam` version constraint doesn't include it. For example, let's say your `gleam.toml` has the constraint `gleam = ">= 1.1.0"` and your code is using some feature introduced in a later version: ```gleam // Concatenating constant strings was introduced in v1.4.0! pub const greeting = "hello " <> "world!" ``` You would now get the following warning: ```txt warning: Incompatible gleam version range ┌─ /Users/giacomocavalieri/Desktop/datalog/src/datalog.gleam:1:22 │ 1 │ pub const greeting = "hello " <> "world!" │ ^^^^^^^^^^^^^^^^^^^^ This requires a Gleam version >= 1.4.0 Constant strings concatenation was introduced in version v1.4.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.1.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.4.0" ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - On the JavaScript target, non-byte aligned integers in bit array patterns are now reported as a compile-time error. ([Richard Viney](https://github.com/richard-viney)) ### Formatter - The formatter now adds a `todo` after a `use` expression if it is the last expression in a block. For example, the following code: ```gleam pub fn main() { use user <- result.try(fetch_user()) } ``` Is rewritten as: ```gleam pub fn main() { use user <- result.try(fetch_user()) todo } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ### Language Server - The language server can now show completions for local variables inside a function. ([Ezekiel Grosfeld](https://github.com/ezegros)) - The language server can now suggest a code action to assign an unused value to `_`. ([Jiangda Wang](https://github.com/frank-iii)) - The language server can now suggest a code action to import modules for existing code which references unimported modules: ```gleam pub fn main() { io.println("Hello, world!") } ``` Becomes: ```gleam import gleam/io pub fn main() { io.println("Hello, world!") } ``` ([Surya Rose](https://github.com/gearsdatapacks)) - The Language Server can now suggest a code action to fill in the missing patterns of a case expression: ```gleam let a = True case a {} ``` Becomes: ```gleam let a = True case a { False -> todo True -> todo } ``` ([Surya Rose](https://github.com/GearsDatapacks)) ### Bug Fixes - Fixed a bug where the warnings were printed above the errors without any new line between them. ([Victor Kobinski](https://github.com/vkobinski)) - Fixed a bug which caused the language server and compiler to crash when two constructors of the same name were created. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where jumping to the definition of an unqualified function would produce the correct location, but remain in the same file. ([Surya Rose](https://github.com/gearsdatapacks)) - Fixed a bug where incorrect syntax error message were shown, when using `:` or `=` in wrong positions in expressions. ([Ankit Goel](https://github.com/crazymerlyn)) - Fixed a bug where the compiler would crash when pattern matching on a type which had constructors of duplicate names. ([Surya Rose](https://github.com/gearsdatapacks)) - Fixed a bug where referencing record constructors in JavaScript constants but not calling them could produce invalid code. ([Louis Pilfold](https://github.com/lpil)) - Fixed a bug where source links in HTML documentation would be incorrect for Codeberg, SourceHut, and Gitea. ([sobolevn](https://github.com/sobolevn)) - Fixed a bug with Erlang code generation for discard utf8 patterns in bit arrays. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug which affected inference of function calls in pipe expressions. ([sobolevn](https://github.com/sobolevn)) - Improved an error message when using variable names starting with an underscore in expression like: `let some = _func()` or `case { 1 -> _func() }` ([sobolevn](https://github.com/sobolevn)) - Fixed a bug where the provided `REBAR_BARE_COMPILER_OUTPUT_DIR` env var would use relative path instead of absolute path causing compilation errors in some packages. ([Gustavo Inacio](https://github.com/gusinacio)) - Fixed a bug where the compiler would print incorrect missing patterns for inexhaustive case expressions matching on more than one subject. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the compiler would not check the target support of a function if it was imported and not used, and generate invalid code. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where an qualified unused constructor wouldn't be reported as unused. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The Language Server now correctly shows completions for values in the Gleam prelude. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the language server wouldn't let you jump to the definition of a function with an external implementation defined in the same module. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ## v1.4.1 - 2024-08-04 ### Bug Fixes - Fix a bug that caused record accessors for private types to not be completed by the LSP, even when in the same module. ([Ameen Radwan](https://github.com/Acepie)) ================================================ FILE: changelog/v1.6.md ================================================ # Changelog ## v1.6.0 - 2024-11-18 ### Bug fixes - Fixed a bug where the language server would delete pieces of code when applying a suggested autocompletion. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ## v1.6.0-rc2 - 2024-11-14 ### Build tool - The version of Erlang used in the GitHub Actions workflow created by `gleam new` has been increased from v26.0.2 to v27.1.2. ([Richard Viney](https://github.com/richard-viney)) ### Bug fixes - Fixed a bug where some reserved field names were not properly escaped in custom types on the JavaScript target. ([yoshi](https://github.com/joshi-monster)) - Fixed a bug where a warning about unsafe integers on the JavaScript target was emitted when the enclosing function has an external JavaScript implementation. ([Richard Viney](https://github.com/richard-viney)) - Fixed a bug where updating a dependency could break the build cache. ([yoshi](https://github.com/joshi-monster)) - Fixed a bug where if the build directory was not writable then the build tool would crash when trying to lock the build directory. ([Zak Farmer](https://github.com/ZakFarmer)) ## v1.6.0-rc1 - 2024-11-10 ### Build tool - The `--template` flag for `gleam new` takes the values `erlang` and `javascript` to specify what target to use, with `erlang` being the default. ([Mohammed Khouni](https://github.com/Tar-Tarus)) - The Erlang/Elixir compiler process is now reused for all packages, shaving off 0.3-0.5s per compiled package. ([yoshi](https://github.com/joshi-monster)) - When a symlink cannot be made on Windows due to lack of permissions the error now includes information on how to enable Windows' developer mode, enabling symlinks. ([Louis Pilfold](https://github.com/lpil)) - The cli can now update individual dependencies. `gleam update` and `gleam deps update` now take an optional list of package names to update: ```sh gleam update package_a gleam deps update package_b package_c ``` This allows for selective updating of dependencies. When package names are provided, only those packages and their unique dependencies are unlocked and updated. If no package names are specified, the command behaves as before, updating all dependencies. ([Jason Sipula](https://github.com/SnakeDoc)) - The `repository` config in `gleam.toml` can now optionally include a `path` so that source links in generated documentation are correct for packages that aren't located at the root of their repository: ```toml [repository] type = "github" user = "gleam-lang" repo = "gleam" path = "packages/my_package" ``` ([Richard Viney](https://github.com/richard-viney)) ### Compiler - The compiler now prints correctly qualified or aliased type names when printing type errors. This code: ```gleam pub type Int pub fn different_int_types(value: Int) { value } pub fn main() { different_int_types(20) } ``` Produces this error: ``` error: Type mismatch ┌─ /src/wibble.gleam:8:23 │ 8 │ different_int_types(20) │ ^^ Expected type: Int Found type: gleam.Int ``` ([Surya Rose](https://github.com/GearsDatapacks)) - The compiler can now suggest to pattern match on a `Result(a, b)` if it's being used where a value of type `a` is expected. For example, this code: ```gleam import gleam/list import gleam/int pub fn main() { let not_a_number = list.first([1, 2, 3]) int.add(1, not_a_number) } ``` Results in the following error: ```txt error: Type mismatch ┌─ /src/one/two.gleam:6:9 │ 6 │ int.add(1, not_a_number) │ ^^^^^^^^^^^^ Expected type: Int Found type: Result(Int, a) Hint: If you want to get a `Int` out of a `Result(Int, a)` you can pattern match on it: case result { Ok(value) -> todo Error(error) -> todo } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Improved the error message for unknown record fields, displaying an additional note on how to have a field accessor only if it makes sense. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The compiler now ignores `optional` dependencies when resolving versions unless explicitly specified. ([Gustavo Inacio](https://github.com/gusinacio)) - Improved the error message for using `@deprecated` with no deprecation message ([Jiangda Wang](https://github.com/frank-iii)) - Optimised creation of bit arrays on the JavaScript target. ([Richard Viney](https://github.com/richard-viney)) - The compiler can now infer the variant of custom types within expressions that construct or pattern match on them. Using this information it can now be more precise with exhaustiveness checking, identifying patterns for the other variants as unnecessary. ```gleam pub type Pet { Dog(name: String, cuteness: Int) Turtle(name: String, speed: Int, times_renamed: Int) } pub fn main() { // We know `charlie` is a `Dog`... let charlie = Dog("Charles", 1000) // ...so you do not need to match on the `Turtle` variant case charlie { Dog(..) -> todo } } ``` This also means that the record update syntax can be used on multi-variant custom types, so long as the variant can be inferred from the surrounding code. ```gleam pub fn rename(pet: Pet, to name: String) -> Pet { case pet { Dog(..) -> Dog(..pet, name:) Turtle(..) -> Turtle(..pet, name:, times_renamed: pet.times_renamed + 1) } } ``` Variant specific fields can also be used with the accessor syntax. ```gleam pub fn speed(pet: Pet) -> Int { case pet { Dog(..) -> 500 Turtle(..) -> pet.speed } } ``` ([Surya Rose](https://github.com/GearsDatapacks)) - When targeting JavaScript the compiler now emits a warning for integer literals and constants that lie outside JavaScript's safe integer range: ```txt warning: Int is outside the safe range on JavaScript ┌─ /Users/richard/Desktop/int_test/src/int_test.gleam:1:15 │ 1 │ pub const i = 9_007_199_254_740_992 │ ^^^^^^^^^^^^^^^^^^^^^ This is not a safe integer on JavaScript This integer value is too large to be represented accurately by JavaScript's number type. To avoid this warning integer values must be in the range -(2^53 - 1) - (2^53 - 1). See JavaScript's Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER properties for more information. ``` ([Richard Viney](https://github.com/richard-viney)) ### Formatter - The formatter no longer removes the first argument from a function which is part of a pipeline if the first argument is a capture and it has a label. This snippet of code is left as is by the formatter: ```gleam pub fn divide(dividend a: Int, divisor b: Int) -> Int { a / b } pub fn main() { 10 |> divide(dividend: _, divisor: 2) } ``` Whereas previously, the label of the capture variable would be lost: ```gleam pub fn divide(dividend a: Int, divisor b: Int) -> Int { a / b } pub fn main() { 10 |> divide(divisor: 2) } ``` ([Surya Rose](https://github.com/GearsDatapacks)) ### Language Server - The Language Server now displays correctly qualified or aliased type names when hovering over a value in a Gleam file: ```gleam import gleam/option const value = option.Some(1) // ^ hovering here shows `option.Option(Int)` ``` ```gleam import gleam/option.{type Option as Maybe} const value = option.Some(1) // ^ hovering here shows `Maybe(Int)` ``` ([Surya Rose](https://github.com/GearsDatapacks)) - The Language Server now suggests a code action to add type annotations to local variables, constants and functions: ```gleam pub fn add_int_to_float(a, b) { a +. int.to_float(b) } ``` Becomes: ```gleam pub fn add_int_to_float(a: Float, b: Int) -> Float { a +. int.to_float(b) } ``` ([Surya Rose](https://github.com/GearsDatapacks)) - The Language Server now suggests a code action to convert qualified imports to unqualified imports, which updates all occurrences of the qualified name throughout the module: ```gleam import option pub fn main() { option.Some(1) } ``` Becomes: ```gleam import option.{Some} pub fn main() { Some(1) } ``` ([Jiangda Wang](https://github.com/Frank-III)) - The Language Server now suggests a code action to convert unqualified imports to qualified imports, which updates all occurrences of the unqualified name throughout the module: ```gleam import list.{map} pub fn main() { map([1, 2, 3], fn(x) { x * 2 }) } ``` Becomes: ```gleam import list.{} pub fn main() { list.map([1, 2, 3], fn(x) { x * 2 }) } ``` ([Jiangda Wang](https://github.com/Frank-III)) ### Bug Fixes - Fixed a bug in the compiler where shadowing a sized value in a bit pattern would cause invalid erlang code to be generated. ([Antonio Iaccarino](https://github.com/eingin)) - Fixed a bug where the formatter would not format strings with big grapheme clusters properly. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed the `BitArray` constructor not being present in the types for the JavaScript prelude. ([Richard Viney](https://github.com/richard-viney)) - Fixed a bug where generated TypeScript definitions were invalid for opaque types that use a private type. ([Richard Viney](https://github.com/richard-viney)) - Fixed the prelude re-export in generated TypeScript definitions. ([Richard Viney](https://github.com/richard-viney)) - Fixed a bug where the compiler would incorrectly type-check and compile calls to functions with labelled arguments in certain cases. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where importing type aliases that reference unimported modules would generate invalid TypeScript definitions. ([Richard Viney](https://github.com/richard-viney)) - When splitting a constant list made of records, the formatter will keep each item on its own line to make things easier to read. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the compiler would crash when pattern matching on a type which was defined with duplicate fields in one of its variants. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the WASM compiler would return incomplete JavaScript when unsupported features were used. It now returns a compilation error. ([Richard Viney](https://github.com/richard-viney)) - Fixed a bug where incorrect code would be generated for external function on the Erlang target if any of their arguments were discarded. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug in the error message when using wrong values in a pipe where the message would swap the "Expected" and "Found" types. ([Markus Pettersson](https://github.com/MarkusPettersson98/)) - Fixed a bug where the parser would incorrectly parse a record constructor with no arguments. ([Louis Pilfold](https://github.com/lpil)) - Fixed a bug where the parser would incorrectly parse a generic type constructor with no arguments. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the parser would incorrectly parse a generic type definition with no arguments. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the language server wouldn't show hints when hovering over the tail of a list. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where attempting to jump to the definition of a type from the annotation of a parameter of an anonymous function would do nothing. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where referencing record constructors in JavaScript guards but not calling them could produce invalid code. ([PgBiel](https://github.com/PgBiel)) - Fixed a bug where using the label shorthand syntax inside of a record update wouldn't emit a warning when the minimum specified Gleam version was < 1.4.0. ([yoshi](https://github.com/joshi-monster)) - Fixed a bug where no error would be reported when duplicate labelled arguments were supplied in a record update. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where an incorrect bit array would be generated on JavaScript for negative `Int` values when the segment's `size` was wider than 48 bits or when the `Int` value was less than the minimum representable value for the segment size. ([Richard Viney](https://github.com/richard-viney)) - Fixed a bug where an incorrect `Int` would be returned when pattern matching to a negative value wider than 48 bits in a bit array. ([Richard Viney](https://github.com/richard-viney)) - Fixed a bug where unused values coming from other modules wouldn't raise a warning. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ## v1.5.1 - 2024-09-26 ### Bug Fixes - Fixed a bug where Erlang file paths would not be escaped on Windows. ([Louis Pilfold](https://github.com/lpil)) ================================================ FILE: changelog/v1.7.md ================================================ # Changelog ## 1.7.0 - 2025-01-05 Happy birthday Louis! 🎁 ## 1.7.0-rc3 - 2025-01-02 ### Formatter - Function captures are now formatted like regular function calls. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Record updates are now formatted like function calls. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ### Bug fixes - Fixed a bug where the "convert from use" code action would generate invalid code with labelled arguments. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where private types would be allowed to be used in other modules. ([Surya Rose](https://github.com/GearsDatapacks)) ## 1.7.0-rc2 - 2024-12-30 ### Bug fixes - Fixed a bug on JavaScript where a trailing `:bytes` segment would give the wrong pattern match result for a sliced bit array. ([Richard Viney](https://github.com/richard-viney)) ## 1.7.0-rc1 - 2024-12-29 ### Compiler - Removed compiler hint about pattern matching a `Result(a, b)` when being used where `a` is expected. ([Kieran O'Reilly](https://github.com/SoTeKie)) - Optimised code generated for record updates. ([yoshi](https://github.com/joshi-monster)) - The compiler now allows for record updates to change the generic type parameters of the record: ```gleam type Box(value) { Box(password: String, value: value) } fn insert(box: Box(a), value: b) -> Box(b) { Box(..box, value:) } ``` ([yoshi](https://github.com/joshi-monster)) - It is now allowed to write a block with no expressions. Like an empty function body, an empty block is considered incomplete as if it contained a `todo` expression. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The shorthand names for the two targets, `erl` and `js` are now deprecated in code such as `@target`. ([Surya Rose](https://github.com/GearsDatapacks)) - A custom panic message can now be specified when asserting a value with `let assert`: ```gleam let assert Ok(regex) = regex.compile("ab?c+") as "This regex is always valid" ``` ([Surya Rose](https://github.com/GearsDatapacks)) - When targeting JavaScript the compiler now generates faster and smaller code for `Int` values in bit array expressions and patterns by evaluating them at compile time where possible. ([Richard Viney](https://github.com/richard-viney)) - Qualified records can now be used in clause guards. ([Surya Rose](https://github.com/GearsDatapacks)) - The compiler now allows deprecating specific custom type variants using the `@deprecated` attribute: ```gleam pub type HashAlgorithm { @deprecated("Please upgrade to another algorithm") Md5 Sha224 Sha512 } pub fn hash_password(input: String) -> String { hash(input:, algorithm: Md5) // Warning: Deprecated value used } ``` ([Iesha](https://github.com/wilbert-mad)) - On the JavaScript target, taking byte-aligned slices of bit arrays is now an O(1) operation instead of O(N), significantly improving performance. ([Richard Viney](https://github.com/richard-viney)) - Better error message for when an existing type constructor is used as a value constructor. ([Jiangda Wang](https://github.com/Frank-III)) - Print better error messages when shell commands used by compiler cannot be found. ([wheatfox](https://github.com/enkerewpo)) ### Build tool - Improved the error message you get when trying to add a package that doesn't exist with `gleam add`. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - External files (such as `.mjs` and `.erl`) are now permitted in subdirectories of `src/` and `test/`. ([PgBiel](https://github.com/PgBiel)) - `gleam publish` now requires more verbose confirmation for publishing Gleam team packages and v0 packages. ([Louis Pilfold](https://github.com/lpil)) - `gleam publish` now warns when publishing packages that define multiple top-level modules, as this can lead to namespace pollution and conflicts for consumers. ([Aleksei Gurianov](https://github.com/guria)) - New projects now require `gleam_stdlib` v0.44.0. ([Louis Pilfold](https://github.com/lpil)) - `gleam remove` no longer requires a network connection. ([yoshi](https://github.com/joshi-monster)) - Commands that work with the Hex package manager API now create and store an API key rather than creating a new one each time. This API key is encrypted with a local password, reducing risk of your Hex password being compromised. ([Louis Pilfold](https://github.com/lpil)) - The build tool now sets the `REBAR_SKIP_PROJECT_PLUGINS` environment variable when using rebar3 to compile Erlang dependencies. With future versions of rebar3 this will cause it to skip project plugins, significantly reducing the amount of code it'll need to download and compile, improving compile times. ([Tristan Sloughter](https://github.com/tsloughter)) ### Language server - The language server now provides type information when hovering over argument labels. ([Surya Rose](https://github.com/GearsDatapacks)) - The language server now suggests a code action to desugar a use expression into the equivalent function call. For example, this snippet of code: ```gleam pub fn main() { use profile <- result.try(fetch_profile(user)) render_welcome(user, profile) } ``` Will be turned into: ```gleam pub fn main() { result.try(fetch_profile(user), fn(profile) { render_welcome(user, profile) }) } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server now suggests a code action to turn a function call into the equivalent use expression. For example, this snippet of code: ```gleam pub fn main() { result.try(fetch_profile(user) fn(profile) { render_welcome(user, profile) }) } ``` Will be turned into: ```gleam pub fn main() { use profile <- result.try(fetch_profile(user)) render_welcome(user, profile) } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server now provides correct information when hovering over patterns in use expressions. ([Surya Rose](https://github.com/GearsDatapacks)) - The language server now suggests a code action to convert an inexhaustive `let` assignment into a `case` expression: ```gleam pub fn unwrap_result(result: Result(a, b)) -> a { let Ok(inner) = result inner } ``` Becomes: ```gleam pub fn unwrap_result(result: Result(a, b)) -> a { let inner = case result { Ok(inner) -> inner Error(_) -> todo } inner } ``` ([Surya Rose](https://github.com/GearsDatapacks)) - The language server now provides an action to extract a value into a variable. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server now suggests a code action to expand a function capture into the equivalent anonymous function. For example, this snippet of code: ```gleam pub fn main() { list.map([1, 2, 3], int.add(_, 11)) } ``` Will be turned into: ```gleam pub fn main() { list.map([1, 2, 3], fn(value) { int.add(value, 11) }) } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server now suggests a code action to generate a dynamic decoder for a custom type. For example, this code: ```gleam pub type Person { Person(name: String, age: Int) } ``` Will become: ```gleam import gleam/dynamic/decode pub type Person { Person(name: String, age: Int) } fn person_decoder() -> decode.Decoder(Person) { use name <- decode.field("name", decode.string) use age <- decode.field("age", decode.int) decode.success(Person(name:, age:)) } ``` ([Surya Rose](https://github.com/GearsDatapacks)) ### Formatter - The formatter now adds a `todo` inside empty blocks. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The formatter now formats lists the same in constants as in expressions. ([Jiangda Wang](https://github.com/Frank-III)) ### Documentation - Canonical links created for documentation pages if published on Hex. Previously published documentation would need to be updated. Resolves versioned pages to point to latest page for search engines. ([Dave Lage](https://github.com/rockerBOO)) ### Bug fixes - The compiler now throws an error when a float literal ends with an `e` and is missing an exponent. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a crash with ENOTEMPTY (os error 39) when building on NTFS partitions. ([Ivan Ermakov](https://github.com/ivanjermakov)) - Fixed a bug where the compiler would crash when pattern matching on multiple subjects and one of them being a constant record. ([Surya Rose](https://github.com/GearsDatapacks)) - Variant inference on prelude types now works correctly if the variant is constant. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where patterns in `use` expressions would not be checked to ensure that they were exhaustive. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where a `module.mjs` file would be overwritten by a `module.gleam` file of same name without warning. It now produces an error. ([PgBiel](https://github.com/PgBiel)) - Modules depending on removed or renamed modules now get automatically recompiled. ([Sakari Bergen](https://github.com/sbergen)) - The compiler now raises a warning for unused case expressions, code blocks and pipelines that would be safe to remove. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where assigning the prefix of a string pattern to a variable nested inside another pattern would produce invalid code on Javascript. ([yoshi](https://github.com/joshi-monster)) - Fixed a bug where expressions which use an unsafe integer on JavaScript would not emit a warning if an external function had been referenced. ([Richard Viney](https://github.com/richard-viney)) - Fixed a bug where nested tuple access would not be parsed correctly when the left-hand side was a function call. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where Gleam would be unable to compile to BEAM bytecode on older versions of Erlang/OTP. ([yoshi](https://github.com/joshi-monster)) - Fixed a bug where the inferred variant of values was not properly cached, leading to incorrect errors on incremental builds and in the language server. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where Gleam would be unable to compile to BEAM bytecode if the project path contains a non-ascii character. ([yoshi](https://github.com/joshi-monster)) - Fixed a bug where the compiler would display incorrect hints about ignoring unused variables in certain cases. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the completer would not include braces in type import completions when it should have. ([Jiangda Wang](https://github.com/Frank-III)) - Fixed a bug where `gleam new` would generate the github `test` workflow configuration with incompatible Erlang OTP and Elixir versions. ([John Strunk](https://github.com/jrstrunk)) ## v1.6.1 - 2024-11-19 ### Bug fixes - Fixed a bug where `gleam update` would fail to update versions. ([Jason Sipula](https://github.com/SnakeDoc)) ================================================ FILE: changelog/v1.8.md ================================================ # Changelog Dedicated to the memory of Len Pilfold. ## v1.8.0 - 2025-02-07 ## v1.8.0-rc1 - 2025-02-03 ### Compiler - Pipelines are now fault tolerant. A type error in the middle of a pipeline won't stop the compiler from figuring out the types of the remaining pieces, enabling the language server to show better suggestions for incomplete pipes. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Improved code generation for blocks in tail position on the Javascript target. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Function documentation comments and module documentation comments are now included in the generated Erlang code and can be browsed from the Erlang shell starting from OTP27. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Parsing of `case` expressions is now fault tolerant. If a `case` expressions is missing its body, the compiler can still perform type inference. This also allows the Language Server to provide completion hints for `case` subjects. ([Surya Rose](https://github.com/GearsDatapacks)) - The compiler can now suggest to wrap a value in an `Ok` or `Error` if that can solve a type mismatch error: ```gleam pub fn greet_logged_user() { use <- bool.guard(when: !logged_in, return: Error(Nil)) "Hello!" } ``` Results in the following error: ```txt error: Type mismatch ┌─ /main.gleam:7:3 │ 7 │ "Hello!" │ ^^^^^^^^ Did you mean to wrap this in an `Ok`? Expected type: Result(a, Nil) Found type: String ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The compiler now shows an improved error message when using an unknown type as a variable name ```txt error: Unknown variable ┌─ /src/one/two.gleam:4:3 │ 4 │ X │ ^ The custom type variant constructor `X` is not in scope here. ``` ([Roeeeee](https://github.com/5c077m4n)) - Erlang `file` module attributes now use paths relative to the root. ([Kasim](https://github.com/oneness)) ### Build tool - `gleam new` now has refined project name validation - rather than failing on invalid project names, it suggests a valid alternative and prompts for confirmation to use it. ([Diemo Gebhardt](https://github.com/diemogebhardt)) - `gleam docs build` generated documentation site now focuses the search input when "Cmd/Ctrl + K", "s" or "/" is pressed. ([Sambit Sahoo](https://github.com/soulsam480)) - `gleam deps` now supports `tree` operation that lists the dependency tree. ```markdown Usage: gleam deps tree [OPTIONS] Options: -p, --package Package to be used as the root of the tree -i, --invert Invert the tree direction and focus on the given package -h, --help Print help ``` For example, if the root project (`project_a`) depends on `package_b` and `package_c`, and `package_c` also depends on `package_b`, the output will be: ```markdown $ gleam deps tree project_a v1.0.0 ├── package_b v0.52.0 └── package_c v1.2.0 └── package_b v0.52.0 $ gleam deps tree --package package_c package_c v1.2.0 └── package_b v0.52.0 $ gleam deps tree --invert package_b package_b v0.52.0 ├── package_c v1.2.0 │ └── project_a v1.0.0 └── project_a v1.0.0 ``` ([Ramkarthik Krishnamurthy](https://github.com/ramkarthik)) - The build tool now checks for modules that would collide with the new Erlang `json` module in addition to the existing Erlang modules it already checked for. ([Louis Pilfold](https://github.com/lpil)) ### Language server - The language server can now generate the definition of functions that do not exist in the current file. For example if I write the following piece of code: ```gleam import gleam/io pub type Pokemon { Pokemon(pokedex_number: Int, name: String) } pub fn main() { io.println(to_string(pokemon)) // ^ If you put your cursor over this function that is // not implemented yet } ``` Triggering the "generate function" code action, the language server will generate the following function for you: ```gleam fn to_string(pokemon: Pokemon) -> String { todo } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server can now fill in the labels of any function call, even when only some of the arguments are provided. For example: ```gleam import gleam/string pub fn main() { string.replace("wibble") } ``` Will be completed to: ```gleam import gleam/string pub fn main() { string.replace("wibble", each: todo, with: todo) } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server now suggests a code action to pattern match on a function's argument. For example: ```gleam pub type Pokemon { Pokemon(pokedex_number: Int, name: String) } pub fn to_string(pokemon: Pokemon) { // ^ If you put your cursor over the argument todo } ``` Triggering the code action on the `pokemon` argument will generate the following code for you: ```gleam pub type Pokemon { Pokemon(pokedex_number: Int, name: String) } pub fn to_string(pokemon: Pokemon) { let Pokemon(pokedex_number:, name:) = pokemon todo } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server now suggests a code action to pattern match on a variable. For example: ```gleam pub fn main() { let result = list.first(a_list) // ^ If you put your cursor over the variable todo } ``` Triggering the code action on the `result` variable will generate the following code for you: ```gleam pub fn main() { let result = list.first(a_list) case result { Ok(value) -> todo Error(value) -> todo } todo } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - When generating functions or variables, the language server can now pick better names using the type of the code it's generating. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The Language Server now provides the ability to rename local variables. For example: ```gleam pub fn main() { let wibble = 10 // ^ If you put your cursor here, and trigger a rename wibble + 1 } ``` Triggering a rename and entering `my_number` results in this code: ```gleam pub fn main() { let my_number = 10 my_number + 1 } ``` ([Surya Rose](https://github.com/GearsDatapacks)) - `Unqualify` Action now get triggered when hovering over the module name for record value constructor. For example: ```gleam pub fn main() { let my_option = option.Some(1) // ^ would trigger "Unqualify option.Some" } ``` ([Jiangda Wang](https://github.com/Frank-III)) ### Formatter ### Bug fixes - Fixed a bug where the "convert from use" code action would generate invalid code for use expressions ending with a trailing comma. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where floats outside of Erlang's floating point range were not causing errors. ([shayan](https://github.com/massivefermion)) - Fixed a bug where build tool could fail to add new dependencies when dependencies with optional dependencies are present in the manifest. ([Louis Pilfold](https://github.com/lpil)) - Fixed a bug where a block expression containing a singular record update would produce invalid erlang. ([yoshi](https://github.com/joshi-monster)) - Fixed a typo in the error message when trying to import a test module into an application module. ([John Strunk](https://github.com/jrstrunk)) - Fixed a bug where the "Extract variable" code action would erroneously extract a pipeline step as a variable. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where variables bound in `let assert` assignments would be allowed to be used in the custom panic message. ([Surya Rose](https://github.com/GearsDatapacks)) ================================================ FILE: changelog/v1.9.md ================================================ # Changelog ## v1.9.0 - 2025-03-09 ## v1.9.0-rc2 - 2025-03-07 ### Compiler - Made runtime warnings regarding the use of deprecated BitArray properties in JavaScript FFI code more compact. They are now one line instead of three. ([Richard Viney](https://github.com/richard-viney)) ### Bug fixes - Fixed a bug that would result in displaying the wrong name when running `gleam --version`. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug in the `generate json encoder` and `generate dynamic decoder` that would result in generating invalid code for variants with no fields. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ## v1.9.0-rc1 - 2025-03-04 ### Compiler - You can now use the `echo` keyword to debug print any value: `echo` can be followed by any expression and it will print it to stderr alongside the module it comes from and its line number. This: ```gleam pub fn main() { echo [1, 2, 3] } ``` Will output to stderr: ```txt /src/module.gleam:2 [1, 2, 3] ``` `echo` can also be used in the middle of a pipeline. This: ```gleam pub fn main() { [1, 2, 3] |> echo |> list.map(fn(x) { x * 2 }) |> echo } ``` Will output to stderr: ```txt /src/module.gleam:3 [1, 2, 3] /src/module.gleam:5 [2, 4, 6] ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Generated Erlang `.app` files now include external modules written in Elixir and Erlang. ([LostKobrakai](https://github.com/lostkobrakai)) - On the JavaScript target, bit array expressions and patterns no longer need to be byte aligned, and the `bits` segment type is now supported in patterns. ([Richard Viney](https://github.com/richard-viney)) - The code generated for list pattern matching on the JavaScript target is now more efficient. Gleam code that relies heavily on list pattern matching can now be up to twice as fast. ([yoshi~](https://github.com/yoshi-monster)) - On the JavaScript target, bit array patterns can now match segments of dynamic size. ([Surya Rose](https://github.com/GearsDatapacks)) ### Build tool - The build tool now supports Git dependencies. For example: ``` [dependencies] gleam_stdlib = { git = "https://github.com/gleam-lang/stdlib.git", ref = "957b83b" } ``` ([Surya Rose](https://github.com/GearsDatapacks)) - The build tool now refuses to publish any incomplete package that has any `echo` debug printing left. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - HexDocs documentation of Gleam packages now uses the ExDocs search data model, allowing for global indexing of Gleam packages in HexDocs, and making Gleam packages discoverable through global search of HexDocs. ([Diemo Gebhardt](https://github.com/diemogebhardt)) - Improved the styling of constructor argument descriptions in the generated documentation. ([Mikko Ahlroth](https://git.ahlcode.fi/nicd)) - Allow users to set the `GLEAM_CACERTS_PATH` environment variable to specify a path to a directory containing CA certificates to install Hex packages. ([winstxnhdw](https://github.com/winstxnhdw)) ### Language server - The language server now has the ability to jump to the type definition of any hovered value. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server now offers a code action to convert the first step of a pipeline to a regular function call. For example, this code: ```gleam import gleam/list pub fn main() { [1, 2, 3] |> list.map(fn(n) { n * 2 }) } ``` Will be rewritten as: ```gleam import gleam/list pub fn main() { list.map([1, 2, 3], fn(n) { n * 2 }) } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server now offers a code action to convert a function call into a pipeline. For example, this code: ```gleam import gleam/list pub fn main() { list.map([1, 2, 3], fn(n) { n * 2 }) } ``` Will be rewritten as: ```gleam import gleam/list pub fn main() { [1, 2, 3] |> list.map(fn(n) { n * 2 }) } ``` You can also pick which argument is going to be piped. In this case: ```gleam import gleam/list pub fn main() { list.map([1, 2, 3], fn(n) { n * 2 }) // ^ If you put your cursor over here } ``` The code will be rewritten as: ```gleam import gleam/list pub fn main() { fn(n) { n * 2 } |> list.map([1, 2, 3], _) } ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server now suggests a code action to generate a function to encode a custom type as JSON using the `gleam_json` package. For example: ```gleam pub type Person { Person(name: String, age: Int) } ``` Will become: ```gleam import gleam/json pub type Person { Person(name: String, age: Int) } fn encode_person(person: Person) -> json.Json { json.object([ #("name", json.string(person.name)), #("age", json.int(person.age)), ]) } ``` ([Surya Rose](https://github.com/GearsDatapacks)) - The language server now suggests a code action to inline a variable which is only used once. For example, this code: ```gleam import gleam/io pub fn main() { let greeting = "Hello!" io.println(greeting) } ``` Will be rewritten as: ```gleam import gleam/io pub fn main() { io.println("Hello!") } ``` ([Surya Rose](https://github.com/GearsDatapacks)) - The code action to generate a dynamic decoder for a custom type can now generate decoders for types with multiple variants. For example this code: ```gleam pub type Person { Adult(age: Int, job: String) Child(age: Int, height: Float) } ``` Becomes: ```gleam import gleam/dynamic/decode pub type Person { Adult(age: Int, job: String) Child(age: Int, height: Float) } fn person_decoder() -> decode.Decoder(Person) { use variant <- decode.field("type", decode.string) case variant { "adult" -> { use age <- decode.field("age", decode.int) use job <- decode.field("job", decode.string) decode.success(Adult(age:, job:)) } "child" -> { use age <- decode.field("age", decode.int) use height <- decode.field("height", decode.float) decode.success(Child(age:, height:)) } _ -> decode.failure(todo as "Zero value for Person", "Person") } } ``` ([Surya Rose](https://github.com/GearsDatapacks)) - The language server now suggests a code action to easily interpolate a value into a string. If the cursor is inside a literal string the language server will offer to split it: ```gleam "wibble | wobble" // ^ Triggering the action with the cursor // here will produce this: "wibble " <> todo <> " wobble" ``` And if the cursor is selecting a valid Gleam name, the language server will offer to interpolate it as a variable: ```gleam "wibble wobble woo" // ^^^^^^ Triggering the code action if you're // selecting an entire name, will produce this: "wibble " <> wobble <> " woo" ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - The language server now shows module documentation when hovering over a module name. ([Surya Rose](https://github.com/GearsDatapacks)) ### Formatter - Redundant function captures that take no additional arguments are now rewritten to not use the function capture syntax. ```gleam some_module.some_function(_) ``` This code is reformatted like so: ```gleam some_module.some_function ``` ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) ### Bug fixes - Fixed a bug where division and remainder operators would not work correctly in guards on the JavaScript target. ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where the "Generate function" code action would ignore the provided labels. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where the "Pattern match on argument" and "Pattern match on variable" code actions would not allow to pattern match on a private type used in the same module it's defined in. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) - Fixed a bug where `gleam export package-interface` would not properly generate the package interface file if some modules were cached. ([Pedro Francisco](https://github.com/mine-tech-oficial)) and ([Surya Rose](https://github.com/GearsDatapacks)) - Fixed a bug where pattern matching using a UTF-8 string constant would not work correctly on the JavaScript target when the string contained escape characters. ([Richard Viney](https://github.com/richard-viney)) - Fixed a bug where `gleam publish` wouldn't include gitignored or nested native files. ([PgBiel](https://github.com/PgBiel)) ## v1.8.1 - 2025-02-11 ### Bug fixes - Fixed a metadata caching bug where accessors for opaque types could sometimes be used in other modules. ([Louis Pilfold](https://github.com/lpil)) ================================================ FILE: compiler-cli/Cargo.toml ================================================ [package] name = "gleam-cli" version = "1.15.1" authors = ["Louis Pilfold "] edition = "2024" license = "Apache-2.0" [dependencies] # The pure compiler gleam-core = { path = "../compiler-core" } # The language server gleam-language-server = { path = "../language-server" } # OS SIGINT and SIGTERM signal handling ctrlc = { version = "3", features = ["termination"] } # Command line interface clap = { version = "4", features = ["derive"] } # Recursively traversing directories ignore = "0" # Allow user to type in sensitive information without showing it in the shell rpassword = "7" # Async runtime tokio = { version = "1", features = ["rt", "rt-multi-thread"] } # Further file system functions (i.e. copy directory) fs_extra = "1" tracing-subscriber = { version = "0.3.20", features = ["fmt", "env-filter"] } # HTTP client reqwest = { version = "0", default-features = false, features = ["rustls-tls"] } # Checksums sha2 = "0" # Getting hostname hostname = "0" # TOML parser/editor that preserves comments & formatting toml_edit = "0" # File locking fslock = "0" # Provides a way to determine if two files are the same using filesystem node ids same-file = "1" async-trait.workspace = true base16.workspace = true camino = { workspace = true, features = ["serde1"] } debug-ignore.workspace = true ecow.workspace = true flate2.workspace = true futures.workspace = true hexpm = { path = "../hexpm" } http.workspace = true http-serde.workspace = true im.workspace = true itertools.workspace = true lsp-server.workspace = true lsp-types.workspace = true opener.workspace = true regex.workspace = true serde.workspace = true serde_json.workspace = true strum.workspace = true tar.workspace = true termcolor.workspace = true toml.workspace = true tracing.workspace = true pubgrub.workspace = true [dev-dependencies] # Creation of temporary directories tempfile = "3" # Setting file modification times for tests filetime = "0.2" pretty_assertions.workspace = true insta.workspace = true ================================================ FILE: compiler-cli/clippy.toml ================================================ disallowed-methods = [ { path = "std::path::Path::new", reason = "Manually constructed paths should use camino::Utf8Path" }, { path = "std::path::PathBuf::new", reason = "Manually constructed pathbufs should use camino::Utf8Path" }, ] ================================================ FILE: compiler-cli/src/add.rs ================================================ use camino::{Utf8Path, Utf8PathBuf}; use gleam_core::{ Error, Result, error::{FileIoAction, FileKind}, paths::ProjectPaths, }; use hexpm::version::{Identifier, Version}; use crate::{ cli, dependencies::{self, parse_gleam_add_specifier}, fs, }; pub fn command(paths: &ProjectPaths, packages_to_add: Vec, dev: bool) -> Result<()> { let config = crate::config::root_config(paths)?; if packages_to_add.iter().any(|name| name == &config.name) { return Err(Error::CannotAddSelfAsDependency { name: config.name.clone(), }); } let mut new_package_requirements = Vec::with_capacity(packages_to_add.len()); for specifier in packages_to_add { new_package_requirements.push(parse_gleam_add_specifier(&specifier)?); } // Insert the new packages into the manifest and perform dependency // resolution to determine suitable versions let manifest = dependencies::resolve_and_download( paths, cli::Reporter::new(), Some((new_package_requirements.clone(), dev)), Vec::new(), dependencies::DependencyManagerConfig { use_manifest: dependencies::UseManifest::Yes, check_major_versions: dependencies::CheckMajorVersions::No, }, )?; // Read gleam.toml and manifest.toml so we can insert new deps into it let mut gleam_toml = read_toml_edit(&paths.root_config())?; let mut manifest_toml = read_toml_edit(&paths.manifest())?; // Insert the new deps for (added_package, _) in new_package_requirements { let added_package = added_package.to_string(); // Pull the selected version out of the new manifest so we know what it is let version = &manifest .packages .iter() .find(|package| package.name == *added_package) .expect("Added package not found in resolved manifest") .version; tracing::info!(version=%version, "new_package_version_resolved"); // Produce a version requirement locked to the major version. // i.e. if 1.2.3 is selected we want >= 1.2.3 and < 2.0.0 let range = format!( ">= {} and < {}.0.0", version_to_string(version), version.major + 1 ); // False positive. This package doesn't use the indexing API correctly. #[allow(clippy::indexing_slicing)] { if dev { let canonical_name = "dev_dependencies"; let deprecated_name = "dev-dependencies"; let has_canonical = gleam_toml.as_table().contains_key(canonical_name); let has_deprecated = gleam_toml.as_table().contains_key(deprecated_name); if !has_canonical && !has_deprecated { gleam_toml["dev_dependencies"] = toml_edit::table(); } let name = if has_deprecated { deprecated_name } else { canonical_name }; gleam_toml[name][&added_package] = toml_edit::value(range.clone()); } else { if !gleam_toml.as_table().contains_key("dependencies") { gleam_toml["dependencies"] = toml_edit::table(); } gleam_toml["dependencies"][&added_package] = toml_edit::value(range.clone()); }; manifest_toml["requirements"][&added_package]["version"] = range.into(); } } // Write the updated config fs::write(&paths.root_config(), &gleam_toml.to_string())?; fs::write(&paths.manifest(), &manifest_toml.to_string())?; Ok(()) } fn read_toml_edit(name: &Utf8Path) -> Result { fs::read(name)? .parse::() .map_err(|e| Error::FileIo { kind: FileKind::File, action: FileIoAction::Parse, path: Utf8PathBuf::from("gleam.toml"), err: Some(e.to_string()), }) } fn version_to_string(version: &Version) -> String { let mut text = String::new(); text.push_str(&format!( "{}.{}.{}", version.major, version.minor, version.patch )); if !version.pre.is_empty() { text.push('-'); for (i, identifier) in version.pre.iter().enumerate() { if i != 0 { text.push('.'); } match *identifier { Identifier::Numeric(ref id) => text.push_str(&id.to_string()), Identifier::AlphaNumeric(ref id) => text.push_str(id), } } } if let Some(build) = version.build.as_ref() { text.push_str(&format!("+{build}")); } text } #[test] fn displays_simple_version_correctly() { let version = Version { major: 1, minor: 23, patch: 4, pre: vec![], build: None, }; let displayed = version_to_string(&version); assert_eq!(displayed, "1.23.4"); } #[test] fn displays_full_version_correctly() { let version = Version { major: 1, minor: 23, patch: 4, pre: vec![ Identifier::Numeric(123), Identifier::AlphaNumeric("123abc".to_string()), ], build: Some("12345abc".to_string()), }; let displayed = version_to_string(&version); assert_eq!(displayed, "1.23.4-123.123abc+12345abc"); } ================================================ FILE: compiler-cli/src/beam_compiler.rs ================================================ use gleam_core::{ Result, error::{Error, ShellCommandFailureReason}, io::{FileSystemWriter, Stdio}, paths, }; use crate::fs::get_os; use std::{ collections::HashSet, io::{self, BufRead, BufReader, Write}, process::{Child, ChildStdin, ChildStdout}, }; use camino::{Utf8Path, Utf8PathBuf}; use itertools::Itertools; #[derive(Debug)] struct BeamCompilerInner { process: Child, stdin: ChildStdin, stdout: BufReader, } #[derive(Debug, Default)] pub struct BeamCompiler { inner: Option, } impl BeamCompiler { pub fn compile( &mut self, io: &IO, out: &Utf8Path, lib: &Utf8Path, modules: &HashSet, stdio: Stdio, ) -> Result, Error> { let inner = match self.inner { Some(ref mut inner) => match inner.process.try_wait() { Ok(None) => inner, _ => self.inner.insert(self.spawn(io, out)?), }, None => self.inner.insert(self.spawn(io, out)?), }; let args = format!( "{{\"{}\", \"{}\", [\"{}\"]}}", escape_path(lib), escape_path(out.join("ebin")), modules .iter() .map(|module| escape_path(out.join(paths::ARTEFACT_DIRECTORY_NAME).join(module))) .join("\", \"") ); tracing::debug!(args=?args, "call_beam_compiler"); writeln!(inner.stdin, "{args}.").map_err(|e| Error::ShellCommand { program: "escript".into(), reason: ShellCommandFailureReason::IoError(e.kind()), })?; let mut buf = String::new(); let mut accumulated_modules: Vec = Vec::new(); while let (Ok(_), Ok(None)) = (inner.stdout.read_line(&mut buf), inner.process.try_wait()) { match buf.trim() { "gleam-compile-result-ok" => { // Return Ok with the accumulated modules return Ok(accumulated_modules); } "gleam-compile-result-error" => { return Err(Error::ShellCommand { program: "escript".into(), reason: ShellCommandFailureReason::Unknown, }); } s if s.starts_with("gleam-compile-module:") => { if let Some(module_content) = s.strip_prefix("gleam-compile-module:") { accumulated_modules.push(module_content.to_string()); } } _ => match stdio { Stdio::Inherit => print!("{buf}"), Stdio::Null => {} }, } buf.clear() } // if we get here, stdout got closed before we got an "ok" or "err". Err(Error::ShellCommand { program: "escript".into(), reason: ShellCommandFailureReason::Unknown, }) } fn spawn( &self, io: &IO, out: &Utf8Path, ) -> Result { let escript_path = out .join(paths::ARTEFACT_DIRECTORY_NAME) .join("gleam@@compile.erl"); let escript_source = std::include_str!("../templates/gleam@@compile.erl"); io.write(&escript_path, escript_source)?; tracing::trace!(escript_path=?escript_path, "spawn_beam_compiler"); let mut process = std::process::Command::new("escript") .arg(escript_path) .stdin(std::process::Stdio::piped()) .stdout(std::process::Stdio::piped()) .spawn() .map_err(|e| match e.kind() { io::ErrorKind::NotFound => Error::ShellProgramNotFound { program: "escript".into(), os: get_os(), }, other => Error::ShellCommand { program: "escript".into(), reason: ShellCommandFailureReason::IoError(other), }, })?; let stdin = process.stdin.take().expect("could not get child stdin"); let stdout = process.stdout.take().expect("could not get child stdout"); Ok(BeamCompilerInner { process, stdin, stdout: BufReader::new(stdout), }) } } impl Drop for BeamCompiler { fn drop(&mut self) { if let Some(mut inner) = self.inner.take() { // closing stdin will cause the erlang process to exit. drop(inner.stdin); let _ = inner.process.wait(); } } } fn escape_path>(path: T) -> String { path.as_ref().replace("\\", "\\\\") } ================================================ FILE: compiler-cli/src/build.rs ================================================ use std::{rc::Rc, time::Instant}; use gleam_core::{ Result, build::{Built, Codegen, NullTelemetry, Options, ProjectCompiler, Telemetry}, manifest::Manifest, paths::ProjectPaths, warning::WarningEmitterIO, }; use crate::{ build_lock::BuildLock, cli, dependencies, fs::{self, ConsoleWarningEmitter}, }; pub fn download_dependencies(paths: &ProjectPaths, telemetry: impl Telemetry) -> Result { dependencies::resolve_and_download( paths, telemetry, None, Vec::new(), dependencies::DependencyManagerConfig { use_manifest: dependencies::UseManifest::Yes, check_major_versions: dependencies::CheckMajorVersions::No, }, ) } pub fn main(paths: &ProjectPaths, options: Options, manifest: Manifest) -> Result { main_with_warnings(paths, options, manifest, Rc::new(ConsoleWarningEmitter)) } pub(crate) fn main_with_warnings( paths: &ProjectPaths, options: Options, manifest: Manifest, warnings: Rc, ) -> Result { let perform_codegen = options.codegen; let root_config = crate::config::root_config(paths)?; let telemetry: &'static dyn Telemetry = if options.no_print_progress { &NullTelemetry } else { &cli::Reporter }; let io = fs::ProjectIO::new(); let start = Instant::now(); let lock = BuildLock::new_target( paths, options.mode, options.target.unwrap_or(root_config.target), )?; tracing::info!("Compiling packages"); let result = { let _guard = lock.lock(telemetry); let compiler = ProjectCompiler::new( root_config, options, manifest.packages, telemetry, warnings, paths.clone(), io, ); compiler.compile()? }; match perform_codegen { Codegen::All | Codegen::DepsOnly => telemetry.compiled_package(start.elapsed()), Codegen::None => telemetry.checked_package(start.elapsed()), }; Ok(result) } ================================================ FILE: compiler-cli/src/build_lock.rs ================================================ use camino::Utf8PathBuf; use gleam_core::{ Error, Result, build::{Mode, Target, Telemetry}, error::{FileIoAction, FileKind}, paths::ProjectPaths, }; use strum::IntoEnumIterator; #[derive(Debug)] pub(crate) struct BuildLock { directory: Utf8PathBuf, filename: String, } impl BuildLock { /// Lock the build directory for the specified mode and target. pub fn new_target(paths: &ProjectPaths, mode: Mode, target: Target) -> Result { let directory = paths.build_directory(); crate::fs::mkdir(&directory)?; let target = match target { Target::Erlang => "erlang", Target::JavaScript => "javascript", }; Ok(Self { directory, filename: format!("gleam-{mode}-{target}.lock"), }) } /// Lock the packages directory. pub fn new_packages(paths: &ProjectPaths) -> Result { let directory = paths.build_packages_directory(); crate::fs::mkdir(&directory)?; Ok(Self { directory, filename: "gleam.lock".to_string(), }) } /// Construct the lock file path pub fn lock_path(&self) -> Utf8PathBuf { self.directory.join(&self.filename) } /// Lock the directory specified by the lock pub fn lock(&self, telemetry: &Telem) -> Result { let lock_path = self.lock_path(); tracing::debug!(path=?lock_path, "locking_directory"); crate::fs::mkdir(&self.directory)?; let mut file = fslock::LockFile::open(lock_path.as_str()).map_err(|e| Error::FileIo { kind: FileKind::File, path: lock_path.clone(), action: FileIoAction::Create, err: Some(e.to_string()), })?; if !file.try_lock_with_pid().expect("Trying directory locking") { telemetry.waiting_for_build_directory_lock(); file.lock_with_pid().expect("Directory locking") } Ok(Guard(file)) } /// Lock all build directories. Does not lock the packages directory. pub fn lock_all_build( paths: &ProjectPaths, telemetry: &Telem, ) -> Result> { let mut locks = vec![]; for mode in Mode::iter() { for target in Target::iter() { locks.push(Self::new_target(paths, mode, target)?.lock(telemetry)?); } } Ok(locks) } } #[derive(Debug)] pub(crate) struct Guard( // False positive. This is used in `drop`. Presumably the lint error is a // bug in clippy. #[allow(dead_code)] fslock::LockFile, ); #[test] fn locking_global() { let paths = crate::project_paths_at_current_directory_without_toml(); let lock = BuildLock::new_packages(&paths).expect("make lock"); let _guard1: Guard = lock.lock(&gleam_core::build::NullTelemetry).unwrap(); println!("Locked!") } #[test] fn locking_dev_erlang() { let paths = crate::project_paths_at_current_directory_without_toml(); let lock = BuildLock::new_target(&paths, Mode::Dev, Target::Erlang).expect("make lock"); let _guard1: Guard = lock.lock(&gleam_core::build::NullTelemetry).unwrap(); println!("Locked!") } #[test] fn locking_prod_erlang() { let paths = crate::project_paths_at_current_directory_without_toml(); let lock = BuildLock::new_target(&paths, Mode::Prod, Target::Erlang).expect("make lock"); let _guard1: Guard = lock.lock(&gleam_core::build::NullTelemetry).unwrap(); println!("Locked!") } #[test] fn locking_lsp_erlang() { let paths = crate::project_paths_at_current_directory_without_toml(); let lock = BuildLock::new_target(&paths, Mode::Lsp, Target::Erlang).expect("make lock"); let _guard1: Guard = lock.lock(&gleam_core::build::NullTelemetry).unwrap(); println!("Locked!") } #[test] fn locking_dev_javascript() { let paths = crate::project_paths_at_current_directory_without_toml(); let lock = BuildLock::new_target(&paths, Mode::Dev, Target::JavaScript).expect("make lock"); let _guard1: Guard = lock.lock(&gleam_core::build::NullTelemetry).unwrap(); println!("Locked!") } #[test] fn locking_prod_javascript() { let paths = crate::project_paths_at_current_directory_without_toml(); let lock = BuildLock::new_target(&paths, Mode::Prod, Target::JavaScript).expect("make lock"); let _guard1: Guard = lock.lock(&gleam_core::build::NullTelemetry).unwrap(); println!("Locked!") } #[test] fn locking_lsp_javascript() { let paths = crate::project_paths_at_current_directory_without_toml(); let lock = BuildLock::new_target(&paths, Mode::Lsp, Target::JavaScript).expect("make lock"); let _guard1: Guard = lock.lock(&gleam_core::build::NullTelemetry).unwrap(); println!("Locked!") } ================================================ FILE: compiler-cli/src/cli.rs ================================================ use ecow::EcoString; use gleam_core::{ build::Telemetry, error::{Error, StandardIoAction}, manifest::{Changed, ChangedGit, PackageChanges}, }; use hexpm::version::Version; use itertools::Itertools as _; use std::{ io::{IsTerminal, Write}, time::{Duration, Instant}, }; use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor}; #[derive(Debug, Default, Clone)] pub struct Reporter; impl Reporter { pub fn new() -> Self { Self } } impl Telemetry for Reporter { fn compiled_package(&self, duration: Duration) { print_compiled(duration); } fn compiling_package(&self, name: &str) { print_compiling(name); } fn checked_package(&self, duration: Duration) { print_checked(duration); } fn checking_package(&self, name: &str) { print_checking(name); } fn downloading_package(&self, name: &str) { print_downloading(name) } fn packages_downloaded(&self, start: Instant, count: usize) { print_packages_downloaded(start, count) } fn resolving_package_versions(&self) { print_resolving_versions() } fn running(&self, name: &str) { print_running(name); } fn waiting_for_build_directory_lock(&self) { print_waiting_for_build_directory_lock() } fn resolved_package_versions(&self, changes: &PackageChanges) { print_package_changes(changes) } } pub fn ask(question: &str) -> Result { print!("{question}: "); std::io::stdout().flush().expect("ask stdout flush"); let mut answer = String::new(); let _ = std::io::stdin() .read_line(&mut answer) .map_err(|e| Error::StandardIo { action: StandardIoAction::Read, err: Some(e.kind()), })?; Ok(answer.trim().to_string()) } pub fn confirm(question: &str) -> Result { let answer = ask(&format!("{question} [y/n]"))?; match answer.as_str() { "y" | "yes" | "Y" | "YES" => Ok(true), _ => Ok(false), } } pub fn confirm_with_text(response: &str) -> Result { let answer = ask(&format!("Type '{response}' to continue"))?; Ok(response == answer) } pub fn ask_password(question: &str) -> Result { let prompt = format!("{question} (will not be printed as you type): "); rpassword::prompt_password(prompt) .map_err(|e| Error::StandardIo { action: StandardIoAction::Read, err: Some(e.kind()), }) .map(|s| EcoString::from(s.trim())) } pub fn print_publishing(name: &str, version: &Version) { print_colourful_prefix("Publishing", &format!("{name} v{version}")) } pub fn print_published(detail: &str) { print_colourful_prefix("Published", detail) } pub fn print_retired(package: &str, version: &str) { print_colourful_prefix("Retired", &format!("{package} {version}")) } pub fn print_unretired(package: &str, version: &str) { print_colourful_prefix("Unretired", &format!("{package} {version}")) } pub fn print_publishing_documentation() { print_colourful_prefix("Publishing", "documentation"); } fn print_downloading(text: &str) { print_colourful_prefix("Downloading", text) } fn print_waiting_for_build_directory_lock() { print_colourful_prefix("Waiting", "for build directory lock") } fn print_resolving_versions() { print_colourful_prefix("Resolving", "versions") } fn print_compiling(text: &str) { print_colourful_prefix("Compiling", text) } pub(crate) fn print_exported(text: &str) { print_colourful_prefix("Exported", text) } pub(crate) fn print_checking(text: &str) { print_colourful_prefix("Checking", text) } pub(crate) fn print_compiled(duration: Duration) { print_colourful_prefix("Compiled", &format!("in {}", seconds(duration))) } pub(crate) fn print_checked(duration: Duration) { print_colourful_prefix("Checked", &format!("in {}", seconds(duration))) } pub(crate) fn print_running(text: &str) { print_colourful_prefix("Running", text) } pub(crate) fn print_package_changes(changes: &PackageChanges) { for (name, version) in changes.added.iter().sorted() { print_added(&format!("{name} v{version}")); } for Changed { name, old, new } in changes.changed.iter().sorted_by_key(|p| &p.name) { print_changed(&format!("{name} v{old} -> v{new}")); } for ChangedGit { name, old_hash, new_hash, } in changes.changed_git.iter().sorted_by_key(|p| &p.name) { print_changed(&format!("{name} {old_hash} -> {new_hash}")); } for name in changes.removed.iter().sorted() { print_removed(name); } } fn print_added(text: &str) { print_colourful_prefix("Added", text) } fn print_changed(text: &str) { print_colourful_prefix("Changed", text) } fn print_removed(text: &str) { print_colourful_prefix("Removed", text) } pub(crate) fn print_generating_documentation() { print_colourful_prefix("Generating", "documentation") } pub(crate) fn print_transferring_ownership() { print_colourful_prefix("Transferring", "ownership"); } pub(crate) fn print_transferred_ownership() { print_colourful_prefix("Transferred", "ownership"); } fn print_packages_downloaded(start: Instant, count: usize) { let elapsed = seconds(start.elapsed()); let msg = match count { 1 => format!("1 package in {elapsed}"), _ => format!("{count} packages in {elapsed}"), }; print_colourful_prefix("Downloaded", &msg) } pub fn seconds(duration: Duration) -> String { format!("{:.2}s", duration.as_millis() as f32 / 1000.) } pub fn print_colourful_prefix(prefix: &str, text: &str) { let buffer_writer = stderr_buffer_writer(); let mut buffer = buffer_writer.buffer(); buffer .set_color( ColorSpec::new() .set_intense(true) .set_fg(Some(Color::Magenta)), ) .expect("print_green_prefix"); write!(buffer, "{prefix: >11}").expect("print_green_prefix"); buffer .set_color(&ColorSpec::new()) .expect("print_green_prefix"); writeln!(buffer, " {text}").expect("print_green_prefix"); buffer_writer.print(&buffer).expect("print_green_prefix"); } pub fn stderr_buffer_writer() -> BufferWriter { // Don't add color codes to the output if standard error isn't connected to a terminal BufferWriter::stderr(color_choice()) } fn colour_forced() -> bool { if let Ok(force) = std::env::var("FORCE_COLOR") { !force.is_empty() } else { false } } fn color_choice() -> ColorChoice { if colour_forced() { ColorChoice::Always } else if std::io::stderr().is_terminal() { ColorChoice::Auto } else { ColorChoice::Never } } ================================================ FILE: compiler-cli/src/compile_package.rs ================================================ use crate::{ CompilePackage, config, fs::{self, ConsoleWarningEmitter, ProjectIO}, }; use camino::Utf8Path; use ecow::EcoString; use gleam_core::{ Error, Result, build::{ Mode, NullTelemetry, PackageCompiler, StaleTracker, Target, TargetCodegenConfiguration, }, error::{FileIoAction, FileKind}, metadata, paths::{self, ProjectPaths}, type_::ModuleInterface, uid::UniqueIdGenerator, warning::WarningEmitter, }; use std::{collections::HashSet, rc::Rc}; pub fn command(options: CompilePackage) -> Result<()> { let ids = UniqueIdGenerator::new(); let mut type_manifests = load_libraries(&ids, &options.libraries_directory)?; let mut defined_modules = im::HashMap::new(); let warnings = WarningEmitter::new(Rc::new(ConsoleWarningEmitter)); let paths = ProjectPaths::new(options.package_directory.clone()); let config = config::read(paths.root_config())?; let target = match options.target { Target::Erlang => TargetCodegenConfiguration::Erlang { app_file: None }, Target::JavaScript => TargetCodegenConfiguration::JavaScript { emit_typescript_definitions: false, prelude_location: options .javascript_prelude .ok_or_else(|| Error::JavaScriptPreludeRequired)?, }, }; tracing::info!("Compiling package"); let mut compiler = PackageCompiler::new( &config, Mode::Dev, &options.package_directory, &options.output_directory, &options.libraries_directory, &target, ids, ProjectIO::new(), ); compiler.write_entrypoint = false; compiler.write_metadata = true; compiler.compile_beam_bytecode = !options.skip_beam_compilation; compiler .compile( &warnings, &mut type_manifests, &mut defined_modules, &mut StaleTracker::default(), &mut HashSet::new(), &NullTelemetry, ) .into_result() .map(|_| ()) } fn load_libraries( ids: &UniqueIdGenerator, lib: &Utf8Path, ) -> Result> { tracing::info!("Reading precompiled module metadata files"); let mut manifests = im::HashMap::new(); for lib in fs::read_dir(lib)?.filter_map(Result::ok) { let path = lib.path().join(paths::ARTEFACT_DIRECTORY_NAME); if !path.is_dir() { continue; } for module in fs::module_caches_paths(path)? { let bytes = fs::read_bytes(module.clone())?; let module = match metadata::decode(&bytes, ids.clone()) { Ok(module) => module, Err(e) => { return Err(Error::FileIo { kind: FileKind::File, action: FileIoAction::Parse, path: module, err: Some(e.to_string()), }); } }; let _ = manifests.insert(module.name.clone(), module); } } Ok(manifests) } ================================================ FILE: compiler-cli/src/config.rs ================================================ use camino::Utf8PathBuf; use gleam_core::{ config::PackageConfig, error::{Error, FileIoAction, FileKind}, manifest::{Manifest, ManifestPackage, ManifestPackageSource}, paths::ProjectPaths, }; #[derive(Debug, Clone, Copy)] pub enum PackageKind { Dependency, Root, } /// Get the config for a dependency module. Return the config for the current /// project if a dependency doesn't have a config file. pub fn find_package_config_for_module( mod_path: &str, manifest: &Manifest, project_paths: &ProjectPaths, ) -> Result<(PackageConfig, PackageKind), Error> { for package in &manifest.packages { // Not a Gleam package if !package.build_tools.contains(&"gleam".into()) { continue; } let root = package_root(package, project_paths); let mut module_path = root.join("src").join(mod_path); _ = module_path.set_extension("gleam"); // This package doesn't have the module we're looking for if !module_path.is_file() { continue; } let configuration = read(root.join("gleam.toml"))?; return Ok((configuration, PackageKind::Dependency)); } Ok((root_config(project_paths)?, PackageKind::Root)) } fn package_root(package: &ManifestPackage, project_paths: &ProjectPaths) -> Utf8PathBuf { match &package.source { ManifestPackageSource::Local { path } => project_paths.root().join(path), ManifestPackageSource::Hex { .. } | ManifestPackageSource::Git { .. } => { project_paths.build_packages_package(&package.name) } } } pub fn root_config(paths: &ProjectPaths) -> Result { read(paths.root_config()) } pub fn read(config_path: Utf8PathBuf) -> Result { let toml = crate::fs::read(&config_path)?; let config: PackageConfig = toml::from_str(&toml).map_err(|e| Error::FileIo { action: FileIoAction::Parse, kind: FileKind::File, path: config_path, err: Some(e.to_string()), })?; config.check_gleam_compatibility()?; Ok(config) } pub fn ensure_config_exists(paths: &ProjectPaths) -> Result<(), Error> { let path = paths.root_config(); if !path.is_file() { return Err(Error::FileIo { action: FileIoAction::Read, kind: FileKind::File, path, err: Some("File not found".into()), }); } Ok(()) } #[cfg(test)] mod tests { use super::*; use gleam_core::manifest::Base16Checksum; #[test] fn package_root_hex() { let paths = ProjectPaths::new(Utf8PathBuf::from("/app")); let package = ManifestPackage { name: "the_package".into(), version: hexpm::version::Version::new(1, 0, 0), build_tools: vec!["gleam".into()], otp_app: None, requirements: vec![], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![]), }, }; assert_eq!( package_root(&package, &paths), Utf8PathBuf::from("/app/build/packages/the_package") ); } #[test] fn package_root_git() { let paths = ProjectPaths::new(Utf8PathBuf::from("/app")); let package = ManifestPackage { name: "the_package".into(), version: hexpm::version::Version::new(1, 0, 0), build_tools: vec!["gleam".into()], otp_app: None, requirements: vec![], source: ManifestPackageSource::Git { repo: "repo".into(), commit: "commit".into(), }, }; assert_eq!( package_root(&package, &paths), Utf8PathBuf::from("/app/build/packages/the_package") ); } #[test] fn package_root_local() { let paths = ProjectPaths::new(Utf8PathBuf::from("/app")); let package = ManifestPackage { name: "the_package".into(), version: hexpm::version::Version::new(1, 0, 0), build_tools: vec!["gleam".into()], otp_app: None, requirements: vec![], source: ManifestPackageSource::Local { path: Utf8PathBuf::from("../wibble"), }, }; assert_eq!( package_root(&package, &paths), Utf8PathBuf::from("/app/../wibble") ); } } ================================================ FILE: compiler-cli/src/dependencies/dependency_manager.rs ================================================ use ecow::EcoString; use futures::future; use gleam_core::{ Error, Result, build::{Mode, Telemetry}, config::PackageConfig, dependency, manifest::{Manifest, ManifestPackageSource, PackageChanges, Resolved}, paths::ProjectPaths, requirement::Requirement, }; use std::collections::HashMap; use crate::{ build_lock::BuildLock, dependencies::{pretty_print_major_versions_available, write_manifest_to_disc}, fs::ProjectIO, }; use super::{ CheckMajorVersions, LocalPackages, UseManifest, add_missing_packages, is_same_requirements, lookup_package, path_dependency_configs_unchanged, provide_git_package, provide_local_package, read_manifest_from_disc, remove_extra_packages, unlock_packages, }; /// Verifies that all specified packages exist in the manifest. pub fn ensure_packages_exist_locally(manifest: &Manifest, packages: &[EcoString]) -> Result<()> { let missing_packages: Vec = packages .iter() .filter(|package_name| !manifest.packages.iter().any(|p| &p.name == *package_name)) .cloned() .collect(); if !missing_packages.is_empty() { return Err(Error::PackagesToUpdateNotExist { packages: missing_packages, }); } Ok(()) } pub struct DependencyManagerConfig { // If `Yes` we read the manifest from disc. If not set then we ignore any // manifest which will result in the latest versions of the dependency // packages being resolved (not the locked ones). pub use_manifest: UseManifest, /// When set to `Yes`, the cli will check for major version updates of direct dependencies and /// print them to the console if the major versions are not upgradeable due to constraints. pub check_major_versions: CheckMajorVersions, } impl DependencyManagerConfig { pub fn into_dependency_manager( self, runtime: tokio::runtime::Handle, package_fetcher: P, telemetry: Telem, mode: Mode, ) -> DependencyManager { DependencyManager { runtime, package_fetcher, telemetry, mode, use_manifest: self.use_manifest, check_major_versions: self.check_major_versions, } } } pub struct DependencyManager { runtime: tokio::runtime::Handle, package_fetcher: P, mode: Mode, use_manifest: UseManifest, telemetry: Telem, check_major_versions: CheckMajorVersions, } impl DependencyManager where P: dependency::PackageFetcher, Telem: Telemetry, { /// Resolve the dependency versions used by a package. /// /// If the `use_manifest` configuration was set to `false` then it'll always resolve all the /// versions, even if there are already versions locked in the manifest. pub fn resolve_versions( &self, paths: &ProjectPaths, config: &PackageConfig, packages_to_update: Vec, ) -> Result { // If there's no manifest then the only thing we can do is attempt to update the versions. if !paths.manifest().exists() { tracing::debug!("manifest_not_present"); let manifest = self.perform_version_resolution(paths, config, None, Vec::new())?; ensure_packages_exist_locally(&manifest, &packages_to_update)?; return Ok(Resolved::all_added(manifest)); } let existing_manifest = read_manifest_from_disc(paths)?; ensure_packages_exist_locally(&existing_manifest, &packages_to_update)?; // If we have been asked not to use the manifest then let (requirements_changed, manifest_for_resolver) = match self.use_manifest { UseManifest::No => (true, None), UseManifest::Yes => { let config_dependencies = config.all_direct_dependencies()?; let same_requirements = is_same_requirements( &existing_manifest.requirements, &config_dependencies, paths.root(), )?; // If the manifest is to be used and the requirements have not changed then there's // no point in performing resolution, it'll always result in the same versions // already specified in the manifest. if packages_to_update.is_empty() && same_requirements && path_dependency_configs_unchanged(&config_dependencies, paths)? { return Ok(Resolved::no_change(existing_manifest)); } // Otherwise, use the manifest to inform resolution. (!same_requirements, Some(&existing_manifest)) } }; tracing::debug!("manifest_outdated"); let new_manifest = self.perform_version_resolution( paths, config, manifest_for_resolver, packages_to_update, )?; let resolved = Resolved { package_changes: PackageChanges::between_manifests(&existing_manifest, &new_manifest), manifest: new_manifest, requirements_changed, }; Ok(resolved) } pub fn resolve_and_download_versions( &self, paths: &ProjectPaths, new_package: Option<(Vec<(EcoString, Requirement)>, bool)>, packages_to_update: Vec, ) -> Result { let span = tracing::info_span!("download_deps"); let _enter = span.enter(); // We do this before acquiring the build lock so that we don't create the // build directory if there is no gleam.toml crate::config::ensure_config_exists(paths)?; let lock = BuildLock::new_packages(paths)?; let _guard = lock.lock(&self.telemetry); let fs = ProjectIO::boxed(); // Read the project config let mut config = crate::config::read(paths.root_config())?; let project_name = config.name.clone(); // Insert the new packages to add, if it exists if let Some((packages, dev)) = new_package { for (package, requirement) in packages { if dev { _ = config.dev_dependencies.insert(package, requirement); } else { _ = config.dependencies.insert(package, requirement); }; } } // Determine what versions we need let resolved = self.resolve_versions(paths, &config, packages_to_update)?; let local = LocalPackages::read_from_disc(paths)?; // Remove any packages that are no longer required due to gleam.toml changes remove_extra_packages(paths, &local, &resolved.manifest, &self.telemetry)?; // Download them from Hex to the local cache self.runtime.block_on(add_missing_packages( paths, fs, &resolved.manifest, &local, project_name, &self.telemetry, ))?; if resolved.any_changes() { // Record new state of the packages directory // TODO: test tracing::debug!("writing_manifest_toml"); write_manifest_to_disc(paths, &resolved.manifest)?; } LocalPackages::from_manifest(&resolved.manifest).write_to_disc(paths)?; // Display the changes in versions to the user. self.telemetry .resolved_package_versions(&resolved.package_changes); // If requested to do so, check if there are major upgrades that could be performed with // more relaxed version requirements, and inform the user if so. if let CheckMajorVersions::Yes = self.check_major_versions { let major_versions_available = dependency::check_for_major_version_updates( &resolved.manifest, &self.package_fetcher, ); if !major_versions_available.is_empty() { eprintln!( "{}", pretty_print_major_versions_available(major_versions_available) ); } } Ok(resolved.manifest) } fn perform_version_resolution( &self, project_paths: &ProjectPaths, config: &PackageConfig, manifest: Option<&Manifest>, packages_to_update: Vec, ) -> Result { self.telemetry.resolving_package_versions(); let dependencies = config.dependencies_for(self.mode)?; let mut locked = config.locked(manifest)?; if !packages_to_update.is_empty() { unlock_packages(&mut locked, &packages_to_update, manifest)?; } // Packages which are provided directly instead of downloaded from hex let mut provided_packages = HashMap::new(); // The version requires of the current project let mut root_requirements = HashMap::new(); // Populate the provided_packages and root_requirements maps for (name, requirement) in dependencies.into_iter() { let version = match requirement { Requirement::Hex { version } => version, Requirement::Path { path } => provide_local_package( name.clone(), &path, project_paths.root(), project_paths, &mut provided_packages, &mut vec![], )?, Requirement::Git { git, ref_ } => { // If this package is locked and we already resolved a commit // hash for it, we want to use that hash rather than pulling // the latest commit. let ref_to_use = if locked.contains_key(&name) && let Some(manifest) = manifest && let Some(package) = manifest .packages .iter() .find(|package| package.name == name) && let ManifestPackageSource::Git { commit, .. } = &package.source { commit } else { // If the package is unlocked or we haven't resolved a version yet, we use // the ref specified in `gleam.toml`. &ref_ }; provide_git_package( name.clone(), &git, ref_to_use, project_paths, &mut provided_packages, &mut Vec::new(), )? } }; let _ = root_requirements.insert(name, version); } // Convert provided packages into hex packages for pub-grub resolve let provided_hex_packages = provided_packages .iter() .map(|(name, package)| (name.clone(), package.to_hex_package(name))) .collect(); let resolved = dependency::resolve_versions( &self.package_fetcher, provided_hex_packages, config.name.clone(), root_requirements.into_iter(), &locked, )?; // Convert the hex packages and local packages into manifest packages let manifest_packages = self.runtime.block_on(future::try_join_all( resolved .into_iter() .map(|(name, version)| lookup_package(name, version, &provided_packages)), ))?; let manifest = Manifest { packages: manifest_packages, requirements: config.all_direct_dependencies()?, }; Ok(manifest) } } ================================================ FILE: compiler-cli/src/dependencies/snapshots/gleam_cli__dependencies__tests__pretty_print_major_versions_available.snap ================================================ --- source: compiler-cli/src/dependencies/tests.rs assertion_line: 1349 expression: output snapshot_kind: text --- The following dependencies have new major versions available: Package Current Latest ------- ------- ------ gleam_stdlib 0.45.0 1.0.0 short_name 1.0.0 2.0.0 very_long_package_name 18.382.43 19.0.38 ================================================ FILE: compiler-cli/src/dependencies/snapshots/gleam_cli__dependencies__tests__pretty_print_version_updates.snap ================================================ --- source: compiler-cli/src/dependencies/tests.rs assertion_line: 1373 expression: output snapshot_kind: text --- Package Current Latest ------- ------- ------ gleam_stdlib 0.45.0 0.46.0 very_long_package_name 12.12.12 120.12.12 wisp 2.1.0 2.1.1 ================================================ FILE: compiler-cli/src/dependencies/tests.rs ================================================ use std::collections::HashMap; use camino::{Utf8Path, Utf8PathBuf}; use ecow::EcoString; use hexpm::version::Version; use pretty_assertions::assert_eq; use gleam_core::{ Error, build::Runtime, config::{DenoConfig, DenoFlag, Docs, ErlangConfig, JavaScriptConfig}, manifest::{Base16Checksum, Manifest, ManifestPackage, ManifestPackageSource}, paths::ProjectPaths, requirement::Requirement, }; use crate::dependencies::*; #[test] fn list_manifest_format() { let mut buffer = vec![]; let manifest = Manifest { requirements: HashMap::new(), packages: vec![ ManifestPackage { name: "root".into(), version: Version::parse("1.0.0").unwrap(), build_tools: ["gleam".into()].into(), otp_app: None, requirements: vec![], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![1, 2, 3, 4]), }, }, ManifestPackage { name: "aaa".into(), version: Version::new(0, 4, 2), build_tools: ["rebar3".into(), "make".into()].into(), otp_app: Some("aaa_app".into()), requirements: vec!["zzz".into(), "gleam_stdlib".into()], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![3, 22]), }, }, ManifestPackage { name: "zzz".into(), version: Version::new(0, 4, 0), build_tools: ["mix".into()].into(), otp_app: None, requirements: vec![], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![3, 22]), }, }, ], }; list_manifest_packages(&mut buffer, manifest).unwrap(); assert_eq!( std::str::from_utf8(&buffer).unwrap(), "Package Version ------- ------- root 1.0.0 aaa 0.4.2 zzz 0.4.0 " ) } #[test] fn tree_format() { let mut buffer = vec![]; let manifest = Manifest { requirements: HashMap::new(), packages: vec![ ManifestPackage { name: "deps_proj".into(), version: Version::parse("1.0.0").unwrap(), build_tools: [].into(), otp_app: None, requirements: vec!["gleam_regexp".into(), "gleam_stdlib".into()], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![1, 2, 3, 4]), }, }, ManifestPackage { name: "gleam_stdlib".into(), version: Version::new(0, 52, 0), build_tools: ["rebar3".into(), "make".into()].into(), otp_app: Some("aaa_app".into()), requirements: vec![], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![3, 22]), }, }, ManifestPackage { name: "gleam_regexp".into(), version: Version::new(1, 0, 0), build_tools: ["mix".into()].into(), otp_app: None, requirements: vec!["gleam_stdlib".into()], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![3, 22]), }, }, ], }; let options = TreeOptions { package: None, invert: None, }; let root_package_name = EcoString::from("deps_proj"); list_package_and_dependencies_tree( &mut buffer, options, manifest.packages.clone(), root_package_name, ) .unwrap(); assert_eq!( std::str::from_utf8(&buffer).unwrap(), r#"deps_proj v1.0.0 ├── gleam_regexp v1.0.0 │ └── gleam_stdlib v0.52.0 └── gleam_stdlib v0.52.0 "# ) } #[test] fn tree_package_format() { let mut buffer = vec![]; let manifest = Manifest { requirements: HashMap::new(), packages: vec![ ManifestPackage { name: "gleam_stdlib".into(), version: Version::new(0, 52, 0), build_tools: ["rebar3".into(), "make".into()].into(), otp_app: Some("aaa_app".into()), requirements: vec![], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![3, 22]), }, }, ManifestPackage { name: "deps_proj".into(), version: Version::parse("1.0.0").unwrap(), build_tools: [].into(), otp_app: None, requirements: vec!["gleam_stdlib".into(), "gleam_regexp".into()], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![1, 2, 3, 4]), }, }, ManifestPackage { name: "gleam_regexp".into(), version: Version::new(1, 0, 0), build_tools: ["mix".into()].into(), otp_app: None, requirements: vec!["gleam_stdlib".into()], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![3, 22]), }, }, ], }; let options = TreeOptions { package: Some("gleam_regexp".to_string()), invert: None, }; let root_package_name = EcoString::from("deps_proj"); list_package_and_dependencies_tree( &mut buffer, options, manifest.packages.clone(), root_package_name, ) .unwrap(); assert_eq!( std::str::from_utf8(&buffer).unwrap(), r#"gleam_regexp v1.0.0 └── gleam_stdlib v0.52.0 "# ) } #[test] fn tree_invert_format() { let mut buffer = vec![]; let manifest = Manifest { requirements: HashMap::new(), packages: vec![ ManifestPackage { name: "gleam_stdlib".into(), version: Version::new(0, 52, 0), build_tools: ["rebar3".into(), "make".into()].into(), otp_app: Some("aaa_app".into()), requirements: vec![], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![3, 22]), }, }, ManifestPackage { name: "deps_proj".into(), version: Version::parse("1.0.0").unwrap(), build_tools: [].into(), otp_app: None, requirements: vec!["gleam_stdlib".into(), "gleam_regexp".into()], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![1, 2, 3, 4]), }, }, ManifestPackage { name: "gleam_regexp".into(), version: Version::new(1, 0, 0), build_tools: ["mix".into()].into(), otp_app: None, requirements: vec!["gleam_stdlib".into()], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![3, 22]), }, }, ], }; let options = TreeOptions { package: None, invert: Some("gleam_stdlib".to_string()), }; let root_package_name = EcoString::from("deps_proj"); list_package_and_dependencies_tree( &mut buffer, options, manifest.packages.clone(), root_package_name, ) .unwrap(); assert_eq!( std::str::from_utf8(&buffer).unwrap(), r#"gleam_stdlib v0.52.0 ├── deps_proj v1.0.0 └── gleam_regexp v1.0.0 └── deps_proj v1.0.0 "# ) } #[test] fn list_tree_invalid_package_format() { let mut buffer = vec![]; let manifest = Manifest { requirements: HashMap::new(), packages: vec![ ManifestPackage { name: "gleam_stdlib".into(), version: Version::new(0, 52, 0), build_tools: ["rebar3".into(), "make".into()].into(), otp_app: Some("aaa_app".into()), requirements: vec![], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![3, 22]), }, }, ManifestPackage { name: "gleam_regexp".into(), version: Version::new(1, 0, 0), build_tools: ["mix".into()].into(), otp_app: None, requirements: vec!["gleam_stdlib".into()], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![3, 22]), }, }, ManifestPackage { name: "root".into(), version: Version::parse("1.0.0").unwrap(), build_tools: [].into(), otp_app: None, requirements: vec!["gleam_regexp".into(), "gleam_stdlib".into()], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![1, 2, 3, 4]), }, }, ], }; let options = TreeOptions { package: Some("zzzzzz".to_string()), invert: None, }; let root_package_name = EcoString::from("deps_proj"); list_package_and_dependencies_tree( &mut buffer, options, manifest.packages.clone(), root_package_name, ) .unwrap(); assert_eq!( std::str::from_utf8(&buffer).unwrap(), r#"Package not found. Please check the package name. "# ) } #[test] fn parse_gleam_add_specifier_invalid_semver() { assert!(parse_gleam_add_specifier("some_package@1.2.3.4").is_err()); } #[test] fn parse_gleam_add_specifier_non_numeric_version() { assert!(parse_gleam_add_specifier("some_package@not_a_version").is_err()); } #[test] fn parse_gleam_add_specifier_default() { let provided = "some_package"; let expected = Requirement::hex(">= 0.0.0").unwrap(); let (package, version) = parse_gleam_add_specifier(provided).unwrap(); assert_eq!(version, expected); assert_eq!("some_package", package); } #[test] fn parse_gleam_add_specifier_major_only() { let provided = "wobble@1"; let expected = Requirement::hex(">= 1.0.0 and < 2.0.0").unwrap(); let (package, version) = parse_gleam_add_specifier(provided).unwrap(); assert_eq!(version, expected); assert_eq!("wobble", package); } #[test] fn parse_gleam_add_specifier_major_and_minor() { let provided = "wibble@1.2"; let expected = Requirement::hex(">= 1.2.0 and < 2.0.0").unwrap(); let (package, version) = parse_gleam_add_specifier(provided).unwrap(); assert_eq!(version, expected); assert_eq!("wibble", package); } #[test] fn parse_gleam_add_specifier_major_minor_and_patch() { let provided = "bobble@1.2.3"; let expected = Requirement::hex("1.2.3").unwrap(); let (package, version) = parse_gleam_add_specifier(provided).unwrap(); assert_eq!(version, expected); assert_eq!("bobble", package); } #[test] fn missing_local_packages() { let manifest = Manifest { requirements: HashMap::new(), packages: vec![ ManifestPackage { name: "root".into(), version: Version::parse("1.0.0").unwrap(), build_tools: ["gleam".into()].into(), otp_app: None, requirements: vec![], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![1, 2, 3, 4]), }, }, ManifestPackage { name: "local1".into(), version: Version::parse("1.0.0").unwrap(), build_tools: ["gleam".into()].into(), otp_app: None, requirements: vec![], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![1, 2, 3, 4, 5]), }, }, ManifestPackage { name: "local2".into(), version: Version::parse("3.0.0").unwrap(), build_tools: ["gleam".into()].into(), otp_app: None, requirements: vec![], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![1, 2, 3, 4, 5]), }, }, ], }; let mut extra = LocalPackages { packages: [ ("local2".into(), Version::parse("2.0.0").unwrap()), ("local3".into(), Version::parse("3.0.0").unwrap()), ] .into(), } .missing_local_packages(&manifest, "root"); extra.sort(); assert_eq!( extra, [ &ManifestPackage { name: "local1".into(), version: Version::parse("1.0.0").unwrap(), build_tools: ["gleam".into()].into(), otp_app: None, requirements: vec![], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![1, 2, 3, 4, 5]), }, }, &ManifestPackage { name: "local2".into(), version: Version::parse("3.0.0").unwrap(), build_tools: ["gleam".into()].into(), otp_app: None, requirements: vec![], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![1, 2, 3, 4, 5]), }, }, ] ) } #[test] fn extra_local_packages() { let mut extra = LocalPackages { packages: [ ("local1".into(), Version::parse("1.0.0").unwrap()), ("local2".into(), Version::parse("2.0.0").unwrap()), ("local3".into(), Version::parse("3.0.0").unwrap()), ] .into(), } .extra_local_packages(&Manifest { requirements: HashMap::new(), packages: vec![ ManifestPackage { name: "local1".into(), version: Version::parse("1.0.0").unwrap(), build_tools: ["gleam".into()].into(), otp_app: None, requirements: vec![], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![1, 2, 3, 4, 5]), }, }, ManifestPackage { name: "local2".into(), version: Version::parse("3.0.0").unwrap(), build_tools: ["gleam".into()].into(), otp_app: None, requirements: vec![], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![4, 5]), }, }, ], }); extra.sort(); assert_eq!( extra, [ ("local2".into(), Version::new(2, 0, 0)), ("local3".into(), Version::new(3, 0, 0)), ] ) } #[test] fn provide_wrong_package() { let mut provided = HashMap::new(); let project_paths = crate::project_paths_at_current_directory_without_toml(); let result = provide_local_package( "wrong_name".into(), Utf8Path::new("./test/hello_world"), Utf8Path::new("./"), &project_paths, &mut provided, &mut vec!["root".into(), "subpackage".into()], ); match result { Err(Error::WrongDependencyProvided { expected, found, .. }) => { assert_eq!(expected, "wrong_name"); assert_eq!(found, "hello_world"); } _ => { panic!("Expected WrongDependencyProvided error") } } } #[test] fn provide_existing_package() { let mut provided = HashMap::new(); let project_paths = crate::project_paths_at_current_directory_without_toml(); let result = provide_local_package( "hello_world".into(), Utf8Path::new("./test/hello_world"), Utf8Path::new("./"), &project_paths, &mut provided, &mut vec!["root".into(), "subpackage".into()], ); assert_eq!( result, Ok(hexpm::version::Range::new("== 0.1.0".into()).unwrap()) ); let result = provide_local_package( "hello_world".into(), Utf8Path::new("./test/hello_world"), Utf8Path::new("./"), &project_paths, &mut provided, &mut vec!["root".into(), "subpackage".into()], ); assert_eq!( result, Ok(hexpm::version::Range::new("== 0.1.0".into()).unwrap()) ); } #[test] fn provide_conflicting_package() { let mut provided = HashMap::new(); let project_paths = crate::project_paths_at_current_directory_without_toml(); let result = provide_local_package( "hello_world".into(), Utf8Path::new("./test/hello_world"), Utf8Path::new("./"), &project_paths, &mut provided, &mut vec!["root".into(), "subpackage".into()], ); assert_eq!( result, Ok(hexpm::version::Range::new("== 0.1.0".into()).unwrap()) ); let result = provide_package( "hello_world".into(), Utf8PathBuf::from("./test/other"), ProvidedPackageSource::Local { path: Utf8Path::new("./test/other").to_path_buf(), }, &project_paths, &mut provided, &mut vec!["root".into(), "subpackage".into()], ); match result { Err(Error::ProvidedDependencyConflict { package, .. }) => { assert_eq!(package, "hello_world"); } _ => { panic!("Expected ProvidedDependencyConflict error") } } } #[test] fn provided_is_absolute() { let mut provided = HashMap::new(); let project_paths = crate::project_paths_at_current_directory_without_toml(); let result = provide_local_package( "hello_world".into(), Utf8Path::new("./test/hello_world"), Utf8Path::new("./"), &project_paths, &mut provided, &mut vec!["root".into(), "subpackage".into()], ); assert_eq!( result, Ok(hexpm::version::Range::new("== 0.1.0".into()).unwrap()) ); let package = provided.get("hello_world").unwrap().clone(); match package.source { ProvidedPackageSource::Local { path } => { assert!(path.is_absolute()) } _ => { panic!("Provide_local_package provided a package that is not local!") } } } #[test] fn provided_recursive() { let mut provided = HashMap::new(); let project_paths = crate::project_paths_at_current_directory_without_toml(); let result = provide_local_package( "hello_world".into(), Utf8Path::new("./test/hello_world"), Utf8Path::new("./"), &project_paths, &mut provided, &mut vec!["root".into(), "hello_world".into(), "subpackage".into()], ); assert_eq!( result, Err(Error::PackageCycle { packages: vec!["subpackage".into(), "hello_world".into()], }) ) } #[test] fn provided_local_to_hex() { let provided_package = ProvidedPackage { version: Version::new(1, 0, 0), source: ProvidedPackageSource::Local { path: "canonical/path/to/package".into(), }, requirements: [ ( "req_1".into(), hexpm::version::Range::new("~> 1.0.0".into()).unwrap(), ), ( "req_2".into(), hexpm::version::Range::new("== 1.0.0".into()).unwrap(), ), ] .into(), }; let hex_package = hexpm::Package { name: "package".into(), repository: "local".into(), releases: vec![hexpm::Release { version: Version::new(1, 0, 0), retirement_status: None, outer_checksum: vec![], meta: (), requirements: [ ( "req_1".into(), hexpm::Dependency { requirement: hexpm::version::Range::new("~> 1.0.0".into()).unwrap(), optional: false, app: None, repository: None, }, ), ( "req_2".into(), hexpm::Dependency { requirement: hexpm::version::Range::new("== 1.0.0".into()).unwrap(), optional: false, app: None, repository: None, }, ), ] .into(), }], }; assert_eq!( provided_package.to_hex_package(&"package".into()), hex_package ); } #[test] fn provided_git_to_hex() { let provided_package = ProvidedPackage { version: Version::new(1, 0, 0), source: ProvidedPackageSource::Git { repo: "https://github.com/gleam-lang/gleam.git".into(), commit: "bd9fe02f72250e6a136967917bcb1bdccaffa3c8".into(), }, requirements: [ ( "req_1".into(), hexpm::version::Range::new("~> 1.0.0".into()).unwrap(), ), ( "req_2".into(), hexpm::version::Range::new("== 1.0.0".into()).unwrap(), ), ] .into(), }; let hex_package = hexpm::Package { name: "package".into(), repository: "local".into(), releases: vec![hexpm::Release { version: Version::new(1, 0, 0), retirement_status: None, outer_checksum: vec![], meta: (), requirements: [ ( "req_1".into(), hexpm::Dependency { requirement: hexpm::version::Range::new("~> 1.0.0".into()).unwrap(), optional: false, app: None, repository: None, }, ), ( "req_2".into(), hexpm::Dependency { requirement: hexpm::version::Range::new("== 1.0.0".into()).unwrap(), optional: false, app: None, repository: None, }, ), ] .into(), }], }; assert_eq!( provided_package.to_hex_package(&"package".into()), hex_package ); } #[test] fn provided_local_to_manifest() { let provided_package = ProvidedPackage { version: Version::new(1, 0, 0), source: ProvidedPackageSource::Local { path: "canonical/path/to/package".into(), }, requirements: [ ( "req_1".into(), hexpm::version::Range::new("~> 1.0.0".into()).unwrap(), ), ( "req_2".into(), hexpm::version::Range::new("== 1.0.0".into()).unwrap(), ), ] .into(), }; let manifest_package = ManifestPackage { name: "package".into(), version: Version::new(1, 0, 0), otp_app: None, build_tools: vec!["gleam".into()], requirements: vec!["req_1".into(), "req_2".into()], source: ManifestPackageSource::Local { path: "canonical/path/to/package".into(), }, }; assert_eq!( provided_package.to_manifest_package("package"), manifest_package ); } #[test] fn provided_git_to_manifest() { let provided_package = ProvidedPackage { version: Version::new(1, 0, 0), source: ProvidedPackageSource::Git { repo: "https://github.com/gleam-lang/gleam.git".into(), commit: "bd9fe02f72250e6a136967917bcb1bdccaffa3c8".into(), }, requirements: [ ( "req_1".into(), hexpm::version::Range::new("~> 1.0.0".into()).unwrap(), ), ( "req_2".into(), hexpm::version::Range::new("== 1.0.0".into()).unwrap(), ), ] .into(), }; let manifest_package = ManifestPackage { name: "package".into(), version: Version::new(1, 0, 0), otp_app: None, build_tools: vec!["gleam".into()], requirements: vec!["req_1".into(), "req_2".into()], source: ManifestPackageSource::Git { repo: "https://github.com/gleam-lang/gleam.git".into(), commit: "bd9fe02f72250e6a136967917bcb1bdccaffa3c8".into(), }, }; assert_eq!( provided_package.to_manifest_package("package"), manifest_package ); } #[test] fn verified_requirements_equality_with_canonicalized_paths() { let temp_dir = tempfile::tempdir().expect("Failed to create a temp directory"); let temp_path = Utf8PathBuf::from_path_buf(temp_dir.path().to_path_buf()) .expect("Path should be valid UTF-8"); let sub_dir = temp_path.join("subdir"); std::fs::create_dir(&sub_dir).expect("Failed to create a subdir"); let file_path = sub_dir.join("file.txt"); fs::write(&file_path, "content").expect("Failed to write to file"); let canonical_path = std::fs::canonicalize(&file_path).expect("Failed to canonicalize path"); let relative_path = temp_path.join("./subdir/../subdir/./file.txt"); let requirements1 = HashMap::from([( EcoString::from("dep1"), Requirement::Path { path: Utf8PathBuf::from(canonical_path.to_str().expect("Path should be valid UTF-8")), }, )]); let requirements2 = HashMap::from([( EcoString::from("dep1"), Requirement::Path { path: Utf8PathBuf::from(relative_path.to_string()), }, )]); assert!( is_same_requirements(&requirements1, &requirements2, &temp_path) .expect("Requirements should be the same") ); } #[test] fn test_path_dependency_config_updates() { let temp_dir = tempfile::tempdir().expect("Failed to create a temp directory"); let root_path = Utf8PathBuf::from_path_buf(temp_dir.path().to_path_buf()) .expect("Path should be valid UTF-8"); let paths = ProjectPaths::new(root_path.clone()); let dep_path = root_path.join("dep"); std::fs::create_dir_all(&dep_path).expect("Failed to create dependency directory"); let build_packages_dir = root_path.join("build").join("packages"); std::fs::create_dir_all(&build_packages_dir) .expect("Failed to create build/packages directory"); let config = "name = \"dep\" version = \"1.0.0\" [dependencies] "; let dep_config_path = dep_path.join("gleam.toml"); fs::write(&dep_config_path, config).expect("Failed to write to manifest file"); let requirements = HashMap::from([( EcoString::from("dep"), Requirement::Path { path: Utf8PathBuf::from("dep"), }, )]); // Initial check testing let unchanged = path_dependency_configs_unchanged(&requirements, &paths).unwrap(); assert!(!unchanged, "fresh always needs resolution"); let fingerprint_path = build_packages_dir.join("dep.config_fingerprint"); assert!(fingerprint_path.exists(), "fingerprint must exist"); let unchanged = path_dependency_configs_unchanged(&requirements, &paths).unwrap(); assert!(unchanged, "is unchanged"); // Set fingerprint mtime to some time in the past. This causes the mtime check to fail, // so it moves on to checking the fingerprint itself. let past = std::time::SystemTime::now() - std::time::Duration::from_secs(10); let past = filetime::FileTime::from_system_time(past); filetime::set_file_mtime(&fingerprint_path, past).unwrap(); let unchanged = path_dependency_configs_unchanged(&requirements, &paths).unwrap(); assert!(unchanged, "mtime is outdated, but content has not changed"); // Writing new content means the mtime and the fingerprint checks will fail. let config = "name = \"dep\" version = \"1.0.0\" [dependencies] blah = \">= 1.0.0\" "; fs::write(&dep_config_path, config).unwrap(); let unchanged = path_dependency_configs_unchanged(&requirements, &paths).unwrap(); assert!(!unchanged, "content has changed"); // Run again, to ensure that the fingerprint has been updated. let unchanged = path_dependency_configs_unchanged(&requirements, &paths).unwrap(); assert!(unchanged, "no changes since last run"); // Test that mtime is checked first, and that content is not checked when the mtime // is still valid. We do this by having having content that would fail the fingerprint // check, but having a fresh mtime so it never gets checked. // This can never happen in reality as you can't update the content without updating // the mtime, but we create the situation in this test to verify the short-circuiting // behaviour. let config = "name = \"dep\" version = \"1.0.0\" [dependencies] blah = \">= 1.0.0\" wub = \">= 1.0.0\" "; fs::write(&dep_config_path, config).unwrap(); let future = std::time::SystemTime::now() + std::time::Duration::from_secs(10); let future = filetime::FileTime::from_system_time(future); filetime::set_file_mtime(&fingerprint_path, future).unwrap(); let unchanged = path_dependency_configs_unchanged(&requirements, &paths).unwrap(); assert!(unchanged, "fingerprint is outdated, but mtime is not"); } fn create_testable_unlock_manifest( packages: Vec<(EcoString, Version, Vec)>, requirements: Vec<(EcoString, EcoString)>, ) -> Manifest { let manifest_packages = packages .into_iter() .map(|(name, version, requirements)| ManifestPackage { name, version, build_tools: vec!["gleam".into()], otp_app: None, requirements, source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![]), }, }) .collect(); let root_requirements = requirements .into_iter() .map(|(name, range)| { ( name, Requirement::Hex { version: hexpm::version::Range::new(range.into()).unwrap(), }, ) }) .collect(); Manifest { packages: manifest_packages, requirements: root_requirements, } } #[test] fn test_unlock_package() { let mut locked = HashMap::from([ ("package_a".into(), Version::new(1, 0, 0)), ("package_b".into(), Version::new(2, 0, 0)), ("package_c".into(), Version::new(3, 0, 0)), ("package_d".into(), Version::new(4, 0, 0)), ]); let packages = vec![ ( "package_a".into(), Version::new(1, 0, 0), vec!["package_b".into()], ), ( "package_b".into(), Version::new(2, 0, 0), vec!["package_c".into()], ), ("package_c".into(), Version::new(3, 0, 0), vec![]), ("package_d".into(), Version::new(4, 0, 0), vec![]), ]; let manifest = create_testable_unlock_manifest(packages, Vec::new()); let packages_to_unlock = vec!["package_a".into()]; unlock_packages(&mut locked, &packages_to_unlock, Some(&manifest)).unwrap(); assert!(!locked.contains_key("package_a")); assert!(!locked.contains_key("package_b")); assert!(!locked.contains_key("package_c")); assert!(locked.contains_key("package_d")); } #[test] fn test_unlock_package_without_manifest() { let mut locked = HashMap::from([ ("package_a".into(), Version::new(1, 0, 0)), ("package_b".into(), Version::new(2, 0, 0)), ("package_c".into(), Version::new(3, 0, 0)), ]); let packages_to_unlock = vec!["package_a".into()]; unlock_packages(&mut locked, &packages_to_unlock, None).unwrap(); assert!(!locked.contains_key("package_a")); assert!(locked.contains_key("package_b")); assert!(locked.contains_key("package_c")); } #[test] fn test_unlock_nonexistent_package() { let initial_locked = HashMap::from([ ("package_a".into(), Version::new(1, 0, 0)), ("package_b".into(), Version::new(2, 0, 0)), ]); let packages = vec![ ( "package_a".into(), Version::new(1, 0, 0), vec!["package_b".into()], ), ("package_b".into(), Version::new(2, 0, 0), vec![]), ]; let manifest = create_testable_unlock_manifest(packages, Vec::new()); let packages_to_unlock = vec!["nonexistent_package".into()]; let mut locked = initial_locked.clone(); unlock_packages(&mut locked, &packages_to_unlock, Some(&manifest)).unwrap(); assert_eq!( initial_locked, locked, "Locked packages should remain unchanged" ); } #[test] fn test_unlock_multiple_packages() { let mut locked = HashMap::from([ ("package_a".into(), Version::new(1, 0, 0)), ("package_b".into(), Version::new(2, 0, 0)), ("package_c".into(), Version::new(3, 0, 0)), ("package_d".into(), Version::new(4, 0, 0)), ("package_e".into(), Version::new(5, 0, 0)), ]); let packages = vec![ ( "package_a".into(), Version::new(1, 0, 0), vec!["package_b".into()], ), ( "package_b".into(), Version::new(2, 0, 0), vec!["package_c".into()], ), ("package_c".into(), Version::new(3, 0, 0), vec![]), ( "package_d".into(), Version::new(4, 0, 0), vec!["package_e".into()], ), ("package_e".into(), Version::new(5, 0, 0), vec![]), ]; let manifest = create_testable_unlock_manifest(packages, Vec::new()); let packages_to_unlock = vec!["package_a".into(), "package_d".into()]; unlock_packages(&mut locked, &packages_to_unlock, Some(&manifest)).unwrap(); assert!(!locked.contains_key("package_a")); assert!(!locked.contains_key("package_b")); assert!(!locked.contains_key("package_c")); assert!(!locked.contains_key("package_d")); assert!(!locked.contains_key("package_e")); } #[test] fn test_unlock_packages_empty_input() { let initial_locked = HashMap::from([ ("package_a".into(), Version::new(1, 0, 0)), ("package_b".into(), Version::new(2, 0, 0)), ]); let packages = vec![ ( "package_a".into(), Version::new(1, 0, 0), vec!["package_b".into()], ), ("package_b".into(), Version::new(2, 0, 0), vec![]), ]; let manifest = create_testable_unlock_manifest(packages, Vec::new()); let packages_to_unlock: Vec = vec![]; let mut locked = initial_locked.clone(); unlock_packages(&mut locked, &packages_to_unlock, Some(&manifest)).unwrap(); assert_eq!( initial_locked, locked, "Locked packages should remain unchanged when no packages are specified to unlock" ); } #[test] fn test_unlock_package_preserve_shared_deps() { let mut locked = HashMap::from([ ("package_a".into(), Version::new(1, 0, 0)), ("package_b".into(), Version::new(2, 0, 0)), ("package_c".into(), Version::new(3, 0, 0)), ]); let packages = vec![ ( "package_a".into(), Version::new(1, 0, 0), vec!["package_c".into()], ), ( "package_b".into(), Version::new(2, 0, 0), vec!["package_c".into()], ), ("package_c".into(), Version::new(3, 0, 0), vec![]), ]; let manifest = create_testable_unlock_manifest(packages, Vec::new()); let packages_to_unlock: Vec = vec!["package_a".into()]; unlock_packages(&mut locked, &packages_to_unlock, Some(&manifest)).unwrap(); assert!(!locked.contains_key("package_a")); assert!(locked.contains_key("package_b")); assert!(locked.contains_key("package_c")); } #[test] fn test_unlock_package_with_root_dep() { let mut locked = HashMap::from([ ("package_a".into(), Version::new(1, 0, 0)), ("package_b".into(), Version::new(2, 0, 0)), ("package_c".into(), Version::new(3, 0, 0)), ]); let packages = vec![ ( "package_a".into(), Version::new(1, 0, 0), vec!["package_b".into()], ), ( "package_b".into(), Version::new(2, 0, 0), vec!["package_c".into()], ), ("package_c".into(), Version::new(3, 0, 0), vec![]), ]; let requirements = vec![("package_b".into(), ">= 2.0.0".into())]; let manifest = create_testable_unlock_manifest(packages, requirements); let packages_to_unlock: Vec = vec!["package_a".into()]; unlock_packages(&mut locked, &packages_to_unlock, Some(&manifest)).unwrap(); assert!(!locked.contains_key("package_a")); assert!(locked.contains_key("package_b")); assert!(locked.contains_key("package_c")); } #[test] fn test_unlock_root_dep_package() { let mut locked = HashMap::from([ ("package_a".into(), Version::new(1, 0, 0)), ("package_b".into(), Version::new(2, 0, 0)), ("package_c".into(), Version::new(3, 0, 0)), ]); let packages = vec![ ( "package_a".into(), Version::new(1, 0, 0), vec!["package_b".into()], ), ("package_b".into(), Version::new(2, 0, 0), vec![]), ("package_c".into(), Version::new(3, 0, 0), vec![]), ]; let requirements = vec![("package_a".into(), ">= 1.0.0".into())]; let manifest = create_testable_unlock_manifest(packages, requirements); let packages_to_unlock: Vec = vec!["package_a".into()]; unlock_packages(&mut locked, &packages_to_unlock, Some(&manifest)).unwrap(); assert!(!locked.contains_key("package_a")); assert!(!locked.contains_key("package_b")); assert!(locked.contains_key("package_c")); } #[test] fn test_unlock_package_with_and_without_root_dep() { let mut locked = HashMap::from([ ("package_a".into(), Version::new(1, 0, 0)), ("package_b".into(), Version::new(2, 0, 0)), ("package_c".into(), Version::new(3, 0, 0)), ]); let packages = vec![ ( "package_a".into(), Version::new(1, 0, 0), vec!["package_b".into(), "package_c".into()], ), ("package_b".into(), Version::new(2, 0, 0), vec![]), ("package_c".into(), Version::new(3, 0, 0), vec![]), ]; let requirements = vec![("package_b".into(), ">= 2.0.0".into())]; let manifest = create_testable_unlock_manifest(packages, requirements); let packages_to_unlock: Vec = vec!["package_a".into()]; unlock_packages(&mut locked, &packages_to_unlock, Some(&manifest)).unwrap(); assert!(!locked.contains_key("package_a")); assert!(locked.contains_key("package_b")); assert!(!locked.contains_key("package_c")); } fn manifest_package(name: &str, version: &str, requirements: Vec) -> ManifestPackage { ManifestPackage { name: name.into(), version: Version::parse(version).unwrap(), build_tools: ["gleam".into()].into(), otp_app: None, requirements, source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![1, 2, 3, 4]), }, } } fn package_config( dependencies: HashMap, dev_dependencies: HashMap, ) -> PackageConfig { PackageConfig { name: "the_package".into(), version: Version::parse("1.0.0").unwrap(), gleam_version: None, licences: vec![], description: "".into(), documentation: Docs { pages: vec![] }, dependencies, dev_dependencies, repository: None, links: vec![], erlang: ErlangConfig { application_start_module: None, application_start_argument: None, extra_applications: vec![], }, javascript: JavaScriptConfig { typescript_declarations: false, runtime: Runtime::NodeJs, deno: DenoConfig { allow_env: DenoFlag::AllowAll, allow_sys: true, allow_hrtime: true, allow_net: DenoFlag::AllowAll, allow_ffi: true, allow_read: DenoFlag::AllowAll, allow_run: DenoFlag::AllowAll, allow_write: DenoFlag::AllowAll, allow_all: true, unstable: true, location: None, }, }, target: Target::Erlang, internal_modules: None, } } #[test] fn test_remove_do_nothing() { let config = package_config( HashMap::from([("a".into(), Requirement::hex("~>1.0").unwrap())]), HashMap::from([("b".into(), Requirement::hex("~>2.0").unwrap())]), ); let mut manifest = Manifest { requirements: HashMap::from([ ("a".into(), Requirement::hex("~>1.0").unwrap()), ("b".into(), Requirement::hex("~>2.0").unwrap()), ]), packages: vec![ manifest_package("a", "1.0.0", vec![]), manifest_package("b", "2.0.8", vec![]), ], }; let manifest_copy = manifest.clone(); remove_extra_requirements(&config, &mut manifest).unwrap(); assert_eq!(manifest.requirements, manifest_copy.requirements); assert_eq!(manifest.packages, manifest_copy.packages); } #[test] fn test_remove_simple() { let config = package_config(HashMap::new(), HashMap::new()); let mut manifest = Manifest { requirements: HashMap::from([("a".into(), Requirement::hex("~>1.0").unwrap())]), packages: vec![manifest_package("a", "1.0.0", vec![])], }; remove_extra_requirements(&config, &mut manifest).unwrap(); assert_eq!(manifest.requirements, config.dependencies); assert_eq!(manifest.packages, vec![]); } #[test] fn test_remove_package_with_transitive_dependencies() { let config = package_config(HashMap::new(), HashMap::new()); let mut manifest = Manifest { requirements: HashMap::from([("a".into(), Requirement::hex("~>1.0").unwrap())]), packages: vec![ manifest_package("a", "1.0.0", vec!["b".into()]), manifest_package("b", "1.2.3", vec!["c".into()]), manifest_package("c", "2.0.0", vec![]), ], }; remove_extra_requirements(&config, &mut manifest).unwrap(); assert_eq!(manifest.requirements, config.dependencies); assert_eq!(manifest.packages, vec![]); } #[test] fn test_remove_package_with_shared_transitive_dependencies() { let config = package_config( HashMap::from([("a".into(), Requirement::hex("~>1.0").unwrap())]), HashMap::new(), ); let mut manifest = Manifest { requirements: HashMap::from([ ("a".into(), Requirement::hex("~>1.0").unwrap()), ("b".into(), Requirement::hex("~>1.0").unwrap()), ]), packages: vec![ manifest_package("a", "1.0.0", vec!["c".into()]), manifest_package("b", "1.2.3", vec!["c".into(), "d".into()]), manifest_package("c", "2.0.0", vec![]), manifest_package("d", "0.1.0", vec![]), ], }; remove_extra_requirements(&config, &mut manifest).unwrap(); assert_eq!(manifest.requirements, config.dependencies); assert_eq!( manifest.packages, vec![ manifest_package("a", "1.0.0", vec!["c".into()]), manifest_package("c", "2.0.0", vec![]), ] ); } #[test] fn test_remove_package_that_is_also_a_transitive_dependency() { let config = package_config( HashMap::from([("a".into(), Requirement::hex("~>1.0").unwrap())]), HashMap::new(), ); let mut manifest = Manifest { requirements: HashMap::from([ ("a".into(), Requirement::hex("~>1.0").unwrap()), ("b".into(), Requirement::hex("~>1.0").unwrap()), ]), packages: vec![ manifest_package("a", "1.0.0", vec!["b".into(), "c".into()]), manifest_package("b", "1.2.3", vec!["c".into(), "d".into()]), manifest_package("c", "2.0.0", vec![]), manifest_package("d", "0.1.0", vec![]), ], }; let manifest_copy = manifest.clone(); remove_extra_requirements(&config, &mut manifest).unwrap(); assert_eq!(manifest.requirements, config.dependencies); assert_eq!(manifest.packages, manifest_copy.packages); } #[test] fn test_pretty_print_major_versions_available() { let versions = vec![ ( "very_long_package_name".to_string(), (Version::new(18, 382, 43), Version::new(19, 0, 38)), ), ( "gleam_stdlib".to_string(), (Version::new(0, 45, 0), Version::new(1, 0, 0)), ), ( "short_name".to_string(), (Version::new(1, 0, 0), Version::new(2, 0, 0)), ), ] .into_iter() .collect(); let output = pretty_print_major_versions_available(versions); insta::assert_snapshot!(output); } #[test] fn test_pretty_print_version_updates() { let versions = vec![ ( "gleam_stdlib".to_string(), (Version::new(0, 45, 0), Version::new(0, 46, 0)), ), ( "wisp".to_string(), (Version::new(2, 1, 0), Version::new(2, 1, 1)), ), ( "very_long_package_name".to_string(), (Version::new(12, 12, 12), Version::new(120, 12, 12)), ), ] .into_iter() .collect(); let output = pretty_print_version_updates(versions); insta::assert_snapshot!(output); } #[test] fn test_ensure_packages_exist_locally_all_present() { let manifest = Manifest { requirements: HashMap::new(), packages: vec![ manifest_package("package_a", "1.0.0", vec![]), manifest_package("package_b", "2.0.0", vec![]), manifest_package("package_c", "3.0.0", vec![]), ], }; let packages_to_check = vec!["package_a".into(), "package_b".into()]; let result = dependency_manager::ensure_packages_exist_locally(&manifest, &packages_to_check); assert!(result.is_ok(), "All packages exist, should return Ok"); } #[test] fn test_ensure_packages_exist_locally_some_missing() { let manifest = Manifest { requirements: HashMap::new(), packages: vec![ manifest_package("package_a", "1.0.0", vec![]), manifest_package("package_b", "2.0.0", vec![]), ], }; let packages_to_check = vec![ "package_a".into(), "package_b".into(), "missing_package".into(), "another_missing".into(), ]; let result = dependency_manager::ensure_packages_exist_locally(&manifest, &packages_to_check); match result { Err(Error::PackagesToUpdateNotExist { packages }) => { assert_eq!(packages.len(), 2); assert!(packages.contains(&"missing_package".into())); assert!(packages.contains(&"another_missing".into())); } _ => panic!("Expected PackagesToUpdateNotExist error"), } } ================================================ FILE: compiler-cli/src/dependencies.rs ================================================ mod dependency_manager; use std::{ cell::RefCell, collections::{HashMap, HashSet}, io::ErrorKind, process::Command, rc::Rc, time::Instant, }; use camino::{Utf8Path, Utf8PathBuf}; use ecow::{EcoString, eco_format}; use flate2::read::GzDecoder; use gleam_core::{ Error, Result, build::{Mode, SourceFingerprint, Target, Telemetry}, config::PackageConfig, dependency::{self, PackageFetchError}, error::{FileIoAction, FileKind, ShellCommandFailureReason, StandardIoAction}, hex::{self, HEXPM_PUBLIC_KEY}, io::{HttpClient as _, TarUnpacker, WrappedReader}, manifest::{Base16Checksum, Manifest, ManifestPackage, ManifestPackageSource, PackageChanges}, paths::ProjectPaths, requirement::Requirement, }; use hexpm::version::Version; use itertools::Itertools; use same_file::is_same_file; use strum::IntoEnumIterator; pub use dependency_manager::DependencyManagerConfig; #[cfg(test)] mod tests; use crate::{ TreeOptions, build_lock::{BuildLock, Guard}, cli, fs::{self, ProjectIO}, http::HttpClient, text_layout::space_table, }; struct Symbols { down: &'static str, tee: &'static str, ell: &'static str, right: &'static str, } static UTF8_SYMBOLS: Symbols = Symbols { down: "│", tee: "├", ell: "└", right: "─", }; /// When set to `Yes`, the cli will check for major version updates of direct dependencies and /// print them to the console if the major versions are not upgradeable due to constraints. #[derive(Debug, Clone, Copy)] pub enum CheckMajorVersions { Yes, No, } pub fn list(paths: &ProjectPaths) -> Result<()> { let (_, manifest) = get_manifest_details(paths)?; list_manifest_packages(std::io::stdout(), manifest) } pub fn tree(paths: &ProjectPaths, options: TreeOptions) -> Result<()> { let (config, manifest) = get_manifest_details(paths)?; // Initialize the root package since it is not part of the manifest let root_package = ManifestPackage { build_tools: vec![], name: config.name.clone(), requirements: config.all_direct_dependencies()?.keys().cloned().collect(), version: config.version.clone(), source: ManifestPackageSource::Local { path: paths.root().to_path_buf(), }, otp_app: None, }; // Get the manifest packages and add the root package to the vec let mut packages = manifest.packages.iter().cloned().collect_vec(); packages.append(&mut vec![root_package.clone()]); list_package_and_dependencies_tree(std::io::stdout(), options, packages.clone(), config.name) } fn get_manifest_details(paths: &ProjectPaths) -> Result<(PackageConfig, Manifest)> { let runtime = tokio::runtime::Runtime::new().expect("Unable to start Tokio async runtime"); let config = crate::config::root_config(paths)?; let package_fetcher = PackageFetcher::new(runtime.handle().clone()); let dependency_manager = DependencyManagerConfig { use_manifest: UseManifest::Yes, check_major_versions: CheckMajorVersions::No, } .into_dependency_manager( runtime.handle().clone(), package_fetcher, cli::Reporter::new(), Mode::Dev, ); let manifest = dependency_manager .resolve_versions(paths, &config, Vec::new())? .manifest; Ok((config, manifest)) } fn list_manifest_packages(mut buffer: W, manifest: Manifest) -> Result<()> { let packages = manifest .packages .into_iter() .map(|package| vec![package.name.to_string(), package.version.to_string()]) .collect_vec(); let out = space_table(&["Package", "Version"], packages); write!(buffer, "{out}").map_err(|e| Error::StandardIo { action: StandardIoAction::Write, err: Some(e.kind()), }) } fn list_package_and_dependencies_tree( mut buffer: W, options: TreeOptions, packages: Vec, root_package_name: EcoString, ) -> Result<()> { let mut invert = false; let package: Option<&ManifestPackage> = if let Some(input_package_name) = options.package { packages.iter().find(|p| p.name == input_package_name) } else if let Some(input_package_name) = options.invert { invert = true; packages.iter().find(|p| p.name == input_package_name) } else { packages.iter().find(|p| p.name == root_package_name) }; if let Some(package) = package { let tree = Vec::from([eco_format!("{0} v{1}", package.name, package.version)]); let tree = list_dependencies_tree( tree.clone(), package.clone(), packages, EcoString::new(), invert, ); tree.iter() .try_for_each(|line| writeln!(buffer, "{line}")) .map_err(|e| Error::StandardIo { action: StandardIoAction::Write, err: Some(e.kind()), }) } else { writeln!(buffer, "Package not found. Please check the package name.").map_err(|e| { Error::StandardIo { action: StandardIoAction::Write, err: Some(e.kind()), } }) } } fn list_dependencies_tree( mut tree: Vec, package: ManifestPackage, packages: Vec, accum: EcoString, invert: bool, ) -> Vec { let dependencies = packages .iter() .filter(|p| { (invert && p.requirements.contains(&package.name)) || (!invert && package.requirements.contains(&p.name)) }) .cloned() .collect_vec(); let dependencies = dependencies.iter().sorted().enumerate(); let deps_length = dependencies.len(); for (index, dependency) in dependencies { let is_last = index == deps_length - 1; let prefix = if is_last { UTF8_SYMBOLS.ell } else { UTF8_SYMBOLS.tee }; tree.push(eco_format!( "{0}{1}{2}{2} {3} v{4}", accum.clone(), prefix, UTF8_SYMBOLS.right, dependency.name, dependency.version )); let accum = accum.clone() + (if !is_last { UTF8_SYMBOLS.down } else { " " }) + " "; tree = list_dependencies_tree( tree.clone(), dependency.clone(), packages.clone(), accum.clone(), invert, ); } tree } pub fn outdated(paths: &ProjectPaths) -> Result<()> { let (_, manifest) = get_manifest_details(paths)?; let runtime = tokio::runtime::Runtime::new().expect("Unable to start Tokio async runtime"); let package_fetcher = PackageFetcher::new(runtime.handle().clone()); let version_updates = dependency::check_for_version_updates(&manifest, &package_fetcher); if !version_updates.is_empty() { print!("{}", pretty_print_version_updates(version_updates)); } Ok(()) } #[derive(Debug, Clone, Copy)] pub enum UseManifest { Yes, No, } pub fn update(paths: &ProjectPaths, packages: Vec) -> Result<()> { let use_manifest = if packages.is_empty() { UseManifest::No } else { UseManifest::Yes }; // Update specific packages _ = resolve_and_download( paths, cli::Reporter::new(), None, packages.into_iter().map(EcoString::from).collect(), DependencyManagerConfig { use_manifest, check_major_versions: CheckMajorVersions::Yes, }, )?; Ok(()) } /// Edit the manifest.toml file in this proejct, removing all extra requirements and packages /// that are no longer present in the gleam.toml config. pub fn cleanup(paths: &ProjectPaths, telemetry: Telem) -> Result { let span = tracing::info_span!("remove_deps"); let _enter = span.enter(); // We do this before acquiring the build lock so that we don't create the // build directory if there is no gleam.toml crate::config::ensure_config_exists(paths)?; let lock = BuildLock::new_packages(paths)?; let _guard: Guard = lock.lock(&telemetry)?; // Read the project config let config = crate::root_config(paths)?; let old_manifest = read_manifest_from_disc(paths)?; let mut manifest = old_manifest.clone(); remove_extra_requirements(&config, &mut manifest)?; // Remove any packages that are no longer required due to manifest changes let local = LocalPackages::read_from_disc(paths)?; remove_extra_packages(paths, &local, &manifest, &telemetry)?; // Record new state of the packages directory tracing::debug!("writing_manifest_toml"); write_manifest_to_disc(paths, &manifest)?; LocalPackages::from_manifest(&manifest).write_to_disc(paths)?; let changes = PackageChanges::between_manifests(&old_manifest, &manifest); telemetry.resolved_package_versions(&changes); Ok(manifest) } /// Remove requirements and unneeded packages from manifest that are no longer present in config. fn remove_extra_requirements(config: &PackageConfig, manifest: &mut Manifest) -> Result<()> { // "extra requirements" are all packages that are requirements in the manifest, but no longer // part of the gleam.toml config. let is_extra_requirement = |name: &EcoString| { !config.dev_dependencies.contains_key(name) && !config.dependencies.contains_key(name) }; // If a requirement is also used as a dependency, we do not want to force-unlock it. // If the dependents get deleted as well, this transitive dependency will be dropped. let is_unlockable_requirement = |name: &EcoString| { manifest .packages .iter() .all(|p| !p.requirements.contains(name)) }; let extra_requirements = manifest .requirements .keys() .filter(|&name| is_extra_requirement(name) && is_unlockable_requirement(name)) .cloned() .collect::>(); manifest .requirements .retain(|name, _| !is_extra_requirement(name)); // Unlock all packages that we we want to remove - this removes them and all unneeded // dependencies from `locked`. let mut locked = config.locked(Some(manifest))?; unlock_packages(&mut locked, extra_requirements.as_slice(), Some(manifest))?; // Remove all unlocked packages from the manifest - these are truly no longer needed. manifest .packages .retain(|package| locked.contains_key(&package.name)); Ok(()) } pub fn parse_gleam_add_specifier(package: &str) -> Result<(EcoString, Requirement)> { let Some((package, version)) = package.split_once('@') else { // Default to the latest version available. return Ok(( package.into(), Requirement::hex(">= 0.0.0").expect("'>= 0.0.0' should be a valid pubgrub range"), )); }; // Parse the major and minor from the provided semantic version. let parts = version.split('.').collect::>(); let major = match parts.first() { Some(major) => Ok(major), None => Err(Error::InvalidVersionFormat { input: package.to_string(), error: "Failed to parse semantic major version".to_string(), }), }?; let minor = match parts.get(1) { Some(minor) => minor, None => "0", }; // Using the major version specifier, calculate the maximum // allowable version (i.e., the next major version). let Ok(num) = major.parse::() else { return Err(Error::InvalidVersionFormat { input: version.to_string(), error: "Failed to parse semantic major version as integer".to_string(), }); }; let max_ver = [&(num + 1).to_string(), "0", "0"].join("."); // Pad the provided version specifier with zeros map to a Hex version. let requirement = match parts.len() { 1 | 2 => { let min_ver = [major, minor, "0"].join("."); Requirement::hex(&[">=", &min_ver, "and", "<", &max_ver].join(" ")) } 3 => Requirement::hex(version), n => { return Err(Error::InvalidVersionFormat { input: version.to_string(), error: format!( "Expected up to 3 numbers in version specifier (MAJOR.MINOR.PATCH), found {n}" ), }); } }?; Ok((package.into(), requirement)) } pub fn resolve_and_download( paths: &ProjectPaths, telemetry: Telem, new_package: Option<(Vec<(EcoString, Requirement)>, bool)>, packages_to_update: Vec, config: DependencyManagerConfig, ) -> Result { // Start event loop so we can run async functions to call the Hex API let runtime = tokio::runtime::Runtime::new().expect("Unable to start Tokio async runtime"); let package_fetcher = PackageFetcher::new(runtime.handle().clone()); let dependency_manager = config.into_dependency_manager( runtime.handle().clone(), package_fetcher, telemetry, Mode::Dev, ); dependency_manager.resolve_and_download_versions(paths, new_package, packages_to_update) } fn format_versions_and_extract_longest_parts( versions: dependency::PackageVersionDiffs, ) -> Vec> { versions .iter() .map(|(name, (v1, v2))| vec![name.to_string(), v1.to_string(), v2.to_string()]) .sorted() .collect_vec() } fn pretty_print_major_versions_available(versions: dependency::PackageVersionDiffs) -> String { let versions = format_versions_and_extract_longest_parts(versions); format!( "\nThe following dependencies have new major versions available:\n\n{}", space_table(&["Package", "Current", "Latest"], &versions) ) } fn pretty_print_version_updates(versions: dependency::PackageVersionDiffs) -> EcoString { let versions = format_versions_and_extract_longest_parts(versions); space_table(&["Package", "Current", "Latest"], &versions) } async fn add_missing_packages( paths: &ProjectPaths, fs: Box, manifest: &Manifest, local: &LocalPackages, project_name: EcoString, telemetry: &Telem, ) -> Result<(), Error> { let missing_packages = local.missing_local_packages(manifest, &project_name); let mut num_to_download = 0; let missing_git_packages = missing_packages .iter() .copied() .filter(|package| package.is_git()) .inspect(|_| { num_to_download += 1; }) .collect_vec(); let mut missing_hex_packages = missing_packages .iter() .copied() .filter(|package| package.is_hex()) .inspect(|_| { num_to_download += 1; }) .peekable(); // If we need to download at-least one package if missing_hex_packages.peek().is_some() || !missing_git_packages.is_empty() { let http = HttpClient::boxed(); let downloader = hex::Downloader::new(fs.clone(), fs, http, Untar::boxed(), paths.clone()); let start = Instant::now(); telemetry.downloading_package("packages"); downloader .download_hex_packages(missing_hex_packages, &project_name) .await?; for package in missing_git_packages { let ManifestPackageSource::Git { repo, commit } = &package.source else { continue; }; let _ = download_git_package(&package.name, repo, commit, paths)?; } telemetry.packages_downloaded(start, num_to_download); } Ok(()) } fn remove_extra_packages( paths: &ProjectPaths, local: &LocalPackages, manifest: &Manifest, telemetry: &Telem, ) -> Result<()> { let _guard = BuildLock::lock_all_build(paths, telemetry)?; for (package_name, version) in local.extra_local_packages(manifest) { // TODO: test // Delete the package source let path = paths.build_packages_package(&package_name); if path.exists() { tracing::debug!(package=%package_name, version=%version, "removing_unneeded_package"); fs::delete_directory(&path)?; } // TODO: test // Delete any build artefacts for the package for mode in Mode::iter() { for target in Target::iter() { let name = manifest .packages .iter() .find(|p| p.name == package_name) .map(|p| p.application_name().as_str()) .unwrap_or(package_name.as_str()); let path = paths.build_directory_for_package(mode, target, name); if path.exists() { tracing::debug!(package=%package_name, version=%version, "deleting_build_cache"); fs::delete_directory(&path)?; } } } } Ok(()) } fn read_manifest_from_disc(paths: &ProjectPaths) -> Result { tracing::debug!("reading_manifest_toml"); let manifest_path = paths.manifest(); let toml = fs::read(&manifest_path)?; let manifest = toml::from_str(&toml).map_err(|e| Error::FileIo { action: FileIoAction::Parse, kind: FileKind::File, path: manifest_path.clone(), err: Some(e.to_string()), })?; Ok(manifest) } fn write_manifest_to_disc(paths: &ProjectPaths, manifest: &Manifest) -> Result<()> { let path = paths.manifest(); fs::write(&path, &manifest.to_toml(paths.root())) } // This is the container for locally pinned packages, representing the current contents of // the `project/build/packages` directory. // For descriptions of packages provided by paths and git deps, see the ProvidedPackage struct. // The same package may appear in both at different times. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] struct LocalPackages { packages: HashMap, } impl LocalPackages { pub fn extra_local_packages(&self, manifest: &Manifest) -> Vec<(String, Version)> { let manifest_packages: HashSet<_> = manifest .packages .iter() .map(|p| (&p.name, &p.version)) .collect(); self.packages .iter() .filter(|(n, v)| !manifest_packages.contains(&(&EcoString::from(*n), v))) .map(|(n, v)| (n.clone(), v.clone())) .collect() } pub fn missing_local_packages<'a>( &self, manifest: &'a Manifest, root: &str, ) -> Vec<&'a ManifestPackage> { manifest .packages .iter() // We don't need to download the root package .filter(|p| p.name != root) // We don't need to download local packages because we use the linked source directly .filter(|p| !p.is_local()) // We don't need to download packages which we have the correct version of .filter(|p| self.packages.get(p.name.as_str()) != Some(&p.version)) .collect() } pub fn read_from_disc(paths: &ProjectPaths) -> Result { let path = paths.build_packages_toml(); if !path.exists() { return Ok(Self { packages: HashMap::new(), }); } let toml = fs::read(&path)?; toml::from_str(&toml).map_err(|e| Error::FileIo { action: FileIoAction::Parse, kind: FileKind::File, path: path.clone(), err: Some(e.to_string()), }) } pub fn write_to_disc(&self, paths: &ProjectPaths) -> Result<()> { let path = paths.build_packages_toml(); let toml = toml::to_string(&self).expect("packages.toml serialization"); fs::write(&path, &toml) } pub fn from_manifest(manifest: &Manifest) -> Self { Self { packages: manifest .packages .iter() .map(|p| (p.name.to_string(), p.version.clone())) .collect(), } } } fn is_same_requirements( requirements1: &HashMap, requirements2: &HashMap, root_path: &Utf8Path, ) -> Result { if requirements1.len() != requirements2.len() { return Ok(false); } for (key, requirement1) in requirements1 { if !same_requirements(requirement1, requirements2.get(key), root_path)? { return Ok(false); } } Ok(true) } /// Returns true if all path dependency configs are unchanged since last build. /// /// If any of the path dependency configs have changed that means that we need to /// re-perform dependency resolution, as their dependencies could have changed /// themselves. /// /// We use gleam.toml rather than manifest.toml as: /// /// 1. The dependency requirements could have changed but resolution not have been /// run in that package yet, so the manifest would be the same, resulting in us /// failing to detect that resolution is required. /// /// 2. Dependency manifests are not used in any way, so a change in the manifest /// may not have any impact on this package. /// /// This does mean that changes unrelated to the path dependency's dependencies /// will trigger resolution, but gleam.toml is edited rarely, and no-change /// resolution is fast enough, so that's OK. /// /// Note: This does not check path dependencies of path dependencies! Changes to /// their configs will fail to be picked up. To resolve this we would need to keep /// a list of all the path dependencies in the project, instead of only the direct /// path dependencies. /// fn path_dependency_configs_unchanged( requirements: &HashMap, paths: &ProjectPaths, ) -> Result { for (name, requirement) in requirements { let Requirement::Path { path } = requirement else { continue; }; let config_path = paths.path_dependency_gleam_toml_path(path); let fingerprint_path = paths.dependency_gleam_toml_fingerprint_path(name.as_str()); // Check mtimes before hashing, to avoid extra work if fingerprint_path.exists() { let config_time = fs::modification_time(&config_path)?; let fingerprint_time = fs::modification_time(&fingerprint_path)?; if config_time <= fingerprint_time { continue; } }; let config_text = fs::read(&config_path)?; let current_fingerprint = SourceFingerprint::new(&config_text).to_numerical_string(); // If cached hash file doesn't exist, this is the first time we're checking this dependency if !fingerprint_path.exists() { // Save the current hash for future comparisons fs::write(&fingerprint_path, ¤t_fingerprint)?; return Ok(false); } let previous_fingerprint = fs::read(&fingerprint_path)?; if previous_fingerprint != current_fingerprint { tracing::debug!("path_dependency_config_changed_forcing_rebuild"); fs::write(&fingerprint_path, ¤t_fingerprint)?; return Ok(false); } } Ok(true) } fn same_requirements( requirement1: &Requirement, requirement2: Option<&Requirement>, root_path: &Utf8Path, ) -> Result { let (left, right) = match (requirement1, requirement2) { (Requirement::Path { path: path1 }, Some(Requirement::Path { path: path2 })) => { let left = fs::canonicalise(&root_path.join(path1))?; let right = fs::canonicalise(&root_path.join(path2))?; (left, right) } (_, Some(requirement2)) => return Ok(requirement1 == requirement2), (_, None) => return Ok(false), }; Ok(left == right) } #[derive(Clone, Eq, PartialEq, Debug)] struct ProvidedPackage { version: Version, source: ProvidedPackageSource, requirements: HashMap, } #[derive(Clone, Eq, Debug)] enum ProvidedPackageSource { Git { repo: EcoString, commit: EcoString }, Local { path: Utf8PathBuf }, } impl ProvidedPackage { fn to_hex_package(&self, name: &EcoString) -> hexpm::Package { let requirements = self .requirements .iter() .map(|(name, version)| { ( name.as_str().into(), hexpm::Dependency { requirement: version.clone(), optional: false, app: None, repository: None, }, ) }) .collect(); let release = hexpm::Release { version: self.version.clone(), requirements, retirement_status: None, outer_checksum: vec![], meta: (), }; hexpm::Package { name: name.as_str().into(), repository: "local".into(), releases: vec![release], } } fn to_manifest_package(&self, name: &str) -> ManifestPackage { let mut package = ManifestPackage { name: name.into(), version: self.version.clone(), otp_app: None, // Note, this will probably need to be set to something eventually build_tools: vec!["gleam".into()], requirements: self.requirements.keys().cloned().collect(), source: self.source.to_manifest_package_source(), }; package.requirements.sort(); package } } impl ProvidedPackageSource { fn to_manifest_package_source(&self) -> ManifestPackageSource { match self { Self::Git { repo, commit } => ManifestPackageSource::Git { repo: repo.clone(), commit: commit.clone(), }, Self::Local { path } => ManifestPackageSource::Local { path: path.clone() }, } } fn to_toml(&self) -> String { match self { Self::Git { repo, commit } => { format!(r#"{{ repo: "{repo}", commit: "{commit}" }}"#) } Self::Local { path } => { format!(r#"{{ path: "{path}" }}"#) } } } } impl PartialEq for ProvidedPackageSource { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Local { path: own_path }, Self::Local { path: other_path }) => { is_same_file(own_path, other_path).unwrap_or(false) } ( Self::Git { repo: own_repo, commit: own_commit, }, Self::Git { repo: other_repo, commit: other_commit, }, ) => own_repo == other_repo && own_commit == other_commit, (Self::Git { .. }, Self::Local { .. }) | (Self::Local { .. }, Self::Git { .. }) => { false } } } } /// Provide a package from a local project fn provide_local_package( package_name: EcoString, package_path: &Utf8Path, parent_path: &Utf8Path, project_paths: &ProjectPaths, provided: &mut HashMap, parents: &mut Vec, ) -> Result { let package_path = if package_path.is_absolute() { package_path.to_path_buf() } else { fs::canonicalise(&parent_path.join(package_path))? }; let package_source = ProvidedPackageSource::Local { path: package_path.clone(), }; provide_package( package_name, package_path, package_source, project_paths, provided, parents, ) } fn execute_command(command: &mut Command) -> Result { let result = command.output(); match result { Ok(output) if output.status.success() => Ok(output), Ok(output) => { let reason = match String::from_utf8(output.stderr) { Ok(stderr) => ShellCommandFailureReason::ShellCommandError(stderr), Err(_) => ShellCommandFailureReason::Unknown, }; Err(Error::ShellCommand { program: "git".into(), reason, }) } Err(error) => Err(match error.kind() { ErrorKind::NotFound => Error::ShellProgramNotFound { program: "git".into(), os: fs::get_os(), }, other => Error::ShellCommand { program: "git".into(), reason: ShellCommandFailureReason::IoError(other), }, }), } } /// Downloads a git package from a remote repository. The commands that are run /// looks like this: /// /// ```sh /// git init /// git remote remove origin /// git remote add origin /// git fetch origin /// git checkout /// git rev-parse HEAD /// ``` /// /// This is somewhat inefficient as we have to fetch the entire git history before /// switching to the exact commit we want. There a few alternatives to this: /// /// - `git clone --depth 1 --branch=""` This works, but only allows us to use /// branch names as refs, however we want to allow commit hashes as well. /// - `git fetch --depth 1 origin ` Similarly, this imposes an unwanted /// restriction. `git fetch` only allows branch names or full commit hashes, /// but we want to allow partial hashes as well. /// /// Since Git dependencies will be used quite rarely, this option was settled upon /// because it allows branch names, full and partial commit hashes as refs. /// /// In the future we can optimise this more, for example first checking if we /// are already checked out to the commit stored in the manifest, or by only /// fetching the history without the objects to resolve partial commit hashes. /// For now though this is good enough until it become an actual performance /// problem. /// fn download_git_package( package_name: &str, repo: &str, ref_: &str, project_paths: &ProjectPaths, ) -> Result { let package_path = project_paths.build_packages_package(package_name); // If the package path exists but is not inside a git work tree, we need to // remove the directory because running `git init` in a non-empty directory // followed by `git checkout ...` is an error. See // https://github.com/gleam-lang/gleam/issues/4488 for details. if !fs::is_git_work_tree_root(&package_path) { fs::delete_directory(&package_path)?; } fs::mkdir(&package_path)?; let _ = execute_command(Command::new("git").arg("init").current_dir(&package_path))?; // If this directory already exists, but the remote URL has been edited in // `gleam.toml` without a `gleam clean`, `git remote add` will fail, causing // the remote to be stuck as the original value. Here we remove the remote // first, which ensures that `git remote add` properly add the remote each // time. If this fails, that means we haven't set the remote in the first // place, so we can safely ignore the error. let _ = Command::new("git") .arg("remote") .arg("remove") .arg("origin") .current_dir(&package_path) .output(); let _ = execute_command( Command::new("git") .arg("remote") .arg("add") .arg("origin") .arg(repo) .current_dir(&package_path), )?; let _ = execute_command( Command::new("git") .arg("fetch") .arg("origin") .current_dir(&package_path), )?; let _ = execute_command( Command::new("git") .arg("checkout") .arg(ref_) .current_dir(&package_path), )?; let output = execute_command( Command::new("git") .arg("rev-parse") .arg("HEAD") .current_dir(&package_path), )?; let commit = String::from_utf8(output.stdout) .expect("Output should be UTF-8") .trim() .into(); Ok(commit) } /// Provide a package from a git repository fn provide_git_package( package_name: EcoString, repo: &str, // A git ref, such as a branch name, commit hash or tag name ref_: &str, project_paths: &ProjectPaths, provided: &mut HashMap, parents: &mut Vec, ) -> Result { let commit = download_git_package(&package_name, repo, ref_, project_paths)?; let package_source = ProvidedPackageSource::Git { repo: repo.into(), commit, }; let package_path = fs::canonicalise(&project_paths.build_packages_package(&package_name))?; provide_package( package_name, package_path, package_source, project_paths, provided, parents, ) } /// Adds a gleam project located at a specific path to the list of "provided packages" fn provide_package( package_name: EcoString, package_path: Utf8PathBuf, package_source: ProvidedPackageSource, project_paths: &ProjectPaths, provided: &mut HashMap, parents: &mut Vec, ) -> Result { // Return early if a package cycle is detected if parents.contains(&package_name) { let mut last_cycle = parents .split(|p| p == &package_name) .next_back() .unwrap_or_default() .to_vec(); last_cycle.push(package_name); return Err(Error::PackageCycle { packages: last_cycle, }); } // Check that we do not have a cached version of this package already match provided.get(&package_name) { Some(package) if package.source == package_source => { // This package has already been provided from this source, return the version let version = hexpm::version::Range::new(format!("== {}", &package.version)) .expect("== {version} should be a valid range"); return Ok(version); } Some(package) => { // This package has already been provided from a different source which conflicts return Err(Error::ProvidedDependencyConflict { package: package_name.into(), source_1: package_source.to_toml(), source_2: package.source.to_toml(), }); } None => (), } // Load the package let config = crate::config::read(package_path.join("gleam.toml"))?; // Check that we are loading the correct project if config.name != package_name { return Err(Error::WrongDependencyProvided { expected: package_name.into(), path: package_path.to_path_buf(), found: config.name.into(), }); }; // Walk the requirements of the package let mut requirements = HashMap::new(); parents.push(package_name); for (name, requirement) in config.dependencies.into_iter() { let version = match requirement { Requirement::Hex { version } => version, Requirement::Path { path } => { // Recursively walk local packages provide_local_package( name.clone(), &path, &package_path, project_paths, provided, parents, )? } Requirement::Git { git, ref_ } => { provide_git_package(name.clone(), &git, &ref_, project_paths, provided, parents)? } }; let _ = requirements.insert(name, version); } let _ = parents.pop(); // Add the package to the provided packages dictionary let version = hexpm::version::Range::new(format!("== {}", &config.version)) .expect("== {version} should be a valid range"); let _ = provided.insert( config.name, ProvidedPackage { version: config.version, source: package_source, requirements, }, ); // Return the version Ok(version) } /// Unlocks specified packages and their unique dependencies. /// /// If a manifest is provided, it also unlocks indirect dependencies that are /// not required by any other package or the root project. pub fn unlock_packages( locked: &mut HashMap, packages_to_unlock: &[EcoString], manifest: Option<&Manifest>, ) -> Result<()> { if let Some(manifest) = manifest { let mut packages_to_unlock: Vec = packages_to_unlock.to_vec(); while let Some(package_name) = packages_to_unlock.pop() { if locked.remove(&package_name).is_some() && let Some(package) = manifest.packages.iter().find(|p| p.name == package_name) { let deps_to_unlock = find_deps_to_unlock(package, locked, manifest); packages_to_unlock.extend(deps_to_unlock); } } } else { for package_name in packages_to_unlock { let _ = locked.remove(package_name); } } Ok(()) } /// Identifies which dependencies of a package should be unlocked. /// /// A dependency is eligible for unlocking if it is currently locked, /// is not a root dependency, and is not required by any locked package. fn find_deps_to_unlock( package: &ManifestPackage, locked: &HashMap, manifest: &Manifest, ) -> Vec { package .requirements .iter() .filter(|&dep| { locked.contains_key(dep) && !manifest.requirements.contains_key(dep) && manifest .packages .iter() .all(|p| !locked.contains_key(&p.name) || !p.requirements.contains(dep)) }) .cloned() .collect() } /// Determine the information to add to the manifest for a specific package async fn lookup_package( name: String, version: Version, provided: &HashMap, ) -> Result { match provided.get(name.as_str()) { Some(provided_package) => Ok(provided_package.to_manifest_package(name.as_str())), None => { let config = hexpm::Config::new(); let release = hex::get_package_release(&name, &version, &config, &HttpClient::new()).await?; let build_tools = release .meta .build_tools .iter() .map(|s| EcoString::from(s.as_str())) .collect_vec(); let requirements = release .requirements .keys() .map(|s| EcoString::from(s.as_str())) .collect_vec(); Ok(ManifestPackage { name: name.into(), version, otp_app: Some(release.meta.app.into()), build_tools, requirements, source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(release.outer_checksum), }, }) } } } struct PackageFetcher { runtime_cache: RefCell>>, runtime: tokio::runtime::Handle, http: HttpClient, } impl PackageFetcher { pub fn new(runtime: tokio::runtime::Handle) -> Self { Self { runtime_cache: RefCell::new(HashMap::new()), runtime, http: HttpClient::new(), } } /// Caches the result of `get_dependencies` so that we don't need to make a network request. /// Currently dependencies are fetched during initial version resolution, and then during check /// for major version availability. fn cache_package(&self, package: &str, result: Rc) { let mut runtime_cache = self.runtime_cache.borrow_mut(); let _ = runtime_cache.insert(package.to_string(), result); } } #[derive(Debug)] pub struct Untar; impl Untar { pub fn boxed() -> Box { Box::new(Self) } } impl TarUnpacker for Untar { fn io_result_entries<'a>( &self, archive: &'a mut tar::Archive, ) -> std::io::Result> { archive.entries() } fn io_result_unpack( &self, path: &Utf8Path, mut archive: tar::Archive>>, ) -> std::io::Result<()> { archive.unpack(path) } } impl dependency::PackageFetcher for PackageFetcher { fn get_dependencies(&self, package: &str) -> Result, PackageFetchError> { { let runtime_cache = self.runtime_cache.borrow(); let result = runtime_cache.get(package); if let Some(result) = result { return Ok(result.clone()); } } tracing::debug!(package = package, "looking_up_hex_package"); let config = hexpm::Config::new(); let request = hexpm::repository_v2_get_package_request(package, None, &config); let response = self .runtime .block_on(self.http.send(request)) .map_err(PackageFetchError::fetch_error)?; let pkg = hexpm::repository_v2_get_package_response(response, HEXPM_PUBLIC_KEY) .map_err(|e| PackageFetchError::from_api_error(e, package))?; let pkg = Rc::new(pkg); let pkg_ref = Rc::clone(&pkg); self.cache_package(package, pkg); Ok(pkg_ref) } } ================================================ FILE: compiler-cli/src/docs.rs ================================================ use std::{collections::HashMap, time::SystemTime}; use camino::{Utf8Path, Utf8PathBuf}; use ecow::EcoString; use crate::{cli, fs::ProjectIO, http::HttpClient}; use gleam_core::{ Result, analyse::TargetSupport, build::{Codegen, Compile, Mode, Options, Package, Target}, config::{DocsPage, PackageConfig}, docs::{Dependency, DependencyKind, DocContext}, error::Error, hex, io::HttpClient as _, manifest::ManifestPackageSource, paths::ProjectPaths, type_, }; pub fn remove(package: String, version: String) -> Result<()> { let runtime = tokio::runtime::Runtime::new().expect("Unable to start Tokio async runtime"); let http = HttpClient::new(); let hex_config = hexpm::Config::new(); let credentials = crate::hex::HexAuthentication::new(&runtime, &http, hex_config.clone()) .get_or_create_api_credentials()?; // Remove docs from API let request = hexpm::api_remove_docs_request( &package, &version, &crate::hex::write_credentials(&credentials)?, &hex_config, ) .map_err(Error::hex)?; let response = runtime.block_on(http.send(request))?; hexpm::api_remove_docs_response(response).map_err(Error::hex)?; // Done! println!("The docs for {package} {version} have been removed from HexDocs"); Ok(()) } #[derive(Debug)] pub struct BuildOptions { /// Whether to open the docs after building. pub open: bool, pub target: Option, } pub fn build(paths: &ProjectPaths, options: BuildOptions) -> Result<()> { let config = crate::config::root_config(paths)?; // Reset the build directory so we know the state of the project crate::fs::delete_directory(&paths.build_directory_for_target(Mode::Prod, config.target))?; let out = paths.build_documentation_directory(&config.name); let manifest = crate::build::download_dependencies(paths, cli::Reporter::new())?; let dependencies = manifest .packages .iter() .map(|package| { ( package.name.clone(), Dependency { version: package.version.clone(), kind: match &package.source { ManifestPackageSource::Hex { .. } => DependencyKind::Hex, ManifestPackageSource::Git { .. } => DependencyKind::Git, ManifestPackageSource::Local { .. } => DependencyKind::Path, }, }, ) }) .collect(); let mut built = crate::build::main( paths, Options { mode: Mode::Prod, target: options.target, codegen: Codegen::All, compile: Compile::All, warnings_as_errors: false, root_target_support: TargetSupport::Enforced, no_print_progress: false, }, manifest, )?; let outputs = build_documentation( paths, &config, dependencies, &mut built.root_package, DocContext::Build, &built.module_interfaces, )?; // Write crate::fs::delete_directory(&out)?; crate::fs::write_outputs_under(&outputs, &out)?; let index_html = out.join("index.html"); println!( "\nThe documentation for {package} has been rendered to \n{index_html}", package = config.name, index_html = index_html ); if options.open { open_docs(&index_html)?; } // We're done! Ok(()) } /// Opens the indicated path in the default program configured by the system. /// /// For the docs this will generally be a browser (unless some other program is /// configured as the default for `.html` files). fn open_docs(path: &Utf8Path) -> Result<()> { opener::open(path).map_err(|error| Error::FailedToOpenDocs { path: path.to_path_buf(), error: error.to_string(), })?; Ok(()) } pub(crate) fn build_documentation( paths: &ProjectPaths, config: &PackageConfig, dependencies: HashMap, compiled: &mut Package, is_hex_publish: DocContext, cached_modules: &im::HashMap, ) -> Result, Error> { compiled.attach_doc_and_module_comments(); cli::print_generating_documentation(); let mut pages = vec![DocsPage { title: "README".into(), path: "index.html".into(), source: paths.readme(), // TODO: support non markdown READMEs. Or a default if there is none. }]; pages.extend(config.documentation.pages.iter().cloned()); let mut outputs = gleam_core::docs::generate_html( paths, gleam_core::docs::DocumentationConfig { package_config: config, dependencies, analysed: compiled.modules.as_slice(), docs_pages: &pages, rendering_timestamp: SystemTime::now(), context: is_hex_publish, }, ProjectIO::new(), ); outputs.push(gleam_core::docs::generate_json_package_interface( Utf8PathBuf::from("package-interface.json"), compiled, cached_modules, )); Ok(outputs) } pub fn publish(paths: &ProjectPaths) -> Result<()> { let config = crate::config::root_config(paths)?; let http = HttpClient::new(); let runtime = tokio::runtime::Runtime::new().expect("Unable to start Tokio async runtime"); let hex_config = hexpm::Config::new(); let credentials = crate::hex::HexAuthentication::new(&runtime, &http, hex_config.clone()) .get_or_create_api_credentials()?; // Reset the build directory so we know the state of the project crate::fs::delete_directory(&paths.build_directory_for_target(Mode::Prod, config.target))?; let manifest = crate::build::download_dependencies(paths, cli::Reporter::new())?; let dependencies = manifest .packages .iter() .map(|package| { ( package.name.clone(), Dependency { version: package.version.clone(), kind: match &package.source { ManifestPackageSource::Hex { .. } => DependencyKind::Hex, ManifestPackageSource::Git { .. } => DependencyKind::Git, ManifestPackageSource::Local { .. } => DependencyKind::Path, }, }, ) }) .collect(); let mut built = crate::build::main( paths, Options { root_target_support: TargetSupport::Enforced, warnings_as_errors: false, codegen: Codegen::All, compile: Compile::All, mode: Mode::Prod, target: None, no_print_progress: false, }, manifest, )?; let outputs = build_documentation( paths, &config, dependencies, &mut built.root_package, DocContext::HexPublish, &built.module_interfaces, )?; let archive = crate::fs::create_tar_archive(outputs)?; cli::print_publishing_documentation(); runtime.block_on(hex::publish_documentation( &config.name, &config.version, archive, &crate::hex::write_credentials(&credentials)?, &hex_config, &http, ))?; cli::print_published("documentation"); Ok(()) } ================================================ FILE: compiler-cli/src/export.rs ================================================ use camino::Utf8PathBuf; use gleam_core::{ Result, analyse::TargetSupport, build::{Codegen, Compile, Mode, Options, Target}, paths::ProjectPaths, }; static ENTRYPOINT_FILENAME_POWERSHELL: &str = "entrypoint.ps1"; static ENTRYPOINT_FILENAME_POSIX_SHELL: &str = "entrypoint.sh"; static ENTRYPOINT_TEMPLATE_POWERSHELL: &str = include_str!("../templates/erlang-shipment-entrypoint.ps1"); static ENTRYPOINT_TEMPLATE_POSIX_SHELL: &str = include_str!("../templates/erlang-shipment-entrypoint.sh"); // TODO: start in embedded mode // TODO: test /// Generate a directory of precompiled Erlang along with a start script. /// Suitable for deployment to a server. /// /// For each Erlang application (aka package) directory these directories are /// copied across: /// - ebin /// - include /// - priv pub(crate) fn erlang_shipment(paths: &ProjectPaths) -> Result<()> { let target = Target::Erlang; let mode = Mode::Prod; let build = paths.build_directory_for_target(mode, target); let out = paths.erlang_shipment_directory(); crate::fs::mkdir(&out)?; // Reset the directories to ensure we have a clean slate and no old code crate::fs::delete_directory(&build)?; crate::fs::delete_directory(&out)?; // Build project in production mode let built = crate::build::main( paths, Options { root_target_support: TargetSupport::Enforced, warnings_as_errors: false, codegen: Codegen::All, compile: Compile::All, mode, target: Some(target), no_print_progress: false, }, crate::build::download_dependencies(paths, crate::cli::Reporter::new())?, )?; for entry in crate::fs::read_dir(&build)?.filter_map(Result::ok) { let path = entry.path(); // We are only interested in package directories if !path.is_dir() { continue; } let name = path.file_name().expect("Directory name"); let build = build.join(name); let out = out.join(name); crate::fs::mkdir(&out)?; // Copy desired package subdirectories for subdirectory in ["ebin", "priv", "include"] { let source = build.join(subdirectory); if source.is_dir() { let source = crate::fs::canonicalise(&source)?; let out = out.join(subdirectory); crate::fs::copy_dir(source, &out)?; } } } // PowerShell entry point script. write_entrypoint_script( &out.join(ENTRYPOINT_FILENAME_POWERSHELL), ENTRYPOINT_TEMPLATE_POWERSHELL, &built.root_package.config.name, )?; // POSIX Shell entry point script. write_entrypoint_script( &out.join(ENTRYPOINT_FILENAME_POSIX_SHELL), ENTRYPOINT_TEMPLATE_POSIX_SHELL, &built.root_package.config.name, )?; crate::cli::print_exported(&built.root_package.config.name); println!( " Your Erlang shipment has been generated to {out}. It can be copied to a compatible server with Erlang installed and run with one of the following scripts: - {ENTRYPOINT_FILENAME_POWERSHELL} (PowerShell script) - {ENTRYPOINT_FILENAME_POSIX_SHELL} (POSIX Shell script) ", ); Ok(()) } fn write_entrypoint_script( entrypoint_output_path: &Utf8PathBuf, entrypoint_template_path: &str, package_name: &str, ) -> Result<()> { let text = entrypoint_template_path.replace("$PACKAGE_NAME_FROM_GLEAM", package_name); crate::fs::write(entrypoint_output_path, &text)?; crate::fs::make_executable(entrypoint_output_path)?; Ok(()) } pub fn hex_tarball(paths: &ProjectPaths) -> Result<()> { let mut config = crate::config::root_config(paths)?; let data: Vec = crate::publish::build_hex_tarball(paths, &mut config)?; let path = paths.build_export_hex_tarball(&config.name, &config.version.to_string()); crate::fs::write_bytes(&path, &data)?; println!( " Your hex tarball has been generated in {}. ", &path ); Ok(()) } pub fn javascript_prelude() -> Result<()> { print!("{}", gleam_core::javascript::PRELUDE); Ok(()) } pub fn typescript_prelude() -> Result<()> { print!("{}", gleam_core::javascript::PRELUDE_TS_DEF); Ok(()) } pub fn package_interface(paths: &ProjectPaths, out: Utf8PathBuf) -> Result<()> { // Build the project let mut built = crate::build::main( paths, Options { mode: Mode::Prod, target: None, codegen: Codegen::None, compile: Compile::All, warnings_as_errors: false, root_target_support: TargetSupport::Enforced, no_print_progress: false, }, crate::build::download_dependencies(paths, crate::cli::Reporter::new())?, )?; built.root_package.attach_doc_and_module_comments(); let out = gleam_core::docs::generate_json_package_interface( out, &built.root_package, &built.module_interfaces, ); crate::fs::write_outputs_under(&[out], paths.root())?; Ok(()) } pub fn package_information(paths: &ProjectPaths, out: Utf8PathBuf) -> Result<()> { let config = crate::config::root_config(paths)?; let out = gleam_core::docs::generate_json_package_information(out, config); crate::fs::write_outputs_under(&[out], paths.root())?; Ok(()) } ================================================ FILE: compiler-cli/src/fix.rs ================================================ use std::rc::Rc; use gleam_core::{ Error, Result, Warning, analyse::TargetSupport, build::{Codegen, Compile, Mode, Options}, error::{FileIoAction, FileKind}, paths::ProjectPaths, type_, warning::VectorWarningEmitterIO, }; use hexpm::version::Version; use crate::{build, cli}; pub fn run(paths: &ProjectPaths) -> Result<()> { // When running gleam fix we want all the compilation warnings to be hidden, // at the same time we need to access those to apply the fixes: so we // accumulate those into a vector. let warnings = Rc::new(VectorWarningEmitterIO::new()); let _built = build::main_with_warnings( paths, Options { root_target_support: TargetSupport::Enforced, warnings_as_errors: false, codegen: Codegen::DepsOnly, compile: Compile::All, mode: Mode::Dev, target: None, no_print_progress: false, }, build::download_dependencies(paths, cli::Reporter::new())?, warnings.clone(), )?; let warnings = warnings.take(); fix_minimum_required_version(paths, warnings)?; println!("Done!"); Ok(()) } fn fix_minimum_required_version(paths: &ProjectPaths, warnings: Vec) -> Result<()> { let Some(minimum_required_version) = minimum_required_version_from_warnings(warnings) else { return Ok(()); }; // Set the version requirement in gleam.toml let root_config = paths.root_config(); let mut toml = crate::fs::read(&root_config)? .parse::() .map_err(|e| Error::FileIo { kind: FileKind::File, action: FileIoAction::Parse, path: root_config.to_path_buf(), err: Some(e.to_string()), })?; #[allow(clippy::indexing_slicing)] { toml["gleam"] = toml_edit::value(format!(">= {minimum_required_version}")); } // Write the updated config crate::fs::write(root_config.as_path(), &toml.to_string())?; println!("- Set required Gleam version to \">= {minimum_required_version}\""); Ok(()) } /// Returns the highest minimum required version among all warnings requiring a /// specific Gleam version that is not allowed by the `gleam` version contraint /// in the `gleam.toml`. fn minimum_required_version_from_warnings(warnings: Vec) -> Option { warnings .iter() .filter_map(|warning| match warning { Warning::Type { warning: type_::Warning::FeatureRequiresHigherGleamVersion { minimum_required_version, .. }, .. } => Some(minimum_required_version), _ => None, }) .reduce(std::cmp::max) .cloned() } ================================================ FILE: compiler-cli/src/format.rs ================================================ use gleam_core::{ error::{Error, FileIoAction, FileKind, Result, StandardIoAction, Unformatted}, io::Content, io::OutputFile, }; use std::{io::Read, str::FromStr}; use camino::{Utf8Path, Utf8PathBuf}; pub fn run(stdin: bool, check: bool, files: Vec) -> Result<()> { if stdin { process_stdin(check) } else { process_files(check, files) } } fn process_stdin(check: bool) -> Result<()> { let src = read_stdin()?.into(); let mut out = String::new(); gleam_core::format::pretty(&mut out, &src, Utf8Path::new(""))?; if !check { print!("{out}"); return Ok(()); } if src != out { return Err(Error::Format { problem_files: vec![Unformatted { source: Utf8PathBuf::from(""), destination: Utf8PathBuf::from(""), input: src, output: out, }], }); } Ok(()) } fn process_files(check: bool, files: Vec) -> Result<()> { if check { check_files(files) } else { format_files(files) } } fn check_files(files: Vec) -> Result<()> { let problem_files = unformatted_files(files)?; if problem_files.is_empty() { Ok(()) } else { Err(Error::Format { problem_files }) } } fn format_files(files: Vec) -> Result<()> { for file in unformatted_files(files)? { crate::fs::write_output(&OutputFile { path: file.destination, content: Content::Text(file.output), })?; } Ok(()) } pub fn unformatted_files(files: Vec) -> Result> { let mut problem_files = Vec::with_capacity(files.len()); for file_path in files { let path = Utf8PathBuf::from_str(&file_path).map_err(|e| Error::FileIo { action: FileIoAction::Open, kind: FileKind::File, path: Utf8PathBuf::from(file_path), err: Some(e.to_string()), })?; if path.is_dir() { for path in crate::fs::gleam_files(&path) { format_file(&mut problem_files, path)?; } } else { format_file(&mut problem_files, path)?; } } Ok(problem_files) } fn format_file(problem_files: &mut Vec, path: Utf8PathBuf) -> Result<()> { let src = crate::fs::read(&path)?.into(); let mut output = String::new(); gleam_core::format::pretty(&mut output, &src, &path)?; if src != output { problem_files.push(Unformatted { source: path.clone(), destination: path, input: src, output, }); } Ok(()) } pub fn read_stdin() -> Result { let mut src = String::new(); let _ = std::io::stdin() .read_to_string(&mut src) .map_err(|e| Error::StandardIo { action: StandardIoAction::Read, err: Some(e.kind()), })?; Ok(src) } ================================================ FILE: compiler-cli/src/fs/tests.rs ================================================ use camino::Utf8Path; use itertools::Itertools; #[test] fn is_inside_git_work_tree_ok() { let tmp_dir = tempfile::tempdir().unwrap(); let path = Utf8Path::from_path(tmp_dir.path()).expect("Non Utf-8 Path"); assert!(!super::is_inside_git_work_tree(path).unwrap()); assert_eq!(super::git_init(path), Ok(())); assert!(super::is_inside_git_work_tree(path).unwrap()) } #[test] fn git_init_success() { let tmp_dir = tempfile::tempdir().unwrap(); let path = Utf8Path::from_path(tmp_dir.path()).expect("Non Utf-8 Path"); let git = path.join(".git"); assert!(!git.exists()); assert_eq!(super::git_init(path), Ok(())); assert!(git.exists()); } #[test] fn git_init_already_in_git() { let tmp_dir = tempfile::tempdir().unwrap(); let git = Utf8Path::from_path(tmp_dir.path()) .expect("Non Utf-8 Path") .join(".git"); assert!(!git.exists()); assert_eq!( super::git_init(Utf8Path::from_path(tmp_dir.path()).expect("Non Utf-8 Path")), Ok(()) ); assert!(git.exists()); let sub = Utf8Path::from_path(tmp_dir.path()) .expect("Non Utf-8 Path") .join("subproject"); let git = sub.join(".git"); crate::fs::mkdir(&sub).unwrap(); assert!(!git.exists()); assert_eq!(super::git_init(&sub), Ok(())); assert!(!git.exists()); } #[test] fn exclude_build_dir() { /* a |-- gleam.toml |-- build | |-- f.gleam # do not count as gleam file b |-- build | |-- f.gleam # count as gleam file */ let tmp_dir = tempfile::tempdir().unwrap(); let path = Utf8Path::from_path(tmp_dir.path()).expect("Non Utf-8 Path"); // excluded gleam file { let gleam_toml = path.join("a/gleam.toml").to_path_buf(); super::write(&gleam_toml, "").unwrap(); let gleam_file = path.join("a/build/f.gleam").to_path_buf(); super::write(&gleam_file, "").unwrap(); }; // included gleam file let gleam_file = path.join("b/build/f.gleam").to_path_buf(); super::write(&gleam_file, "").unwrap(); let files = super::gleam_files(path).collect::>(); assert_eq!(files, vec![gleam_file]); } #[test] fn erlang_files_include_gitignored_files() { let tmp_dir = tempfile::tempdir().unwrap(); let path = Utf8Path::from_path(tmp_dir.path()).expect("Non Utf-8 Path"); let included_files = &[ ".hidden.erl", "abc.erl", "abc.hrl", "build/include/abc.erl", "build/include/abc.hrl", "ignored.erl", "ignored.hrl", ]; let excluded_files = &[ ".gitignore", "abc.gleam", "abc.js", "build/abc.gleam", "build/abc.js", ]; let gitignore = "build/ ignored.*"; for &file in included_files.iter().chain(excluded_files) { let contents = match file { ".gitignore" => gitignore, _ => "", }; super::write(&path.join(file), contents).unwrap(); } let mut chosen_files = super::erlang_files(path).collect_vec(); chosen_files.sort_unstable(); let expected_files = included_files.iter().map(|s| path.join(s)).collect_vec(); assert_eq!(expected_files, chosen_files); } #[test] fn is_gleam_path_test() { assert!(super::is_gleam_path( Utf8Path::new("/some-prefix/a.gleam"), Utf8Path::new("/some-prefix/") )); assert!(super::is_gleam_path( Utf8Path::new("/some-prefix/one_two/a.gleam"), Utf8Path::new("/some-prefix/") )); assert!(super::is_gleam_path( Utf8Path::new("/some-prefix/one_two/a123.gleam"), Utf8Path::new("/some-prefix/") )); assert!(super::is_gleam_path( Utf8Path::new("/some-prefix/one_2/a123.gleam"), Utf8Path::new("/some-prefix/") )); } #[test] fn extract_distro_id_test() { let os_release = " PRETTY_NAME=\"Debian GNU/Linux 12 (bookworm)\" NAME=\"Debian GNU/Linux\" VERSION_ID=\"12\" VERSION=\"12 (bookworm)\" VERSION_CODENAME=bookworm ID=debian HOME_URL=\"https://www.debian.org/\" "; assert_eq!(super::extract_distro_id(os_release.to_string()), "debian"); let os_release = " VERSION_CODENAME=jammy ID=ubuntu ID_LIKE=debian HOME_URL=\"https://www.ubuntu.com/\" "; assert_eq!(super::extract_distro_id(os_release.to_string()), "ubuntu"); assert_eq!(super::extract_distro_id("".to_string()), ""); assert_eq!(super::extract_distro_id("\n".to_string()), ""); assert_eq!(super::extract_distro_id("ID=".to_string()), ""); assert_eq!(super::extract_distro_id("ID= ".to_string()), " "); assert_eq!( super::extract_distro_id("ID= space test ".to_string()), " space test " ); assert_eq!(super::extract_distro_id("id=ubuntu".to_string()), ""); assert_eq!( super::extract_distro_id("NAME=\"Debian\"\nID=debian".to_string()), "debian" ); assert_eq!( super::extract_distro_id("\n\nNAME=\n\n\nID=test123\n".to_string()), "test123" ); assert_eq!( super::extract_distro_id("\nID=\"id first\"\nID=another_id".to_string()), "id first" ); } ================================================ FILE: compiler-cli/src/fs.rs ================================================ use gleam_core::{ Result, Warning, build::{NullTelemetry, Target}, error::{Error, FileIoAction, FileKind, OS, ShellCommandFailureReason, parse_os}, io::{ BeamCompiler, Command, CommandExecutor, Content, DirEntry, FileSystemReader, FileSystemWriter, OutputFile, ReadDir, Stdio, WrappedReader, is_native_file_extension, }, manifest::Manifest, paths::ProjectPaths, warning::WarningEmitterIO, }; use gleam_language_server::{DownloadDependencies, Locker, MakeLocker}; use std::{ collections::HashSet, fmt::Debug, fs::{File, exists}, io::{self, BufRead, BufReader, Write}, sync::{Arc, Mutex, OnceLock}, time::SystemTime, }; use camino::{ReadDirUtf8, Utf8Path, Utf8PathBuf}; use crate::{dependencies, lsp::LspLocker}; #[cfg(test)] mod tests; /// Return the current directory as a UTF-8 Path pub fn get_current_directory() -> Result { let curr_dir = std::env::current_dir().map_err(|e| Error::FileIo { kind: FileKind::Directory, action: FileIoAction::Open, path: ".".into(), err: Some(e.to_string()), })?; Utf8PathBuf::from_path_buf(curr_dir.clone()).map_err(|_| Error::NonUtf8Path { path: curr_dir }) } // Return the first directory with a gleam.toml as a UTF-8 Path pub fn get_project_root(path: Utf8PathBuf) -> Result { fn walk(dir: Utf8PathBuf) -> Option { match dir.join("gleam.toml").is_file() { true => Some(dir), false => match dir.parent() { Some(p) => walk(p.into()), None => None, }, } } walk(path.clone()).ok_or(Error::UnableToFindProjectRoot { path: path.to_string(), }) } pub fn get_os() -> OS { parse_os(std::env::consts::OS, get_distro_str().as_str()) } // try to extract the distro id from /etc/os-release pub fn extract_distro_id(os_release: String) -> String { let distro = os_release.lines().find(|line| line.starts_with("ID=")); if let Some(distro) = distro { let id = distro.split('=').nth(1).unwrap_or("").replace("\"", ""); return id; } "".to_string() } pub fn get_distro_str() -> String { let path = Utf8Path::new("/etc/os-release"); if std::env::consts::OS != "linux" || !path.exists() { return "other".to_string(); } let os_release = read(path); match os_release { Ok(os_release) => extract_distro_id(os_release), Err(_) => "other".to_string(), } } /// A `FileWriter` implementation that writes to the file system. #[derive(Debug, Clone, Default)] pub struct ProjectIO { beam_compiler: Arc>, } impl ProjectIO { pub fn new() -> Self { Self { beam_compiler: Default::default(), } } pub fn boxed() -> Box { Box::new(Self::new()) } } impl FileSystemReader for ProjectIO { fn read(&self, path: &Utf8Path) -> Result { read(path) } fn read_bytes(&self, path: &Utf8Path) -> Result, Error> { read_bytes(path) } fn is_file(&self, path: &Utf8Path) -> bool { path.is_file() } fn is_directory(&self, path: &Utf8Path) -> bool { path.is_dir() } fn reader(&self, path: &Utf8Path) -> Result { reader(path) } fn read_dir(&self, path: &Utf8Path) -> Result { read_dir(path).map(|entries| { entries .map(|result| result.map(|entry| DirEntry::from_path(entry.path()))) .collect() }) } fn modification_time(&self, path: &Utf8Path) -> Result { modification_time(path) } fn canonicalise(&self, path: &Utf8Path) -> Result { canonicalise(path) } } pub fn modification_time(path: &Utf8Path) -> std::result::Result { path.metadata() .map(|m| m.modified().unwrap_or_else(|_| SystemTime::now())) .map_err(|e| Error::FileIo { action: FileIoAction::ReadMetadata, kind: FileKind::File, path: path.to_path_buf(), err: Some(e.to_string()), }) } impl FileSystemWriter for ProjectIO { fn delete_directory(&self, path: &Utf8Path) -> Result<()> { delete_directory(path) } fn copy(&self, from: &Utf8Path, to: &Utf8Path) -> Result<()> { copy(from, to) } fn copy_dir(&self, from: &Utf8Path, to: &Utf8Path) -> Result<()> { copy_dir(from, to) } fn mkdir(&self, path: &Utf8Path) -> Result<(), Error> { mkdir(path) } fn hardlink(&self, from: &Utf8Path, to: &Utf8Path) -> Result<(), Error> { hardlink(from, to) } fn symlink_dir(&self, from: &Utf8Path, to: &Utf8Path) -> Result<(), Error> { symlink_dir(from, to) } fn delete_file(&self, path: &Utf8Path) -> Result<()> { delete_file(path) } fn write(&self, path: &Utf8Path, content: &str) -> Result<(), Error> { write(path, content) } fn write_bytes(&self, path: &Utf8Path, content: &[u8]) -> Result<(), Error> { write_bytes(path, content) } fn exists(&self, path: &Utf8Path) -> bool { path.exists() } } impl CommandExecutor for ProjectIO { fn exec(&self, command: Command) -> Result { let Command { program, args, env, cwd, stdio, } = command; tracing::debug!(program=program, args=?args.join(" "), env=?env, cwd=?cwd, "command_exec"); let result = std::process::Command::new(&program) .args(args) .stdin(stdio.get_process_stdio()) .stdout(stdio.get_process_stdio()) .envs(env.iter().map(|pair| (&pair.0, &pair.1))) .current_dir(cwd.unwrap_or_else(|| Utf8Path::new("./").to_path_buf())) .status(); match result { Ok(status) => Ok(status.code().unwrap_or_default()), Err(error) => Err(match error.kind() { io::ErrorKind::NotFound => Error::ShellProgramNotFound { program, os: get_os(), }, other => Error::ShellCommand { program, reason: ShellCommandFailureReason::IoError(other), }, }), } } } impl BeamCompiler for ProjectIO { fn compile_beam( &self, out: &Utf8Path, lib: &Utf8Path, modules: &HashSet, stdio: Stdio, ) -> Result, Error> { self.beam_compiler .lock() .as_mut() .expect("could not get beam_compiler") .compile(self, out, lib, modules, stdio) } } impl MakeLocker for ProjectIO { fn make_locker(&self, paths: &ProjectPaths, target: Target) -> Result> { let locker = LspLocker::new(paths, target)?; Ok(Box::new(locker)) } } impl DownloadDependencies for ProjectIO { fn download_dependencies(&self, paths: &ProjectPaths) -> Result { dependencies::resolve_and_download( paths, NullTelemetry, None, Vec::new(), dependencies::DependencyManagerConfig { use_manifest: dependencies::UseManifest::Yes, check_major_versions: dependencies::CheckMajorVersions::No, }, ) } } pub fn delete_directory(dir: &Utf8Path) -> Result<(), Error> { tracing::debug!(path=?dir, "deleting_directory"); if dir.exists() { std::fs::remove_dir_all(dir).map_err(|e| Error::FileIo { action: FileIoAction::Delete, kind: FileKind::Directory, path: dir.to_path_buf(), err: Some(e.to_string()), })?; } else { tracing::debug!(path=?dir, "directory_did_not_exist_for_deletion"); } Ok(()) } pub fn delete_file(file: &Utf8Path) -> Result<(), Error> { tracing::debug!("Deleting file {:?}", file); if file.exists() { std::fs::remove_file(file).map_err(|e| Error::FileIo { action: FileIoAction::Delete, kind: FileKind::File, path: file.to_path_buf(), err: Some(e.to_string()), })?; } else { tracing::debug!("Did not exist for deletion: {:?}", file); } Ok(()) } pub fn write_outputs_under(outputs: &[OutputFile], base: &Utf8Path) -> Result<(), Error> { for file in outputs { let path = base.join(&file.path); match &file.content { Content::Binary(buffer) => write_bytes(&path, buffer), Content::Text(buffer) => write(&path, buffer), }?; } Ok(()) } pub fn write_output(file: &OutputFile) -> Result<(), Error> { let OutputFile { path, content } = file; match content { Content::Binary(buffer) => write_bytes(path, buffer), Content::Text(buffer) => write(path, buffer), } } pub fn write(path: &Utf8Path, text: &str) -> Result<(), Error> { write_bytes(path, text.as_bytes()) } #[cfg(target_family = "unix")] pub fn make_executable(path: impl AsRef) -> Result<(), Error> { use std::os::unix::fs::PermissionsExt; tracing::debug!(path = ?path.as_ref(), "setting_permissions"); std::fs::set_permissions(path.as_ref(), std::fs::Permissions::from_mode(0o755)).map_err( |e| Error::FileIo { action: FileIoAction::UpdatePermissions, kind: FileKind::File, path: path.as_ref().to_path_buf(), err: Some(e.to_string()), }, )?; Ok(()) } #[cfg(not(target_family = "unix"))] pub fn make_executable(_path: impl AsRef) -> Result<(), Error> { Ok(()) } pub fn write_bytes(path: &Utf8Path, bytes: &[u8]) -> Result<(), Error> { tracing::debug!(path=?path, "writing_file"); let dir_path = path.parent().ok_or_else(|| Error::FileIo { action: FileIoAction::FindParent, kind: FileKind::Directory, path: path.to_path_buf(), err: None, })?; std::fs::create_dir_all(dir_path).map_err(|e| Error::FileIo { action: FileIoAction::Create, kind: FileKind::Directory, path: dir_path.to_path_buf(), err: Some(e.to_string()), })?; let mut f = File::create(path).map_err(|e| Error::FileIo { action: FileIoAction::Create, kind: FileKind::File, path: path.to_path_buf(), err: Some(e.to_string()), })?; f.write_all(bytes).map_err(|e| Error::FileIo { action: FileIoAction::WriteTo, kind: FileKind::File, path: path.to_path_buf(), err: Some(e.to_string()), })?; Ok(()) } fn is_gleam_path(path: &Utf8Path, dir: impl AsRef) -> bool { use regex::Regex; static RE: OnceLock = OnceLock::new(); RE.get_or_init(|| { Regex::new(&format!( "^({module}{slash})*{module}\\.gleam$", module = "[a-z][_a-z0-9]*", slash = "(/|\\\\)", )) .expect("is_gleam_path() RE regex") }) .is_match( path.strip_prefix(dir.as_ref()) .expect("is_gleam_path(): strip_prefix") .as_str(), ) } fn is_gleam_build_dir(e: &ignore::DirEntry) -> bool { if !e.path().is_dir() || !e.path().ends_with("build") { return false; } let Some(parent_path) = e.path().parent() else { return false; }; parent_path.join("gleam.toml").exists() } /// Walks through all Gleam module files in the directory, even if ignored, /// except for those in the `build/` directory. Excludes any Gleam files within /// invalid module paths, for example if they or a folder they're in contain a /// dot or a hyphen within their names. pub fn gleam_files(dir: &Utf8Path) -> impl Iterator + '_ { ignore::WalkBuilder::new(dir) .follow_links(true) .standard_filters(false) .filter_entry(|entry| !is_gleam_build_dir(entry)) .build() .filter_map(Result::ok) .filter(|entry| { entry .file_type() .map(|type_| type_.is_file()) .unwrap_or(false) }) .map(ignore::DirEntry::into_path) .map(|path| Utf8PathBuf::from_path_buf(path).expect("Non Utf-8 Path")) .filter(move |d| is_gleam_path(d, dir)) } /// Walks through all native files in the directory, such as `.mjs` and `.erl`, /// even if ignored. pub fn native_files(dir: &Utf8Path) -> impl Iterator + '_ { ignore::WalkBuilder::new(dir) .follow_links(true) .standard_filters(false) .filter_entry(|entry| !is_gleam_build_dir(entry)) .build() .filter_map(Result::ok) .filter(|entry| { entry .file_type() .map(|type_| type_.is_file()) .unwrap_or(false) }) .map(ignore::DirEntry::into_path) .map(|path| Utf8PathBuf::from_path_buf(path).expect("Non Utf-8 Path")) .filter(|path| { let extension = path.extension().unwrap_or_default(); is_native_file_extension(extension) }) } /// Walks through all files in the directory, even if ignored. pub fn private_files(dir: &Utf8Path) -> impl Iterator + '_ { ignore::WalkBuilder::new(dir) .follow_links(true) .standard_filters(false) .build() .filter_map(Result::ok) .filter(|entry| { entry .file_type() .map(|type_| type_.is_file()) .unwrap_or(false) }) .map(ignore::DirEntry::into_path) .map(|path| Utf8PathBuf::from_path_buf(path).expect("Non Utf-8 Path")) } /// Walks through all `.erl` and `.hrl` files in the directory, even if ignored. pub fn erlang_files(dir: &Utf8Path) -> impl Iterator + '_ { ignore::WalkBuilder::new(dir) .follow_links(true) .standard_filters(false) .build() .filter_map(Result::ok) .filter(|entry| { entry .file_type() .map(|type_| type_.is_file()) .unwrap_or(false) }) .map(ignore::DirEntry::into_path) .map(|path| Utf8PathBuf::from_path_buf(path).expect("Non Utf-8 Path")) .filter(|path| { let extension = path.extension().unwrap_or_default(); extension == "erl" || extension == "hrl" }) } pub fn create_tar_archive(outputs: Vec) -> Result, Error> { tracing::debug!("creating_tar_archive"); let encoder = flate2::write::GzEncoder::new(vec![], flate2::Compression::default()); let mut builder = tar::Builder::new(encoder); for file in outputs { let mut header = tar::Header::new_gnu(); header.set_path(&file.path).map_err(|e| Error::AddTar { path: file.path.clone(), err: e.to_string(), })?; header.set_size(file.content.as_bytes().len() as u64); header.set_cksum(); builder .append(&header, file.content.as_bytes()) .map_err(|e| Error::AddTar { path: file.path.clone(), err: e.to_string(), })?; } builder .into_inner() .map_err(|e| Error::TarFinish(e.to_string()))? .finish() .map_err(|e| Error::Gzip(e.to_string())) } pub fn mkdir(path: impl AsRef + Debug) -> Result<(), Error> { if path.as_ref().exists() { return Ok(()); } tracing::debug!(path=?path, "creating_directory"); std::fs::create_dir_all(path.as_ref()).map_err(|err| Error::FileIo { kind: FileKind::Directory, path: Utf8PathBuf::from(path.as_ref()), action: FileIoAction::Create, err: Some(err.to_string()), }) } pub fn read_dir(path: impl AsRef + Debug) -> Result { tracing::debug!(path=?path,"reading_directory"); Utf8Path::read_dir_utf8(path.as_ref()).map_err(|e| Error::FileIo { action: FileIoAction::Read, kind: FileKind::Directory, path: Utf8PathBuf::from(path.as_ref()), err: Some(e.to_string()), }) } pub fn module_caches_paths( path: impl AsRef + Debug, ) -> Result, Error> { Ok(read_dir(path)? .filter_map(Result::ok) .map(|f| f.into_path()) .filter(|p| p.extension() == Some("cache"))) } pub fn read(path: impl AsRef + Debug) -> Result { tracing::debug!(path=?path,"reading_file"); std::fs::read_to_string(path.as_ref()).map_err(|err| Error::FileIo { action: FileIoAction::Read, kind: FileKind::File, path: Utf8PathBuf::from(path.as_ref()), err: Some(err.to_string()), }) } pub fn read_bytes(path: impl AsRef + Debug) -> Result, Error> { tracing::debug!(path=?path,"reading_file"); std::fs::read(path.as_ref()).map_err(|err| Error::FileIo { action: FileIoAction::Read, kind: FileKind::File, path: Utf8PathBuf::from(path.as_ref()), err: Some(err.to_string()), }) } pub fn reader(path: impl AsRef + Debug) -> Result { tracing::debug!(path=?path,"opening_file_reader"); let reader = File::open(path.as_ref()).map_err(|err| Error::FileIo { action: FileIoAction::Open, kind: FileKind::File, path: Utf8PathBuf::from(path.as_ref()), err: Some(err.to_string()), })?; Ok(WrappedReader::new(path.as_ref(), Box::new(reader))) } pub fn buffered_reader + Debug>(path: P) -> Result { tracing::debug!(path=?path,"opening_file_buffered_reader"); let reader = File::open(path.as_ref()).map_err(|err| Error::FileIo { action: FileIoAction::Open, kind: FileKind::File, path: Utf8PathBuf::from(path.as_ref()), err: Some(err.to_string()), })?; Ok(BufReader::new(reader)) } pub fn copy( path: impl AsRef + Debug, to: impl AsRef + Debug, ) -> Result<(), Error> { tracing::debug!(from=?path, to=?to, "copying_file"); // TODO: include the destination in the error message std::fs::copy(path.as_ref(), to.as_ref()) .map_err(|err| Error::FileIo { action: FileIoAction::Copy, kind: FileKind::File, path: Utf8PathBuf::from(path.as_ref()), err: Some(err.to_string()), }) .map(|_| ()) } // pub fn rename(path: impl AsRef + Debug, to: impl AsRef + Debug) -> Result<(), Error> { // tracing::debug!(from=?path, to=?to, "renaming_file"); // // TODO: include the destination in the error message // std::fs::rename(&path, &to) // .map_err(|err| Error::FileIo { // action: FileIoAction::Rename, // kind: FileKind::File, // path: Utf8PathBuf::from(path.as_ref()), // err: Some(err.to_string()), // }) // .map(|_| ()) // } pub fn copy_dir( path: impl AsRef + Debug, to: impl AsRef + Debug, ) -> Result<(), Error> { tracing::debug!(from=?path, to=?to, "copying_directory"); // TODO: include the destination in the error message fs_extra::dir::copy( path.as_ref(), to.as_ref(), &fs_extra::dir::CopyOptions::new() .copy_inside(false) .content_only(true), ) .map_err(|err| Error::FileIo { action: FileIoAction::Copy, kind: FileKind::Directory, path: Utf8PathBuf::from(path.as_ref()), err: Some(err.to_string()), }) .map(|_| ()) } pub fn symlink_dir( src: impl AsRef + Debug, dest: impl AsRef + Debug, ) -> Result<(), Error> { tracing::debug!(src=?src, dest=?dest, "symlinking"); let src = canonicalise(src.as_ref())?; #[cfg(target_family = "windows")] let result = std::os::windows::fs::symlink_dir(src, dest.as_ref()); #[cfg(not(target_family = "windows"))] let result = std::os::unix::fs::symlink(src, dest.as_ref()); result.map_err(|err| Error::FileIo { action: FileIoAction::Link, kind: FileKind::File, path: Utf8PathBuf::from(dest.as_ref()), err: Some(err.to_string()), })?; Ok(()) } pub fn hardlink( from: impl AsRef + Debug, to: impl AsRef + Debug, ) -> Result<(), Error> { tracing::debug!(from=?from, to=?to, "hardlinking"); std::fs::hard_link(from.as_ref(), to.as_ref()) .map_err(|err| Error::FileIo { action: FileIoAction::Link, kind: FileKind::File, path: Utf8PathBuf::from(from.as_ref()), err: Some(err.to_string()), }) .map(|_| ()) } /// Check if the given path is inside a git work tree. /// This is done by running `git rev-parse --is-inside-work-tree --quiet` in the /// given path. If git is not installed then we assume we're not in a git work /// tree. /// fn is_inside_git_work_tree(path: &Utf8Path) -> Result { tracing::debug!(path=?path, "checking_for_git_repo"); let args: Vec<&str> = vec!["rev-parse", "--is-inside-work-tree", "--quiet"]; // Ignore all output, rely on the exit code instead. // git will display a fatal error on stderr if rev-parse isn't run inside of a git work tree, // so send stderr to /dev/null let result = std::process::Command::new("git") .args(args) .stdin(std::process::Stdio::null()) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .current_dir(path) .status(); match result { Ok(status) => Ok(status.success()), Err(error) => match error.kind() { io::ErrorKind::NotFound => Ok(false), other => Err(Error::ShellCommand { program: "git".into(), reason: ShellCommandFailureReason::IoError(other), }), }, } } pub(crate) fn is_git_work_tree_root(path: &Utf8Path) -> bool { tracing::debug!(path=?path, "checking_for_git_repo_root"); exists(path.join(".git")).unwrap_or(false) } /// Run `git init` in the given path. /// If git is not installed then we do nothing. pub fn git_init(path: &Utf8Path) -> Result<(), Error> { tracing::debug!(path=?path, "initializing git"); if is_inside_git_work_tree(path)? { tracing::debug!(path=?path, "git_repo_already_exists"); return Ok(()); } let args = vec!["init".into(), "--quiet".into(), path.to_string()]; let command = Command { program: "git".to_string(), args, env: vec![], cwd: None, stdio: Stdio::Inherit, }; match ProjectIO::new().exec(command) { Ok(_) => Ok(()), Err(err) => match err { Error::ShellProgramNotFound { .. } => Ok(()), _ => Err(Error::GitInitialization { error: err.to_string(), }), }, } } pub fn canonicalise(path: &Utf8Path) -> Result { std::fs::canonicalize(path) .map_err(|err| Error::FileIo { action: FileIoAction::Canonicalise, kind: FileKind::File, path: Utf8PathBuf::from(path), err: Some(err.to_string()), }) .map(|pb| Utf8PathBuf::from_path_buf(pb).expect("Non Utf8 Path")) } #[derive(Debug, Clone, Copy)] pub struct ConsoleWarningEmitter; impl WarningEmitterIO for ConsoleWarningEmitter { fn emit_warning(&self, warning: Warning) { let buffer_writer = crate::cli::stderr_buffer_writer(); let mut buffer = buffer_writer.buffer(); warning.pretty(&mut buffer); buffer_writer .print(&buffer) .expect("Writing warning to stderr"); } } ================================================ FILE: compiler-cli/src/hex/auth.rs ================================================ use crate::{cli, http::HttpClient}; use ecow::EcoString; use gleam_core::{ Error, Result, encryption, error::{FileIoAction, FileKind}, io::HttpClient as _, paths, }; use hexpm::OAuthTokens; use sha2::Digest as _; pub const HEX_OAUTH_CLIENT_ID: &str = "877731e8-cb88-45e1-9b84-9214de7da421"; pub const LOCAL_PASS_PROMPT: &str = "Local password"; pub const API_ENV_NAME: &str = "HEXPM_API_KEY"; #[derive(Debug)] pub struct EncryptedLegacyApiKey { pub name: String, } pub struct HexAuthentication<'runtime> { runtime: &'runtime tokio::runtime::Runtime, http: &'runtime HttpClient, local_password: Option, hex_config: hexpm::Config, } impl<'runtime> HexAuthentication<'runtime> { /// Reads the stored API key from disc, if it exists. /// pub fn new( runtime: &'runtime tokio::runtime::Runtime, http: &'runtime HttpClient, hex_config: hexpm::Config, ) -> Self { Self { runtime, http, local_password: None, hex_config, } } /// Create new OAuth tokens by sending the user through the OAuth flow. /// /// Any already-existing tokens stored on the file system are revoked. /// pub fn create_and_store_new_credentials_via_oauth(&mut self) -> Result { let previous_oauth_token = self.read_stored_encrypted_oauth_refresh_token()?; let previous_legacy_key = self.read_stored_legacy_api_key()?; // Create a device authorisation with Hex, starting the oauth flow. let mut device_authorisation = self.create_oauth_device_authorisation()?; // Show the user their code, and send them to Hex to log in. send_user_to_oauth_verification_url(&device_authorisation); // The user has been sent to the Hex website to authenticate. // Poll the Hex API until they accept or reject the request, or it times out. let tokens = loop { let next = self.poll_for_oauth_next_step(&mut device_authorisation)?; match next { hexpm::PollStep::Done(tokens) => break tokens, hexpm::PollStep::SleepThenPollAgain(duration) => std::thread::sleep(duration), } }; // We are creating a new API key, so we need a new local password // to encrypt it with. self.ask_for_new_local_password()?; self.encrypt_and_store_oauth_refresh_token(&tokens)?; let credentials = tokens.as_credentials(); // Dispose of old tokens, if there was any. self.revoke_old_tokens(&credentials, previous_oauth_token, previous_legacy_key)?; self.delete_legacy_api_key_from_filesystem()?; Ok(credentials) } fn poll_for_oauth_next_step( &mut self, device_authorisation: &mut hexpm::OAuthDeviceAuthorisation, ) -> Result { let request = device_authorisation.poll_token_request(&self.hex_config); let response = self.runtime.block_on(self.http.send(request))?; let next = device_authorisation .poll_token_response(response) .map_err(Error::hex)?; Ok(next) } fn create_oauth_device_authorisation( &mut self, ) -> Result { // Create a recognisable name for the client, so folks can more easily understand which // session is which in the Hex console. // It is expected that we can always get the hostname. let hostname = hostname::get().expect("hostname"); let client_name = format!("Gleam ({})", hostname.to_string_lossy()); let request = hexpm::oauth_device_authorisation_request( HEX_OAUTH_CLIENT_ID, &client_name, &self.hex_config, ); let response = self.runtime.block_on(self.http.send(request))?; hexpm::oauth_device_authorisation_response(HEX_OAUTH_CLIENT_ID.to_string(), response) .map_err(Error::hex) } fn encrypt_and_store_oauth_refresh_token(&mut self, tokens: &OAuthTokens) -> Result<(), Error> { let path = paths::global_hexpm_oauth_credentials_path(); let local_password = self.get_local_password()?; let encrypted_refresh_token = encryption::encrypt_with_passphrase(tokens.refresh_token.as_bytes(), &local_password) .map_err(|e| Error::FailedToEncryptLocalHexApiKey { detail: e.to_string(), })?; let credentials = StoredOAuthCredentials { hexpm: StoredOAuthRepoCredentials { api: self.hex_config.api_base.clone(), repository: self.hex_config.repository_base.clone(), refresh_token: encrypted_refresh_token, refresh_token_hash: { let mut hasher = sha2::Sha256::new(); hasher.update(tokens.refresh_token.as_bytes()); base16::encode_lower(&hasher.finalize()) }, }, }; let toml = toml::to_string(&credentials).expect("OAuth credentials TOML encoding"); crate::fs::write(&path, &toml)?; Ok(()) } /// Create a new local password. /// /// The password must be long enough. /// /// The old password will be discarded, and the new one will be both /// returned and stored in `self.local_password` /// fn ask_for_new_local_password(&mut self) -> Result<()> { let required_length = 8; self.local_password = None; println!( "Please enter a new unique password, at least {required_length} characters long. It will be used to locally encrypt your Hex API tokens. " ); loop { let password = cli::ask_password(LOCAL_PASS_PROMPT)?; if password.chars().count() < required_length { println!("\nPlease use a password at least {required_length} characters long.\n") } else { self.local_password = Some(password.clone()); return Ok(()); } } } fn get_local_password(&mut self) -> Result { if let Some(password) = self.local_password.as_ref() { return Ok(password.clone()); } let password = cli::ask_password(LOCAL_PASS_PROMPT)?; self.local_password = Some(password.clone()); Ok(password) } /// Get a token that can be used to authenticate with the Hex API. /// In order, it will try these sources: /// /// 1. An API key from the HEXPM_API_KEY environment variable. /// 2. An OAuth refresh token from the file system, which is then exchanged for /// an access token. /// 3. The OAuth flow. pub fn get_or_create_api_credentials(&mut self) -> Result { if let Some(key) = Self::read_env_api_key()? { return Ok(key); } if let Some(tokens) = self.read_and_decrypt_and_refresh_stored_tokens()? { return Ok(tokens.as_credentials()); } self.create_and_store_new_credentials_via_oauth() } fn read_env_api_key() -> Result> { let api_key = std::env::var(API_ENV_NAME).unwrap_or_default(); if api_key.trim().is_empty() { Ok(None) } else { Ok(Some(hexpm::Credentials::ApiKey(EcoString::from(api_key)))) } } /// Read, decrypt, and refresh OAuth keys stored on the filesystem. /// /// The new refresh is encrypted and stored on the file system for next use. /// /// If there is no stored key then this return `Ok(None)`, and you'll /// need to create a new one. /// fn read_and_decrypt_and_refresh_stored_tokens(&mut self) -> Result> { let Some(encrypted_credentials) = self.read_stored_encrypted_oauth_refresh_token()? else { return Ok(None); }; let local_password = self.get_local_password()?; let refresh_token = encryption::decrypt_with_passphrase( encrypted_credentials.refresh_token.as_bytes(), &local_password, ) .map_err(|e| Error::FailedToDecryptLocalHexApiKey { detail: e.to_string(), })?; // Use a refresh token, consuming it to get a new set of tokens. let request = hexpm::oauth_refresh_token_request( HEX_OAUTH_CLIENT_ID, &refresh_token, &self.hex_config, ); let response = self.runtime.block_on(self.http.send(request))?; let tokens = hexpm::oauth_refresh_token_response(response).map_err(Error::hex)?; // Store the refresh token for future use. self.encrypt_and_store_oauth_refresh_token(&tokens)?; Ok(Some(tokens)) } fn read_stored_encrypted_oauth_refresh_token( &self, ) -> Result> { let path = paths::global_hexpm_oauth_credentials_path(); if !path.exists() { return Ok(None); } let toml = crate::fs::read(&path)?; let credentials: StoredOAuthCredentials = toml::from_str(&toml).map_err(|e| Error::FileIo { action: FileIoAction::Parse, kind: FileKind::File, path, err: Some(e.to_string()), })?; Ok(Some(credentials.hexpm)) } fn delete_legacy_api_key_from_filesystem(&self) -> Result<()> { let path = paths::global_hexpm_legacy_credentials_path(); if !path.exists() { return Ok(()); } crate::fs::delete_file(&path) } fn read_stored_legacy_api_key(&self) -> Result> { let path = paths::global_hexpm_legacy_credentials_path(); if !path.exists() { return Ok(None); } let text = crate::fs::read(&path)?; let mut chunks = text.splitn(2, '\n'); let Some(name) = chunks.next() else { return Ok(None); }; // We no longer use this encrypted key, but we still parse it to ensure that // the file is the correct format. If it is not then we cannot safely use it. let Some(_encrypted) = chunks.next() else { return Ok(None); }; Ok(Some(EncryptedLegacyApiKey { name: name.to_string(), })) } fn revoke_old_tokens( &self, credentials: &hexpm::Credentials, previous_oauth_token: Option, previous_legacy_key: Option, ) -> Result<()> { if previous_oauth_token.is_none() && previous_legacy_key.is_none() { return Ok(()); } println!("\nRevoking old authentication credentials"); let credentials = crate::hex::write_credentials(credentials)?; if let Some(token) = previous_oauth_token { let hash = token.refresh_token_hash; let request = hexpm::revoke_oauth_token_by_hash_request(&hash, &credentials, &self.hex_config); let response = self.runtime.block_on(self.http.send(request))?; hexpm::revoke_oauth_token_by_hash_response(response).map_err(Error::hex)?; } if let Some(key) = previous_legacy_key { let request = hexpm::api_remove_api_key_request(&key.name, &credentials, &self.hex_config); let response = self.runtime.block_on(self.http.send(request))?; hexpm::api_remove_api_key_response(response).map_err(Error::hex)?; } Ok(()) } } fn send_user_to_oauth_verification_url(device_authorisation: &hexpm::OAuthDeviceAuthorisation) { // We make them press Enter to open the browser instead of going there immediately, // to make sure they see the code before the browser window potentially hides the // terminal window. let uri = &device_authorisation.verification_uri; println!( "Your Hex verification code: {code} Verify this code matches what is shown in your browser. Press Enter to open {uri}", code = device_authorisation.user_code, ); let _ = std::io::stdin() .read_line(&mut String::new()) .expect("stdin read_line"); if opener::open_browser(uri).is_err() { println!("\nFailed to open the browser, please navigate to {uri}"); } } #[derive(Debug, serde::Serialize, serde::Deserialize)] struct StoredOAuthRepoCredentials { #[serde(with = "http_serde::uri")] api: http::Uri, #[serde(with = "http_serde::uri")] repository: http::Uri, /// An encrypted refresh token. refresh_token: String, /// The hash of the token, so it can be revoked even if it cannot be encrypted. refresh_token_hash: String, } #[derive(Debug, serde::Serialize, serde::Deserialize)] struct StoredOAuthCredentials { hexpm: StoredOAuthRepoCredentials, } ================================================ FILE: compiler-cli/src/hex.rs ================================================ mod auth; use crate::{cli, http::HttpClient}; use gleam_core::{ Error, Result, hex::{self, RetirementReason}, io::HttpClient as _, paths::ProjectPaths, }; pub use auth::HexAuthentication; /// Prepare credentials for user for write actions. /// This will prompt for a one-time-password if needed. pub fn write_credentials( credentials: &hexpm::Credentials, ) -> Result { match credentials { hexpm::Credentials::ApiKey(key) => Ok(hexpm::WriteActionCredentials::ApiKey(key.clone())), hexpm::Credentials::OAuthAccessToken(token) => { let one_time_password = cli::ask("Enter your two-factor authentication code")?.into(); Ok(hexpm::WriteActionCredentials::OAuthAccessToken { access_token: token.clone(), one_time_password, }) } } } pub fn retire( package: String, version: String, reason: RetirementReason, message: Option, ) -> Result<()> { let runtime = tokio::runtime::Runtime::new().expect("Unable to start Tokio async runtime"); let config = hexpm::Config::new(); let http = HttpClient::new(); let credentials = HexAuthentication::new(&runtime, &http, config.clone()).get_or_create_api_credentials()?; runtime.block_on(hex::retire_release( &package, &version, reason, message.as_deref(), &write_credentials(&credentials)?, &config, &http, ))?; cli::print_retired(&package, &version); Ok(()) } pub fn unretire(package: String, version: String) -> Result<()> { let runtime = tokio::runtime::Runtime::new().expect("Unable to start Tokio async runtime"); let http = HttpClient::new(); let config = hexpm::Config::new(); let credentials = HexAuthentication::new(&runtime, &http, config.clone()).get_or_create_api_credentials()?; runtime.block_on(hex::unretire_release( &package, &version, &write_credentials(&credentials)?, &config, &http, ))?; cli::print_unretired(&package, &version); Ok(()) } pub fn revert( paths: &ProjectPaths, package: Option, version: Option, ) -> Result<()> { let (package, version) = match (package, version) { (Some(pkg), Some(ver)) => (pkg, ver), (None, Some(ver)) => (crate::config::root_config(paths)?.name.to_string(), ver), (Some(pkg), None) => { let query = format!("Which version of package {pkg} do you want to revert?"); let ver = cli::ask(&query)?; (pkg, ver) } (None, None) => { // Only want to access root_config once rather than twice let config = crate::config::root_config(paths)?; (config.name.to_string(), config.version.to_string()) } }; let question = format!("Do you wish to revert {package} version {version}?"); if !cli::confirm(&question)? { println!("Not reverting."); return Ok(()); } let runtime = tokio::runtime::Runtime::new().expect("Unable to start Tokio async runtime"); let hex_config = hexpm::Config::new(); let http = HttpClient::new(); let credentials = HexAuthentication::new(&runtime, &http, hex_config.clone()) .get_or_create_api_credentials()?; // Revert release from API let request = hexpm::api_revert_release_request( &package, &version, &write_credentials(&credentials)?, &hex_config, ) .map_err(Error::hex)?; let response = runtime.block_on(http.send(request))?; hexpm::api_revert_release_response(response).map_err(Error::hex)?; // Done! println!("{package} {version} has been removed from Hex"); Ok(()) } pub(crate) fn authenticate() -> Result<()> { let runtime = tokio::runtime::Runtime::new().expect("Unable to start Tokio async runtime"); let http = HttpClient::new(); let config = hexpm::Config::new(); let credentials = HexAuthentication::new(&runtime, &http, config.clone()) .create_and_store_new_credentials_via_oauth()?; let request = hexpm::get_me_request(&credentials, &config); let response = runtime.block_on(http.send(request))?; let me = hexpm::get_me_response(response).map_err(Error::hex)?; println!("\nSuccessfully logged in as {}", me.username); Ok(()) } ================================================ FILE: compiler-cli/src/http.rs ================================================ use std::convert::TryInto; use std::sync::OnceLock; use async_trait::async_trait; use camino::Utf8PathBuf; use gleam_core::{ Error, Result, error::{FileIoAction, FileKind}, }; use http::{Request, Response}; use reqwest::{Certificate, Client}; use crate::fs; static REQWEST_CLIENT: OnceLock = OnceLock::new(); #[derive(Debug)] pub struct HttpClient; impl HttpClient { pub fn new() -> Self { Self } pub fn boxed() -> Box { Box::new(Self::new()) } } #[async_trait] impl gleam_core::io::HttpClient for HttpClient { async fn send(&self, request: Request>) -> Result>> { tracing::debug!( method = request.method().as_str(), url = request.uri().to_string(), "http-send", ); let request = request .try_into() .expect("Unable to convert HTTP request for use by reqwest library"); let client = init_client().map_err(Error::http)?; let mut response = client.execute(request).await.map_err(Error::http)?; let mut builder = Response::builder() .status(response.status()) .version(response.version()); if let Some(headers) = builder.headers_mut() { std::mem::swap(headers, response.headers_mut()); } builder .body(response.bytes().await.map_err(Error::http)?.to_vec()) .map_err(Error::http) } } fn init_client() -> Result<&'static Client, Error> { if let Some(client) = REQWEST_CLIENT.get() { return Ok(client); } let certificate_path = match std::env::var("GLEAM_CACERTS_PATH") { Ok(path) => { tracing::trace!("Using GLEAM_CACERTS_PATH environment variable"); path } Err(_) => { return Ok(REQWEST_CLIENT.get_or_init(|| { Client::builder() .build() .expect("Failed to create reqwest client") })); } }; let certificate_bytes = fs::read_bytes(&certificate_path)?; let certificate = Certificate::from_pem(&certificate_bytes).map_err(|error| Error::FileIo { kind: FileKind::File, action: FileIoAction::Parse, path: Utf8PathBuf::from(&certificate_path), err: Some(error.to_string()), })?; Ok(REQWEST_CLIENT.get_or_init(|| { Client::builder() .add_root_certificate(certificate) .build() .expect("Failed to create reqwest client") })) } ================================================ FILE: compiler-cli/src/lib.rs ================================================ #![warn( clippy::all, clippy::dbg_macro, clippy::todo, clippy::mem_forget, clippy::use_self, clippy::filter_map_next, clippy::needless_continue, clippy::needless_borrow, clippy::match_wildcard_for_single_variants, clippy::imprecise_flops, clippy::suboptimal_flops, clippy::lossy_float_literal, clippy::rest_pat_in_fully_bound_structs, clippy::fn_params_excessive_bools, clippy::inefficient_to_string, clippy::linkedlist, clippy::macro_use_imports, clippy::option_option, clippy::verbose_file_reads, clippy::unnested_or_patterns, rust_2018_idioms, missing_debug_implementations, missing_copy_implementations, trivial_casts, trivial_numeric_casts, nonstandard_style, unexpected_cfgs, unused_import_braces, unused_qualifications )] #![deny( clippy::await_holding_lock, clippy::if_let_mutex, clippy::indexing_slicing, clippy::mem_forget, clippy::ok_expect, clippy::unimplemented, clippy::unwrap_used, unsafe_code, unstable_features, unused_results )] #![allow( clippy::match_single_binding, clippy::inconsistent_struct_constructor, clippy::assign_op_pattern, clippy::len_without_is_empty )] #[cfg(test)] #[macro_use] extern crate pretty_assertions; mod add; mod beam_compiler; mod build; mod build_lock; mod cli; mod compile_package; mod config; mod dependencies; mod docs; mod export; mod fix; mod format; pub mod fs; mod hex; mod http; mod lsp; mod new; mod owner; mod panic; mod publish; mod remove; pub mod run; mod shell; mod text_layout; use config::root_config; use fs::{get_current_directory, get_project_root}; pub use gleam_core::error::{Error, Result}; use gleam_core::{ analyse::TargetSupport, build::{Codegen, Compile, Mode, NullTelemetry, Options, Runtime, Target}, hex::RetirementReason, paths::ProjectPaths, version::COMPILER_VERSION, }; use std::str::FromStr; use camino::Utf8PathBuf; use clap::{ Args, Parser, Subcommand, builder::{PossibleValuesParser, Styles, TypedValueParser, styling}, }; use strum::VariantNames; #[derive(Args, Debug, Clone)] struct UpdateOptions { /// (optional) Names of the packages to update /// If omitted, all dependencies will be updated #[arg(verbatim_doc_comment)] packages: Vec, } #[derive(Args, Debug, Clone)] struct TreeOptions { /// Name of the package to get the dependency tree for #[arg( short, long, ignore_case = true, conflicts_with = "invert", help = "Package to be used as the root of the tree" )] package: Option, /// Name of the package to get the inverted dependency tree for #[arg( short, long, ignore_case = true, conflicts_with = "package", help = "Invert the tree direction and focus on the given package", value_name = "PACKAGE" )] invert: Option, } #[derive(Parser, Debug)] #[command( version, name = "gleam", next_display_order = None, help_template = "\ {before-help}{name} {version} {usage-heading} {usage} {all-args}{after-help}", styles = Styles::styled() .header(styling::AnsiColor::Yellow.on_default()) .usage(styling::AnsiColor::Yellow.on_default()) .literal(styling::AnsiColor::Green.on_default()) )] enum Command { /// Build the project Build { /// Consider the build failed if the package contains any warnings #[arg(long)] warnings_as_errors: bool, /// Which compilation target to use #[arg(short, long, ignore_case = true, help = target_doc())] target: Option, /// Don't print progress information #[clap(long)] no_print_progress: bool, }, /// Type check the project Check { /// Which compilation target to use #[arg(short, long, ignore_case = true, help = target_doc())] target: Option, }, /// Publish the project to the Hex package repository /// /// Please ensure your package is suitable for production use before /// publishing. If you have a prototype package that you wish to use /// in another project then use git dependencies instead of publishing /// to the package repository. /// /// This command optionally accepts the environment variable /// `HEXPM_API_KEY`, which can hold a Hex API key to authenticate /// with Hex. /// #[command(verbatim_doc_comment)] Publish { #[arg(long)] replace: bool, #[arg(short, long)] yes: bool, }, /// Render HTML documentation for the package /// /// There are several options in `gleam.toml` that can be used to /// configure the output. /// /// repository = { type = "github", user = "lpil", repo = "wibble" } /// /// Specify the location of the source code repository for the package. /// This will add a link to the side bar, and add "view code" links for /// the documented types and values. /// /// links = [ /// { title = "Home page", href = "https://example.com" }, /// { title = "Other site", href = "https://another.example.com" }, /// ] /// /// Specify some additional links to include in the sidebar. /// /// [documentation] /// pages = [ /// { title = "My Page", path = "my-page.html", source = "./path/to/my-page.md" }, /// ] /// /// Specify additional markdown pages to include in the documentation, /// with links for each added to the sidebar. /// #[command(subcommand, verbatim_doc_comment)] Docs(Docs), /// Work with dependency packages /// /// The packages and the acceptable version ranges are specified in /// `gleam.toml`. You can edit this file manually, or use the `gleam add` /// and `gleam remove` commands. /// /// Package versions follow semantic versioning: MAJOR.MINOR.PATCH. /// - Major updates include breaking changes, either type changes or /// semantic changes. /// - Minor updates include new functionality, but no breaking changes. /// - Patch updates include only bug fixes. /// /// Dependency resolution will be performed automatically by any command /// that builds your project. Once versions have been selected they are /// written to `manifest.toml`, which locks the package to those versions, /// making your build deterministic. You should not edit this file manually. /// /// To upgrade the dependencies you can use the `gleam update` command, /// which will select the newest versions compatible with the requirements /// in `gleam.toml`. /// /// The `[dependencies]` section of `gleam.toml` holds the production /// dependencies. These are included in your production application, or /// are used as the dependencies when published as a library. The /// `[dev_dependencies]` section holds dependencies that are only used /// during development, e.g. code used for testing the package. /// /// ## Syntax examples: /// /// [dependencies] /// wibble = ">= 1.2.0 and < 2.0.0" /// /// Require the package `wibble`, permitting versions greater than or /// equal to 1.2.0, but lower than 2.0.0. /// /// [dependencies] /// wibble = ">= 1.2.0 and < 2.0.0 and != 1.4.1" /// /// Permit a range, but deny a specific version within that range. This /// could be useful if there is a version known to have a bug. /// /// [dependencies] /// wibble = { git = "https://example.com/wibble.git", ref = "a8b3c5d82" } /// /// A dependency fetched from Git instead of from Hex. This is useful /// for using packages of yours that are not-yet production-ready, or /// for bug fixes that have not yet been published to Hex. /// /// [dependencies] /// wibble = { path = "../wibble" } /// /// A local dependency, on your computer. This is useful for testing and /// and for applications made of multiple packages in a single version /// control repository. /// #[command(subcommand, verbatim_doc_comment)] Deps(Dependencies), /// Update dependency packages to their latest versions Update(UpdateOptions), /// Work with the Hex package manager #[command(subcommand)] Hex(Hex), /// Create a new project New(NewOptions), /// Format source code Format { /// The files or directories to format #[arg(default_value = ".")] files: Vec, /// Read source from standard-input #[arg(long)] stdin: bool, /// Only check if inputs are formatted correctly, erroring if they are not #[arg(long)] check: bool, }, /// Rewrite deprecated Gleam code Fix, /// Start an Erlang REPL with the Gleam code loaded Shell, /// Run the project /// /// This command runs the `main` function from the `` module. #[command(trailing_var_arg = true)] Run { #[arg(short, long, ignore_case = true, help = target_doc())] /// Which compilation target to use #[arg(short, long, ignore_case = true, help = target_doc())] target: Option, #[arg(long, ignore_case = true, help = runtime_doc())] runtime: Option, /// The module to run #[arg(short, long)] module: Option, /// Don't print progress information #[clap(long)] no_print_progress: bool, arguments: Vec, }, /// Run the project tests /// /// This command runs the `main` function from the `_test` module. #[command(trailing_var_arg = true)] Test { #[arg(short, long, ignore_case = true, help = target_doc())] /// Which compilation target to use #[arg(short, long, ignore_case = true, help = target_doc())] target: Option, #[arg(long, ignore_case = true, help = runtime_doc())] runtime: Option, arguments: Vec, }, /// Run the project development entrypoint /// /// This command runs the `main` function from the `_dev` module. #[command(trailing_var_arg = true)] Dev { #[arg(short, long, ignore_case = true, help = target_doc())] /// Which compilation target to use #[arg(short, long, ignore_case = true, help = target_doc())] target: Option, #[arg(long, ignore_case = true, help = runtime_doc())] runtime: Option, arguments: Vec, }, /// A low-level API for compiling a single Gleam package /// /// This is to be used by other build tools to implement support for Gleam /// code. It is not used directly by humans. /// #[command(verbatim_doc_comment)] CompilePackage(CompilePackage), /// Read and print gleam.toml for debugging #[command(hide = true)] PrintConfig, /// Add new dependencies /// /// The newest compatible version of the package is determined, and then /// `gleam.toml` is updated to require at least that version, with a range /// permitting future patch and minor updates. /// /// Add the package "wibble": /// gleam add wibble /// /// Add the package "wibble", requiring version >= v2.0.0 and < v3.0.0: /// gleam add wibble@2 /// /// Add the package "wibble", requiring version >= v2.5.1 and < v3.0.0: /// gleam add wibble@2.5.1 /// /// Add multiple packages: /// gleam add wibble@2 warble@1 /// /// Add a package as a non-production dependency: /// gleam add --dev wibble /// /// You can also edit `gleam.toml` directly, for further control over your /// package dependencies. Run `gleam help deps` for documentation on the /// format. /// #[command(verbatim_doc_comment)] Add { /// The names of Hex packages to add #[arg(required = true)] packages: Vec, /// Add the packages as dev-only dependencies #[arg(long)] dev: bool, }, /// Remove project dependencies Remove { /// The names of packages to remove #[arg(required = true)] packages: Vec, }, /// Delete any build artifacts for this project Clean, /// Run the language server, to be used by editors #[command(name = "lsp")] LanguageServer, /// Export something useful from the Gleam project #[command(subcommand)] Export(ExportTarget), } fn template_doc() -> &'static str { "The template to use" } fn target_doc() -> String { format!("The platform to target ({})", Target::VARIANTS.join("|")) } fn runtime_doc() -> String { format!("The runtime to target ({})", Runtime::VARIANTS.join("|")) } #[derive(Subcommand, Debug, Clone)] pub enum ExportTarget { /// Precompiled Erlang, suitable for deployment ErlangShipment, /// The package bundled into a tarball, suitable for publishing to Hex HexTarball, /// The JavaScript prelude module JavascriptPrelude, /// The TypeScript prelude module TypescriptPrelude, /// Information on the modules, functions, and types in the project in JSON format PackageInterface { #[arg(long = "out", required = true)] /// The path to write the JSON file to output: Utf8PathBuf, }, /// Package information (gleam.toml) in JSON format PackageInformation { #[arg(long = "out", required = true)] /// The path to write the JSON file to output: Utf8PathBuf, }, } #[derive(Args, Debug, Clone)] pub struct NewOptions { /// Location of the project root pub project_root: String, /// Name of the project #[arg(long)] pub name: Option, #[arg(long, ignore_case = true, default_value = "erlang", help = template_doc())] pub template: new::Template, /// Skip git initialization and creation of .gitignore, .git/* and .github/* files #[arg(long)] pub skip_git: bool, /// Skip creation of .github/* files #[arg(long)] pub skip_github: bool, } #[derive(Args, Debug)] pub struct CompilePackage { /// The compilation target for the generated project #[arg(long, ignore_case = true)] target: Target, /// The directory of the Gleam package #[arg(long = "package")] package_directory: Utf8PathBuf, /// A directory to write compiled package to #[arg(long = "out")] output_directory: Utf8PathBuf, /// A directories of precompiled Gleam projects #[arg(long = "lib")] libraries_directory: Utf8PathBuf, /// The location of the JavaScript prelude module, relative to the `out` /// directory. /// /// Required when compiling to JavaScript. /// /// This likely wants to be a `.mjs` file as NodeJS does not permit /// importing of other JavaScript file extensions. /// #[arg(verbatim_doc_comment, long = "javascript-prelude")] javascript_prelude: Option, /// Skip Erlang to BEAM bytecode compilation #[arg(long = "no-beam")] skip_beam_compilation: bool, } #[derive(Subcommand, Debug)] enum Dependencies { /// List all dependency packages List, /// Download all dependency packages Download, /// List all outdated dependencies Outdated, /// Update dependency packages to their latest versions Update(UpdateOptions), /// Tree of all the dependency packages Tree(TreeOptions), } #[derive(Subcommand, Debug)] enum Hex { /// Retire a release from Hex /// /// This command uses the environment variable: /// /// - HEXPM_API_KEY: (optional) A Hex API key to authenticate with the Hex package manager. /// #[command(verbatim_doc_comment)] Retire { package: String, version: String, #[arg(value_parser = PossibleValuesParser::new(RetirementReason::VARIANTS).map(|s| RetirementReason::from_str(&s).unwrap()))] reason: RetirementReason, message: Option, }, /// Un-retire a release from Hex /// /// This command uses this environment variable: /// /// - HEXPM_API_KEY: (optional) A Hex API key to authenticate with the Hex package manager. /// #[command(verbatim_doc_comment)] Unretire { package: String, version: String }, /// Revert a release from Hex /// /// This command uses this environment variable: /// /// - HEXPM_API_KEY: (optional) A Hex API key to authenticate with the Hex package manager. /// #[command(verbatim_doc_comment)] Revert { #[arg(long)] package: Option, #[arg(long)] version: Option, }, /// Deal with package ownership #[command(subcommand)] Owner(Owner), /// Log in to Hex. Replaces the credentials with new ones if already logged in. Authenticate, } #[derive(Subcommand, Debug)] enum Owner { /// Transfers ownership of the given package to a new Hex user /// /// This command uses this environment variable: /// /// - HEXPM_API_KEY: (optional) A Hex API key to authenticate against the Hex package manager. /// #[command(verbatim_doc_comment)] Transfer { package: String, /// The username or email of the new owner #[arg(long = "to")] username_or_email: String, }, } #[derive(Subcommand, Debug)] enum Docs { /// Render HTML docs locally Build { /// Opens the docs in a browser after rendering #[arg(long)] open: bool, #[arg(short, long, ignore_case = true, help = target_doc())] target: Option, }, /// Publish HTML docs to HexDocs /// /// This command uses this environment variable: /// /// - HEXPM_API_KEY: (optional) A Hex API key to authenticate with the Hex package manager. /// #[command(verbatim_doc_comment)] Publish, /// Remove HTML docs from HexDocs /// /// This command uses this environment variable: /// /// - HEXPM_API_KEY: (optional) A Hex API key to authenticate with the Hex package manager. /// #[command(verbatim_doc_comment)] Remove { /// The name of the package #[arg(long)] package: String, /// The version of the docs to remove #[arg(long)] version: String, }, } pub fn main() { initialise_logger(); panic::add_handler(); let stderr = cli::stderr_buffer_writer(); let result = parse_and_run_command(); match result { Ok(_) => { tracing::info!("Successfully completed"); } Err(error) => { tracing::error!(error = ?error, "Failed"); let mut buffer = stderr.buffer(); error.pretty(&mut buffer); stderr.print(&buffer).expect("Final result error writing"); std::process::exit(1); } } } fn parse_and_run_command() -> Result<(), Error> { match Command::parse() { Command::Build { target, warnings_as_errors, no_print_progress, } => { let paths = find_project_paths()?; command_build(&paths, target, warnings_as_errors, no_print_progress) } Command::Check { target } => { let paths = find_project_paths()?; command_check(&paths, target) } Command::Docs(Docs::Build { open, target }) => { let paths = find_project_paths()?; docs::build(&paths, docs::BuildOptions { open, target }) } Command::Docs(Docs::Publish) => { let paths = find_project_paths()?; docs::publish(&paths) } Command::Docs(Docs::Remove { package, version }) => docs::remove(package, version), Command::Format { stdin, files, check, } => format::run(stdin, check, files), Command::Fix => { let paths = find_project_paths()?; fix::run(&paths) } Command::Deps(Dependencies::List) => { let paths = find_project_paths()?; dependencies::list(&paths) } Command::Deps(Dependencies::Download) => { let paths = find_project_paths()?; download_dependencies(&paths) } Command::Deps(Dependencies::Outdated) => { let paths = find_project_paths()?; dependencies::outdated(&paths) } Command::Deps(Dependencies::Update(options)) => { let paths = find_project_paths()?; dependencies::update(&paths, options.packages) } Command::Deps(Dependencies::Tree(options)) => { let paths = find_project_paths()?; dependencies::tree(&paths, options) } Command::Hex(Hex::Authenticate) => hex::authenticate(), Command::New(options) => new::create(options, COMPILER_VERSION), Command::Shell => { let paths = find_project_paths()?; shell::command(&paths) } Command::Run { target, arguments, runtime, module, no_print_progress, } => { let paths = find_project_paths()?; run::command( &paths, arguments, target, runtime, module, run::Which::Src, no_print_progress, ) } Command::Test { target, arguments, runtime, } => { let paths = find_project_paths()?; run::command( &paths, arguments, target, runtime, None, run::Which::Test, false, ) } Command::Dev { target, arguments, runtime, } => { let paths = find_project_paths()?; run::command( &paths, arguments, target, runtime, None, run::Which::Dev, false, ) } Command::CompilePackage(opts) => compile_package::command(opts), Command::Publish { replace, yes } => { let paths = find_project_paths()?; publish::command(&paths, replace, yes) } Command::PrintConfig => { let paths = find_project_paths()?; print_config(&paths) } Command::Hex(Hex::Retire { package, version, reason, message, }) => hex::retire(package, version, reason, message), Command::Hex(Hex::Unretire { package, version }) => hex::unretire(package, version), Command::Hex(Hex::Revert { package, version }) => { let paths = find_project_paths()?; hex::revert(&paths, package, version) } Command::Hex(Hex::Owner(Owner::Transfer { package, username_or_email, })) => owner::transfer(package, username_or_email), Command::Add { packages, dev } => { let paths = find_project_paths()?; add::command(&paths, packages, dev) } Command::Remove { packages } => { let paths = find_project_paths()?; remove::command(&paths, packages) } Command::Update(options) => { let paths = find_project_paths()?; dependencies::update(&paths, options.packages) } Command::Clean => { let paths = find_project_paths()?; clean(&paths) } Command::LanguageServer => lsp::main(), Command::Export(ExportTarget::ErlangShipment) => { let paths = find_project_paths()?; export::erlang_shipment(&paths) } Command::Export(ExportTarget::HexTarball) => { let paths = find_project_paths()?; export::hex_tarball(&paths) } Command::Export(ExportTarget::JavascriptPrelude) => export::javascript_prelude(), Command::Export(ExportTarget::TypescriptPrelude) => export::typescript_prelude(), Command::Export(ExportTarget::PackageInterface { output }) => { let paths = find_project_paths()?; export::package_interface(&paths, output) } Command::Export(ExportTarget::PackageInformation { output }) => { let paths = find_project_paths()?; export::package_information(&paths, output) } } } fn command_check(paths: &ProjectPaths, target: Option) -> Result<()> { let _ = build::main( paths, Options { root_target_support: TargetSupport::Enforced, warnings_as_errors: false, codegen: Codegen::DepsOnly, compile: Compile::All, mode: Mode::Dev, target, no_print_progress: false, }, build::download_dependencies(paths, cli::Reporter::new())?, )?; Ok(()) } fn command_build( paths: &ProjectPaths, target: Option, warnings_as_errors: bool, no_print_progress: bool, ) -> Result<()> { let manifest = if no_print_progress { build::download_dependencies(paths, NullTelemetry)? } else { build::download_dependencies(paths, cli::Reporter::new())? }; let _ = build::main( paths, Options { root_target_support: TargetSupport::Enforced, warnings_as_errors, codegen: Codegen::All, compile: Compile::All, mode: Mode::Dev, target, no_print_progress, }, manifest, )?; Ok(()) } fn print_config(paths: &ProjectPaths) -> Result<()> { let config = root_config(paths)?; println!("{config:#?}"); Ok(()) } fn clean(paths: &ProjectPaths) -> Result<()> { fs::delete_directory(&paths.build_directory()) } fn initialise_logger() { let enable_colours = std::env::var("GLEAM_LOG_NOCOLOUR").is_err(); tracing_subscriber::fmt() .with_writer(std::io::stderr) .with_env_filter(std::env::var("GLEAM_LOG").unwrap_or_else(|_| "off".into())) .with_target(false) .with_ansi(enable_colours) .without_time() .init(); } fn find_project_paths() -> Result { let current_dir = get_current_directory()?; get_project_root(current_dir).map(ProjectPaths::new) } #[cfg(test)] fn project_paths_at_current_directory_without_toml() -> ProjectPaths { let current_dir = get_current_directory().expect("Failed to get current directory"); ProjectPaths::new(current_dir) } fn download_dependencies(paths: &ProjectPaths) -> Result<()> { _ = dependencies::resolve_and_download( paths, cli::Reporter::new(), None, Vec::new(), dependencies::DependencyManagerConfig { use_manifest: dependencies::UseManifest::Yes, check_major_versions: dependencies::CheckMajorVersions::No, }, )?; Ok(()) } ================================================ FILE: compiler-cli/src/lsp.rs ================================================ use crate::{ build_lock::{BuildLock, Guard}, fs::ProjectIO, }; use gleam_core::{ Result, build::{Mode, NullTelemetry, Target}, paths::ProjectPaths, }; use gleam_language_server::{LanguageServer, LockGuard, Locker}; pub fn main() -> Result<()> { tracing::info!("language_server_starting"); if std::io::IsTerminal::is_terminal(&std::io::stdin()) { eprintln!( "Hello human! This command is intended to be run by language server clients such as a text editor rather than being run directly in the console. Many editors will automatically start the language server for you when you open a Gleam project. If yours does not you may need to look up how to configure your editor to use a language server. If you are seeing this in the logs of your editor you can safely ignore this message. If you have run `gleam lsp` yourself in your terminal then exit this program by pressing ctrl+c. " ); } // Create the transport. Includes the stdio (stdin and stdout) versions but this could // also be implemented to use sockets or HTTP. let (connection, io_threads) = lsp_server::Connection::stdio(); // Run the server and wait for the two threads to end, typically by trigger // LSP Exit event. LanguageServer::new(&connection, ProjectIO::new())?.run()?; // Shut down gracefully. drop(connection); io_threads.join().expect("joining_lsp_threads"); tracing::info!("language_server_stopped"); Ok(()) } #[derive(Debug)] pub struct LspLocker(BuildLock); impl LspLocker { pub fn new(paths: &ProjectPaths, target: Target) -> Result { let build_lock = BuildLock::new_target(paths, Mode::Lsp, target)?; Ok(Self(build_lock)) } } impl Locker for LspLocker { fn lock_for_build(&self) -> Result { let guard: Guard = self.0.lock(&NullTelemetry)?; Ok(LockGuard(Box::new(guard))) } } ================================================ FILE: compiler-cli/src/new/snapshots/gleam_cli__new__tests__new_with_default_template@src__my_project.gleam.snap ================================================ --- source: compiler-cli/src/new/tests.rs expression: "crate::fs::read(Utf8PathBuf::from_path_buf(file_path.to_path_buf()).expect(\"Non Utf8 Path\"),).unwrap()" --- import gleam/io pub fn main() -> Nil { io.println("Hello from my_project!") } ================================================ FILE: compiler-cli/src/new/snapshots/gleam_cli__new__tests__new_with_default_template@test__my_project_test.gleam.snap ================================================ --- source: compiler-cli/src/new/tests.rs assertion_line: 56 expression: "crate::fs::read(Utf8PathBuf::from_path_buf(file_path.to_path_buf()).expect(\"Non Utf8 Path\"),).unwrap()" snapshot_kind: text --- import gleeunit pub fn main() -> Nil { gleeunit.main() } // gleeunit test functions end in `_test` pub fn hello_world_test() { let name = "Joe" let greeting = "Hello, " <> name <> "!" assert greeting == "Hello, Joe!" } ================================================ FILE: compiler-cli/src/new/snapshots/gleam_cli__new__tests__new_with_javascript_template@src__my_project.gleam.snap ================================================ --- source: compiler-cli/src/new/tests.rs expression: "crate::fs::read(Utf8PathBuf::from_path_buf(file_path.to_path_buf()).expect(\"Non Utf8 Path\"),).unwrap()" --- import gleam/io pub fn main() -> Nil { io.println("Hello from my_project!") } ================================================ FILE: compiler-cli/src/new/snapshots/gleam_cli__new__tests__new_with_javascript_template@test__my_project_test.gleam.snap ================================================ --- source: compiler-cli/src/new/tests.rs assertion_line: 86 expression: "crate::fs::read(Utf8PathBuf::from_path_buf(file_path.to_path_buf()).expect(\"Non Utf8 Path\"),).unwrap()" snapshot_kind: text --- import gleeunit pub fn main() -> Nil { gleeunit.main() } // gleeunit test functions end in `_test` pub fn hello_world_test() { let name = "Joe" let greeting = "Hello, " <> name <> "!" assert greeting == "Hello, Joe!" } ================================================ FILE: compiler-cli/src/new/tests.rs ================================================ use std::path::PathBuf; use camino::{Utf8Path, Utf8PathBuf}; use gleam_core::Error; #[test] fn new() { let tmp = tempfile::tempdir().unwrap(); let path = Utf8PathBuf::from_path_buf(tmp.path().join("my_project")).expect("Non Utf8 Path"); let creator = super::Creator::new( super::NewOptions { project_root: path.to_string(), template: super::Template::Erlang, name: None, skip_git: false, skip_github: false, }, "1.0.0-gleam", ) .unwrap(); creator.run().unwrap(); assert!(path.join(".git").exists()); assert!(path.join("README.md").exists()); assert!(path.join("gleam.toml").exists()); assert!(path.join("src/my_project.gleam").exists()); assert!(path.join("test/my_project_test.gleam").exists()); assert!(path.join(".github/workflows/test.yml").exists()); let toml = crate::fs::read(path.join("gleam.toml")).unwrap(); assert!(toml.contains("name = \"my_project\"")); } #[test] fn new_with_default_template() { let tmp = tempfile::tempdir().unwrap(); let path = Utf8PathBuf::from_path_buf(tmp.into_path()).expect("Non Utf8 Path"); let creator = super::Creator::new( super::NewOptions { project_root: path.join("my_project").to_string(), template: super::Template::Erlang, name: None, skip_git: false, skip_github: true, }, "1.0.0-gleam", ) .unwrap(); creator.run().unwrap(); insta::glob!(&path, "my_project/[^.]**/*.*", |file_path| { if !file_path.is_dir() { insta::assert_snapshot!( crate::fs::read( Utf8PathBuf::from_path_buf(file_path.to_path_buf()).expect("Non Utf8 Path"), ) .unwrap() ); } }); } #[test] fn new_with_javascript_template() { let tmp = tempfile::tempdir().unwrap(); let path = Utf8PathBuf::from_path_buf(tmp.into_path()).expect("Non Utf8 Path"); let creator = super::Creator::new( super::NewOptions { project_root: path.join("my_project").to_string(), template: super::Template::JavaScript, name: None, skip_git: false, skip_github: true, }, "1.0.0-gleam", ) .unwrap(); creator.run().unwrap(); insta::glob!(&path, "my_project/[^.]**/*.*", |file_path| { if !file_path.is_dir() { insta::assert_snapshot!( crate::fs::read( Utf8PathBuf::from_path_buf(file_path.to_path_buf()).expect("Non Utf8 Path"), ) .unwrap() ); } }); } #[test] fn new_with_skip_git() { let tmp = tempfile::tempdir().unwrap(); let path = Utf8PathBuf::from_path_buf(tmp.path().join("my_project")).expect("Non Utf8 Path"); let creator = super::Creator::new( super::NewOptions { project_root: path.to_string(), template: super::Template::Erlang, name: None, skip_git: true, skip_github: false, }, "1.0.0-gleam", ) .unwrap(); creator.run().unwrap(); assert!(!path.join(".git").exists()); assert!(!path.join(".github").exists()); } #[test] fn new_with_skip_github() { let tmp = tempfile::tempdir().unwrap(); let path = Utf8PathBuf::from_path_buf(tmp.path().join("my_project")).expect("Non Utf8 Path"); let creator = super::Creator::new( super::NewOptions { project_root: path.to_string(), template: super::Template::Erlang, name: None, skip_git: false, skip_github: true, }, "1.0.0-gleam", ) .unwrap(); creator.run().unwrap(); assert!(path.join(".git").exists()); assert!(!path.join(".github").exists()); assert!(!path.join(".github/workflows/test.yml").exists()); } #[test] fn new_with_skip_git_and_github() { let tmp = tempfile::tempdir().unwrap(); let path = Utf8PathBuf::from_path_buf(tmp.path().join("my_project")).expect("Non Utf8 Path"); let creator = super::Creator::new( super::NewOptions { project_root: path.to_string(), template: super::Template::Erlang, name: None, skip_git: true, skip_github: true, }, "1.0.0-gleam", ) .unwrap(); creator.run().unwrap(); assert!(!path.join(".git").exists()); assert!(!path.join(".github").exists()); assert!(!path.join(".github/workflows/test.yml").exists()); } #[test] fn invalid_path() { let tmp = tempfile::tempdir().unwrap(); let path = Utf8PathBuf::from_path_buf(tmp.path().join("-------")).expect("Non Utf8 Path"); assert!( super::Creator::new( super::NewOptions { project_root: path.to_string(), template: super::Template::Erlang, name: None, skip_git: false, skip_github: false, }, "1.0.0-gleam", ) .is_err() ); } #[test] fn invalid_name() { let tmp = tempfile::tempdir().unwrap(); let path = Utf8PathBuf::from_path_buf(tmp.path().join("projec")).expect("Non Utf8 Path"); assert!( super::Creator::new( super::NewOptions { project_root: path.to_string(), template: super::Template::Erlang, name: Some("-".into()), skip_git: false, skip_github: false, }, "1.0.0-gleam", ) .is_err() ); } #[test] fn existing_directory_no_files() { let tmp = tempfile::tempdir().unwrap(); let path = Utf8PathBuf::from_path_buf(tmp.path().join("my_project")).expect("Non Utf8 Path"); crate::fs::mkdir(&path).unwrap(); let creator = super::Creator::new( super::NewOptions { project_root: path.to_string(), template: super::Template::Erlang, name: None, skip_git: true, skip_github: true, }, "1.0.0-gleam", ) .unwrap(); creator.run().unwrap(); assert!(path.join("README.md").exists()); } #[test] fn existing_directory_with_one_existing_file() { let tmp = tempfile::tempdir().unwrap(); let path = Utf8PathBuf::from_path_buf(tmp.path().join("my_project")).expect("Non Utf8 Path"); crate::fs::mkdir(&path).unwrap(); let _ = std::fs::File::create(PathBuf::from(&path).join("README.md")).unwrap(); let _ = std::fs::File::create(PathBuf::from(&path).join("my_project.gleam")).unwrap(); assert!( super::Creator::new( super::NewOptions { project_root: path.to_string(), template: super::Template::Erlang, name: None, skip_git: true, skip_github: true, }, "1.0.0-gleam", ) .is_err() ); } #[test] fn existing_directory_with_non_generated_file() { let tmp = tempfile::tempdir().unwrap(); let path = Utf8PathBuf::from_path_buf(tmp.path().join("my_project")).expect("Non Utf8 Path"); crate::fs::mkdir(&path).unwrap(); let file_path = PathBuf::from(&path).join("some_fake_thing_that_is_not_generated.md"); let _ = std::fs::File::create(file_path).unwrap(); let creator = super::Creator::new( super::NewOptions { project_root: path.to_string(), template: super::Template::Erlang, name: None, skip_git: true, skip_github: true, }, "1.0.0-gleam", ) .unwrap(); creator.run().unwrap(); assert!(path.join("README.md").exists()); assert!( path.join("some_fake_thing_that_is_not_generated.md") .exists() ); } #[test] fn conflict_with_existing_files() { let tmp = tempfile::tempdir().unwrap(); let path = Utf8PathBuf::from_path_buf(tmp.path().join("my_project")).expect("Non Utf8 Path"); crate::fs::mkdir(&path).unwrap(); let _ = std::fs::File::create(PathBuf::from(&path).join("README.md")).unwrap(); assert_eq!( super::Creator::new( super::NewOptions { project_root: path.to_string(), template: super::Template::Erlang, name: None, skip_git: true, skip_github: true, }, "1.0.0-gleam", ) .err(), Some(Error::OutputFilesAlreadyExist { file_names: vec![path.join("README.md")] }) ); } #[test] fn skip_existing_git_files_when_skip_git_is_true() { let tmp = tempfile::tempdir().unwrap(); let path = Utf8PathBuf::from_path_buf(tmp.path().join("my_project")).expect("Non Utf8 Path"); crate::fs::mkdir(&path).unwrap(); let file_path = PathBuf::from(&path).join(".gitignore"); let _ = std::fs::File::create(file_path).unwrap(); let creator = super::Creator::new( super::NewOptions { project_root: path.to_string(), template: super::Template::Erlang, name: None, skip_git: true, skip_github: true, }, "1.0.0-gleam", ) .unwrap(); creator.run().unwrap(); assert!(path.join("README.md").exists()); assert!(path.join(".gitignore").exists()); } #[test] fn suggested_project_name_updates_directory() { let tmp = tempfile::tempdir().unwrap(); let base = Utf8PathBuf::from_path_buf(tmp.path().to_path_buf()).expect("Non Utf8 Path"); let original_root = base.join("gleam_testproject"); let creator = super::Creator::new_with_confirmation( super::NewOptions { project_root: original_root.to_string(), template: super::Template::Erlang, name: None, skip_git: true, skip_github: true, }, "1.0.0-gleam", |_| Ok::(true), ) .unwrap(); let expected_root = base.join("testproject"); assert_eq!(creator.project_name, "testproject"); assert_eq!( Utf8Path::new(&creator.options.project_root), expected_root.as_path() ); creator.run().unwrap(); assert!(expected_root.exists()); assert!(!original_root.exists()); } #[test] fn validate_name_format() { assert!(crate::new::validate_name("project").is_ok()); assert!(crate::new::validate_name("project_name").is_ok()); assert!(crate::new::validate_name("project2").is_ok()); let invalid = ["Project", "PROJECT", "Project_Name"]; for name in invalid { assert!(matches!( crate::new::validate_name(name), Err(Error::InvalidProjectName { name: _, reason: crate::new::InvalidProjectNameReason::FormatNotLowercase }) )); } let invalid = ["0project", "_project", "project-name"]; for name in invalid { assert!(matches!( crate::new::validate_name(name), Err(Error::InvalidProjectName { name: _, reason: crate::new::InvalidProjectNameReason::Format }) )); } } #[test] fn suggest_valid_names() { assert_eq!( crate::new::suggest_valid_name( "gleam_", &crate::new::InvalidProjectNameReason::GleamPrefix ), None ); assert_eq!( crate::new::suggest_valid_name( "gleam_project", &crate::new::InvalidProjectNameReason::GleamPrefix ), Some("project".to_string()) ); assert_eq!( crate::new::suggest_valid_name( "try", &crate::new::InvalidProjectNameReason::ErlangReservedWord ), Some("try_app".to_string()) ); assert_eq!( crate::new::suggest_valid_name( "erl_eval", &crate::new::InvalidProjectNameReason::ErlangStandardLibraryModule ), Some("erl_eval_app".to_string()) ); assert_eq!( crate::new::suggest_valid_name( "assert", &crate::new::InvalidProjectNameReason::GleamReservedWord ), Some("assert_app".to_string()) ); assert_eq!( crate::new::suggest_valid_name( "gleam", &crate::new::InvalidProjectNameReason::GleamReservedModule ), Some("app_gleam".to_string()) ); assert_eq!( crate::new::suggest_valid_name( "Project_Name", &crate::new::InvalidProjectNameReason::FormatNotLowercase ), Some("project_name".to_string()) ); assert_eq!( crate::new::suggest_valid_name( "Pr0ject-n4me!", &crate::new::InvalidProjectNameReason::Format ), Some("pr0ject_n4me_".to_string()) ); assert_eq!( crate::new::suggest_valid_name( "Pr0ject--n4me!", &crate::new::InvalidProjectNameReason::Format ), Some("pr0ject_n4me_".to_string()) ); assert_eq!( crate::new::suggest_valid_name( "_pr0ject-name", &crate::new::InvalidProjectNameReason::Format ), None ); } ================================================ FILE: compiler-cli/src/new.rs ================================================ use camino::{Utf8Path, Utf8PathBuf}; use clap::ValueEnum; use gleam_core::{ Result, erlang, error, error::{Error, FileIoAction, FileKind, InvalidProjectNameReason}, parse, }; use serde::{Deserialize, Serialize}; use std::fs::File; use std::{env, io::Write}; use strum::{Display, EnumIter, EnumString, IntoEnumIterator, VariantNames}; #[cfg(test)] mod tests; use crate::{NewOptions, fs::get_current_directory}; const GLEAM_STDLIB_REQUIREMENT: &str = ">= 0.44.0 and < 2.0.0"; const GLEEUNIT_REQUIREMENT: &str = ">= 1.0.0 and < 2.0.0"; const ERLANG_OTP_VERSION: &str = "28"; const REBAR3_VERSION: &str = "3"; const ELIXIR_VERSION: &str = "1"; #[derive( Debug, Serialize, Deserialize, Display, EnumString, VariantNames, ValueEnum, Clone, Copy, )] #[strum(serialize_all = "lowercase")] #[clap(rename_all = "lower")] pub enum Template { #[clap(skip)] Lib, Erlang, JavaScript, } #[derive(Debug)] pub struct Creator { root: Utf8PathBuf, src: Utf8PathBuf, test: Utf8PathBuf, github: Utf8PathBuf, workflows: Utf8PathBuf, gleam_version: &'static str, options: NewOptions, project_name: String, } #[derive(EnumIter, PartialEq, Eq, Debug, Hash)] enum FileToCreate { Readme, Gitignore, SrcModule, TestModule, GleamToml, GithubCi, } impl FileToCreate { pub fn location(&self, creator: &Creator) -> Utf8PathBuf { let project_name = &creator.project_name; match self { Self::Readme => creator.root.join(Utf8PathBuf::from("README.md")), Self::Gitignore => creator.root.join(Utf8PathBuf::from(".gitignore")), Self::SrcModule => creator .src .join(Utf8PathBuf::from(format!("{project_name}.gleam"))), Self::TestModule => creator .test .join(Utf8PathBuf::from(format!("{project_name}_test.gleam"))), Self::GleamToml => creator.root.join(Utf8PathBuf::from("gleam.toml")), Self::GithubCi => creator.workflows.join(Utf8PathBuf::from("test.yml")), } } pub fn contents(&self, creator: &Creator) -> Option { let project_name = &creator.project_name; let skip_git = creator.options.skip_git; let skip_github = creator.options.skip_github; let gleam_version = creator.gleam_version; let target = match creator.options.template { Template::JavaScript => "target = \"javascript\"\n", Template::Lib | Template::Erlang => "", }; match self { Self::Readme => Some(default_readme(project_name)), Self::Gitignore if !skip_git => Some( "*.beam *.ez /build erl_crash.dump " .into(), ), Self::SrcModule => Some(format!( r#"import gleam/io pub fn main() -> Nil {{ io.println("Hello from {project_name}!") }} "#, )), Self::TestModule => Some( r#"import gleeunit pub fn main() -> Nil { gleeunit.main() } // gleeunit test functions end in `_test` pub fn hello_world_test() { let name = "Joe" let greeting = "Hello, " <> name <> "!" assert greeting == "Hello, Joe!" } "# .into(), ), Self::GleamToml => Some(format!( r#"name = "{project_name}" version = "1.0.0" {target} # Fill out these fields if you intend to generate HTML documentation or publish # your project to the Hex package manager. # # description = "" # licences = ["Apache-2.0"] # repository = {{ type = "github", user = "", repo = "" }} # links = [{{ title = "Website", href = "" }}] # # For a full reference of all the available options, you can have a look at # https://gleam.run/writing-gleam/gleam-toml/. [dependencies] gleam_stdlib = "{GLEAM_STDLIB_REQUIREMENT}" [dev_dependencies] gleeunit = "{GLEEUNIT_REQUIREMENT}" "#, )), Self::GithubCi if !skip_git && !skip_github => Some(format!( r#"name: test on: push: branches: - master - main pull_request: jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: erlef/setup-beam@v1 with: otp-version: "{ERLANG_OTP_VERSION}" gleam-version: "{gleam_version}" rebar3-version: "{REBAR3_VERSION}" # elixir-version: "{ELIXIR_VERSION}" - run: gleam deps download - run: gleam test - run: gleam format --check src test "#, )), Self::GithubCi | Self::Gitignore => None, } } } pub fn default_readme(project_name: &str) -> String { format!( r#"# {project_name} [![Package Version](https://img.shields.io/hexpm/v/{project_name})](https://hex.pm/packages/{project_name}) [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/{project_name}/) ```sh gleam add {project_name}@1 ``` ```gleam import {project_name} pub fn main() -> Nil {{ // TODO: An example of the project in use }} ``` Further documentation can be found at . ## Development ```sh gleam run # Run the project gleam test # Run the tests ``` "#, ) } impl Creator { fn new(options: NewOptions, gleam_version: &'static str) -> Result { Self::new_with_confirmation(options, gleam_version, crate::cli::confirm) } fn new_with_confirmation( mut options: NewOptions, gleam_version: &'static str, confirm: impl Fn(&str) -> Result, ) -> Result { let name = get_valid_project_name(options.name.as_deref(), &options.project_root, &confirm)?; options.project_root = name.project_root(&options.project_root); let root = get_current_directory()?.join(&options.project_root); let src = root.join("src"); let test = root.join("test"); let github = root.join(".github"); let workflows = github.join("workflows"); let me = Self { root: root.clone(), src, test, github, workflows, gleam_version, options, project_name: name.decided().to_string(), }; validate_root_folder(&me)?; Ok(me) } fn run(&self) -> Result<()> { crate::fs::mkdir(&self.root)?; crate::fs::mkdir(&self.src)?; crate::fs::mkdir(&self.test)?; if !self.options.skip_git && !self.options.skip_github { crate::fs::mkdir(&self.github)?; crate::fs::mkdir(&self.workflows)?; } if !self.options.skip_git { crate::fs::git_init(&self.root)?; } match self.options.template { Template::Lib | Template::Erlang | Template::JavaScript => { for file in FileToCreate::iter() { let path = file.location(self); if let Some(contents) = file.contents(self) { write(path, &contents)?; } } } } Ok(()) } } pub fn create(options: NewOptions, version: &'static str) -> Result<()> { let creator = Creator::new(options.clone(), version)?; creator.run()?; let cd_folder = if options.project_root == "." { "".into() } else { format!("\tcd {}\n", creator.options.project_root) }; println!( "Your Gleam project {} has been successfully created. The project can be compiled and tested by running these commands: {}\tgleam test ", creator.project_name, cd_folder, ); Ok(()) } fn write(path: Utf8PathBuf, contents: &str) -> Result<()> { let mut f = File::create(&path).map_err(|err| Error::FileIo { kind: FileKind::File, path: path.clone(), action: FileIoAction::Create, err: Some(err.to_string()), })?; f.write_all(contents.as_bytes()) .map_err(|err| Error::FileIo { kind: FileKind::File, path, action: FileIoAction::WriteTo, err: Some(err.to_string()), })?; Ok(()) } fn validate_root_folder(creator: &Creator) -> Result<(), Error> { let mut duplicate_files: Vec = Vec::new(); for t in FileToCreate::iter() { let full_path = t.location(creator); let content = t.contents(creator); if full_path.exists() && content.is_some() { duplicate_files.push(full_path); } } if !duplicate_files.is_empty() { return Err(Error::OutputFilesAlreadyExist { file_names: duplicate_files, }); } Ok(()) } fn validate_name(name: &str) -> Result<(), Error> { if name.starts_with("gleam_") { Err(Error::InvalidProjectName { name: name.to_string(), reason: InvalidProjectNameReason::GleamPrefix, }) } else if erlang::is_erlang_reserved_word(name) { Err(Error::InvalidProjectName { name: name.to_string(), reason: InvalidProjectNameReason::ErlangReservedWord, }) } else if erlang::is_erlang_standard_library_module(name) { Err(Error::InvalidProjectName { name: name.to_string(), reason: InvalidProjectNameReason::ErlangStandardLibraryModule, }) } else if parse::lexer::str_to_keyword(name).is_some() { Err(Error::InvalidProjectName { name: name.to_string(), reason: InvalidProjectNameReason::GleamReservedWord, }) } else if name == "gleam" { Err(Error::InvalidProjectName { name: name.to_string(), reason: InvalidProjectNameReason::GleamReservedModule, }) } else if regex::Regex::new("^[a-z][a-z0-9_]*$") .expect("failed regex to match valid name format") .is_match(name) { Ok(()) } else if regex::Regex::new("^[a-zA-Z][a-zA-Z0-9_]*$") .expect("failed regex to match valid but non-lowercase name format") .is_match(name) { Err(Error::InvalidProjectName { name: name.to_string(), reason: InvalidProjectNameReason::FormatNotLowercase, }) } else { Err(Error::InvalidProjectName { name: name.to_string(), reason: InvalidProjectNameReason::Format, }) } } fn suggest_valid_name(invalid_name: &str, reason: &InvalidProjectNameReason) -> Option { match reason { InvalidProjectNameReason::GleamPrefix => match invalid_name.strip_prefix("gleam_") { Some(stripped) if invalid_name != "gleam_" => { let suggestion = stripped.to_string(); match validate_name(&suggestion) { Ok(_) => Some(suggestion), Err(_) => None, } } _ => None, }, InvalidProjectNameReason::ErlangReservedWord => Some(format!("{invalid_name}_app")), InvalidProjectNameReason::ErlangStandardLibraryModule => { Some(format!("{invalid_name}_app")) } InvalidProjectNameReason::GleamReservedWord => Some(format!("{invalid_name}_app")), InvalidProjectNameReason::GleamReservedModule => { if invalid_name == "gleam" { Some("app_gleam".into()) } else { Some(format!("{invalid_name}_app")) } } InvalidProjectNameReason::FormatNotLowercase => Some(invalid_name.to_lowercase()), InvalidProjectNameReason::Format => { let suggestion = regex::Regex::new(r"[^a-z0-9]") .expect("failed regex to match any non-lowercase and non-alphanumeric characters") .replace_all(&invalid_name.to_lowercase(), "_") .to_string(); let suggestion = regex::Regex::new(r"_+") .expect("failed regex to match consecutive underscores") .replace_all(&suggestion, "_") .to_string(); match validate_name(&suggestion) { Ok(_) => Some(suggestion), Err(_) => None, } } } } fn get_valid_project_name( provided_name: Option<&str>, project_root: &str, confirm: impl Fn(&str) -> Result, ) -> Result { let initial_name = match provided_name { Some(name) => name.trim().to_string(), None => get_foldername(project_root)?.trim().to_string(), }; let invalid_reason = match validate_name(&initial_name) { Ok(_) => { return Ok(match provided_name { Some(_) => ProjectName::Provided { decided: initial_name, }, None => ProjectName::Derived { folder: initial_name.clone(), decided: initial_name, }, }); } Err(Error::InvalidProjectName { reason, .. }) => reason, Err(error) => return Err(error), }; let suggested_name = match suggest_valid_name(&initial_name, &invalid_reason) { Some(suggested_name) => suggested_name, None => { return Err(Error::InvalidProjectName { name: initial_name, reason: invalid_reason, }); } }; let prompt_for_suggested_name = error::format_invalid_project_name_error( &initial_name, &invalid_reason, &Some(suggested_name.clone()), ); if confirm(&prompt_for_suggested_name)? { return Ok(match provided_name { Some(_) => ProjectName::Provided { decided: suggested_name, }, None => ProjectName::Derived { folder: initial_name, decided: suggested_name, }, }); } Err(Error::InvalidProjectName { name: initial_name, reason: invalid_reason, }) } fn get_foldername(path: &str) -> Result { match path { "." => env::current_dir() .expect("invalid folder") .file_name() .and_then(|x| x.to_str()) .map(ToString::to_string) .ok_or(Error::UnableToFindProjectRoot { path: path.to_string(), }), _ => Utf8Path::new(path) .file_name() .map(ToString::to_string) .ok_or(Error::UnableToFindProjectRoot { path: path.to_string(), }), } } #[derive(Debug, Clone)] enum ProjectName { Provided { decided: String }, Derived { folder: String, decided: String }, } impl ProjectName { fn decided(&self) -> &str { match self { Self::Provided { decided } | Self::Derived { decided, .. } => decided, } } fn project_root(&self, current_root: &str) -> String { match self { Self::Provided { .. } => current_root.to_string(), Self::Derived { folder, decided } => { if current_root == "." || folder == decided { return current_root.to_string(); } // If the name was invalid and generated suggestion was accepted, // align the directory path with the new name. let original_root = Utf8Path::new(current_root); let new_root = match original_root.parent() { Some(parent) if !parent.as_str().is_empty() => parent.join(decided), Some(_) | None => Utf8PathBuf::from(decided), }; new_root.to_string() } } } } ================================================ FILE: compiler-cli/src/owner.rs ================================================ use crate::{cli, http::HttpClient}; use gleam_core::{Result, hex}; pub fn transfer(package: String, new_owner_username_or_email: String) -> Result<()> { println!( "Transferring ownership of this package will remove all current owners and make {new_owner_username_or_email} its new owner. Do you wish to transfer ownership of `{package}` to {new_owner_username_or_email}?", ); let should_transfer_ownership = cli::confirm_with_text(&package)?; if !should_transfer_ownership { println!("Not transferring ownership."); return Ok(()); } let runtime = tokio::runtime::Runtime::new().expect("Unable to start Tokio async runtime"); let http = HttpClient::new(); let hex_config = hexpm::Config::new(); let credentials = crate::hex::HexAuthentication::new(&runtime, &http, hex_config.clone()) .get_or_create_api_credentials()?; cli::print_transferring_ownership(); runtime.block_on(hex::transfer_owner( &crate::hex::write_credentials(&credentials)?, package, new_owner_username_or_email, &hex_config, &HttpClient::new(), ))?; cli::print_transferred_ownership(); Ok(()) } ================================================ FILE: compiler-cli/src/panic.rs ================================================ #![allow(clippy::unwrap_used)] use std::panic::PanicHookInfo; pub fn add_handler() { std::panic::set_hook(Box::new(move |info: &PanicHookInfo<'_>| { print_compiler_bug_message(info) })); } fn print_compiler_bug_message(info: &PanicHookInfo<'_>) { let message = match ( info.payload().downcast_ref::<&str>(), info.payload().downcast_ref::(), ) { (Some(s), _) => (*s).to_string(), (_, Some(s)) => s.to_string(), (None, None) => "unknown error".into(), }; let location = match info.location() { None => "".into(), Some(location) => format!("{}:{}\n\t", location.file(), location.line()), }; let buffer_writer = crate::cli::stderr_buffer_writer(); let mut buffer = buffer_writer.buffer(); use std::io::Write; use termcolor::{Color, ColorSpec, WriteColor}; buffer .set_color(ColorSpec::new().set_bold(true).set_fg(Some(Color::Red))) .unwrap(); write!(buffer, "error").unwrap(); buffer.set_color(ColorSpec::new().set_bold(true)).unwrap(); write!(buffer, ": Fatal compiler bug!\n\n").unwrap(); buffer.set_color(&ColorSpec::new()).unwrap(); writeln!( buffer, "This is a bug in the Gleam compiler, sorry! Please report this crash to https://github.com/gleam-lang/gleam/issues/new and include this error message with your report. Panic: {location}{message} Gleam version: {version} Operating system: {os} If you can also share your code and say what file you were editing or any steps to reproduce the crash that would be a great help. You may also want to try again with the `GLEAM_LOG=trace` environment variable set. ", location = location, message = message, version = env!("CARGO_PKG_VERSION"), os = std::env::consts::OS, ) .unwrap(); buffer_writer.print(&buffer).unwrap(); } ================================================ FILE: compiler-cli/src/publish.rs ================================================ use camino::{Utf8Path, Utf8PathBuf}; use ecow::EcoString; use flate2::{Compression, write::GzEncoder}; use gleam_core::{ Error, Result, analyse::TargetSupport, ast::{CallArg, Statement, TypedExpr, TypedFunction}, build::{Codegen, Compile, Mode, Options, Package, Target}, config::{GleamVersion, PackageConfig, SpdxLicense}, docs::{Dependency, DependencyKind, DocContext}, error::{InvalidReadmeReason, SmallVersion, wrap}, hex, manifest::ManifestPackageSource, paths::{self, ProjectPaths}, requirement::Requirement, type_, }; use hexpm::version::{Range, Version}; use itertools::Itertools; use sha2::Digest; use std::{collections::HashMap, io::Write, path::PathBuf}; use crate::{build, cli, docs, fs, http::HttpClient, new::default_readme}; const CORE_TEAM_PUBLISH_PASSWORD: &str = "Trans rights are human rights"; pub fn command(paths: &ProjectPaths, replace: bool, i_am_sure: bool) -> Result<()> { let mut config = crate::config::root_config(paths)?; let should_publish = check_for_gleam_prefix(&config)? && check_for_version_zero(&config)? && check_repo_url(&config, i_am_sure)?; check_for_invalid_readme(&config, paths)?; if !should_publish { println!("Not publishing."); return Ok(()); } let Tarball { mut compile_result, cached_modules, data: package_tarball, src_files_added, generated_files_added, dependencies, } = do_build_hex_tarball(paths, &mut config)?; check_for_name_squatting(&compile_result)?; check_for_multiple_top_level_modules(&compile_result, i_am_sure)?; check_for_default_main(&compile_result)?; // Build HTML documentation let docs_tarball = fs::create_tar_archive(docs::build_documentation( paths, &config, dependencies, &mut compile_result, DocContext::HexPublish, &cached_modules, )?)?; // Ask user if this is correct if !generated_files_added.is_empty() { println!("\nGenerated files:"); for file in generated_files_added.iter().sorted() { println!(" - {}", file.0); } } println!("\nSource files:"); for file in src_files_added.iter().sorted() { println!(" - {file}"); } println!("\nName: {}", config.name); println!("Version: {}", config.version); let should_publish = i_am_sure || cli::confirm("\nDo you wish to publish this package?")?; if !should_publish { println!("Not publishing."); return Ok(()); } let runtime = tokio::runtime::Runtime::new().expect("Unable to start Tokio async runtime"); let http = HttpClient::new(); let hex_config = hexpm::Config::new(); let credentials = crate::hex::HexAuthentication::new(&runtime, &http, hex_config.clone()) .get_or_create_api_credentials()?; let credentials = crate::hex::write_credentials(&credentials)?; cli::print_publishing(&config.name, &config.version); runtime.block_on(hex::publish_package( package_tarball, config.version.to_string(), &config.name, &credentials, &hex_config, replace, &http, ))?; cli::print_publishing_documentation(); runtime.block_on(hex::publish_documentation( &config.name, &config.version, docs_tarball, &credentials, &hex_config, &http, ))?; cli::print_published("package and documentation"); println!( "\nView your package at https://hex.pm/packages/{}", &config.name ); // Prompt the user to make a git tag if they have not. let has_repo = config.repository.is_some(); let git = PathBuf::from(".git"); let tag_name = config.tag_for_version(&config.version); let git_tag = git.join("refs").join("tags").join(&tag_name); if has_repo && git.exists() && !git_tag.exists() { println!( " Please push a git tag for this release so source code links in the HTML documentation will work: git tag {tag_name} git push origin {tag_name} " ) } Ok(()) } fn check_for_invalid_readme(config: &PackageConfig, paths: &ProjectPaths) -> Result<(), Error> { let normalise = |string: String| { string .trim() .replace("\r\n", "") .replace("\n", "") .replace("\t", "") .replace(" ", "") }; let project_readme = match fs::read(paths.readme()) { Err(Error::FileIo { err: Some(message), .. }) if message.contains("No such file or directory") => { return Err(Error::CannotPublishWithInvalidReadme { reason: InvalidReadmeReason::Missing, }); } Err(error) => return Err(error), Ok(project_readme) => project_readme, }; let normalised_project_readme = normalise(project_readme); if normalised_project_readme.is_empty() { return Err(Error::CannotPublishWithInvalidReadme { reason: InvalidReadmeReason::Empty, }); } let default_readme = default_readme(config.name.as_str()); if normalised_project_readme == normalise(default_readme) { return Err(Error::CannotPublishWithInvalidReadme { reason: InvalidReadmeReason::Default, }); } Ok(()) } fn check_for_name_squatting(package: &Package) -> Result<(), Error> { if package.modules.len() > 1 { return Ok(()); } let Some(module) = package.modules.first() else { return Err(Error::HexPackageSquatting); }; if module.dependencies.len() > 1 { return Ok(()); } if module.ast.definitions_len() > 2 { return Ok(()); } let Some(main) = module .ast .definitions .functions .iter() .find_map(|function| function.main_function()) else { return Ok(()); }; if let Some(first) = &main.body.first() && first.is_println() { return Err(Error::HexPackageSquatting); } Ok(()) } /// Checks if publishing packages contain default main functions. /// Main functions with documentation are considered intentional and allowed. fn check_for_default_main(package: &Package) -> Result<(), Error> { let package_name = &package.config.name; let has_default_main = package .modules .iter() .flat_map(|module| module.ast.definitions.functions.iter()) .filter_map(|function| function.main_function()) .any(|main| main.documentation.is_none() && is_default_main(main, package_name)); if has_default_main { return Err(Error::CannotPublishWithDefaultMain { package_name: package_name.clone(), }); } Ok(()) } fn is_default_main(main: &TypedFunction, package_name: &EcoString) -> bool { if main.body.len() != 1 { return false; } let Some(Statement::Expression(expression)) = main.body.first() else { return false; }; if !expression.is_println() { return false; } match expression { TypedExpr::Call { arguments, .. } => { if arguments.len() != 1 { return false; } match arguments.first() { Some(CallArg { value: TypedExpr::String { value, .. }, .. }) => { let default_argument = format!("Hello from {}!", &package_name); value == &default_argument } _ => false, } } _ => false, } } fn check_for_multiple_top_level_modules(package: &Package, i_am_sure: bool) -> Result<(), Error> { // Collect top-level module names let mut top_level_module_names = package .modules .iter() .filter_map(|module| { // Top-level modules are those that don't contain any path separators if module.name.contains('/') { None } else { Some(module.name.clone()) } }) .collect::>(); // Remove duplicates top_level_module_names.sort_unstable(); top_level_module_names.dedup(); // If more than one top-level module name is found, prompt for confirmation if top_level_module_names.len() > 1 { let text = wrap(&format!( "Your package defines multiple top-level modules: {}. Defining multiple top-level modules can lead to namespace pollution \ and potential conflicts for consumers. To fix this, move all your modules under a single top-level module of your choice. For example: src/{1}.gleam src/{1}/module1.gleam src/{1}/module2.gleam", top_level_module_names.join(", "), package.config.name )); println!("{text}\n"); let should_publish = i_am_sure || cli::confirm("\nDo you wish to continue publishing this package?")?; println!(); if !should_publish { println!("Not publishing."); std::process::exit(0); } } Ok(()) } fn check_repo_url(config: &PackageConfig, i_am_sure: bool) -> Result { let Some(repo) = config.repository.as_ref() else { return Ok(true); }; let url = repo.url(); let runtime = tokio::runtime::Runtime::new().expect("Unable to start Tokio async runtime"); let response = runtime.block_on(reqwest::get(&url)).map_err(Error::http)?; if response.status().is_success() { return Ok(true); } println!( "The repository configuration in your `gleam.toml` file does not appear to be valid, {} returned status {}", &url, response.status() ); let should_publish = i_am_sure || cli::confirm("\nDo you wish to continue?")?; println!(); Ok(should_publish) } /// Ask for confirmation if the package name if a v0.x.x version fn check_for_version_zero(config: &PackageConfig) -> Result { if config.version.major != 0 { return Ok(true); } println!( "You are about to publish a release that is below version 1.0.0. Semantic versioning doesn't apply to version 0.x.x releases, so your users will not be protected from breaking changes. This can result in a poor user experience where packages can break unexpectedly with updates that would normally be safe. If your package is not ready to be used in production it should not be published. \n" ); let should_publish = cli::confirm_with_text("I am not using semantic versioning")?; println!(); Ok(should_publish) } /// Ask for confirmation if the package name if `gleam_*` fn check_for_gleam_prefix(config: &PackageConfig) -> Result { if !config.name.starts_with("gleam_") || config.name.starts_with("gleam_community_") { return Ok(true); } println!( "You are about to publish a package with a name that starts with the prefix `gleam_`, which is preferred for packages maintained by the Gleam core team. Security: do not assume the owner of a package from the name, always check the maintainers listed on https://hex.pm/. \n", ); let password = cli::ask_password("Please enter the core team password to continue")?; println!(); Ok(password == CORE_TEAM_PUBLISH_PASSWORD) } struct Tarball { compile_result: Package, cached_modules: im::HashMap, data: Vec, src_files_added: Vec, generated_files_added: Vec<(Utf8PathBuf, String)>, dependencies: HashMap, } pub fn build_hex_tarball(paths: &ProjectPaths, config: &mut PackageConfig) -> Result> { let Tarball { data, .. } = do_build_hex_tarball(paths, config)?; Ok(data) } fn do_build_hex_tarball(paths: &ProjectPaths, config: &mut PackageConfig) -> Result { let target = config.target; check_config_for_publishing(config)?; // Reset the build directory so we know the state of the project fs::delete_directory(&paths.build_directory_for_target(Mode::Prod, target))?; let manifest = build::download_dependencies(paths, cli::Reporter::new())?; let dependencies = manifest .packages .iter() .map(|package| { ( package.name.clone(), Dependency { version: package.version.clone(), kind: match &package.source { ManifestPackageSource::Hex { .. } => DependencyKind::Hex, ManifestPackageSource::Git { .. } => DependencyKind::Git, ManifestPackageSource::Local { .. } => DependencyKind::Path, }, }, ) }) .collect(); // Build the project to check that it is valid let built = build::main( paths, Options { root_target_support: TargetSupport::Enforced, warnings_as_errors: false, mode: Mode::Prod, target: Some(target), codegen: Codegen::All, compile: Compile::All, no_print_progress: false, }, manifest, )?; let minimum_required_version = built.minimum_required_version(); match &config.gleam_version { // If the package has no explicit `gleam` version in its `gleam.toml` // then we want to add the automatically inferred one so we know it's // correct and folks getting the package from Hex won't have unpleasant // surprises if the author forgot to manualy write it down. None => { // If we're automatically adding the minimum required version // constraint we want it to at least be `>= 1.0.0`, even if the // inferred lower bound could be lower. let minimum_required_version = std::cmp::max(minimum_required_version, Version::new(1, 0, 0)); let inferred_version_range = pubgrub::Range::higher_than(minimum_required_version); config.gleam_version = Some(GleamVersion::from_pubgrub(inferred_version_range)); } // Otherwise we need to check that the annotated version range is // correct and includes the minimum required version. Some(gleam_version) => { if let Some(lowest_allowed_version) = gleam_version.lowest_version() && lowest_allowed_version < minimum_required_version { return Err(Error::CannotPublishWrongVersion { minimum_required_version: SmallVersion::from_hexpm(minimum_required_version), wrongfully_allowed_version: SmallVersion::from_hexpm(lowest_allowed_version), }); } } } // If any of the modules in the package contain a todo or an echo then // refuse to publish as the package is not yet finished. let mut modules_containing_todo = vec![]; let mut modules_containing_echo = vec![]; for module in built.root_package.modules.iter() { if module.ast.type_info.contains_todo() { modules_containing_todo.push(module.name.clone()); } else if module.ast.type_info.contains_echo { modules_containing_echo.push(module.name.clone()); } } if !modules_containing_todo.is_empty() { return Err(Error::CannotPublishTodo { unfinished: modules_containing_todo, }); } if !modules_containing_echo.is_empty() { return Err(Error::CannotPublishEcho { unfinished: modules_containing_echo, }); } // empty_modules is a list of modules that do not export any values or types. // We do not allow publishing packages that contain empty modules. let empty_modules: Vec<_> = built .root_package .modules .iter() .filter(|module| { built .module_interfaces .get(&module.name) .map(|interface| { // Check if the module exports any values or types interface.values.is_empty() && interface.types.is_empty() }) .unwrap_or(false) }) .map(|module| module.name.clone()) .collect(); if !empty_modules.is_empty() { return Err(Error::CannotPublishEmptyModules { unfinished: empty_modules, }); } // TODO: If any of the modules in the package contain a leaked internal type then // refuse to publish as the package is not yet finished. // We need to move aliases in to the type system first. // context: https://discord.com/channels/768594524158427167/768594524158427170/1227250677734969386 // Collect all the files we want to include in the tarball let generated_files = match target { Target::Erlang => generated_erlang_files(paths, &built.root_package)?, Target::JavaScript => vec![], }; let src_files = project_files(Utf8Path::new(""))?; let contents_tar_gz = contents_tarball(&src_files, &generated_files)?; let version = "3"; let metadata = metadata_config(&built.root_package.config, &src_files, &generated_files)?; // Calculate checksum let mut hasher = sha2::Sha256::new(); hasher.update(version.as_bytes()); hasher.update(metadata.as_bytes()); hasher.update(contents_tar_gz.as_slice()); let checksum = base16::encode_upper(&hasher.finalize()); tracing::info!(checksum = %checksum, "Generated Hex package inner checksum"); // Build tarball let mut tarball = Vec::new(); { let mut tarball = tar::Builder::new(&mut tarball); add_to_tar(&mut tarball, "VERSION", version.as_bytes())?; add_to_tar(&mut tarball, "metadata.config", metadata.as_bytes())?; add_to_tar(&mut tarball, "contents.tar.gz", contents_tar_gz.as_slice())?; add_to_tar(&mut tarball, "CHECKSUM", checksum.as_bytes())?; tarball.finish().map_err(Error::finish_tar)?; } tracing::info!("Generated package Hex release tarball"); Ok(Tarball { compile_result: built.root_package, cached_modules: built.module_interfaces, data: tarball, src_files_added: src_files, generated_files_added: generated_files, dependencies, }) } fn check_config_for_publishing(config: &PackageConfig) -> Result<()> { // These fields are required to publish a Hex package. Hex will reject // packages without them. if config.description.is_empty() || config.licences.is_empty() { Err(Error::MissingHexPublishFields { description_missing: config.description.is_empty(), licence_missing: config.licences.is_empty(), }) } else { Ok(()) } } fn metadata_config<'a>( config: &'a PackageConfig, source_files: &[Utf8PathBuf], generated_files: &[(Utf8PathBuf, String)], ) -> Result { let repo_url = http::Uri::try_from( config .repository .as_ref() .map(|r| r.url()) .unwrap_or_default(), ) .ok(); let requirements: Result>> = config .dependencies .iter() .map(|(name, requirement)| match requirement { Requirement::Hex { version } => Ok(ReleaseRequirement { name, requirement: version, }), _ => Err(Error::PublishNonHexDependencies { package: name.to_string(), }), }) .collect(); let metadata = ReleaseMetadata { name: &config.name, version: &config.version, description: &config.description, source_files, generated_files, licenses: &config.licences, links: config .links .iter() .map(|l| (l.title.as_str(), l.href.clone())) .chain(repo_url.into_iter().map(|u| ("Repository", u))) .collect(), requirements: requirements?, build_tools: vec!["gleam"], } .as_erlang(); tracing::info!(contents = ?metadata, "Generated Hex metadata.config"); Ok(metadata) } fn contents_tarball( files: &[Utf8PathBuf], data_files: &[(Utf8PathBuf, String)], ) -> Result, Error> { let mut contents_tar_gz = Vec::new(); { let mut tarball = tar::Builder::new(GzEncoder::new(&mut contents_tar_gz, Compression::default())); for path in files { add_path_to_tar(&mut tarball, path)?; } for (path, contents) in data_files { add_to_tar(&mut tarball, path, contents.as_bytes())?; } tarball.finish().map_err(Error::finish_tar)?; } tracing::info!("Generated contents.tar.gz"); Ok(contents_tar_gz) } fn project_files(base_path: &Utf8Path) -> Result> { let src = base_path.join(Utf8Path::new("src")); let mut files: Vec = fs::gleam_files(&src) .chain(fs::native_files(&src)) .collect(); let private = base_path.join(Utf8Path::new("priv")); let mut private_files: Vec = fs::private_files(&private).collect(); files.append(&mut private_files); let mut add = |path| { let path = base_path.join(path); if path.exists() { files.push(path); } }; add("README"); add("README.md"); add("README.txt"); add("gleam.toml"); add("LICENSE"); add("LICENCE"); add("LICENSE.md"); add("LICENCE.md"); add("LICENSE.txt"); add("LICENCE.txt"); add("NOTICE"); add("NOTICE.md"); add("NOTICE.txt"); Ok(files) } // TODO: test fn generated_erlang_files( paths: &ProjectPaths, package: &Package, ) -> Result> { let mut files = vec![]; let dir = paths.build_directory_for_package(Mode::Prod, Target::Erlang, &package.config.name); let ebin = dir.join("ebin"); let build = dir.join(paths::ARTEFACT_DIRECTORY_NAME); let include = dir.join("include"); let tar_src = Utf8Path::new("src"); let tar_include = Utf8Path::new("include"); // Erlang modules for module in &package.modules { // Do not publish test/ and dev/ code if !module.origin.is_src() { continue; } let name = module.compiled_erlang_path(); files.push((tar_src.join(&name), fs::read(build.join(name))?)); } // Erlang headers if include.is_dir() { for file in fs::erlang_files(&include) { let name = file.file_name().expect("generated_files include file name"); files.push((tar_include.join(name), fs::read(file)?)); } } // src/package.app.src file let app = format!("{}.app", &package.config.name); let appsrc = format!("{}.src", &app); files.push((tar_src.join(appsrc), fs::read(ebin.join(app))?)); Ok(files) } fn add_to_tar(tarball: &mut tar::Builder, path: P, data: &[u8]) -> Result<()> where P: AsRef, W: Write, { let path = path.as_ref(); tracing::info!(file=?path, "Adding file to tarball"); let mut header = tar::Header::new_gnu(); header.set_mode(0o600); header.set_size(data.len() as u64); header.set_cksum(); tarball .append_data(&mut header, path, data) .map_err(|e| Error::add_tar(path, e)) } fn add_path_to_tar(tarball: &mut tar::Builder, path: P) -> Result<()> where P: AsRef, W: Write, { let path = path.as_ref(); tracing::info!(file=?path, "Adding file to tarball"); tarball .append_path(path) .map_err(|e| Error::add_tar(path, e)) } #[derive(Debug, Clone)] pub struct ReleaseMetadata<'a> { name: &'a str, version: &'a Version, description: &'a str, source_files: &'a [Utf8PathBuf], generated_files: &'a [(Utf8PathBuf, String)], licenses: &'a Vec, links: Vec<(&'a str, http::Uri)>, requirements: Vec>, build_tools: Vec<&'a str>, // What should this be? I can't find it in the API anywhere. // extra: (kvlist(string => kvlist(...))) (optional) } impl ReleaseMetadata<'_> { pub fn as_erlang(&self) -> String { fn link(link: &(&str, http::Uri)) -> String { format!( "\n {{<<\"{name}\">>, <<\"{url}\">>}}", name = link.0, url = link.1 ) } fn file(name: impl AsRef) -> String { format!("\n <<\"{name}\">>", name = name.as_ref()) } format!( r#"{{<<"name">>, <<"{name}">>}}. {{<<"app">>, <<"{name}">>}}. {{<<"version">>, <<"{version}">>}}. {{<<"description">>, <<"{description}"/utf8>>}}. {{<<"licenses">>, [{licenses}]}}. {{<<"build_tools">>, [{build_tools}]}}. {{<<"links">>, [{links} ]}}. {{<<"requirements">>, [{requirements} ]}}. {{<<"files">>, [{files} ]}}. "#, name = self.name, version = self.version, description = self.description, files = self .source_files .iter() .chain(self.generated_files.iter().map(|(p, _)| p)) .map(file) .sorted() .join(","), links = self.links.iter().map(link).join(","), licenses = self.licenses.iter().map(|l| quotes(l.as_ref())).join(", "), build_tools = self.build_tools.iter().map(|l| quotes(l)).join(", "), requirements = self .requirements .iter() .map(ReleaseRequirement::as_erlang) .join(",") ) } } #[derive(Debug, Clone)] struct ReleaseRequirement<'a> { name: &'a str, // optional: bool, requirement: &'a Range, // Support alternate repositories at a later date. // repository: String, } impl ReleaseRequirement<'_> { pub fn as_erlang(&self) -> String { format!( r#" {{<<"{app}">>, [ {{<<"app">>, <<"{app}">>}}, {{<<"optional">>, false}}, {{<<"requirement">>, <<"{requirement}">>}} ]}}"#, app = self.name, requirement = self.requirement, ) } } #[test] fn release_metadata_as_erlang() { let licences = vec![ SpdxLicense { licence: "MIT".into(), }, SpdxLicense { licence: "MPL-2.0".into(), }, ]; let version = "1.2.3".try_into().unwrap(); let homepage = "https://gleam.run".parse().unwrap(); let github = "https://github.com/lpil/myapp".parse().unwrap(); let req1 = Range::new("~> 1.2.3 or >= 5.0.0".into()).unwrap(); let req2 = Range::new("~> 1.2".into()).unwrap(); let meta = ReleaseMetadata { name: "myapp", version: &version, description: "description goes here 🌈", source_files: &[ Utf8PathBuf::from("gleam.toml"), Utf8PathBuf::from("src/thingy.gleam"), Utf8PathBuf::from("src/whatever.gleam"), ], generated_files: &[ (Utf8PathBuf::from("src/myapp.app"), "".into()), (Utf8PathBuf::from("src/thingy.erl"), "".into()), (Utf8PathBuf::from("src/whatever.erl"), "".into()), ], licenses: &licences, links: vec![("homepage", homepage), ("github", github)], requirements: vec![ ReleaseRequirement { name: "wibble", requirement: &req1, }, ReleaseRequirement { name: "wobble", requirement: &req2, }, ], build_tools: vec!["gleam", "rebar3"], }; assert_eq!( meta.as_erlang(), r#"{<<"name">>, <<"myapp">>}. {<<"app">>, <<"myapp">>}. {<<"version">>, <<"1.2.3">>}. {<<"description">>, <<"description goes here 🌈"/utf8>>}. {<<"licenses">>, [<<"MIT">>, <<"MPL-2.0">>]}. {<<"build_tools">>, [<<"gleam">>, <<"rebar3">>]}. {<<"links">>, [ {<<"homepage">>, <<"https://gleam.run/">>}, {<<"github">>, <<"https://github.com/lpil/myapp">>} ]}. {<<"requirements">>, [ {<<"wibble">>, [ {<<"app">>, <<"wibble">>}, {<<"optional">>, false}, {<<"requirement">>, <<"~> 1.2.3 or >= 5.0.0">>} ]}, {<<"wobble">>, [ {<<"app">>, <<"wobble">>}, {<<"optional">>, false}, {<<"requirement">>, <<"~> 1.2">>} ]} ]}. {<<"files">>, [ <<"gleam.toml">>, <<"src/myapp.app">>, <<"src/thingy.erl">>, <<"src/thingy.gleam">>, <<"src/whatever.erl">>, <<"src/whatever.gleam">> ]}. "# .to_string() ); } #[test] fn prevent_publish_local_dependency() { let config = PackageConfig { dependencies: [("provided".into(), Requirement::path("./path/to/package"))].into(), ..Default::default() }; assert_eq!( metadata_config(&config, &[], &[]), Err(Error::PublishNonHexDependencies { package: "provided".into() }) ); } #[test] fn prevent_publish_git_dependency() { let config = PackageConfig { dependencies: [( "provided".into(), Requirement::git("https://github.com/gleam-lang/gleam.git", "da6e917"), )] .into(), ..Default::default() }; assert_eq!( metadata_config(&config, &[], &[]), Err(Error::PublishNonHexDependencies { package: "provided".into() }) ); } fn quotes(x: &str) -> String { format!(r#"<<"{x}">>"#) } #[test] fn exported_project_files_test() { let tmp = tempfile::tempdir().unwrap(); let path = Utf8PathBuf::from_path_buf(tmp.path().join("my_project")).expect("Non Utf8 Path"); let exported_project_files = &[ "LICENCE", "LICENCE.md", "LICENCE.txt", "LICENSE", "LICENSE.md", "LICENSE.txt", "NOTICE", "NOTICE.md", "NOTICE.txt", "README", "README.md", "README.txt", "gleam.toml", "priv/ignored", "priv/wibble", "priv/wobble.js", "src/.hidden/hidden_ffi.erl", "src/.hidden/hidden_ffi.mjs", "src/.hidden_ffi.erl", "src/.hidden_ffi.mjs", "src/exported.gleam", "src/exported_ffi.erl", "src/exported_ffi.ex", "src/exported_ffi.hrl", "src/exported_ffi.js", "src/exported_ffi.mjs", "src/exported_ffi.ts", "src/ignored.gleam", "src/ignored_ffi.erl", "src/ignored_ffi.mjs", "src/nested/exported.gleam", "src/nested/exported_ffi.erl", "src/nested/exported_ffi.ex", "src/nested/exported_ffi.hrl", "src/nested/exported_ffi.js", "src/nested/exported_ffi.mjs", "src/nested/exported_ffi.ts", "src/nested/ignored.gleam", "src/nested/ignored_ffi.erl", "src/nested/ignored_ffi.mjs", ]; let unexported_project_files = &[ ".git/", ".github/workflows/test.yml", ".gitignore", "build/", "ignored.txt", "src/.hidden/hidden.gleam", // Not a valid Gleam module path "src/.hidden.gleam", // Not a valid Gleam module name "src/also-ignored.gleam", // Not a valid Gleam module name "test/exported_test.gleam", "test/exported_test_ffi.erl", "test/exported_test_ffi.ex", "test/exported_test_ffi.hrl", "test/exported_test_ffi.js", "test/exported_test_ffi.mjs", "test/exported_test_ffi.ts", "test/ignored_test.gleam", "test/ignored_test_ffi.erl", "test/ignored_test_ffi.mjs", "test/nested/exported_test.gleam", "test/nested/exported_test_ffi.erl", "test/nested/exported_test_ffi.ex", "test/nested/exported_test_ffi.hrl", "test/nested/exported_test_ffi.js", "test/nested/exported_test_ffi.mjs", "test/nested/exported_test_ffi.ts", "test/nested/ignored.gleam", "test/nested/ignored_test_ffi.erl", "test/nested/ignored_test_ffi.mjs", "dev/exported_test_ffi.erl", "dev/exported_test_ffi.ex", "dev/exported_test_ffi.hrl", "dev/exported_test_ffi.js", "dev/exported_test_ffi.mjs", "dev/exported_test_ffi.ts", "dev/ignored_test.gleam", "dev/ignored_test_ffi.erl", "dev/ignored_test_ffi.mjs", "dev/nested/exported_test.gleam", "dev/nested/exported_test_ffi.erl", "dev/nested/exported_test_ffi.ex", "dev/nested/exported_test_ffi.hrl", "dev/nested/exported_test_ffi.js", "dev/nested/exported_test_ffi.mjs", "dev/nested/exported_test_ffi.ts", "dev/nested/ignored.gleam", "dev/nested/ignored_test_ffi.erl", "dev/nested/ignored_test_ffi.mjs", "unrelated-file.txt", ]; let gitignore = "ignored* src/also-ignored.gleam"; for &file in exported_project_files .iter() .chain(unexported_project_files) { if file.ends_with("/") { fs::mkdir(path.join(file)).unwrap(); continue; } let contents = match file { ".gitignore" => gitignore, _ => "", }; fs::write(&path.join(file), contents).unwrap(); } let mut chosen_exported_files = project_files(&path).unwrap(); chosen_exported_files.sort_unstable(); let expected_exported_files = exported_project_files .iter() .map(|s| path.join(s)) .collect_vec(); assert_eq!(expected_exported_files, chosen_exported_files); } ================================================ FILE: compiler-cli/src/remove.rs ================================================ use gleam_core::{ Error, Result, error::{FileIoAction, FileKind}, paths::ProjectPaths, }; use crate::{cli, fs}; pub fn command(paths: &ProjectPaths, packages: Vec) -> Result<()> { // Read gleam.toml so we can remove deps from it let root_config = paths.root_config(); let mut toml = fs::read(&root_config)? .parse::() .map_err(|e| Error::FileIo { kind: FileKind::File, action: FileIoAction::Parse, path: root_config.to_path_buf(), err: Some(e.to_string()), })?; // Remove the specified dependencies let mut packages_not_exist = vec![]; for package_to_remove in packages.iter() { let remove = |toml: &mut toml_edit::DocumentMut, name| { #[allow(clippy::indexing_slicing)] toml[name] .as_table_like_mut() .and_then(|deps| deps.remove(package_to_remove)) }; // dev-dependencies is the old deprecated name for dev_dependencies let removed = remove(&mut toml, "dependencies") .or_else(|| remove(&mut toml, "dev_dependencies")) .or_else(|| remove(&mut toml, "dev-dependencies")); if removed.is_none() { packages_not_exist.push(package_to_remove.into()); } } if !packages_not_exist.is_empty() { return Err(Error::RemovedPackagesNotExist { packages: packages_not_exist, }); } // Write the updated config fs::write(root_config.as_path(), &toml.to_string())?; _ = crate::dependencies::cleanup(paths, cli::Reporter::new())?; Ok(()) } ================================================ FILE: compiler-cli/src/run.rs ================================================ use std::sync::OnceLock; use camino::Utf8PathBuf; use ecow::EcoString; use gleam_core::{ analyse::TargetSupport, build::{Built, Codegen, Compile, Mode, NullTelemetry, Options, Runtime, Target, Telemetry}, config::{DenoFlag, PackageConfig}, error::Error, io::{Command, CommandExecutor, Stdio}, paths::ProjectPaths, type_::ModuleFunction, version::COMPILER_VERSION, }; use crate::{config::PackageKind, fs::ProjectIO}; #[derive(Debug, Clone, Copy)] pub enum Which { Src, Test, Dev, } // TODO: test pub fn command( paths: &ProjectPaths, arguments: Vec, target: Option, runtime: Option, module: Option, which: Which, no_print_progress: bool, ) -> Result<(), Error> { // Don't exit on ctrl+c as it is used by child erlang shell ctrlc::set_handler(move || {}).expect("Error setting Ctrl-C handler"); let command = setup( paths, arguments, target, runtime, module, which, no_print_progress, )?; let status = ProjectIO::new().exec(command)?; std::process::exit(status); } pub fn setup( paths: &ProjectPaths, arguments: Vec, target: Option, runtime: Option, module: Option, which: Which, no_print_progress: bool, ) -> Result { // Validate the module path if let Some(mod_path) = &module && !is_gleam_module(mod_path) { return Err(Error::InvalidModuleName { module: mod_path.to_owned(), }); }; let telemetry: &'static dyn Telemetry = if no_print_progress { &NullTelemetry } else { &crate::cli::Reporter }; // Download dependencies let manifest = if no_print_progress { crate::build::download_dependencies(paths, NullTelemetry)? } else { crate::build::download_dependencies(paths, crate::cli::Reporter::new())? }; // Get the config for the module that is being run to check the target. // Also get the kind of the package the module belongs to: wether the module // belongs to a dependency or to the root package. let (mod_config, package_kind) = match &module { Some(mod_path) => { crate::config::find_package_config_for_module(mod_path, &manifest, paths)? } _ => (crate::config::root_config(paths)?, PackageKind::Root), }; // The root config is required to run the project. let root_config = crate::config::root_config(paths)?; // Determine which module to run let module = module.unwrap_or(match which { Which::Src => root_config.name.to_string(), Which::Test => format!("{}_test", &root_config.name), Which::Dev => format!("{}_dev", &root_config.name), }); let target = target.unwrap_or(mod_config.target); let options = Options { warnings_as_errors: false, compile: match package_kind { // If we're trying to run a dependecy module we do not compile and // check the root package. So we can run the main function from a // dependency's module even if the root package doesn't compile. PackageKind::Dependency => Compile::DepsOnly, PackageKind::Root => Compile::All, }, codegen: Codegen::All, mode: Mode::Dev, target: Some(target), root_target_support: match package_kind { // The module we want to run is in the root package, so we make sure that the package // can compile successfully for the current target. PackageKind::Root => TargetSupport::Enforced, // On the other hand, if we're trying to run a module that belongs to a dependency, we // only care if the dependency can compile for the current target. PackageKind::Dependency => TargetSupport::NotEnforced, }, no_print_progress, }; let built = crate::build::main(paths, options, manifest)?; // A module can not be run if it does not exist or does not have a public main function. let main_function = get_or_suggest_main_function(built, &module, target)?; telemetry.running(&format!("{module}.main")); // Get the command to run the project. match target { Target::Erlang => match runtime { Some(r) => Err(Error::InvalidRuntime { target: Target::Erlang, invalid_runtime: r, }), _ => run_erlang_command(paths, &root_config.name, &module, arguments), }, Target::JavaScript => match runtime.unwrap_or(mod_config.javascript.runtime) { Runtime::Deno => run_javascript_deno_command( paths, &root_config, &main_function.package, &module, arguments, ), Runtime::NodeJs => { run_javascript_node_command(paths, &main_function.package, &module, arguments) } Runtime::Bun => { run_javascript_bun_command(paths, &main_function.package, &module, arguments) } }, } } fn run_erlang_command( paths: &ProjectPaths, package: &str, module: &str, arguments: Vec, ) -> Result { let mut args = vec![]; // Specify locations of Erlang applications let packages = paths.build_directory_for_target(Mode::Dev, Target::Erlang); for entry in crate::fs::read_dir(packages)?.filter_map(Result::ok) { args.push("-pa".into()); args.push(entry.path().join("ebin").into()); } // gleam modules are separated by `/`. Erlang modules are separated by `@`. let module = module.replace('/', "@"); args.push("-eval".into()); args.push(format!("{package}@@main:run({module})")); // Don't run the Erlang shell args.push("-noshell".into()); // Tell the BEAM that any following argument are for the program args.push("-extra".into()); for argument in arguments.into_iter() { args.push(argument); } Ok(Command { program: "erl".to_string(), args, env: vec![], cwd: None, stdio: Stdio::Inherit, }) } fn run_javascript_bun_command( paths: &ProjectPaths, package: &str, module: &str, arguments: Vec, ) -> Result { let mut args = vec!["run".to_string()]; let entry = write_javascript_entrypoint(paths, package, module)?; args.push(entry.to_string()); for arg in arguments.into_iter() { args.push(arg); } Ok(Command { program: "bun".to_string(), args, env: vec![], cwd: None, stdio: Stdio::Inherit, }) } fn run_javascript_node_command( paths: &ProjectPaths, package: &str, module: &str, arguments: Vec, ) -> Result { let mut args = vec![]; let entry = write_javascript_entrypoint(paths, package, module)?; args.push(entry.to_string()); for argument in arguments.into_iter() { args.push(argument); } Ok(Command { program: "node".to_string(), args, env: vec![], cwd: None, stdio: Stdio::Inherit, }) } fn write_javascript_entrypoint( paths: &ProjectPaths, package: &str, module: &str, ) -> Result { let path = paths .build_directory_for_package(Mode::Dev, Target::JavaScript, package) .to_path_buf() .join(format!("gleam@@private_main_v{}.mjs", COMPILER_VERSION)); let module = format!( r#"import {{ main }} from "./{module}.mjs"; main(); "#, ); crate::fs::write(&path, &module)?; Ok(path) } fn run_javascript_deno_command( paths: &ProjectPaths, config: &PackageConfig, package: &str, module: &str, arguments: Vec, ) -> Result { let mut args = vec![]; // Run the main function. args.push("run".into()); // Enable unstable features and APIs if config.javascript.deno.unstable { args.push("--unstable".into()) } // Enable location API if let Some(location) = &config.javascript.deno.location { args.push(format!("--location={location}")); } // Set deno permissions if config.javascript.deno.allow_all { // Allow all args.push("--allow-all".into()) } else { // Allow env add_deno_flag(&mut args, "--allow-env", &config.javascript.deno.allow_env); // Allow sys if config.javascript.deno.allow_sys { args.push("--allow-sys".into()) } // Allow hrtime if config.javascript.deno.allow_hrtime { args.push("--allow-hrtime".into()) } // Allow net add_deno_flag(&mut args, "--allow-net", &config.javascript.deno.allow_net); // Allow ffi if config.javascript.deno.allow_ffi { args.push("--allow-ffi".into()) } // Allow read add_deno_flag( &mut args, "--allow-read", &config.javascript.deno.allow_read, ); // Allow run add_deno_flag(&mut args, "--allow-run", &config.javascript.deno.allow_run); // Allow write add_deno_flag( &mut args, "--allow-write", &config.javascript.deno.allow_write, ); } let entrypoint = write_javascript_entrypoint(paths, package, module)?; args.push(entrypoint.to_string()); for argument in arguments.into_iter() { args.push(argument); } Ok(Command { program: "deno".to_string(), args, env: vec![], cwd: None, stdio: Stdio::Inherit, }) } fn add_deno_flag(args: &mut Vec, flag: &str, flags: &DenoFlag) { match flags { DenoFlag::AllowAll => args.push(flag.to_owned()), DenoFlag::Allow(allow) => { if !allow.is_empty() { args.push(format!("{}={}", flag.to_owned(), allow.join(","))); } } } } /// Check if a module name is a valid gleam module name. fn is_gleam_module(module: &str) -> bool { use regex::Regex; static RE: OnceLock = OnceLock::new(); RE.get_or_init(|| { Regex::new(&format!( "^({module}{slash})*{module}$", module = "[a-z][_a-z0-9]*", slash = "/", )) .expect("is_gleam_module() RE regex") }) .is_match(module) } /// If provided module is not executable, suggest a possible valid module. fn get_or_suggest_main_function( built: Built, module: &str, target: Target, ) -> Result { // Check if the module exists let error = match built.get_main_function(&module.into(), target) { Ok(main_fn) => return Ok(main_fn), Err(error) => error, }; // Otherwise see if the module has been prefixed with "src/", "test/" or "dev/". for prefix in ["src/", "test/", "dev/"] { let other = match module.strip_prefix(prefix) { Some(other) => other.into(), None => continue, }; if built.get_main_function(&other, target).is_ok() { return Err(Error::ModuleDoesNotExist { module: EcoString::from(module), suggestion: Some(other), }); } } Err(error) } #[test] fn invalid_module_names() { for mod_name in [ "", "/mod/name", "/mod/name/", "mod/name/", "/mod/", "mod/", "common-invalid-character", ] { assert!(!is_gleam_module(mod_name)); } } #[test] fn valid_module_names() { for mod_name in ["valid", "valid/name", "valid/mod/name"] { assert!(is_gleam_module(mod_name)); } } ================================================ FILE: compiler-cli/src/shell.rs ================================================ use gleam_core::{ analyse::TargetSupport, build::{Codegen, Compile, Mode, Options, Target}, error::{Error, ShellCommandFailureReason}, paths::ProjectPaths, }; use std::process::Command; pub fn command(paths: &ProjectPaths) -> Result<(), Error> { // Build project let _ = crate::build::main( paths, Options { root_target_support: TargetSupport::Enforced, warnings_as_errors: false, codegen: Codegen::All, compile: Compile::All, mode: Mode::Dev, target: Some(Target::Erlang), no_print_progress: false, }, crate::build::download_dependencies(paths, crate::cli::Reporter::new())?, )?; // Don't exit on ctrl+c as it is used by child erlang shell ctrlc::set_handler(move || {}).expect("Error setting Ctrl-C handler"); // Prepare the Erlang shell command let mut command = Command::new("erl"); // Print character lists as lists let _ = command.arg("-stdlib").arg("shell_strings").arg("false"); // Specify locations of .beam files let packages = paths.build_directory_for_target(Mode::Dev, Target::Erlang); for entry in crate::fs::read_dir(packages)?.filter_map(Result::ok) { let _ = command.arg("-pa").arg(entry.path().join("ebin")); } crate::cli::print_running("Erlang shell"); // Run the shell tracing::info!("Running OS process {:?}", command); let _ = command.status().map_err(|e| Error::ShellCommand { program: "erl".into(), reason: ShellCommandFailureReason::IoError(e.kind()), })?; Ok(()) } ================================================ FILE: compiler-cli/src/text_layout.rs ================================================ use ecow::EcoString; /// Generates a string delimeted table with 2 spaces between each column, columns padded with /// enough spaces to be aligned, and hyphens under the headers (excluding the final column of each /// row). Rows should have the right number of columns. /// /// ## Example /// /// ```txt /// Package Current Latest /// ------- ------- ------ /// wibble 1.4.0 1.4.1 /// wobble 1.0.1 2.3.0 /// ``` /// pub fn space_table(headers: &[impl AsRef], data: Grid) -> EcoString where Grid: AsRef<[Row]>, Row: AsRef<[Cell]>, Cell: AsRef, { let mut output = EcoString::new(); let mut column_widths: Vec = headers.iter().map(|header| header.as_ref().len()).collect(); for row in data.as_ref() { for (index, cell) in row.as_ref().iter().enumerate() { if let Some(width) = column_widths.get_mut(index) { let cell = cell.as_ref(); *width = (*width).max(cell.len()); } } } for (index, (header, width)) in headers.iter().zip(column_widths.iter()).enumerate() { if index > 0 { output.push_str(" "); } let header = header.as_ref(); output.push_str(header); if index < headers.len() - 1 { let padding = width - header.len(); if padding > 0 { output.push_str(&" ".repeat(padding)); } } } output.push('\n'); for (index, (header, width)) in headers.iter().zip(column_widths.iter()).enumerate() { if index > 0 { output.push_str(" "); } let header = header.as_ref(); output.push_str(&"-".repeat(header.len())); if index < headers.len() - 1 { let padding = width - header.len(); if padding > 0 { output.push_str(&" ".repeat(padding)); } } } output.push('\n'); for row in data.as_ref() { for (index, (cell, width)) in row.as_ref().iter().zip(column_widths.iter()).enumerate() { if index > 0 { output.push_str(" "); } let cell = cell.as_ref(); output.push_str(cell); if index < headers.len() - 1 { let padding = width - cell.len(); if padding > 0 { output.push_str(&" ".repeat(padding)); } } } output.push('\n'); } output } ================================================ FILE: compiler-cli/templates/erlang-shipment-entrypoint.ps1 ================================================ $ErrorActionPreference = "Stop" $PackageName = "$PACKAGE_NAME_FROM_GLEAM" $BaseDirectory = $PSScriptRoot $ScriptCommand = $args[0] $CodePath = Join-Path -Path $BaseDirectory -ChildPath "\*\ebin" -Resolve function Run { erl ` -pa $CodePath ` -eval "$PackageName@@main:run($PackageName)" ` -noshell ` -extra $args } function Shell { erl -pa $CodePath } switch ($ScriptCommand) { "run" { Run $args[1..($args.Length - 1)] } "shell" { Shell } default { Write-Host "usage:" Write-Host " entrypoint.ps1 `$COMMAND" Write-Host "" Write-Host "commands:" Write-Host " run Run the project main function" Write-Host " shell Run an Erlang shell" exit 1 } } ================================================ FILE: compiler-cli/templates/erlang-shipment-entrypoint.sh ================================================ #!/bin/sh set -eu PACKAGE=$PACKAGE_NAME_FROM_GLEAM BASE=$(dirname "$0") COMMAND="${1-default}" run() { exec erl \ -pa "$BASE"/*/ebin \ -eval "$PACKAGE@@main:run($PACKAGE)" \ -noshell \ -extra "$@" } shell() { erl -pa "$BASE"/*/ebin } case "$COMMAND" in run) shift run "$@" ;; shell) shell ;; *) echo "usage:" >&2 echo " entrypoint.sh \$COMMAND" >&2 echo "" >&2 echo "commands:" >&2 echo " run Run the project main function" >&2 echo " shell Run an Erlang shell" >&2 exit 1 ;; esac ================================================ FILE: compiler-cli/templates/gleam@@compile.erl ================================================ #!/usr/bin/env escript -mode(compile). % TODO: Don't concurrently print warnings and errors % TODO: Some tests main(_) -> ok = io:setopts([binary, {encoding, utf8}]), ok = configure_logging(), compile_package_loop(). compile_package_loop() -> case io:get_line("") of eof -> ok; Line -> Chars = unicode:characters_to_list(Line), {ok, Tokens, _} = erl_scan:string(Chars), {ok, {Lib, Out, Modules}} = erl_parse:parse_term(Tokens), case compile_package(Lib, Out, Modules) of {ok, ModuleNames} -> PrintModuleName = fun(ModuleName) -> io:put_chars("gleam-compile-module:" ++ atom_to_list(ModuleName) ++ "\n") end, lists:map(PrintModuleName, ModuleNames), io:put_chars("gleam-compile-result-ok\n"); err -> io:put_chars("gleam-compile-result-error\n") end, compile_package_loop() end. compile_package(Lib, Out, Modules) -> IsElixirModule = fun(Module) -> filename:extension(Module) =:= ".ex" end, {ElixirModules, ErlangModules} = lists:partition(IsElixirModule, Modules), ok = filelib:ensure_dir([Out, $/]), ok = add_lib_to_erlang_path(Lib), {ErlangOk, ErlangBeams} = compile_erlang(ErlangModules, Out), {ElixirOk, ElixirBeams} = case ErlangOk of true -> compile_elixir(ElixirModules, Out); false -> {false, []} end, ok = del_lib_from_erlang_path(Lib), case ErlangOk andalso ElixirOk of true -> ModuleNames = proplists:get_keys(ErlangBeams ++ ElixirBeams), {ok, ModuleNames}; false -> err end. compile_erlang(Modules, Out) -> Workers = start_compiler_workers(Out), ok = producer_loop(Modules, Workers), collect_results({true, []}). collect_results(Acc = {Result, Beams}) -> receive {compiled, ModuleName, Beam} -> collect_results({Result, [{ModuleName, Beam} | Beams]}); failed -> collect_results({false, Beams}) after 0 -> Acc end. producer_loop([], 0) -> ok; producer_loop([], Workers) -> receive {work_please, _} -> producer_loop([], Workers - 1) end; producer_loop([Module | Modules], Workers) -> receive {work_please, Worker} -> erlang:send(Worker, {module, Module}), producer_loop(Modules, Workers) end. start_compiler_workers(Out) -> Parent = self(), NumSchedulers = erlang:system_info(schedulers), SpawnWorker = fun(_) -> erlang:spawn_link(fun() -> worker_loop(Parent, Out) end) end, lists:foreach(SpawnWorker, lists:seq(1, NumSchedulers)), NumSchedulers. worker_loop(Parent, Out) -> Options = [report_errors, report_warnings, debug_info, {outdir, Out}], erlang:send(Parent, {work_please, self()}), receive {module, Module} -> log({compiling, Module}), case compile:file(Module, Options) of {ok, ModuleName} -> Beam = filename:join(Out, ModuleName) ++ ".beam", Message = {compiled, ModuleName, Beam}, log(Message), erlang:send(Parent, Message); error -> log({failed, Module}), erlang:send(Parent, failed) end, worker_loop(Parent, Out) end. compile_elixir(Modules, Out) -> Error = [ "The program elixir was not found. Is it installed?", $\n, "Documentation for installing Elixir can be viewed here:", $\n, "https://elixir-lang.org/install.html" ], case Modules of [] -> {true, []}; _ -> log({starting, "compiler.app,elixir.app"}), case application:ensure_all_started([compiler, elixir]) of {ok, _} -> do_compile_elixir(Modules, Out); _ -> io:put_chars(standard_error, [Error, $\n]), {false, []} end end. do_compile_elixir(Modules, Out) -> ModuleBins = lists:map(fun(Module) -> log({compiling, Module}), list_to_binary(Module) end, Modules), OutBin = list_to_binary(Out), Options = [{dest, OutBin}, {return_diagnostics, true}], % Silence "redefining module" warnings. % Compiled modules in the build directory are added to the code path. % These warnings result from recompiling loaded modules. % TODO: This line can likely be removed if/when the build directory is cleaned before every compilation. 'Elixir.Code':compiler_options([{ignore_module_conflict, true}]), case 'Elixir.Kernel.ParallelCompiler':compile_to_path(ModuleBins, OutBin, Options) of {ok, ModuleAtoms, _} -> ToBeam = fun(ModuleAtom) -> Beam = filename:join(Out, atom_to_list(ModuleAtom)) ++ ".beam", log({compiled, Beam}), {ModuleAtom, Beam} end, {true, lists:map(ToBeam, ModuleAtoms)}; {error, Errors, _} -> % Log all filenames associated with modules that failed to compile. % Note: The compiler prints compilation errors upon encountering them. ErrorFiles = lists:usort([File || {File, _, _} <- Errors]), Log = fun(File) -> log({failed, binary_to_list(File)}) end, lists:foreach(Log, ErrorFiles), {false, []}; _ -> {false, []} end. add_lib_to_erlang_path(Lib) -> code:add_paths(expand_lib_paths(Lib)). -if(?OTP_RELEASE >= 26). del_lib_from_erlang_path(Lib) -> code:del_paths(expand_lib_paths(Lib)). -else. del_lib_from_erlang_path(Lib) -> lists:foreach(fun code:del_path/1, expand_lib_paths(Lib)). -endif. expand_lib_paths(Lib) -> filelib:wildcard([Lib, "/*/ebin"]). configure_logging() -> Enabled = os:getenv("GLEAM_LOG") /= false, persistent_term:put(gleam_logging_enabled, Enabled). log(Term) -> case persistent_term:get(gleam_logging_enabled) of true -> io:fwrite("~p~n", [Term]), ok; false -> ok end. ================================================ FILE: compiler-cli/test/hello_world/.gitignore ================================================ build ================================================ FILE: compiler-cli/test/hello_world/gleam.toml ================================================ name = "hello_world" version = "0.1.0" [dependencies] gleam_stdlib = "~> 0.28" [dev_dependencies] gleeunit = "~> 0.10" ================================================ FILE: compiler-cli/test/hello_world/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "gleam_stdlib", version = "0.34.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "1FB8454D2991E9B4C0C804544D8A9AD0F6184725E20D63C3155F0AEB4230B016" }, { name = "gleeunit", version = "0.11.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "1397E5C4AC4108769EE979939AC39BF7870659C5AFB714630DEEEE16B8272AD5" }, ] [requirements] gleam_stdlib = { version = "~> 0.28" } gleeunit = { version = "~> 0.10" } ================================================ FILE: compiler-core/Cargo.toml ================================================ [package] name = "gleam-core" version = "1.15.1" authors = ["Louis Pilfold "] edition = "2024" license = "Apache-2.0" [dependencies] # Error message and warning formatting codespan-reporting = "0" # Graph data structures petgraph = "0.8" # Template rendering askama = "0" # Markdown parsing pulldown-cmark = { version = "0", default-features = false, features = [ "html", ] } # XDG directory locations dirs-next = "2" # SPDX license parsing spdx = "0" # Binary format de-serialization bincode = { version = "2", features = ["alloc", "serde"] } # cross platform single glob and glob set matching globset = { version = "0", features = ["serde1"] } # Checksums xxhash-rust = { version = "0", features = ["xxh3"] } # Pubgrub dependency resolution algorithm pubgrub = "0.3" # Used for converting absolute path to relative path pathdiff = { version = "0", features = ["camino"] } # Memory arena using ids rather than references id-arena = "2" # Unicode grapheme traversal unicode-segmentation = "1.12.0" # Bijective (bi-directional) hashmap bimap = "0.6.3" # Parsing of arbitrary width int values num-bigint = { version = "0.4.6", features = ["serde"] } num-traits = "0.2.19" # Encryption age = { version = "0.11", features = ["armor"] } radix_trie = "0.3" # Ensuring recursive type-checking doesn't stack overflow stacker = "0.1.21" # Manipulating bit arrays bitvec = { version = "1", features = ["serde"] } async-trait.workspace = true base16.workspace = true camino = { workspace = true, features = ["serde1"] } debug-ignore.workspace = true ecow = { workspace = true, features = ["serde"] } flate2.workspace = true futures.workspace = true hexpm = { path = "../hexpm" } http.workspace = true im.workspace = true itertools.workspace = true lsp-server.workspace = true lsp-types.workspace = true regex.workspace = true serde.workspace = true serde_json.workspace = true strum.workspace = true tar.workspace = true termcolor.workspace = true thiserror.workspace = true toml.workspace = true tracing.workspace = true indexmap = "2.12.1" vec1.workspace = true [build-dependencies] # Data (de)serialisation serde_derive = "1" [dev-dependencies] pretty_assertions.workspace = true insta.workspace = true # Random value generation rand = "0.9" ================================================ FILE: compiler-core/clippy.toml ================================================ disallowed-methods = [ { path = "std::env::current_dir", reason = "IO is not permitted in core" }, { path = "std::path::Path::canonicalize", reason = "IO is not permitted in core" }, { path = "std::path::Path::exists", reason = "IO is not permitted in core" }, { path = "std::path::Path::is_dir", reason = "IO is not permitted in core" }, { path = "std::path::Path::is_file", reason = "IO is not permitted in core" }, { path = "std::path::Path::is_symlink", reason = "IO is not permitted in core" }, { path = "std::path::Path::read_dir", reason = "IO is not permitted in core" }, { path = "std::path::Path::read_link", reason = "IO is not permitted in core" }, { path = "std::path::Path::symlink_metadata", reason = "IO is not permitted in core" }, { path = "std::path::Path::try_exists", reason = "IO is not permitted in core" }, { path = "camino::Utf8Path::canonicalize", reason = "IO is not permitted in core" }, { path = "camino::Utf8Path::exists", reason = "IO is not permitted in core" }, { path = "camino::Utf8Path::is_dir", reason = "IO is not permitted in core" }, { path = "camino::Utf8Path::is_file", reason = "IO is not permitted in core" }, { path = "camino::Utf8Path::is_symlink", reason = "IO is not permitted in core" }, { path = "camino::Utf8Path::read_dir", reason = "IO is not permitted in core" }, { path = "camino::Utf8Path::read_link", reason = "IO is not permitted in core" }, { path = "camino::Utf8Path::symlink_metadata", reason = "IO is not permitted in core" }, { path = "camino::Utf8Path::try_exists", reason = "IO is not permitted in core" }, { path = "std::path::Path::new", reason = "Manually constructed paths should use camino::Utf8Path" }, { path = "std::path::PathBuf::new", reason = "Manually constructed pathbufs should use camino::Utf8Path" }, ] ================================================ FILE: compiler-core/src/analyse/imports.rs ================================================ use ecow::EcoString; use crate::{ ast::{Publicity, SrcSpan, UnqualifiedImport, UntypedImport}, build::Origin, reference::{EntityKind, ReferenceKind}, type_::{ Environment, Error, ModuleInterface, Problems, ValueConstructorVariant, Warning, error::InvalidImportKind, }, }; use super::Imported; #[derive(Debug)] pub struct Importer<'context, 'problems> { origin: Origin, environment: Environment<'context>, problems: &'problems mut Problems, } impl<'context, 'problems> Importer<'context, 'problems> { pub fn new( origin: Origin, environment: Environment<'context>, problems: &'problems mut Problems, ) -> Self { Self { origin, environment, problems, } } pub fn run<'code>( origin: Origin, env: Environment<'context>, imports: &'code [UntypedImport], problems: &'problems mut Problems, ) -> Environment<'context> { let mut importer = Self::new(origin, env, problems); for import in imports { importer.register_import(import) } importer.environment } fn register_import(&mut self, import: &UntypedImport) { let location = import.location; let name = import.module.clone(); // Find imported module let Some(module_info) = self.environment.importable_modules.get(&name) else { self.problems.error(Error::UnknownModule { location, name: name.clone(), suggestions: self.environment.suggest_modules(&name, Imported::Module), }); return; }; if let Err(e) = self.check_for_invalid_imports(module_info, location) { self.problems.error(e); } if let Err(e) = self.register_module(import, module_info) { self.problems.error(e); return; } // Insert unqualified imports into scope let module_name = &module_info.name; for type_ in &import.unqualified_types { self.register_unqualified_type(type_, module_name.clone(), module_info); } for value in &import.unqualified_values { self.register_unqualified_value(value, module_name.clone(), module_info); } } fn register_unqualified_type( &mut self, import: &UnqualifiedImport, module_name: EcoString, module: &ModuleInterface, ) { let imported_name = import.as_name.as_ref().unwrap_or(&import.name); // Register the unqualified import if it is a type constructor let Some(type_info) = module.get_public_type(&import.name) else { // TODO: refine to a type specific error self.problems.error(Error::UnknownModuleType { location: import.location, name: import.name.clone(), module_name: module.name.clone(), type_constructors: module.public_type_names(), value_with_same_name: module.get_public_value(&import.name).is_some(), }); return; }; let type_info = type_info.clone().with_location(import.location); self.environment.names.type_in_scope( imported_name.clone(), type_info.type_.as_ref(), &type_info.parameters, ); self.environment.references.register_type( imported_name.clone(), EntityKind::ImportedType { module: module_name, }, import.location, Publicity::Private, ); self.environment.references.register_type_reference( type_info.module.clone(), import.name.clone(), imported_name, import.imported_name_location, ReferenceKind::Import, ); if let Err(e) = self .environment .insert_type_constructor(imported_name.clone(), type_info) { self.problems.error(e); } } fn register_unqualified_value( &mut self, import: &UnqualifiedImport, module_name: EcoString, module: &ModuleInterface, ) { let import_name = &import.name; let location = import.location; let used_name = import.as_name.as_ref().unwrap_or(&import.name); // Register the unqualified import if it is a value let variant = match module.get_public_value(import_name) { Some(value) => { let implementations = value.variant.implementations(); // Check the target support of the imported value if self.environment.target_support.is_enforced() && !implementations.supports(self.environment.target) { self.problems.error(Error::UnsupportedExpressionTarget { target: self.environment.target, location, }) } self.environment.insert_variable( used_name.clone(), value.variant.clone(), value.type_.clone(), value.publicity, value.deprecation.clone(), ); &value.variant } None => { self.problems.error(Error::UnknownModuleValue { location, name: import_name.clone(), module_name: module.name.clone(), value_constructors: module.public_value_names(), type_with_same_name: module.get_public_type(import_name).is_some(), context: crate::type_::error::ModuleValueUsageContext::UnqualifiedImport, }); return; } }; match variant { ValueConstructorVariant::Record { name, module, .. } => { self.environment.names.named_constructor_in_scope( module.clone(), name.clone(), used_name.clone(), ); self.environment.references.register_value( used_name.clone(), EntityKind::ImportedConstructor { module: module_name, }, location, Publicity::Private, ); self.environment.references.register_value_reference( module.clone(), import_name.clone(), used_name, import.imported_name_location, ReferenceKind::Import, ); } ValueConstructorVariant::ModuleConstant { module, .. } | ValueConstructorVariant::ModuleFn { module, .. } => { self.environment.references.register_value( used_name.clone(), EntityKind::ImportedValue { module: module_name, }, location, Publicity::Private, ); self.environment.references.register_value_reference( module.clone(), import_name.clone(), used_name, import.imported_name_location, ReferenceKind::Import, ); } ValueConstructorVariant::LocalVariable { .. } => {} }; // Check if value already was imported if let Some(previous) = self.environment.unqualified_imported_names.get(used_name) { self.problems.error(Error::DuplicateImport { location, previous_location: *previous, name: import_name.clone(), }); return; } // Register the name as imported so it can't be imported a // second time in future let _ = self .environment .unqualified_imported_names .insert(used_name.clone(), location); } /// Check for invalid imports, such as `src` importing `test` or `dev`. fn check_for_invalid_imports( &mut self, module_info: &ModuleInterface, location: SrcSpan, ) -> Result<(), Error> { if self.origin.is_src() && self .environment .dev_dependencies .contains(&module_info.package) { return Err(Error::SrcImportingDevDependency { importing_module: self.environment.current_module.clone(), imported_module: module_info.name.clone(), package: module_info.package.clone(), location, }); } let kind = match (self.origin, module_info.origin) { // `src` cannot import `test` or `dev` (Origin::Src, Origin::Test) => InvalidImportKind::SrcImportingTest, (Origin::Src, Origin::Dev) => InvalidImportKind::SrcImportingDev, // `dev` cannot import `test` (Origin::Dev, Origin::Test) => InvalidImportKind::DevImportingTest, _ => return Ok(()), }; Err(Error::InvalidImport { location, importing_module: self.environment.current_module.clone(), imported_module: module_info.name.clone(), kind, }) } fn register_module( &mut self, import: &UntypedImport, import_info: &'context ModuleInterface, ) -> Result<(), Error> { let Some(used_name) = import.used_name() else { return Ok(()); }; self.check_not_a_duplicate_import(&used_name, import.location)?; if let Some(alias_location) = import.alias_location() { self.environment.references.register_aliased_module( used_name.clone(), import.module.clone(), alias_location, import.location, ); } else { self.environment.references.register_module( used_name.clone(), import.module.clone(), import.location, ); } // Insert imported module into scope let _ = self .environment .imported_modules .insert(used_name.clone(), (import.location, import_info)); // Register this module as being imported // // Emit a warning if the module had already been imported. // This isn't an error so long as the modules have different local aliases. In Gleam v2 // this will likely become an error. if let Some(previous) = self.environment.names.imported_module( import.module.clone(), used_name, import.location, ) { self.problems.warning(Warning::ModuleImportedTwice { name: import.module.clone(), first: previous, second: import.location, }); } Ok(()) } fn check_not_a_duplicate_import( &self, used_name: &EcoString, location: SrcSpan, ) -> Result<(), Error> { // Check if a module was already imported with this name if let Some((previous_location, _)) = self.environment.imported_modules.get(used_name) { return Err(Error::DuplicateImport { location, previous_location: *previous_location, name: used_name.clone(), }); } Ok(()) } } ================================================ FILE: compiler-core/src/analyse/name.rs ================================================ use std::sync::OnceLock; use ecow::{EcoString, eco_format}; use regex::Regex; use crate::{ ast::{ArgNames, SrcSpan}, strings::{to_snake_case, to_upper_camel_case}, type_::Problems, }; use super::{Error, Named}; static VALID_NAME_PATTERN: OnceLock = OnceLock::new(); fn valid_name(name: &EcoString) -> bool { // Some of the internally generated variables (such as `_capture` and `_use0`) // start with underscores, so we allow underscores here. let valid_name_pattern = VALID_NAME_PATTERN .get_or_init(|| Regex::new("^_?[a-z][a-z0-9_]*$").expect("Regex is correct")); valid_name_pattern.is_match(name) } static VALID_DISCARD_PATTERN: OnceLock = OnceLock::new(); fn valid_discard_name(name: &EcoString) -> bool { let valid_discard_pattern = VALID_DISCARD_PATTERN .get_or_init(|| Regex::new("^_[a-z0-9_]*$").expect("Regex is correct")); valid_discard_pattern.is_match(name) } static VALID_UPNAME_PATTERN: OnceLock = OnceLock::new(); fn valid_upname(name: &EcoString) -> bool { let valid_upname_pattern = VALID_UPNAME_PATTERN .get_or_init(|| Regex::new("^[A-Z][A-Za-z0-9]*$").expect("Regex is correct")); valid_upname_pattern.is_match(name) } pub fn check_name_case(location: SrcSpan, name: &EcoString, kind: Named) -> Result<(), Error> { let valid = match kind { Named::Type | Named::TypeAlias | Named::CustomTypeVariant => valid_upname(name), Named::Variable | Named::TypeVariable | Named::Argument | Named::Label | Named::Constant | Named::Function => valid_name(name), Named::Discard => valid_discard_name(name), }; if valid { return Ok(()); } Err(Error::BadName { location, kind, name: name.clone(), }) } pub fn correct_name_case(name: &EcoString, kind: Named) -> EcoString { match kind { Named::Type | Named::TypeAlias | Named::CustomTypeVariant => to_upper_camel_case(name), Named::Variable | Named::TypeVariable | Named::Argument | Named::Label | Named::Constant | Named::Function => to_snake_case(name), Named::Discard => eco_format!("_{}", to_snake_case(name)), } } pub fn check_argument_names(names: &ArgNames, problems: &mut Problems) { match names { ArgNames::Discard { name, location } => { if let Err(error) = check_name_case(*location, name, Named::Discard) { problems.error(error); } } ArgNames::LabelledDiscard { label, label_location, name, name_location, } => { if let Err(error) = check_name_case(*label_location, label, Named::Label) { problems.error(error); } if let Err(error) = check_name_case(*name_location, name, Named::Discard) { problems.error(error); } } ArgNames::Named { name, location } => { if let Err(error) = check_name_case(*location, name, Named::Argument) { problems.error(error); } } ArgNames::NamedLabelled { name, name_location, label, label_location, } => { if let Err(error) = check_name_case(*label_location, label, Named::Label) { problems.error(error); } if let Err(error) = check_name_case(*name_location, name, Named::Argument) { problems.error(error); } } } } ================================================ FILE: compiler-core/src/analyse/tests.rs ================================================ use super::*; #[test] fn module_name_validation() { assert!(validate_module_name(&"dream".into()).is_ok()); assert!(validate_module_name(&"gleam".into()).is_err()); assert!(validate_module_name(&"gleam/ok".into()).is_ok()); assert!(validate_module_name(&"ok/gleam".into()).is_ok()); assert!(validate_module_name(&"type".into()).is_err()); assert!(validate_module_name(&"pub".into()).is_err()); assert!(validate_module_name(&"ok/type".into()).is_err()); assert!(validate_module_name(&"ok/pub".into()).is_err()); } ================================================ FILE: compiler-core/src/analyse.rs ================================================ mod imports; pub mod name; #[cfg(test)] mod tests; use crate::{ GLEAM_CORE_PACKAGE_NAME, STDLIB_PACKAGE_NAME, ast::{ self, Arg, BitArrayOption, CustomType, DefinitionLocation, Function, GroupedDefinitions, Import, ModuleConstant, Publicity, RecordConstructor, RecordConstructorArg, SrcSpan, Statement, TypeAlias, TypeAst, TypeAstConstructor, TypeAstFn, TypeAstHole, TypeAstTuple, TypeAstVar, TypedCustomType, TypedDefinitions, TypedExpr, TypedFunction, TypedImport, TypedModule, TypedModuleConstant, TypedTypeAlias, UntypedArg, UntypedCustomType, UntypedFunction, UntypedImport, UntypedModule, UntypedModuleConstant, UntypedStatement, UntypedTypeAlias, }, build::{Origin, Outcome, Target}, call_graph::{CallGraphNode, into_dependency_order}, config::PackageConfig, dep_tree, inline::{self, InlinableFunction}, line_numbers::LineNumbers, parse::SpannedString, reference::{EntityKind, ReferenceKind}, type_::{ self, AccessorsMap, Deprecation, FieldMap, ModuleInterface, Opaque, PatternConstructor, RecordAccessor, References, Type, TypeAliasConstructor, TypeConstructor, TypeValueConstructor, TypeValueConstructorField, TypeVariantConstructors, ValueConstructor, ValueConstructorVariant, Warning, environment::*, error::{Error, FeatureKind, MissingAnnotation, Named, Problems, convert_unify_error}, expression::{ExprTyper, FunctionDefinition, Implementations, Purity}, fields::FieldMapBuilder, hydrator::Hydrator, prelude::*, }, uid::UniqueIdGenerator, warning::TypeWarningEmitter, }; use camino::Utf8PathBuf; use ecow::{EcoString, eco_format}; use hexpm::version::Version; use itertools::Itertools; use name::{check_argument_names, check_name_case}; use std::{ collections::{HashMap, HashSet}, ops::Deref, sync::{Arc, OnceLock}, }; use vec1::Vec1; use self::imports::Importer; #[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)] pub enum Inferred { Known(T), #[default] Unknown, } impl Inferred { pub fn expect(self, message: &str) -> T { match self { Inferred::Known(value) => Some(value), Inferred::Unknown => None, } .expect(message) } pub fn expect_ref(&self, message: &str) -> &T { match self { Inferred::Known(value) => Some(value), Inferred::Unknown => None, } .expect(message) } } impl Inferred { pub fn definition_location(&self) -> Option { match self { Inferred::Known(value) => value.definition_location(), Inferred::Unknown => None, } } pub fn get_documentation(&self) -> Option<&str> { match self { Inferred::Known(value) => value.get_documentation(), Inferred::Unknown => None, } } pub fn field_map(&self) -> Option<&FieldMap> { match self { Inferred::Known(value) => value.field_map.as_ref(), Inferred::Unknown => None, } } } /// How the compiler should treat target support. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum TargetSupport { /// Target support is enfored, meaning if a function is found to not have an implementation for /// the current target then an error is emitted and compilation halts. /// /// This is used when compiling the root package, with the exception of when using /// `gleam run --module $module` to run a module from a dependency package, in which case we do /// not want to error as the root package code isn't going to be run. Enforced, /// Target support is enfored, meaning if a function is found to not have an implementation for /// the current target it will continue onwards and not generate any code for this function. /// /// This is used when compiling dependencies. NotEnforced, } impl TargetSupport { /// Returns `true` if the target support is [`Enforced`]. /// /// [`Enforced`]: TargetSupport::Enforced #[must_use] pub fn is_enforced(&self) -> bool { match self { Self::Enforced => true, Self::NotEnforced => false, } } } impl From for Outcome> { fn from(error: Error) -> Self { Outcome::TotalFailure(Vec1::new(error)) } } /// This struct is used to take the data required for analysis. It is used to /// construct the private ModuleAnalyzer which has this data plus any /// internal state. /// #[derive(Debug)] pub struct ModuleAnalyzerConstructor<'a, A> { pub target: Target, pub ids: &'a UniqueIdGenerator, pub origin: Origin, pub importable_modules: &'a im::HashMap, pub warnings: &'a TypeWarningEmitter, pub direct_dependencies: &'a HashMap, pub dev_dependencies: &'a HashSet, pub target_support: TargetSupport, pub package_config: &'a PackageConfig, } impl ModuleAnalyzerConstructor<'_, A> { /// Crawl the AST, annotating each node with the inferred type or /// returning an error. /// pub fn infer_module( self, module: UntypedModule, line_numbers: LineNumbers, src_path: Utf8PathBuf, ) -> Outcome> { ModuleAnalyzer { target: self.target, ids: self.ids, origin: self.origin, importable_modules: self.importable_modules, warnings: self.warnings, direct_dependencies: self.direct_dependencies, dev_dependencies: self.dev_dependencies, target_support: self.target_support, package_config: self.package_config, line_numbers, src_path, problems: Problems::new(), value_names: HashMap::with_capacity(module.definitions.len()), hydrators: HashMap::with_capacity(module.definitions.len()), module_name: module.name.clone(), inline_functions: HashMap::new(), minimum_required_version: Version::new(0, 1, 0), } .infer_module(module) } } struct ModuleAnalyzer<'a, A> { target: Target, ids: &'a UniqueIdGenerator, origin: Origin, importable_modules: &'a im::HashMap, warnings: &'a TypeWarningEmitter, direct_dependencies: &'a HashMap, dev_dependencies: &'a HashSet, target_support: TargetSupport, package_config: &'a PackageConfig, line_numbers: LineNumbers, src_path: Utf8PathBuf, problems: Problems, value_names: HashMap, hydrators: HashMap, module_name: EcoString, inline_functions: HashMap, /// The minimum Gleam version required to compile the analysed module. minimum_required_version: Version, } impl<'a, A> ModuleAnalyzer<'a, A> { pub fn infer_module(mut self, mut module: UntypedModule) -> Outcome> { if let Err(error) = validate_module_name(&self.module_name) { return self.all_errors(error); } let documentation = std::mem::take(&mut module.documentation); let env = EnvironmentArguments { ids: self.ids.clone(), current_package: self.package_config.name.clone(), gleam_version: self .package_config .gleam_version .clone() .map(|version| version.into()), current_module: self.module_name.clone(), target: self.target, importable_modules: self.importable_modules, target_support: self.target_support, current_origin: self.origin, dev_dependencies: self.dev_dependencies, } .build(); let definitions = GroupedDefinitions::new(module.into_iter_definitions(self.target)); // Register any modules, types, and values being imported // We process imports first so that anything imported can be referenced // anywhere in the module. let mut env = Importer::run(self.origin, env, &definitions.imports, &mut self.problems); // Register types so they can be used in constructors and functions // earlier in the module. for type_ in &definitions.custom_types { if let Err(error) = self.register_types_from_custom_type(type_, &mut env) { return self.all_errors(error); } } let sorted_aliases = match sorted_type_aliases(&definitions.type_aliases) { Ok(sorted_aliases) => sorted_aliases, Err(error) => return self.all_errors(error), }; for type_alias in sorted_aliases { self.register_type_alias(type_alias, &mut env); } for function in &definitions.functions { self.register_value_from_function(function, &mut env); } // Infer the types of each statement in the module let typed_imports = definitions .imports .into_iter() .filter_map(|import| self.analyse_import(import, &env)) .collect_vec(); let typed_custom_types = definitions .custom_types .into_iter() .filter_map(|custom_type| self.analyse_custom_type(custom_type, &mut env)) .collect_vec(); let typed_type_aliases = definitions .type_aliases .into_iter() .map(|type_alias| analyse_type_alias(type_alias, &mut env)) .collect_vec(); // Sort functions and constants into dependency order for inference. // Definitions that do not depend on other definitions are inferred // first, then ones that depend on those, etc. let mut typed_functions = Vec::with_capacity(definitions.functions.len()); let mut typed_constants = Vec::with_capacity(definitions.constants.len()); let definition_groups = match into_dependency_order(definitions.functions, definitions.constants) { Ok(definition_groups) => definition_groups, Err(error) => return self.all_errors(error), }; let mut working_constants = vec![]; let mut working_functions = vec![]; for group in definition_groups { // A group may have multiple functions and constants that depend on // each other by mutual reference. for definition in group { match definition { CallGraphNode::Function(function) => { working_functions.push(self.infer_function(function, &mut env)) } CallGraphNode::ModuleConstant(constant) => { working_constants.push(self.infer_module_constant(constant, &mut env)) } }; } // Now that the entire group has been inferred, generalise their types. for inferred_constant in working_constants.drain(..) { typed_constants.push(generalise_module_constant( inferred_constant, &mut env, &self.module_name, )) } for inferred_function in working_functions.drain(..) { typed_functions.push(generalise_function( inferred_function, &mut env, &self.module_name, )); } } let typed_definitions = TypedDefinitions { imports: typed_imports, constants: typed_constants, custom_types: typed_custom_types, type_aliases: typed_type_aliases, functions: typed_functions, }; // Generate warnings for unused items let unused_definition_positions = env.handle_unused(&mut self.problems); // Remove imported types and values to create the public interface // Private types and values are retained so they can be used in the language // server, but are filtered out when type checking to prevent using private // items. env.module_types .retain(|_, info| info.module == self.module_name); // Ensure no exported values have private types in their type signature for value in env.module_values.values() { self.check_for_type_leaks(value) } // Resolve deferred type variable aliases now that all unification is // done and link chains are stable. env.resolve_deferred_type_variable_aliases(); let Environment { module_types: types, module_types_constructors: types_constructors, module_values: values, accessors, names: type_names, module_type_aliases: type_aliases, echo_found, .. } = env; let is_internal = self .package_config .is_internal_module(self.module_name.as_str()); // We sort warnings and errors to ensure they are emitted in a // deterministic order, making them easier to test and debug, and to // make the output predictable. self.problems.sort(); let warnings = self.problems.take_warnings(); for warning in &warnings { // TODO: remove this clone self.warnings.emit(warning.clone()); } let module = ast::Module { documentation: documentation.clone(), name: self.module_name.clone(), definitions: typed_definitions, names: type_names, unused_definition_positions, type_info: ModuleInterface { name: self.module_name, types, types_value_constructors: types_constructors, values, accessors, origin: self.origin, package: self.package_config.name.clone(), is_internal, line_numbers: self.line_numbers, src_path: self.src_path, warnings, minimum_required_version: self.minimum_required_version, type_aliases, documentation, contains_echo: echo_found, references: References { imported_modules: env .imported_modules .values() .map(|(_location, module)| module.name.clone()) .collect(), value_references: env.references.value_references, type_references: env.references.type_references, }, inline_functions: self.inline_functions, }, }; match Vec1::try_from_vec(self.problems.take_errors()) { Err(_) => Outcome::Ok(module), Ok(errors) => Outcome::PartialFailure(module, errors), } } fn all_errors(&mut self, error: Error) -> Outcome> { Outcome::TotalFailure(Vec1::from_vec_push(self.problems.take_errors(), error)) } fn infer_module_constant( &mut self, c: UntypedModuleConstant, environment: &mut Environment<'_>, ) -> TypedModuleConstant { let ModuleConstant { documentation: doc, location, name, name_location, annotation, publicity, value, deprecation, .. } = c; self.check_name_case(name_location, &name, Named::Constant); // If the constant's name matches an unqualified import, emit a warning: self.check_shadow_import(&name, c.location, environment); environment.references.begin_constant(); let definition = FunctionDefinition { has_body: true, has_erlang_external: false, has_javascript_external: false, }; let mut expr_typer = ExprTyper::new(environment, definition, &mut self.problems); let typed_expr = expr_typer.infer_const(&annotation, *value); let type_ = typed_expr.type_(); let implementations = expr_typer.implementations; let minimum_required_version = expr_typer.minimum_required_version; if minimum_required_version > self.minimum_required_version { self.minimum_required_version = minimum_required_version; } match publicity { Publicity::Private | Publicity::Public | Publicity::Internal { attribute_location: None, } => (), Publicity::Internal { attribute_location: Some(location), } => self.track_feature_usage(FeatureKind::InternalAnnotation, location), } let variant = ValueConstructor { publicity, deprecation: deprecation.clone(), variant: ValueConstructorVariant::ModuleConstant { documentation: doc.as_ref().map(|(_, doc)| doc.clone()), location, literal: typed_expr.clone(), module: self.module_name.clone(), name: name.clone(), implementations, }, type_: type_.clone(), }; environment.insert_variable( name.clone(), variant.variant.clone(), type_.clone(), publicity, Deprecation::NotDeprecated, ); environment.insert_module_value(name.clone(), variant); environment .references .register_constant(name.clone(), location, publicity); environment.references.register_value_reference( environment.current_module.clone(), name.clone(), &name, name_location, ReferenceKind::Definition, ); ModuleConstant { documentation: doc, location, name, name_location, annotation, publicity, value: Box::new(typed_expr), type_, deprecation, implementations, } } // TODO: Extract this into a class of its own! Or perhaps it just wants some // helper methods extracted. There's a whole bunch of state in this one // function, and it does a handful of things. fn infer_function( &mut self, f: UntypedFunction, environment: &mut Environment<'_>, ) -> TypedFunction { let Function { documentation: doc, location, name, publicity, arguments, body, body_start, return_annotation, end_position: end_location, deprecation, external_erlang, external_javascript, return_type: (), implementations: _, purity: _, } = f; let (name_location, name) = name.expect("Function in a definition must be named"); let target = environment.target; let body_location = body .last() .map(|statement| statement.location()) .unwrap_or(location); let preregistered_fn = environment .get_variable(&name) .expect("Could not find preregistered type for function"); let field_map = preregistered_fn.field_map().cloned(); let preregistered_type = preregistered_fn.type_.clone(); let (prereg_arguments_types, prereg_return_type) = preregistered_type .fn_types() .expect("Preregistered type for fn was not a fn"); // Ensure that folks are not writing inline JavaScript expressions as // the implementation for JS externals. self.assert_valid_javascript_external(&name, external_javascript.as_ref(), location); // Find the external implementation for the current target, if one has been given. let external = target_function_implementation(target, &external_erlang, &external_javascript); // The function must have at least one implementation somewhere. let has_implementation = self.ensure_function_has_an_implementation( &body, &external_erlang, &external_javascript, location, ); if external.is_some() { // There was an external implementation, so type annotations are // mandatory as the Gleam implementation may be absent, and because we // think you should always specify types for external functions for // clarity + to avoid accidental mistakes. self.ensure_annotations_present(&arguments, return_annotation.as_ref(), location); } let has_body = !body.is_empty(); let definition = FunctionDefinition { has_body, has_erlang_external: external_erlang.is_some(), has_javascript_external: external_javascript.is_some(), }; // We have already registered the function in the `register_value_from_function` // method, but here we must set this as the current function again, so that anything // we reference in the body of it can be tracked properly in the call graph. environment.references.set_current_node(name.clone()); let mut typed_arguments = Vec::with_capacity(arguments.len()); // Infer the type using the preregistered args + return types as a starting point let result = environment.in_new_scope(&mut self.problems, |environment, problems| { for (argument, type_) in arguments.into_iter().zip(&prereg_arguments_types) { let argument = argument.set_type(type_.clone()); // We track which arguments are discarded so we can provide nice // error messages when someone match &argument.names { ast::ArgNames::Named { .. } | ast::ArgNames::NamedLabelled { .. } => (), ast::ArgNames::Discard { name, location } | ast::ArgNames::LabelledDiscard { name, name_location: location, .. } => { let _ = environment.discarded_names.insert(name.clone(), *location); } } typed_arguments.push(argument); } let mut expr_typer = ExprTyper::new(environment, definition, problems); expr_typer.hydrator = self .hydrators .remove(&name) .expect("Could not find hydrator for fn"); let (arguments, body) = expr_typer.infer_fn_with_known_types( Some(name.clone()), typed_arguments.clone(), body, Some(prereg_return_type.clone()), )?; let arguments_types = arguments.iter().map(|a| a.type_.clone()).collect(); let return_type = body .last() .map_or(prereg_return_type.clone(), |last| last.type_()); // `dict.do_fold` is a bit special: since it belongs to the stdlib // it is considered pure by default. // However, since it ends up calling its function argument its // purity should actually be `Impure`. We need to special case it // and set the value ourselves. // // You might wonder why `do_fold` needs this but other similar // functions like `list.each` don't need this special handling. // The key difference is `list.each` calls the higher order function // in its gleam body: // // ```gleam // fn each(list, fun) { // case list { // [] -> Nil // [first, ..rest] -> { // fun(first) // // ^^^ Here we're calling `fun`. It's happening in Gleam so // // the compiler can see this and understand that `each` // // is impure. // // You might argue the purity actually depends on the // // purity of `fun` itself. That's true! But it's a // // separate known problem. For the time being we always // // assume a function argument is impure. // each(rest, fun) // } // } // } // ``` // // But since `do_fold` is an external the compiler can't know what // is going on with its function argument and keeps thinking it must // be pure // // ```gleam // @external(erlang, "", "") // fn do_fold(dict: Dict(k, v), fun: fn(k, v) -> a) -> Nil // ``` // let purity = if expr_typer.environment.current_package == STDLIB_PACKAGE_NAME && expr_typer.environment.current_module == "gleam/dict" && name == "do_fold" { Purity::Impure } else { expr_typer.purity }; let type_ = fn_(arguments_types, return_type); Ok(( type_, body, expr_typer.implementations, expr_typer.minimum_required_version, purity, )) }); // If we could not successfully infer the type etc information of the // function then register the error and continue anaylsis using the best // information that we have, so we can still learn about the rest of the // module. let (type_, body, implementations, required_version, purity) = match result { Ok((type_, body, implementations, required_version, purity)) => { (type_, body, implementations, required_version, purity) } Err(error) => { self.problems.error(error); let type_ = preregistered_type.clone(); let body = vec![Statement::Expression(TypedExpr::Invalid { type_: prereg_return_type.clone(), location: SrcSpan { start: body_location.end, end: body_location.end, }, extra_information: None, })]; let implementations = Implementations::supporting_all(); ( type_, body, implementations, Version::new(1, 0, 0), Purity::Impure, ) } }; if required_version > self.minimum_required_version { self.minimum_required_version = required_version; } match publicity { Publicity::Private | Publicity::Public | Publicity::Internal { attribute_location: None, } => (), Publicity::Internal { attribute_location: Some(location), } => self.track_feature_usage(FeatureKind::InternalAnnotation, location), } if let Some((module, _, location)) = &external_javascript && module.contains('@') { self.track_feature_usage(FeatureKind::AtInJavascriptModules, *location) } // Assert that the inferred type matches the type of any recursive call if let Err(error) = unify(preregistered_type.clone(), type_) { self.problems.error(convert_unify_error(error, location)); } // Ensure that the current target has an implementation for the function. // This is done at the expression level while inferring the function body, but we do it again // here as externally implemented functions may not have a Gleam body. // // We don't emit this error if there is no implementation, as this would // have already emitted an error above. if has_implementation && publicity.is_importable() && environment.target_support.is_enforced() && !implementations.supports(target) // We don't emit this error if there is a body // since this would be caught at the statement level && !has_body { self.problems.error(Error::UnsupportedPublicFunctionTarget { name: name.clone(), target, location, }); } let variant = ValueConstructorVariant::ModuleFn { documentation: doc.as_ref().map(|(_, doc)| doc.clone()), name: name.clone(), external_erlang: external_erlang .as_ref() .map(|(m, f, _)| (m.clone(), f.clone())), external_javascript: external_javascript .as_ref() .map(|(m, f, _)| (m.clone(), f.clone())), field_map, module: environment.current_module.clone(), arity: typed_arguments.len(), location, implementations, purity, }; environment.insert_variable( name.clone(), variant, preregistered_type.clone(), publicity, deprecation.clone(), ); environment.references.register_value_reference( environment.current_module.clone(), name.clone(), &name, name_location, ReferenceKind::Definition, ); let function = Function { documentation: doc, location, name: Some((name_location, name.clone())), publicity, deprecation, arguments: typed_arguments, body_start, end_position: end_location, return_annotation, return_type: preregistered_type .return_type() .expect("Could not find return type for fn"), body, external_erlang, external_javascript, implementations, purity, }; if let Some(inline_function) = inline::function_to_inlinable( &environment.current_package, &environment.current_module, &function, ) { _ = self.inline_functions.insert(name, inline_function); } function } fn assert_valid_javascript_external( &mut self, function_name: &EcoString, external_javascript: Option<&(EcoString, EcoString, SrcSpan)>, location: SrcSpan, ) { use regex::Regex; static MODULE: OnceLock = OnceLock::new(); static FUNCTION: OnceLock = OnceLock::new(); let (module, function) = match external_javascript { None => return, Some((module, function, _location)) => (module, function), }; if !MODULE .get_or_init(|| Regex::new("^[@a-zA-Z0-9\\./:_-]+$").expect("regex")) .is_match(module) { self.problems.error(Error::InvalidExternalJavascriptModule { location, module: module.clone(), name: function_name.clone(), }); } if !FUNCTION .get_or_init(|| Regex::new("^[a-zA-Z_][a-zA-Z0-9_]*$").expect("regex")) .is_match(function) { self.problems .error(Error::InvalidExternalJavascriptFunction { location, function: function.clone(), name: function_name.clone(), }); } } fn ensure_annotations_present( &mut self, arguments: &[UntypedArg], return_annotation: Option<&TypeAst>, location: SrcSpan, ) { for arg in arguments { if arg.annotation.is_none() { self.problems.error(Error::ExternalMissingAnnotation { location: arg.location, kind: MissingAnnotation::Parameter, }); } } if return_annotation.is_none() { self.problems.error(Error::ExternalMissingAnnotation { location, kind: MissingAnnotation::Return, }); } } fn ensure_function_has_an_implementation( &mut self, body: &[UntypedStatement], external_erlang: &Option<(EcoString, EcoString, SrcSpan)>, external_javascript: &Option<(EcoString, EcoString, SrcSpan)>, location: SrcSpan, ) -> bool { match (external_erlang, external_javascript) { (None, None) if body.is_empty() => { self.problems.error(Error::NoImplementation { location }); false } _ => true, } } fn analyse_import( &mut self, i: UntypedImport, environment: &Environment<'_>, ) -> Option { let Import { documentation, location, module_location, module, as_name, unqualified_values, unqualified_types, .. } = i; // Find imported module let Some(module_info) = environment.importable_modules.get(&module) else { // Here the module being imported doesn't exist. We don't emit an // error here as the `Importer` that was run earlier will have // already emitted an error for this. return None; }; // Modules should belong to a package that is a direct dependency of the // current package to be imported. // Upgrade this to an error in future. if module_info.package != GLEAM_CORE_PACKAGE_NAME && module_info.package != self.package_config.name && !self.direct_dependencies.contains_key(&module_info.package) { self.warnings.emit(Warning::TransitiveDependencyImported { location, module: module_info.name.clone(), package: module_info.package.clone(), }) } Some(Import { documentation, location, module_location, module, as_name, unqualified_values, unqualified_types, package: module_info.package.clone(), }) } fn analyse_custom_type( &mut self, t: UntypedCustomType, environment: &mut Environment<'_>, ) -> Option { match self.do_analyse_custom_type(t, environment) { Ok(custom_type) => Some(custom_type), Err(error) => { self.problems.error(error); None } } } // TODO: split this into a new class. fn do_analyse_custom_type( &mut self, t: UntypedCustomType, environment: &mut Environment<'_>, ) -> Result { self.register_values_from_custom_type( &t, environment, &t.parameters.iter().map(|(_, name)| name).collect_vec(), )?; let CustomType { documentation: doc, location, end_position, publicity, opaque, name, name_location, parameters, constructors, deprecation, external_erlang, external_javascript, .. } = t; match publicity { Publicity::Private | Publicity::Public | Publicity::Internal { attribute_location: None, } => (), Publicity::Internal { attribute_location: Some(location), } => self.track_feature_usage(FeatureKind::InternalAnnotation, location), } let constructors: Vec>> = constructors .into_iter() .map( |RecordConstructor { location, name_location, name, arguments, documentation, deprecation: constructor_deprecation, }| { self.check_name_case(name_location, &name, Named::CustomTypeVariant); if constructor_deprecation.is_deprecated() { self.track_feature_usage( FeatureKind::VariantWithDeprecatedAnnotation, location, ); } let preregistered_fn = environment .get_variable(&name) .expect("Could not find preregistered type for function"); let preregistered_type = preregistered_fn.type_.clone(); let arguments = match preregistered_type.fn_types() { Some((arguments_types, _return_type)) => arguments .into_iter() .zip(&arguments_types) .map(|(argument, type_)| { if let Some((location, label)) = &argument.label { self.check_name_case(*location, label, Named::Label); } RecordConstructorArg { label: argument.label, ast: argument.ast, location: argument.location, type_: type_.clone(), doc: argument.doc, } }) .collect(), _ => { vec![] } }; RecordConstructor { location, name_location, name, arguments, documentation, deprecation: constructor_deprecation, } }, ) .collect(); let typed_parameters = environment .get_type_constructor(&None, &name) .expect("Could not find preregistered type constructor") .parameters .clone(); // Check if all constructors are deprecated if so error. if !constructors.is_empty() && constructors .iter() .all(|record| record.deprecation.is_deprecated()) { self.problems .error(Error::AllVariantsDeprecated { location }); } // If any constructor record/varient is deprecated while // the type is deprecated as a whole that is considered an error. if deprecation.is_deprecated() && !constructors.is_empty() && constructors .iter() .any(|record| record.deprecation.is_deprecated()) { // Report error on all variants attibuted with deprecated constructors .iter() .filter(|record| record.deprecation.is_deprecated()) .for_each(|record| { self.problems .error(Error::DeprecatedVariantOnDeprecatedType { location: record.location, }); }); } if external_erlang.is_some() || external_javascript.is_some() { self.track_feature_usage(FeatureKind::ExternalCustomType, location); if !constructors.is_empty() { self.problems .error(Error::ExternalTypeWithConstructors { location }); } } Ok(CustomType { documentation: doc, location, end_position, publicity, opaque, name, name_location, parameters, constructors, typed_parameters, deprecation, external_erlang, external_javascript, }) } fn register_values_from_custom_type( &mut self, t: &UntypedCustomType, environment: &mut Environment<'_>, type_parameters: &[&EcoString], ) -> Result<(), Error> { let CustomType { publicity, opaque, name, constructors, deprecation, .. } = t; let mut hydrator = self .hydrators .remove(name) .expect("Could not find hydrator for register_values custom type"); hydrator.disallow_new_type_variables(); let type_ = environment .module_types .get(name) .expect("Type for custom type not found in register_values") .type_ .clone(); let mut constructors_data = vec![]; let mut index = 0; for constructor in constructors.iter() { if let Err(error) = assert_unique_name( &mut self.value_names, &constructor.name, constructor.location, ) { self.problems.error(error); continue; } // If the constructor belongs to an opaque type then it's going to be // considered as private. let value_constructor_publicity = if *opaque { Publicity::Private } else { *publicity }; environment.references.register_value( constructor.name.clone(), EntityKind::Constructor, constructor.location, value_constructor_publicity, ); environment .references .register_type_reference_in_call_graph(name.clone()); let mut field_map_builder = FieldMapBuilder::new(constructor.arguments.len() as u32); let mut arguments_types = Vec::with_capacity(constructor.arguments.len()); let mut fields = Vec::with_capacity(constructor.arguments.len()); for RecordConstructorArg { label, ast, location, doc, .. } in constructor.arguments.iter() { // Build a type from the annotation AST let t = match hydrator.type_from_ast(ast, environment, &mut self.problems) { Ok(t) => t, Err(e) => { self.problems.error(e); environment.new_unbound_var() } }; fields.push(TypeValueConstructorField { type_: t.clone(), label: label.as_ref().map(|(_location, label)| label.clone()), documentation: doc.as_ref().map(|(_, documentation)| documentation.clone()), }); // Register the type for this parameter arguments_types.push(t); let (label_location, label) = match label { Some((location, label)) => (*location, Some(label)), None => (*location, None), }; // Register the label for this parameter if let Err(error) = field_map_builder.add(label, label_location) { self.problems.error(error); } } let field_map = field_map_builder.finish(); // Insert constructor function into module scope let mut type_ = type_.deref().clone(); type_.set_custom_type_variant(index as u16); let type_ = match constructor.arguments.len() { 0 => Arc::new(type_), _ => fn_(arguments_types.clone(), Arc::new(type_)), }; let constructor_info = ValueConstructorVariant::Record { documentation: constructor .documentation .as_ref() .map(|(_, doc)| doc.clone()), variants_count: constructors.len() as u16, name: constructor.name.clone(), arity: constructor.arguments.len() as u16, field_map: field_map.clone(), location: constructor.location, module: self.module_name.clone(), variant_index: index as u16, }; index += 1; // If the whole custom type is deprecated all of its varints are too. // Otherwise just the varint(s) attributed as deprecated are. let deprecate_constructor = if deprecation.is_deprecated() { deprecation } else { &constructor.deprecation }; environment.insert_module_value( constructor.name.clone(), ValueConstructor { publicity: value_constructor_publicity, deprecation: deprecate_constructor.clone(), type_: type_.clone(), variant: constructor_info.clone(), }, ); environment.references.register_value_reference( environment.current_module.clone(), constructor.name.clone(), &constructor.name, constructor.name_location, ReferenceKind::Definition, ); constructors_data.push(TypeValueConstructor { name: constructor.name.clone(), parameters: fields, documentation: constructor .documentation .as_ref() .map(|(_, documentation)| documentation.clone()), }); environment.insert_variable( constructor.name.clone(), constructor_info, type_, value_constructor_publicity, deprecate_constructor.clone(), ); environment.names.named_constructor_in_scope( environment.current_module.clone(), constructor.name.clone(), constructor.name.clone(), ); } let Accessors { shared_accessors, variant_specific_accessors, positional_accessors, } = custom_type_accessors(&constructors_data)?; let map = AccessorsMap { publicity: if *opaque { Publicity::Private } else { *publicity }, shared_accessors, // TODO: improve the ownership here so that we can use the // `return_type_constructor` below rather than looking it up twice. type_: type_.clone(), variant_specific_accessors, variant_positional_accessors: positional_accessors, }; environment.insert_accessors(name.clone(), map); let opaque = if *opaque { Opaque::Opaque } else { Opaque::NotOpaque }; // Now record the constructors for the type. environment.insert_type_to_constructors( name.clone(), TypeVariantConstructors::new(constructors_data, type_parameters, opaque, hydrator), ); Ok(()) } fn register_types_from_custom_type( &mut self, t: &UntypedCustomType, environment: &mut Environment<'a>, ) -> Result<(), Error> { let CustomType { name, name_location, publicity, parameters, location, deprecation, opaque, constructors, documentation, .. } = t; // We exit early here as we don't yet have a good way to handle the two // duplicate definitions in the later pass of the analyser which // register the constructor values for the types. The latter would end up // overwriting the former, but here in type registering we keep the // former. I think we want to really keep the former both times. // The fact we can't straightforwardly do this indicated to me that we // could improve our approach here somewhat. environment.assert_unique_type_name(name, *location)?; self.check_name_case(*name_location, name, Named::Type); let mut hydrator = Hydrator::new(); let parameters = self.make_type_vars(parameters, &mut hydrator, environment); hydrator.clear_ridgid_type_names(); // We check is the type comes from an internal module and restrict its // publicity. let publicity = match publicity { // It's important we only restrict the publicity of public types. Publicity::Public if self.package_config.is_internal_module(&self.module_name) => { Publicity::Internal { attribute_location: None, } } // If a type is private we don't want to make it internal just because // it comes from an internal module, so in that case the publicity is // left unchanged. Publicity::Public | Publicity::Private | Publicity::Internal { .. } => *publicity, }; let type_ = Arc::new(Type::Named { publicity, package: environment.current_package.clone(), module: self.module_name.to_owned(), name: name.clone(), arguments: parameters.clone(), inferred_variant: None, }); let _ = self.hydrators.insert(name.clone(), hydrator); environment .insert_type_constructor( name.clone(), TypeConstructor { origin: *location, module: self.module_name.clone(), deprecation: deprecation.clone(), parameters, publicity, type_, documentation: documentation.as_ref().map(|(_, doc)| doc.clone()), }, ) .expect("name uniqueness checked above"); environment.names.named_type_in_scope( environment.current_module.clone(), name.clone(), name.clone(), ); environment .references .register_type(name.clone(), EntityKind::Type, *location, publicity); environment.references.register_type_reference( environment.current_module.clone(), name.clone(), name, *name_location, ReferenceKind::Definition, ); if *opaque && constructors.is_empty() { self.problems.warning(Warning::OpaqueExternalType { location: *location, }); } if *opaque && publicity.is_private() { self.problems.error(Error::PrivateOpaqueType { location: SrcSpan { start: location.start, end: location.start + 6, }, }); } Ok(()) } fn register_type_alias(&mut self, t: &UntypedTypeAlias, environment: &mut Environment<'_>) { let TypeAlias { location, publicity, parameters: arguments, alias: name, name_location, type_ast: resolved_type, deprecation, type_: _, documentation, } = t; // A type alias must not have the same name as any other type in the module. if let Err(error) = environment.assert_unique_type_name(name, *location) { self.problems.error(error); // A type already exists with the name so we cannot continue and // register this new type with the same name. return; } self.check_name_case(*name_location, name, Named::TypeAlias); environment .references .register_type(name.clone(), EntityKind::Type, *location, *publicity); // Use the hydrator to convert the AST into a type, erroring if the AST was invalid // in some fashion. let mut hydrator = Hydrator::new(); let parameters = self.make_type_vars(arguments, &mut hydrator, environment); let arity = parameters.len(); let tryblock = || { hydrator.disallow_new_type_variables(); let type_ = hydrator.type_from_ast(resolved_type, environment, &mut self.problems)?; environment .names .type_in_scope(name.clone(), type_.as_ref(), ¶meters); // Insert the alias so that it can be used by other code. environment.insert_type_constructor( name.clone(), TypeConstructor { origin: *location, module: self.module_name.clone(), parameters: parameters.clone(), type_: type_.clone(), deprecation: deprecation.clone(), publicity: *publicity, documentation: documentation.as_ref().map(|(_, doc)| doc.clone()), }, )?; let alias = TypeAliasConstructor { origin: *location, module: self.module_name.clone(), type_, publicity: *publicity, deprecation: deprecation.clone(), documentation: documentation.as_ref().map(|(_, doc)| doc.clone()), arity, parameters, }; environment.names.maybe_register_reexport_alias( &environment.current_package, name, &alias, ); environment.insert_type_alias(name.clone(), alias)?; if let Some(name) = hydrator.unused_type_variables().next() { return Err(Error::UnusedTypeAliasParameter { location: *location, name: name.clone(), }); } Ok(()) }; let result = tryblock(); self.record_if_error(result); } fn make_type_vars( &mut self, arguments: &[SpannedString], hydrator: &mut Hydrator, environment: &mut Environment<'_>, ) -> Vec> { arguments .iter() .map(|(location, name)| { self.check_name_case(*location, name, Named::TypeVariable); match hydrator.add_type_variable(name, environment) { Ok(t) => t, Err(t) => { self.problems.error(Error::DuplicateTypeParameter { location: *location, name: name.clone(), }); t } } }) .collect() } fn record_if_error(&mut self, result: Result<(), Error>) { if let Err(error) = result { self.problems.error(error); } } fn register_value_from_function( &mut self, f: &UntypedFunction, environment: &mut Environment<'_>, ) { let Function { name, arguments, location, return_annotation, publicity, documentation, external_erlang, external_javascript, deprecation, end_position: _, body: _, body_start: _, return_type: _, implementations, purity, } = f; let (name_location, name) = name.as_ref().expect("A module's function must be named"); self.check_name_case(*name_location, name, Named::Function); // If the function's name matches an unqualified import, emit a warning: self.check_shadow_import(name, f.location, environment); environment.references.register_value( name.clone(), EntityKind::Function, *location, *publicity, ); let mut builder = FieldMapBuilder::new(arguments.len() as u32); for Arg { names, location, .. } in arguments.iter() { check_argument_names(names, &mut self.problems); if let Err(error) = builder.add(names.get_label(), *location) { self.problems.error(error); } } let field_map = builder.finish(); let mut hydrator = Hydrator::new(); // When external implementations are present then the type annotations // must be given in full, so we disallow holes in the annotations. hydrator.permit_holes(external_erlang.is_none() && external_javascript.is_none()); let arguments_types = arguments .iter() .map(|argument| { match hydrator.type_from_option_ast( &argument.annotation, environment, &mut self.problems, ) { Ok(type_) => type_, Err(error) => { self.problems.error(error); environment.new_unbound_var() } } }) .collect(); let return_type = match hydrator.type_from_option_ast(return_annotation, environment, &mut self.problems) { Ok(type_) => type_, Err(error) => { self.problems.error(error); environment.new_unbound_var() } }; let type_ = fn_(arguments_types, return_type); let _ = self.hydrators.insert(name.clone(), hydrator); let variant = ValueConstructorVariant::ModuleFn { documentation: documentation.as_ref().map(|(_, doc)| doc.clone()), name: name.clone(), field_map, external_erlang: external_erlang .as_ref() .map(|(m, f, _)| (m.clone(), f.clone())), external_javascript: external_javascript .as_ref() .map(|(m, f, _)| (m.clone(), f.clone())), module: environment.current_module.clone(), arity: arguments.len(), location: *location, implementations: *implementations, purity: *purity, }; environment.insert_variable( name.clone(), variant, type_, *publicity, deprecation.clone(), ); } fn check_for_type_leaks(&mut self, value: &ValueConstructor) { // A private value doesn't export anything so it can't leak anything. if value.publicity.is_private() { return; } // If a private or internal value references a private type if let Some(leaked) = value.type_.find_private_type() { self.problems.error(Error::PrivateTypeLeak { location: value.variant.definition_location(), leaked, }); } } fn check_name_case(&mut self, location: SrcSpan, name: &EcoString, kind: Named) { if let Err(error) = check_name_case(location, name, kind) { self.problems.error(error); } } fn track_feature_usage(&mut self, feature_kind: FeatureKind, location: SrcSpan) { let minimum_required_version = feature_kind.required_version(); // Then if the required version is not in the specified version for the // range we emit a warning highlighting the usage of the feature. if let Some(gleam_version) = &self.package_config.gleam_version && let Some(lowest_allowed_version) = gleam_version.lowest_version() { // There is a version in the specified range that is lower than // the one required by this feature! This means that the // specified range is wrong and would allow someone to run a // compiler that is too old to know of this feature. if minimum_required_version > lowest_allowed_version { self.problems .warning(Warning::FeatureRequiresHigherGleamVersion { location, feature_kind, minimum_required_version: minimum_required_version.clone(), wrongfully_allowed_version: lowest_allowed_version, }) } } if minimum_required_version > self.minimum_required_version { self.minimum_required_version = minimum_required_version; } } fn check_shadow_import( &mut self, name: &EcoString, location: SrcSpan, environment: &mut Environment<'_>, ) { if environment.unqualified_imported_names.contains_key(name) { self.problems .warning(Warning::TopLevelDefinitionShadowsImport { location, name: name.clone(), }); } } } fn validate_module_name(name: &EcoString) -> Result<(), Error> { if is_prelude_module(name) { return Err(Error::ReservedModuleName { name: name.clone() }); }; for segment in name.split('/') { if crate::parse::lexer::str_to_keyword(segment).is_some() { return Err(Error::KeywordInModuleName { name: name.clone(), keyword: segment.into(), }); } } Ok(()) } fn target_function_implementation<'a>( target: Target, external_erlang: &'a Option<(EcoString, EcoString, SrcSpan)>, external_javascript: &'a Option<(EcoString, EcoString, SrcSpan)>, ) -> &'a Option<(EcoString, EcoString, SrcSpan)> { match target { Target::Erlang => external_erlang, Target::JavaScript => external_javascript, } } fn analyse_type_alias(t: UntypedTypeAlias, environment: &mut Environment<'_>) -> TypedTypeAlias { let TypeAlias { documentation: doc, location, publicity, alias, name_location, parameters: arguments, type_ast: resolved_type, deprecation, .. } = t; // There could be no type alias registered if it was invalid in some way. // analysis aims to be fault tolerant to get the best possible feedback for // the programmer in the language server, so the analyser gets here even // though there was previously errors. let type_ = match environment.get_type_constructor(&None, &alias) { Ok(constructor) => constructor.type_.clone(), Err(_) => environment.new_generic_var(), }; TypeAlias { documentation: doc, location, publicity, alias, name_location, parameters: arguments, type_ast: resolved_type, type_, deprecation, } } pub fn infer_bit_array_option( segment_option: BitArrayOption, mut type_check: Typer, ) -> Result, Error> where Typer: FnMut(UntypedValue, Arc) -> Result, { match segment_option { BitArrayOption::Size { value, location, short_form, .. } => { let value = type_check(*value, int())?; Ok(BitArrayOption::Size { location, short_form, value: Box::new(value), }) } BitArrayOption::Unit { location, value } => Ok(BitArrayOption::Unit { location, value }), BitArrayOption::Bytes { location } => Ok(BitArrayOption::Bytes { location }), BitArrayOption::Int { location } => Ok(BitArrayOption::Int { location }), BitArrayOption::Float { location } => Ok(BitArrayOption::Float { location }), BitArrayOption::Bits { location } => Ok(BitArrayOption::Bits { location }), BitArrayOption::Utf8 { location } => Ok(BitArrayOption::Utf8 { location }), BitArrayOption::Utf16 { location } => Ok(BitArrayOption::Utf16 { location }), BitArrayOption::Utf32 { location } => Ok(BitArrayOption::Utf32 { location }), BitArrayOption::Utf8Codepoint { location } => { Ok(BitArrayOption::Utf8Codepoint { location }) } BitArrayOption::Utf16Codepoint { location } => { Ok(BitArrayOption::Utf16Codepoint { location }) } BitArrayOption::Utf32Codepoint { location } => { Ok(BitArrayOption::Utf32Codepoint { location }) } BitArrayOption::Signed { location } => Ok(BitArrayOption::Signed { location }), BitArrayOption::Unsigned { location } => Ok(BitArrayOption::Unsigned { location }), BitArrayOption::Big { location } => Ok(BitArrayOption::Big { location }), BitArrayOption::Little { location } => Ok(BitArrayOption::Little { location }), BitArrayOption::Native { location } => Ok(BitArrayOption::Native { location }), } } fn generalise_module_constant( constant: ModuleConstant, EcoString>, environment: &mut Environment<'_>, module_name: &EcoString, ) -> TypedModuleConstant { let ModuleConstant { documentation: doc, location, name, name_location, annotation, publicity, value, type_, deprecation, implementations, } = constant; let type_ = type_.clone(); let type_ = type_::generalise(type_); let variant = ValueConstructorVariant::ModuleConstant { documentation: doc.as_ref().map(|(_, doc)| doc.clone()), location, literal: *value.clone(), module: module_name.clone(), implementations, name: name.clone(), }; environment.insert_variable( name.clone(), variant.clone(), type_.clone(), publicity, deprecation.clone(), ); environment.insert_module_value( name.clone(), ValueConstructor { publicity, variant, deprecation: deprecation.clone(), type_: type_.clone(), }, ); ModuleConstant { documentation: doc, location, name, name_location, annotation, publicity, value, type_, deprecation, implementations, } } fn generalise_function( function: TypedFunction, environment: &mut Environment<'_>, module_name: &EcoString, ) -> TypedFunction { let Function { documentation: doc, location, name, publicity, deprecation, arguments, body, return_annotation, end_position: end_location, body_start, return_type, external_erlang, external_javascript, implementations, purity, } = function; let (name_location, name) = name.expect("Function in a definition must be named"); // Lookup the inferred function information let function = environment .get_variable(&name) .expect("Could not find preregistered type for function"); let field_map = function.field_map().cloned(); let type_ = function.type_.clone(); let type_ = type_::generalise(type_); // Insert the function into the module's interface let variant = ValueConstructorVariant::ModuleFn { documentation: doc.as_ref().map(|(_, doc)| doc.clone()), name: name.clone(), field_map, external_erlang: external_erlang .as_ref() .map(|(m, f, _)| (m.clone(), f.clone())), external_javascript: external_javascript .as_ref() .map(|(m, f, _)| (m.clone(), f.clone())), module: module_name.clone(), arity: arguments.len(), location, implementations, purity, }; environment.insert_variable( name.clone(), variant.clone(), type_.clone(), publicity, deprecation.clone(), ); environment.insert_module_value( name.clone(), ValueConstructor { publicity, deprecation: deprecation.clone(), type_, variant, }, ); Function { documentation: doc, location, name: Some((name_location, name)), publicity, deprecation, arguments, end_position: end_location, body_start, return_annotation, return_type, body, external_erlang, external_javascript, implementations, purity, } } fn assert_unique_name( names: &mut HashMap, name: &EcoString, location: SrcSpan, ) -> Result<(), Error> { match names.insert(name.clone(), location) { Some(previous_location) => Err(Error::DuplicateName { location_a: location, location_b: previous_location, name: name.clone(), }), None => Ok(()), } } struct Accessors { shared_accessors: HashMap, variant_specific_accessors: Vec>, positional_accessors: Vec>>, } fn custom_type_accessors(constructors: &[TypeValueConstructor]) -> Result { let accessors = get_compatible_record_fields(constructors); let mut shared_accessors = HashMap::with_capacity(accessors.len()); for accessor in accessors { let _ = shared_accessors.insert(accessor.label.clone(), accessor); } let mut variant_specific_accessors = Vec::with_capacity(constructors.len()); let mut positional_accessors = Vec::with_capacity(constructors.len()); for constructor in constructors { let mut fields = HashMap::with_capacity(constructor.parameters.len()); let mut positional_fields = Vec::new(); for (index, parameter) in constructor.parameters.iter().enumerate() { if let Some(label) = ¶meter.label { _ = fields.insert( label.clone(), RecordAccessor { index: index as u64, label: label.clone(), type_: parameter.type_.clone(), documentation: parameter.documentation.clone(), }, ); } else { positional_fields.push(parameter.type_.clone()); } } variant_specific_accessors.push(fields); positional_accessors.push(positional_fields); } Ok(Accessors { shared_accessors, variant_specific_accessors, positional_accessors, }) } /// Returns the fields that have the same label and type across all variants of /// the given type. fn get_compatible_record_fields(constructors: &[TypeValueConstructor]) -> Vec { let mut compatible = vec![]; let first = match constructors.first() { Some(first) => first, None => return compatible, }; 'next_argument: for (index, first_parameter) in first.parameters.iter().enumerate() { // Fields without labels do not have accessors let first_label = match first_parameter.label.as_ref() { Some(label) => label, None => continue 'next_argument, }; let mut documentation = if constructors.len() == 1 { // If there is only one constructor, we simply show the documentation // for the field. first_parameter.documentation.clone() } else { // If there are multiple constructors, we show the documentation of // this field for each of the variants. first_parameter .documentation .as_ref() .map(|field_documentation| { eco_format!("## {}\n\n{}", first.name, field_documentation) }) }; // Check each variant to see if they have an field in the same position // with the same label and the same type for constructor in constructors.iter().skip(1) { // The field must exist in all variants let parameter = match constructor.parameters.get(index) { Some(argument) => argument, None => continue 'next_argument, }; // The labels must be the same if parameter .label .as_ref() .is_none_or(|arg_label| arg_label != first_label) { continue 'next_argument; } // The types must be the same if !parameter.type_.same_as(&first_parameter.type_) { continue 'next_argument; } if let Some(field_documentation) = ¶meter.documentation { let field_documentation = eco_format!("## {}\n\n{}", constructor.name, field_documentation); match &mut documentation { None => { documentation = Some(field_documentation); } Some(documentation) => { documentation.push('\n'); documentation.push_str(&field_documentation); } } } } // The previous loop did not find any incompatible fields in the other // variants so this field is compatible across variants and we should // generate an accessor for it. compatible.push(RecordAccessor { index: index as u64, label: first_label.clone(), type_: first_parameter.type_.clone(), documentation, }) } compatible } /// Given a type, return a list of all the types it depends on fn get_type_dependencies(type_: &TypeAst) -> Vec { let mut deps = Vec::with_capacity(1); match type_ { TypeAst::Var(TypeAstVar { .. }) => (), TypeAst::Hole(TypeAstHole { .. }) => (), TypeAst::Constructor(TypeAstConstructor { name, arguments, module, .. }) => { deps.push(match module { Some((module, _)) => format!("{name}.{module}").into(), None => name.clone(), }); for arg in arguments { deps.extend(get_type_dependencies(arg)) } } TypeAst::Fn(TypeAstFn { arguments, return_, .. }) => { for arg in arguments { deps.extend(get_type_dependencies(arg)) } deps.extend(get_type_dependencies(return_)) } TypeAst::Tuple(TypeAstTuple { elements, .. }) => { for element in elements { deps.extend(get_type_dependencies(element)) } } } deps } fn sorted_type_aliases(aliases: &Vec) -> Result, Error> { let mut deps: Vec<(EcoString, Vec)> = Vec::with_capacity(aliases.len()); for alias in aliases { deps.push((alias.alias.clone(), get_type_dependencies(&alias.type_ast))) } let sorted_deps = dep_tree::toposort_deps(deps).map_err(|err| { let dep_tree::Error::Cycle(cycle) = err; let last = cycle.last().expect("Cycle should not be empty"); let alias = aliases .iter() .find(|alias| alias.alias == *last) .expect("Could not find alias for cycle"); Error::RecursiveTypeAlias { cycle, location: alias.location, } })?; Ok(aliases .iter() .sorted_by_key(|alias| sorted_deps.iter().position(|x| x == &alias.alias)) .collect()) } ================================================ FILE: compiler-core/src/ast/constant.rs ================================================ use super::*; use crate::analyse::Inferred; use crate::type_::{FieldMap, HasType}; pub type TypedConstant = Constant, EcoString>; pub type UntypedConstant = Constant<(), ()>; // TODO: remove RecordTag paramter #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum Constant { Int { location: SrcSpan, value: EcoString, int_value: BigInt, }, Float { location: SrcSpan, value: EcoString, float_value: LiteralFloatValue, }, String { location: SrcSpan, value: EcoString, }, Tuple { location: SrcSpan, elements: Vec, type_: T, }, List { location: SrcSpan, elements: Vec, type_: T, tail: Option>, }, Record { location: SrcSpan, module: Option<(EcoString, SrcSpan)>, name: EcoString, arguments: Vec>, tag: RecordTag, type_: T, field_map: Inferred, record_constructor: Option>, }, RecordUpdate { location: SrcSpan, constructor_location: SrcSpan, module: Option<(EcoString, SrcSpan)>, name: EcoString, record: RecordBeingUpdated, arguments: Vec>, tag: RecordTag, type_: T, field_map: Inferred, }, BitArray { location: SrcSpan, segments: Vec>, }, Var { location: SrcSpan, module: Option<(EcoString, SrcSpan)>, name: EcoString, constructor: Option>, type_: T, }, StringConcatenation { location: SrcSpan, left: Box, right: Box, }, /// A placeholder constant used to allow module analysis to continue /// even when there are type errors. Should never end up in generated code. Invalid { location: SrcSpan, type_: T, /// Extra information about the invalid expression, useful for providing /// addition help or information, such as code actions to fix invalid /// states. extra_information: Option, }, } impl TypedConstant { pub fn type_(&self) -> Arc { match self { Constant::Int { .. } => type_::int(), Constant::Float { .. } => type_::float(), Constant::String { .. } | Constant::StringConcatenation { .. } => type_::string(), Constant::BitArray { .. } => type_::bit_array(), Constant::List { type_, .. } | Constant::Tuple { type_, .. } | Constant::Record { type_, .. } | Constant::RecordUpdate { type_, .. } | Constant::Var { type_, .. } | Constant::Invalid { type_, .. } => type_.clone(), } } pub fn find_node(&self, byte_index: u32) -> Option> { if !self.location().contains(byte_index) { return None; } Some(match self { Constant::Int { .. } | Constant::Float { .. } | Constant::String { .. } | Constant::Invalid { .. } => Located::Constant(self), Constant::Var { module: Some((module_alias, location)), constructor: Some(constructor), .. } | Constant::Record { module: Some((module_alias, location)), record_constructor: Some(constructor), .. } if location.contains(byte_index) => match &constructor.variant { ValueConstructorVariant::ModuleConstant { module, .. } | ValueConstructorVariant::ModuleFn { module, .. } | ValueConstructorVariant::Record { module, .. } => Located::ModuleName { location: *location, module_name: module.clone(), module_alias: module_alias.clone(), layer: Layer::Value, }, ValueConstructorVariant::LocalVariable { .. } => Located::Constant(self), }, Constant::Var { .. } => Located::Constant(self), Constant::Tuple { elements, .. } => elements .iter() .find_map(|element| element.find_node(byte_index)) .unwrap_or(Located::Constant(self)), Constant::List { elements, tail, .. } => elements .iter() .find_map(|element| element.find_node(byte_index)) .or_else(|| tail.as_deref().and_then(|tail| tail.find_node(byte_index))) .unwrap_or(Located::Constant(self)), Constant::Record { arguments, .. } => arguments .iter() .find_map(|argument| argument.find_node(byte_index)) .unwrap_or(Located::Constant(self)), Constant::RecordUpdate { record, arguments, .. } => record .base .find_node(byte_index) .or_else(|| { arguments .iter() .find_map(|arg| arg.value.find_node(byte_index)) }) .unwrap_or(Located::Constant(self)), Constant::BitArray { segments, .. } => segments .iter() .find_map(|segment| segment.find_node(byte_index)) .unwrap_or(Located::Constant(self)), Constant::StringConcatenation { left, right, .. } => left .find_node(byte_index) .or_else(|| right.find_node(byte_index)) .unwrap_or(Located::Constant(self)), }) } pub fn definition_location(&self) -> Option { match self { Constant::Int { .. } | Constant::Float { .. } | Constant::String { .. } | Constant::Tuple { .. } | Constant::List { .. } | Constant::BitArray { .. } | Constant::StringConcatenation { .. } | Constant::Invalid { .. } => None, Constant::Record { record_constructor: value_constructor, .. } | Constant::Var { constructor: value_constructor, .. } => value_constructor .as_ref() .map(|constructor| constructor.definition_location()), Constant::RecordUpdate { .. } => None, } } pub(crate) fn referenced_variables(&self) -> im::HashSet<&EcoString> { match self { Constant::Var { name, .. } => im::hashset![name], Constant::Invalid { .. } | Constant::Int { .. } | Constant::Float { .. } | Constant::String { .. } => im::hashset![], Constant::List { elements, .. } | Constant::Tuple { elements, .. } => elements .iter() .map(|element| element.referenced_variables()) .fold(im::hashset![], im::HashSet::union), Constant::Record { arguments, .. } => arguments .iter() .map(|argument| argument.value.referenced_variables()) .fold(im::hashset![], im::HashSet::union), Constant::RecordUpdate { record, arguments, .. } => record.base.referenced_variables().union( arguments .iter() .map(|arg| arg.value.referenced_variables()) .fold(im::hashset![], im::HashSet::union), ), Constant::BitArray { segments, .. } => segments .iter() .map(|segment| { segment .options .iter() .map(|option| option.referenced_variables()) .fold(segment.value.referenced_variables(), im::HashSet::union) }) .fold(im::hashset![], im::HashSet::union), Constant::StringConcatenation { left, right, .. } => left .referenced_variables() .union(right.referenced_variables()), } } pub(crate) fn syntactically_eq(&self, other: &Self) -> bool { match (self, other) { (Constant::Int { int_value: n, .. }, Constant::Int { int_value: m, .. }) => n == m, (Constant::Int { .. }, _) => false, (Constant::Float { float_value: n, .. }, Constant::Float { float_value: m, .. }) => { n == m } (Constant::Float { .. }, _) => false, ( Constant::String { value, .. }, Constant::String { value: other_value, .. }, ) => value == other_value, (Constant::String { .. }, _) => false, ( Constant::Tuple { elements, .. }, Constant::Tuple { elements: other_elements, .. }, ) => pairwise_all(elements, other_elements, |(one, other)| { one.syntactically_eq(other) }), (Constant::Tuple { .. }, _) => false, ( Constant::List { elements, .. }, Constant::List { elements: other_elements, .. }, ) => pairwise_all(elements, other_elements, |(one, other)| { one.syntactically_eq(other) }), (Constant::List { .. }, _) => false, ( Constant::Record { module, name, arguments, .. }, Constant::Record { module: other_module, name: other_name, arguments: other_arguments, .. }, ) => { let modules_are_equal = match (module, other_module) { (None, None) => true, (None, Some(_)) | (Some(_), None) => false, (Some((one, _)), Some((other, _))) => one == other, }; modules_are_equal && name == other_name && pairwise_all(arguments, other_arguments, |(one, other)| { one.label == other.label && one.value.syntactically_eq(&other.value) }) } (Constant::Record { .. }, _) => false, ( Constant::RecordUpdate { module, name, record, arguments, .. }, Constant::RecordUpdate { module: other_module, name: other_name, record: other_record, arguments: other_arguments, .. }, ) => { let modules_are_equal = match (module, other_module) { (None, None) => true, (None, Some(_)) | (Some(_), None) => false, (Some((one, _)), Some((other, _))) => one == other, }; modules_are_equal && name == other_name && record.base.syntactically_eq(&other_record.base) && pairwise_all(arguments, other_arguments, |(one, other)| { one.label == other.label && one.value.syntactically_eq(&other.value) }) } (Constant::RecordUpdate { .. }, _) => false, ( Constant::BitArray { segments, .. }, Constant::BitArray { segments: other_segments, .. }, ) => pairwise_all(segments, other_segments, |(one, other)| { one.syntactically_eq(other) }), (Constant::BitArray { .. }, _) => false, ( Constant::Var { module, name, .. }, Constant::Var { module: other_module, name: other_name, .. }, ) => { let modules_are_equal = match (module, other_module) { (None, None) => true, (None, Some(_)) | (Some(_), None) => false, (Some((one, _)), Some((other, _))) => one == other, }; modules_are_equal && name == other_name } (Constant::Var { .. }, _) => false, ( Constant::StringConcatenation { left, right, .. }, Constant::StringConcatenation { left: other_left, right: other_right, .. }, ) => left.syntactically_eq(other_left) && right.syntactically_eq(other_right), (Constant::StringConcatenation { .. }, _) => false, (Constant::Invalid { .. }, _) => false, } } pub fn list_elements(&self) -> Option> { match self { Constant::List { elements, tail, .. } => Some( elements .iter() .chain( tail.as_deref() .and_then(|tail| tail.list_elements()) .unwrap_or_default(), ) .collect(), ), Constant::Var { constructor: Some(constructor), .. } => match &constructor.variant { ValueConstructorVariant::ModuleConstant { literal, .. } => literal.list_elements(), ValueConstructorVariant::LocalVariable { .. } | ValueConstructorVariant::ModuleFn { .. } | ValueConstructorVariant::Record { .. } => None, }, Constant::Int { .. } | Constant::Float { .. } | Constant::String { .. } | Constant::Tuple { .. } | Constant::Record { .. } | Constant::RecordUpdate { .. } | Constant::BitArray { .. } | Constant::StringConcatenation { .. } | Constant::Var { .. } | Constant::Invalid { .. } => None, } } } impl HasType for TypedConstant { fn type_(&self) -> Arc { self.type_() } } impl Constant { pub fn location(&self) -> SrcSpan { match self { Constant::Int { location, .. } | Constant::List { location, .. } | Constant::Float { location, .. } | Constant::Tuple { location, .. } | Constant::String { location, .. } | Constant::Record { location, .. } | Constant::RecordUpdate { location, .. } | Constant::BitArray { location, .. } | Constant::Var { location, .. } | Constant::Invalid { location, .. } | Constant::StringConcatenation { location, .. } => *location, } } #[must_use] pub fn can_have_multiple_per_line(&self) -> bool { match self { Constant::Int { .. } | Constant::Float { .. } | Constant::String { .. } | Constant::Var { .. } => true, Constant::Tuple { .. } | Constant::List { .. } | Constant::Record { .. } | Constant::RecordUpdate { .. } | Constant::BitArray { .. } | Constant::StringConcatenation { .. } | Constant::Invalid { .. } => false, } } } impl HasLocation for Constant { fn location(&self) -> SrcSpan { self.location() } } impl bit_array::GetLiteralValue for Constant { fn as_int_literal(&self) -> Option { if let Constant::Int { int_value, .. } = self { Some(int_value.clone()) } else { None } } } ================================================ FILE: compiler-core/src/ast/tests.rs ================================================ use std::sync::Arc; use camino::Utf8PathBuf; use ecow::EcoString; use crate::analyse::TargetSupport; use crate::build::{ExpressionPosition, Origin, Target}; use crate::config::PackageConfig; use crate::line_numbers::LineNumbers; use crate::type_::error::{VariableDeclaration, VariableOrigin, VariableSyntax}; use crate::type_::expression::{FunctionDefinition, Purity}; use crate::type_::{Deprecation, PRELUDE_MODULE_NAME, Problems}; use crate::warning::WarningEmitter; use crate::{ ast::{SrcSpan, TypedExpr}, build::Located, type_::{ self, AccessorsMap, EnvironmentArguments, ExprTyper, FieldMap, ModuleValueConstructor, RecordAccessor, Type, ValueConstructor, ValueConstructorVariant, }, uid::UniqueIdGenerator, warning::TypeWarningEmitter, }; use super::{Publicity, Statement, TypedModule, TypedStatement}; fn compile_module(src: &str) -> TypedModule { use crate::type_::build_prelude; let parsed = crate::parse::parse_module(Utf8PathBuf::from("test/path"), src, &WarningEmitter::null()) .expect("syntax error"); let ast = parsed.module; let ids = UniqueIdGenerator::new(); let mut config = PackageConfig::default(); config.name = "thepackage".into(); let mut modules = im::HashMap::new(); // DUPE: preludeinsertion // TODO: Currently we do this here and also in the tests. It would be better // to have one place where we create all this required state for use in each // place. let _ = modules.insert(PRELUDE_MODULE_NAME.into(), build_prelude(&ids)); let line_numbers = LineNumbers::new(src); let mut config = PackageConfig::default(); config.name = "thepackage".into(); crate::analyse::ModuleAnalyzerConstructor::<()> { target: Target::Erlang, ids: &ids, origin: Origin::Src, importable_modules: &modules, warnings: &TypeWarningEmitter::null(), direct_dependencies: &std::collections::HashMap::new(), dev_dependencies: &std::collections::HashSet::new(), target_support: TargetSupport::Enforced, package_config: &config, } .infer_module(ast, line_numbers, "".into()) .expect("should successfully infer") } fn get_bare_expression(statement: &TypedStatement) -> &TypedExpr { match statement { Statement::Expression(expression) => expression, Statement::Use(_) | Statement::Assignment(_) | Statement::Assert(_) => { panic!("Expected expression, got {statement:?}") } } } fn compile_expression(src: &str) -> TypedStatement { let ast = crate::parse::parse_statement_sequence(src).expect("syntax error"); let mut modules = im::HashMap::new(); let ids = UniqueIdGenerator::new(); // DUPE: preludeinsertion // TODO: Currently we do this here and also in the tests. It would be better // to have one place where we create all this required state for use in each // place. let _ = modules.insert(PRELUDE_MODULE_NAME.into(), type_::build_prelude(&ids)); let dev_dependencies = std::collections::HashSet::new(); let mut environment = EnvironmentArguments { ids, current_package: "thepackage".into(), gleam_version: None, current_module: "mymod".into(), target: Target::Erlang, importable_modules: &modules, target_support: TargetSupport::Enforced, current_origin: Origin::Src, dev_dependencies: &dev_dependencies, } .build(); // Insert a cat record to use in the tests let cat_type = Arc::new(Type::Named { publicity: Publicity::Public, package: "mypackage".into(), module: "mymod".into(), name: "Cat".into(), arguments: vec![], inferred_variant: None, }); let variant = ValueConstructorVariant::Record { documentation: Some("wibble".into()), variants_count: 1, name: "Cat".into(), arity: 2, location: SrcSpan { start: 12, end: 15 }, field_map: Some(FieldMap { arity: 2, fields: [("name".into(), 0), ("age".into(), 1)].into(), }), module: "mymod".into(), variant_index: 0, }; environment.insert_variable( "Cat".into(), variant, type_::fn_(vec![type_::string(), type_::int()], cat_type.clone()), Publicity::Public, Deprecation::NotDeprecated, ); let accessors = [ ( "name".into(), RecordAccessor { index: 0, label: "name".into(), type_: type_::string(), documentation: None, }, ), ( "age".into(), RecordAccessor { index: 1, label: "age".into(), type_: type_::int(), documentation: None, }, ), ]; environment.insert_accessors( "Cat".into(), AccessorsMap { publicity: Publicity::Public, type_: cat_type, shared_accessors: accessors.clone().into(), variant_specific_accessors: vec![accessors.into()], variant_positional_accessors: vec![vec![]], }, ); let mut problems = Problems::new(); ExprTyper::new( &mut environment, FunctionDefinition { has_body: true, has_erlang_external: false, has_javascript_external: false, }, &mut problems, ) .infer_statements(ast) .first() .clone() } #[test] fn find_node_todo() { let statement = compile_expression(r#" todo "#); let expr = get_bare_expression(&statement); assert_eq!(expr.find_node(0), None); assert_eq!( expr.find_node(1), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(4), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(5), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!(expr.find_node(6), None); } #[test] fn find_node_todo_with_string() { let statement = compile_expression(r#" todo as "ok" "#); let expr = get_bare_expression(&statement); let message = TypedExpr::String { location: SrcSpan { start: 9, end: 13 }, type_: type_::string(), value: "ok".into(), }; assert_eq!(expr.find_node(0), None); assert_eq!( expr.find_node(1), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(12), Some(Located::Expression { expression: &message, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(13), Some(Located::Expression { expression: &message, position: ExpressionPosition::Expression }) ); assert_eq!(expr.find_node(14), None); } #[test] fn find_node_string() { let statement = compile_expression(r#" "ok" "#); let expr = get_bare_expression(&statement); assert_eq!(expr.find_node(0), None); assert_eq!( expr.find_node(1), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(4), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(5), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!(expr.find_node(6), None); } #[test] fn find_node_float() { let statement = compile_expression(r#" 1.02 "#); let expr = get_bare_expression(&statement); assert_eq!(expr.find_node(0), None); assert_eq!( expr.find_node(1), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(4), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(5), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!(expr.find_node(6), None); } #[test] fn find_node_int() { let statement = compile_expression(r#" 1302 "#); let expr = get_bare_expression(&statement); assert_eq!(expr.find_node(0), None); assert_eq!( expr.find_node(1), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(4), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(5), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!(expr.find_node(6), None); } #[test] fn find_node_var() { let statement = compile_expression( r#"{let wibble = 1 wibble}"#, ); let expr = get_bare_expression(&statement); let int1 = TypedExpr::Int { location: SrcSpan { start: 14, end: 15 }, value: "1".into(), int_value: 1.into(), type_: type_::int(), }; let var = TypedExpr::Var { location: SrcSpan { start: 16, end: 22 }, constructor: ValueConstructor { deprecation: Deprecation::NotDeprecated, publicity: Publicity::Private, variant: ValueConstructorVariant::LocalVariable { location: SrcSpan { start: 5, end: 11 }, origin: VariableOrigin { syntax: VariableSyntax::Variable("wibble".into()), declaration: VariableDeclaration::LetPattern, }, }, type_: type_::int(), }, name: "wibble".into(), }; assert_eq!( expr.find_node(15), Some(Located::Expression { expression: &int1, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(16), Some(Located::Expression { expression: &var, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(21), Some(Located::Expression { expression: &var, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(22), Some(Located::Expression { expression: &var, position: ExpressionPosition::Expression }) ); } #[test] fn find_node_sequence() { let block = compile_expression(r#"{ 1 2 3 }"#); assert!(block.find_node(0).is_none()); assert!(block.find_node(1).is_none()); assert!(block.find_node(2).is_some()); assert!(block.find_node(3).is_some()); assert!(block.find_node(4).is_some()); assert!(block.find_node(5).is_some()); assert!(block.find_node(6).is_some()); assert!(block.find_node(7).is_some()); } #[test] fn find_node_list() { let statement = compile_expression(r#"[1, 2, 3]"#); let list = get_bare_expression(&statement); let int1 = TypedExpr::Int { location: SrcSpan { start: 1, end: 2 }, type_: type_::int(), value: "1".into(), int_value: 1.into(), }; let int2 = TypedExpr::Int { location: SrcSpan { start: 4, end: 5 }, type_: type_::int(), value: "2".into(), int_value: 2.into(), }; let int3 = TypedExpr::Int { location: SrcSpan { start: 7, end: 8 }, type_: type_::int(), value: "3".into(), int_value: 3.into(), }; assert_eq!( list.find_node(0), Some(Located::Expression { expression: list, position: ExpressionPosition::Expression }) ); assert_eq!( list.find_node(1), Some(Located::Expression { expression: &int1, position: ExpressionPosition::Expression }) ); assert_eq!( list.find_node(2), Some(Located::Expression { expression: &int1, position: ExpressionPosition::Expression }) ); assert_eq!( list.find_node(3), Some(Located::Expression { expression: list, position: ExpressionPosition::Expression }) ); assert_eq!( list.find_node(4), Some(Located::Expression { expression: &int2, position: ExpressionPosition::Expression }) ); assert_eq!( list.find_node(5), Some(Located::Expression { expression: &int2, position: ExpressionPosition::Expression }) ); assert_eq!( list.find_node(6), Some(Located::Expression { expression: list, position: ExpressionPosition::Expression }) ); assert_eq!( list.find_node(7), Some(Located::Expression { expression: &int3, position: ExpressionPosition::Expression }) ); assert_eq!( list.find_node(8), Some(Located::Expression { expression: &int3, position: ExpressionPosition::Expression }) ); assert_eq!( list.find_node(9), Some(Located::Expression { expression: list, position: ExpressionPosition::Expression }) ); } #[test] fn find_node_tuple() { let statement = compile_expression(r#"#(1, 2, 3)"#); let tuple = get_bare_expression(&statement); let int1 = TypedExpr::Int { location: SrcSpan { start: 2, end: 3 }, type_: type_::int(), value: "1".into(), int_value: 1.into(), }; let int2 = TypedExpr::Int { location: SrcSpan { start: 5, end: 6 }, type_: type_::int(), value: "2".into(), int_value: 2.into(), }; let int3 = TypedExpr::Int { location: SrcSpan { start: 8, end: 9 }, type_: type_::int(), value: "3".into(), int_value: 3.into(), }; assert_eq!( tuple.find_node(0), Some(Located::Expression { expression: tuple, position: ExpressionPosition::Expression }) ); assert_eq!( tuple.find_node(1), Some(Located::Expression { expression: tuple, position: ExpressionPosition::Expression }) ); assert_eq!( tuple.find_node(2), Some(Located::Expression { expression: &int1, position: ExpressionPosition::Expression }) ); assert_eq!( tuple.find_node(3), Some(Located::Expression { expression: &int1, position: ExpressionPosition::Expression }) ); assert_eq!( tuple.find_node(4), Some(Located::Expression { expression: tuple, position: ExpressionPosition::Expression }) ); assert_eq!( tuple.find_node(5), Some(Located::Expression { expression: &int2, position: ExpressionPosition::Expression }) ); assert_eq!( tuple.find_node(6), Some(Located::Expression { expression: &int2, position: ExpressionPosition::Expression }) ); assert_eq!( tuple.find_node(7), Some(Located::Expression { expression: tuple, position: ExpressionPosition::Expression }) ); assert_eq!( tuple.find_node(8), Some(Located::Expression { expression: &int3, position: ExpressionPosition::Expression }) ); assert_eq!( tuple.find_node(9), Some(Located::Expression { expression: &int3, position: ExpressionPosition::Expression }) ); assert_eq!( tuple.find_node(10), Some(Located::Expression { expression: tuple, position: ExpressionPosition::Expression }) ); } #[test] fn find_node_binop() { let statement = compile_expression(r#"1 + 2"#); let expr = get_bare_expression(&statement); assert!(expr.find_node(0).is_some()); assert!(expr.find_node(1).is_some()); assert!(expr.find_node(2).is_none()); assert!(expr.find_node(3).is_none()); assert!(expr.find_node(4).is_some()); assert!(expr.find_node(5).is_some()); } #[test] fn find_node_tuple_index() { let statement = compile_expression(r#"#(1).0"#); let expr = get_bare_expression(&statement); let int = TypedExpr::Int { location: SrcSpan { start: 2, end: 3 }, value: "1".into(), int_value: 1.into(), type_: type_::int(), }; assert_eq!( expr.find_node(2), Some(Located::Expression { expression: &int, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(5), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(6), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); } #[test] fn find_node_module_select() { let expr = TypedExpr::ModuleSelect { location: SrcSpan { start: 1, end: 4 }, field_start: 2, type_: type_::int(), label: "label".into(), module_name: "name".into(), module_alias: "alias".into(), constructor: ModuleValueConstructor::Fn { module: "module".into(), name: "function".into(), external_erlang: None, external_javascript: None, location: SrcSpan { start: 1, end: 55 }, documentation: None, field_map: None, purity: Purity::Pure, }, }; assert_eq!(expr.find_node(0), None); assert_eq!( expr.find_node(1), Some(Located::ModuleName { location: SrcSpan::new(1, 6), module_name: "name".into(), module_alias: "alias".into(), layer: super::Layer::Value }) ); assert_eq!( expr.find_node(2), Some(Located::Expression { expression: &expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(3), Some(Located::Expression { expression: &expr, position: ExpressionPosition::Expression }) ); } #[test] fn find_node_fn() { let statement = compile_expression("fn() { 1 }"); let expr = get_bare_expression(&statement); let int = TypedExpr::Int { location: SrcSpan { start: 7, end: 8 }, value: "1".into(), int_value: 1.into(), type_: type_::int(), }; assert_eq!( expr.find_node(0), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(6), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(7), Some(Located::Expression { expression: &int, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(8), Some(Located::Expression { expression: &int, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(9), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(10), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); } #[test] fn find_node_call() { let statement = compile_expression("fn(_, _) { 1 }(1, 2)"); let expr = get_bare_expression(&statement); let return_ = TypedExpr::Int { location: SrcSpan { start: 11, end: 12 }, value: "1".into(), int_value: 1.into(), type_: type_::int(), }; let arg1 = TypedExpr::Int { location: SrcSpan { start: 15, end: 16 }, value: "1".into(), int_value: 1.into(), type_: type_::int(), }; let arg2 = TypedExpr::Int { location: SrcSpan { start: 18, end: 19 }, value: "2".into(), int_value: 2.into(), type_: type_::int(), }; let TypedExpr::Call { fun: called_function, arguments: function_arguments, .. } = expr else { panic!("Expression was not a function call"); }; assert_eq!( expr.find_node(11), Some(Located::Expression { expression: &return_, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(15), Some(Located::Expression { expression: &arg1, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(16), Some(Located::Expression { expression: &arg1, position: ExpressionPosition::ArgumentOrLabel { called_function, function_arguments } }) ); assert_eq!( expr.find_node(17), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(18), Some(Located::Expression { expression: &arg2, position: ExpressionPosition::Expression }) ); assert_eq!( expr.find_node(19), Some(Located::Expression { expression: &arg2, position: ExpressionPosition::ArgumentOrLabel { called_function, function_arguments } }) ); assert_eq!( expr.find_node(20), Some(Located::Expression { expression: expr, position: ExpressionPosition::Expression }) ); } #[test] fn find_node_record_access() { let statement = compile_expression(r#"Cat("Nubi", 3).name"#); let access = get_bare_expression(&statement); let string = TypedExpr::String { location: SrcSpan { start: 4, end: 10 }, value: "Nubi".into(), type_: type_::string(), }; let int = TypedExpr::Int { location: SrcSpan { start: 12, end: 13 }, value: "3".into(), int_value: 3.into(), type_: type_::int(), }; assert_eq!( access.find_node(4), Some(Located::Expression { expression: &string, position: ExpressionPosition::Expression }) ); assert_eq!( access.find_node(9), Some(Located::Expression { expression: &string, position: ExpressionPosition::Expression }) ); assert_eq!( access.find_node(12), Some(Located::Expression { expression: &int, position: ExpressionPosition::Expression }) ); assert_eq!( access.find_node(15), Some(Located::Expression { expression: access, position: ExpressionPosition::Expression }) ); assert_eq!( access.find_node(18), Some(Located::Expression { expression: access, position: ExpressionPosition::Expression }) ); assert_eq!( access.find_node(19), Some(Located::Expression { expression: access, position: ExpressionPosition::Expression }) ); } #[test] fn find_node_record_update() { let statement = compile_expression(r#"Cat(..Cat("Nubi", 3), age: 4)"#); let update = get_bare_expression(&statement); let cat = TypedExpr::Var { location: SrcSpan { start: 0, end: 3 }, constructor: ValueConstructor { publicity: Publicity::Public, deprecation: Deprecation::NotDeprecated, variant: ValueConstructorVariant::Record { name: "Cat".into(), arity: 2, field_map: Some(FieldMap { arity: 2, fields: [(EcoString::from("age"), 1), (EcoString::from("name"), 0)].into(), }), location: SrcSpan { start: 12, end: 15 }, module: "mymod".into(), variants_count: 1, variant_index: 0, documentation: Some("wibble".into()), }, type_: type_::fn_( vec![type_::string(), type_::int()], type_::named("mypackage", "mymod", "Cat", Publicity::Public, vec![]), ), }, name: "Cat".into(), }; let int = TypedExpr::Int { location: SrcSpan { start: 27, end: 28 }, value: "4".into(), int_value: 4.into(), type_: type_::int(), }; assert_eq!( update.find_node(0), Some(Located::Expression { expression: &cat, position: ExpressionPosition::Expression }) ); assert_eq!( update.find_node(3), Some(Located::Expression { expression: &cat, position: ExpressionPosition::Expression }) ); assert_eq!( update.find_node(27), Some(Located::Expression { expression: &int, position: ExpressionPosition::Expression }) ); assert_eq!( update.find_node(28), Some(Located::Expression { expression: &int, position: ExpressionPosition::Expression }) ); assert_eq!( update.find_node(29), Some(Located::Expression { expression: update, position: ExpressionPosition::Expression }) ); } #[test] fn find_node_case() { let statement = compile_expression( r#" case 1, 2 { _, _ -> 3 } "#, ); let case = get_bare_expression(&statement); let int1 = TypedExpr::Int { location: SrcSpan { start: 6, end: 7 }, value: "1".into(), int_value: 1.into(), type_: type_::int(), }; let int2 = TypedExpr::Int { location: SrcSpan { start: 9, end: 10 }, value: "2".into(), int_value: 2.into(), type_: type_::int(), }; let int3 = TypedExpr::Int { location: SrcSpan { start: 23, end: 24 }, value: "3".into(), int_value: 3.into(), type_: type_::int(), }; assert_eq!( case.find_node(1), Some(Located::Expression { expression: case, position: ExpressionPosition::Expression }) ); assert_eq!( case.find_node(6), Some(Located::Expression { expression: &int1, position: ExpressionPosition::Expression }) ); assert_eq!( case.find_node(9), Some(Located::Expression { expression: &int2, position: ExpressionPosition::Expression }) ); assert_eq!( case.find_node(23), Some(Located::Expression { expression: &int3, position: ExpressionPosition::Expression }) ); assert_eq!( case.find_node(25), Some(Located::Expression { expression: case, position: ExpressionPosition::Expression }) ); assert_eq!( case.find_node(26), Some(Located::Expression { expression: case, position: ExpressionPosition::Expression }) ); assert_eq!(case.find_node(27), None); } #[test] fn find_node_bool() { let statement = compile_expression(r#"!True"#); let negate = get_bare_expression(&statement); let bool = TypedExpr::Var { location: SrcSpan { start: 1, end: 5 }, constructor: ValueConstructor { deprecation: Deprecation::NotDeprecated, publicity: Publicity::Public, variant: ValueConstructorVariant::Record { documentation: None, variants_count: 2, name: "True".into(), arity: 0, field_map: None, location: SrcSpan { start: 0, end: 0 }, module: PRELUDE_MODULE_NAME.into(), variant_index: 0, }, type_: type_::bool_with_variant(Some(true)), }, name: "True".into(), }; assert_eq!( negate.find_node(0), Some(Located::Expression { expression: negate, position: ExpressionPosition::Expression }) ); assert_eq!( negate.find_node(1), Some(Located::Expression { expression: &bool, position: ExpressionPosition::Expression }) ); assert_eq!( negate.find_node(2), Some(Located::Expression { expression: &bool, position: ExpressionPosition::Expression }) ); assert_eq!( negate.find_node(3), Some(Located::Expression { expression: &bool, position: ExpressionPosition::Expression }) ); assert_eq!( negate.find_node(4), Some(Located::Expression { expression: &bool, position: ExpressionPosition::Expression }) ); assert_eq!( negate.find_node(5), Some(Located::Expression { expression: &bool, position: ExpressionPosition::Expression }) ); } #[test] fn find_node_statement_fn() { let module = compile_module( r#" pub fn main() { Nil } "#, ); assert!(module.find_node(0).is_none()); assert!(module.find_node(1).is_none()); // The fn assert!(module.find_node(2).is_some()); assert!(module.find_node(24).is_some()); assert!(module.find_node(25).is_some()); assert!(module.find_node(26).is_none()); } #[test] fn find_node_statement_import() { let module = compile_module( r#" import gleam "#, ); assert!(module.find_node(0).is_none()); // The import assert!(module.find_node(1).is_some()); assert!(module.find_node(12).is_some()); assert!(module.find_node(13).is_some()); assert!(module.find_node(14).is_none()); } #[test] fn find_node_use() { let use_ = compile_expression( r#" use x <- fn(f) { f(1) } 124 "#, ); assert!(use_.find_node(0).is_none()); assert!(use_.find_node(1).is_some()); // The use assert!(use_.find_node(23).is_some()); assert!(use_.find_node(26).is_some()); // The int } ================================================ FILE: compiler-core/src/ast/typed.rs ================================================ use type_::{FieldMap, TypedCallArg}; use super::*; use crate::{ build::ExpressionPosition, exhaustiveness::CompiledCase, parse::LiteralFloatValue, type_::{HasType, Type, ValueConstructorVariant, bool}, }; #[derive(Debug, Clone, PartialEq, Eq)] pub enum TypedExpr { Int { location: SrcSpan, type_: Arc, value: EcoString, int_value: BigInt, }, Float { location: SrcSpan, type_: Arc, value: EcoString, float_value: LiteralFloatValue, }, String { location: SrcSpan, type_: Arc, value: EcoString, }, Block { location: SrcSpan, statements: Vec1, }, /// A chain of pipe expressions. /// By this point the type checker has expanded it into a series of /// assignments and function calls, but we still have a Pipeline AST node as /// even though it is identical to `Block` we want to use different /// locations when showing it in error messages, etc. Pipeline { location: SrcSpan, first_value: TypedPipelineAssignment, assignments: Vec<(TypedPipelineAssignment, PipelineAssignmentKind)>, finally: Box, finally_kind: PipelineAssignmentKind, }, Var { location: SrcSpan, constructor: ValueConstructor, name: EcoString, }, Fn { location: SrcSpan, type_: Arc, kind: FunctionLiteralKind, arguments: Vec, body: Vec1, return_annotation: Option, purity: Purity, }, List { location: SrcSpan, type_: Arc, elements: Vec, tail: Option>, }, Call { location: SrcSpan, type_: Arc, fun: Box, arguments: Vec>, }, BinOp { location: SrcSpan, type_: Arc, name: BinOp, name_location: SrcSpan, left: Box, right: Box, }, Case { location: SrcSpan, type_: Arc, subjects: Vec, clauses: Vec, EcoString>>, compiled_case: CompiledCase, }, RecordAccess { location: SrcSpan, field_start: u32, type_: Arc, label: EcoString, index: u64, record: Box, documentation: Option, }, /// Generated internally for accessing unlabelled fields of a custom type, /// such as for record updates. PositionalAccess { location: SrcSpan, type_: Arc, index: u64, record: Box, }, ModuleSelect { location: SrcSpan, field_start: u32, type_: Arc, label: EcoString, module_name: EcoString, module_alias: EcoString, constructor: ModuleValueConstructor, }, Tuple { location: SrcSpan, type_: Arc, elements: Vec, }, TupleIndex { location: SrcSpan, type_: Arc, index: u64, tuple: Box, }, Todo { location: SrcSpan, message: Option>, kind: TodoKind, type_: Arc, }, Panic { location: SrcSpan, message: Option>, type_: Arc, }, Echo { location: SrcSpan, type_: Arc, expression: Option>, message: Option>, }, BitArray { location: SrcSpan, type_: Arc, segments: Vec, }, /// A record update gets desugared to a block expression of the form /// /// { /// let _record = record /// Constructor(explicit_arg: explicit_value(), implicit_arg: _record.implicit_arg) /// } /// /// We still keep a separate `RecordUpdate` AST node for the same reasons as /// we do for pipelines. RecordUpdate { location: SrcSpan, type_: Arc, /// If the record is an expression that is not a variable we will need to assign to a /// variable so it can be referred multiple times. record_assignment: Option>, constructor: Box, arguments: Vec>, }, NegateBool { location: SrcSpan, value: Box, }, NegateInt { location: SrcSpan, value: Box, }, /// A placeholder expression used to allow module analysis to continue /// even when there are type errors. Should never end up in generated code. Invalid { location: SrcSpan, type_: Arc, /// Extra information about the invalid expression, useful for providing /// addition help or information, such as code actions to fix invalid /// states. extra_information: Option, }, } impl TypedExpr { pub fn is_println(&self) -> bool { let fun = if let TypedExpr::Call { fun, arguments, .. } = self && arguments.len() == 1 { fun.as_ref() } else { return false; }; if let TypedExpr::ModuleSelect { label, module_name, .. } = fun { label == "println" && module_name == "gleam/io" } else { false } } pub fn find_node(&self, byte_index: u32) -> Option> { match self { Self::Var { .. } | Self::Int { .. } | Self::Float { .. } | Self::String { .. } | Self::Invalid { .. } | Self::PositionalAccess { .. } => self.self_if_contains_location(byte_index), Self::ModuleSelect { location, field_start, module_name, module_alias, .. } => { // We want to return the `ModuleSelect` only when we're hovering // over the selected field, not on the module part. let field_span = SrcSpan { start: *field_start, end: location.end, }; let module_span = SrcSpan::new(location.start, location.start + (module_alias.len() as u32)); if field_span.contains(byte_index) { Some(self.into()) } else if SrcSpan::new(location.start, field_start - 1).contains(byte_index) { Some(Located::ModuleName { location: module_span, module_name: module_name.clone(), module_alias: module_alias.clone(), layer: Layer::Value, }) } else { None } } Self::Echo { expression, message, .. } => expression .as_ref() .and_then(|expression| expression.find_node(byte_index)) .or_else(|| { message .as_ref() .and_then(|message| message.find_node(byte_index)) }) .or_else(|| self.self_if_contains_location(byte_index)), Self::Panic { message, .. } => message .as_ref() .and_then(|message| message.find_node(byte_index)) .or_else(|| self.self_if_contains_location(byte_index)), Self::Todo { kind, message, .. } => match kind { TodoKind::Keyword => message .as_ref() .and_then(|message| message.find_node(byte_index)) .or_else(|| self.self_if_contains_location(byte_index)), // We don't want to match on todos that were implicitly inserted // by the compiler as it would result in confusing suggestions // from the LSP. TodoKind::EmptyFunction { .. } | TodoKind::EmptyBlock | TodoKind::IncompleteUse => { None } }, Self::Pipeline { first_value, assignments, finally, .. } => first_value .find_node(byte_index) .or_else(|| { assignments .iter() .find_map(|(e, _)| e.find_node(byte_index)) }) .or_else(|| finally.find_node(byte_index)), // Exit the search and return None if during iteration a statement // is found with a start index beyond the index under search. Self::Block { statements, .. } => { for statement in statements { if statement.location().start > byte_index { break; } if let Some(located) = statement.find_node(byte_index) { return Some(located); } } None } // Exit the search and return the encompassing type (e.g., list or tuple) // if during iteration, an element is encountered with a start index // beyond the index under search. Self::Tuple { elements: expressions, .. } => { for expression in expressions { if expression.location().start > byte_index { break; } if let Some(located) = expression.find_node(byte_index) { return Some(located); } } self.self_if_contains_location(byte_index) } Self::List { elements: expressions, tail, .. } => { for expression in expressions { if expression.location().start > byte_index { break; } if let Some(located) = expression.find_node(byte_index) { return Some(located); } } if let Some(tail) = tail && let Some(node) = tail.find_node(byte_index) { return Some(node); } self.self_if_contains_location(byte_index) } Self::NegateBool { value, .. } | Self::NegateInt { value, .. } => value .find_node(byte_index) .or_else(|| self.self_if_contains_location(byte_index)), Self::Fn { body, arguments, .. } => arguments .iter() .find_map(|arg| arg.find_node(byte_index)) .or_else(|| body.iter().find_map(|s| s.find_node(byte_index))) .or_else(|| self.self_if_contains_location(byte_index)), Self::Call { fun, arguments, .. } => arguments .iter() .find_map(|argument| argument.find_node(byte_index, fun, arguments)) .or_else(|| fun.find_node(byte_index)) .or_else(|| self.self_if_contains_location(byte_index)), Self::BinOp { left, right, .. } => left .find_node(byte_index) .or_else(|| right.find_node(byte_index)), Self::Case { subjects, clauses, .. } => subjects .iter() .find_map(|subject| subject.find_node(byte_index)) .or_else(|| clauses.iter().find_map(|c| c.find_node(byte_index))) .or_else(|| self.self_if_contains_location(byte_index)), Self::RecordAccess { record: expression, .. } | Self::TupleIndex { tuple: expression, .. } => expression .find_node(byte_index) .or_else(|| self.self_if_contains_location(byte_index)), Self::BitArray { segments, .. } => segments .iter() .find_map(|arg| arg.find_node(byte_index)) .or_else(|| self.self_if_contains_location(byte_index)), Self::RecordUpdate { record_assignment, constructor, arguments, .. } => arguments .iter() .filter(|argument| argument.implicit.is_none()) .find_map(|argument| argument.find_node(byte_index, constructor, arguments)) .or_else(|| constructor.find_node(byte_index)) .or_else(|| { record_assignment .as_ref() .and_then(|assignment| assignment.find_node(byte_index)) }) .or_else(|| self.self_if_contains_location(byte_index)), } } pub fn find_statement(&self, byte_index: u32) -> Option<&TypedStatement> { match self { Self::Var { .. } | Self::Int { .. } | Self::Float { .. } | Self::String { .. } | Self::ModuleSelect { .. } | Self::Invalid { .. } | Self::PositionalAccess { .. } => None, Self::Pipeline { first_value, assignments, finally, .. } => first_value .find_statement(byte_index) .or_else(|| { assignments .iter() .find_map(|(e, _)| e.find_statement(byte_index)) }) .or_else(|| finally.find_statement(byte_index)), // Exit the search and return None if during iteration a statement // is found with a start index beyond the index under search. Self::Block { statements, .. } => { for statement in statements { if statement.location().start > byte_index { break; } if let Some(located) = statement.find_statement(byte_index) { return Some(located); } } None } // Exit the search and return the encompassing type (e.g., list or tuple) // if during iteration, an element is encountered with a start index // beyond the index under search. Self::Tuple { elements: expressions, .. } => { for expression in expressions { if expression.location().start > byte_index { break; } if let Some(located) = expression.find_statement(byte_index) { return Some(located); } } None } Self::List { elements: expressions, tail, .. } => { for expression in expressions { if expression.location().start > byte_index { break; } if let Some(located) = expression.find_statement(byte_index) { return Some(located); } } if let Some(tail) = tail && let Some(node) = tail.find_statement(byte_index) { return Some(node); } None } Self::NegateBool { value, .. } | Self::NegateInt { value, .. } => { value.find_statement(byte_index) } Self::Fn { body, .. } => body.iter().find_map(|s| s.find_statement(byte_index)), Self::Call { fun, arguments, .. } => arguments .iter() .find_map(|argument| argument.find_statement(byte_index)) .or_else(|| fun.find_statement(byte_index)), Self::BinOp { left, right, .. } => left .find_statement(byte_index) .or_else(|| right.find_statement(byte_index)), Self::Case { subjects, clauses, .. } => subjects .iter() .find_map(|subject| subject.find_statement(byte_index)) .or_else(|| { clauses .iter() .find_map(|c| c.then.find_statement(byte_index)) }), Self::RecordAccess { record: expression, .. } | Self::TupleIndex { tuple: expression, .. } => expression.find_statement(byte_index), Self::Echo { expression, message, .. } => expression .as_ref() .and_then(|expression| expression.find_statement(byte_index)) .or_else(|| { message .as_ref() .and_then(|message| message.find_statement(byte_index)) }), Self::Todo { message, kind, .. } => match kind { TodoKind::EmptyFunction { .. } | TodoKind::IncompleteUse | TodoKind::EmptyBlock => { None } TodoKind::Keyword => message .as_ref() .and_then(|message| message.find_statement(byte_index)), }, Self::Panic { message, .. } => message .as_ref() .and_then(|message| message.find_statement(byte_index)), Self::BitArray { segments, .. } => segments .iter() .find_map(|arg| arg.value.find_statement(byte_index)), Self::RecordUpdate { record_assignment, arguments, .. } => arguments .iter() .filter(|arg| arg.implicit.is_none()) .find_map(|arg| arg.find_statement(byte_index)) .or_else(|| { record_assignment .as_ref() .and_then(|r| r.value.find_statement(byte_index)) }), } } fn self_if_contains_location(&self, byte_index: u32) -> Option> { if self.location().contains(byte_index) { Some(self.into()) } else { None } } pub fn is_non_zero_compile_time_number(&self) -> bool { match self { Self::Int { int_value, .. } => int_value != &BigInt::ZERO, Self::Float { float_value, .. } => !float_value.value().is_zero(), Self::String { .. } | Self::Block { .. } | Self::Pipeline { .. } | Self::Var { .. } | Self::Fn { .. } | Self::List { .. } | Self::Call { .. } | Self::BinOp { .. } | Self::Case { .. } | Self::RecordAccess { .. } | Self::PositionalAccess { .. } | Self::ModuleSelect { .. } | Self::Tuple { .. } | Self::TupleIndex { .. } | Self::Todo { .. } | Self::Panic { .. } | Self::Echo { .. } | Self::BitArray { .. } | Self::RecordUpdate { .. } | Self::NegateBool { .. } | Self::NegateInt { .. } | Self::Invalid { .. } => false, } } pub fn is_zero_compile_time_number(&self) -> bool { match self { Self::Int { int_value, .. } => int_value == &BigInt::ZERO, Self::Float { float_value, .. } => float_value.value().is_zero(), Self::String { .. } | Self::Block { .. } | Self::Pipeline { .. } | Self::Var { .. } | Self::Fn { .. } | Self::List { .. } | Self::Call { .. } | Self::BinOp { .. } | Self::Case { .. } | Self::RecordAccess { .. } | Self::PositionalAccess { .. } | Self::ModuleSelect { .. } | Self::Tuple { .. } | Self::TupleIndex { .. } | Self::Todo { .. } | Self::Panic { .. } | Self::Echo { .. } | Self::BitArray { .. } | Self::RecordUpdate { .. } | Self::NegateBool { .. } | Self::NegateInt { .. } | Self::Invalid { .. } => false, } } pub fn location(&self) -> SrcSpan { match self { Self::Fn { location, .. } | Self::Int { location, .. } | Self::Var { location, .. } | Self::Todo { location, .. } | Self::Echo { location, .. } | Self::Case { location, .. } | Self::Call { location, .. } | Self::List { location, .. } | Self::Float { location, .. } | Self::BinOp { location, .. } | Self::Tuple { location, .. } | Self::Panic { location, .. } | Self::Block { location, .. } | Self::String { location, .. } | Self::NegateBool { location, .. } | Self::NegateInt { location, .. } | Self::Pipeline { location, .. } | Self::BitArray { location, .. } | Self::TupleIndex { location, .. } | Self::ModuleSelect { location, .. } | Self::RecordAccess { location, .. } | Self::PositionalAccess { location, .. } | Self::RecordUpdate { location, .. } | Self::Invalid { location, .. } => *location, } } pub fn type_defining_location(&self) -> SrcSpan { match self { Self::Fn { location, .. } | Self::Int { location, .. } | Self::Var { location, .. } | Self::Todo { location, .. } | Self::Echo { location, .. } | Self::Case { location, .. } | Self::Call { location, .. } | Self::List { location, .. } | Self::Float { location, .. } | Self::BinOp { location, .. } | Self::Tuple { location, .. } | Self::String { location, .. } | Self::Panic { location, .. } | Self::NegateBool { location, .. } | Self::NegateInt { location, .. } | Self::Pipeline { location, .. } | Self::BitArray { location, .. } | Self::TupleIndex { location, .. } | Self::ModuleSelect { location, .. } | Self::RecordAccess { location, .. } | Self::PositionalAccess { location, .. } | Self::RecordUpdate { location, .. } | Self::Invalid { location, .. } => *location, Self::Block { statements, .. } => statements.last().location(), } } pub fn definition_location(&self) -> Option { match self { TypedExpr::Fn { .. } | TypedExpr::Int { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::Case { .. } | TypedExpr::Todo { .. } | TypedExpr::Echo { .. } | TypedExpr::Panic { .. } | TypedExpr::BinOp { .. } | TypedExpr::Float { .. } | TypedExpr::Tuple { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::BitArray { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | Self::Invalid { .. } => None, // TODO: test // TODO: definition TypedExpr::RecordUpdate { .. } => None, // TODO: test TypedExpr::ModuleSelect { module_name, constructor, .. } => Some(DefinitionLocation { module: Some(module_name.clone()), span: constructor.location(), }), // TODO: test TypedExpr::Var { constructor, .. } => Some(constructor.definition_location()), } } pub fn type_(&self) -> Arc { match self { Self::NegateBool { .. } => bool(), Self::NegateInt { value, .. } => value.type_(), Self::Var { constructor, .. } => constructor.type_.clone(), Self::Fn { type_, .. } | Self::Int { type_, .. } | Self::Todo { type_, .. } | Self::Echo { type_, .. } | Self::Case { type_, .. } | Self::List { type_, .. } | Self::Call { type_, .. } | Self::Float { type_, .. } | Self::Panic { type_, .. } | Self::BinOp { type_, .. } | Self::Tuple { type_, .. } | Self::String { type_, .. } | Self::BitArray { type_, .. } | Self::TupleIndex { type_, .. } | Self::ModuleSelect { type_, .. } | Self::RecordAccess { type_, .. } | Self::PositionalAccess { type_, .. } | Self::RecordUpdate { type_, .. } | Self::Invalid { type_, .. } => type_.clone(), Self::Pipeline { finally, .. } => finally.type_(), Self::Block { statements, .. } => statements.last().type_(), } } pub fn is_literal(&self) -> bool { match self { Self::Int { .. } | Self::Float { .. } | Self::String { .. } => true, Self::List { elements, .. } | Self::Tuple { elements, .. } => { elements.iter().all(|value| value.is_literal()) } Self::BitArray { segments, .. } => { segments.iter().all(|segment| segment.value.is_literal()) } // Calls are literals if they are records and all the arguemnts are also literals. Self::Call { fun, arguments, .. } => { fun.is_record_literal() && arguments.iter().all(|argument| argument.value.is_literal()) } // Variables are literals if they are record constructors that take no arguments. Self::Var { constructor: ValueConstructor { variant: ValueConstructorVariant::Record { arity: 0, .. }, .. }, .. } => true, Self::Block { .. } | Self::Pipeline { .. } | Self::Var { .. } | Self::Fn { .. } | Self::BinOp { .. } | Self::Case { .. } | Self::RecordAccess { .. } | Self::PositionalAccess { .. } | Self::ModuleSelect { .. } | Self::TupleIndex { .. } | Self::Todo { .. } | Self::Panic { .. } | Self::Echo { .. } | Self::RecordUpdate { .. } | Self::NegateBool { .. } | Self::NegateInt { .. } | Self::Invalid { .. } => false, } } pub fn is_known_bool(&self) -> bool { match self { TypedExpr::BinOp { left, right, name, .. } if name.is_bool_operator() => left.is_known_bool() && right.is_known_bool(), TypedExpr::NegateBool { value, .. } => value.is_known_bool(), TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => self.is_literal(), } } pub fn is_literal_string(&self) -> bool { matches!(self, Self::String { .. }) } /// Returns `true` if the typed expr is [`Var`]. /// /// [`Var`]: TypedExpr::Var #[must_use] pub fn is_var(&self) -> bool { matches!(self, Self::Var { .. }) } pub fn get_documentation(&self) -> Option<&str> { match self { TypedExpr::Var { constructor, .. } => constructor.get_documentation(), TypedExpr::ModuleSelect { constructor, .. } => constructor.get_documentation(), TypedExpr::RecordAccess { documentation, .. } => documentation.as_deref(), TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Echo { .. } | TypedExpr::Panic { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::Invalid { .. } => None, } } /// Returns `true` if the typed expr is [`Case`]. /// /// [`Case`]: TypedExpr::Case #[must_use] pub fn is_case(&self) -> bool { matches!(self, Self::Case { .. }) } /// Returns `true` if the typed expr is [`Pipeline`]. /// /// [`Pipeline`]: TypedExpr::Pipeline #[must_use] pub fn is_pipeline(&self) -> bool { matches!(self, Self::Pipeline { .. }) } pub fn is_pure_value_constructor(&self) -> bool { match self { TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::List { .. } | TypedExpr::Tuple { .. } | TypedExpr::BitArray { .. } | TypedExpr::Var { .. } | TypedExpr::BinOp { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::Fn { .. } => true, TypedExpr::NegateBool { value, .. } | TypedExpr::NegateInt { value, .. } => { value.is_pure_value_constructor() } // Just selecting a value from a module never has any effects. The // selected thing might be a function but it has no side effects as // long as it's not called! TypedExpr::ModuleSelect { .. } => true, // A pipeline is a pure value constructor if its last step is a record builder, // or a call to a pure function. For example: // - `wibble() |> wobble() |> Ok` // - `"hello" |> fn(s) { s <> " world!" }` TypedExpr::Pipeline { first_value, assignments, finally, .. } => { first_value.value.is_pure_value_constructor() && assignments .iter() .all(|(assignment, _)| assignment.value.is_pure_value_constructor()) && finally.is_pure_value_constructor() } TypedExpr::Call { fun, arguments, .. } => { (fun.is_record_literal() || fun.called_function_purity().is_pure()) && arguments .iter() .all(|argument| argument.value.is_pure_value_constructor()) } // A block is pure if all the statements it's made of are pure. // For example `{ True 1 }` TypedExpr::Block { statements, .. } => { statements.iter().all(|s| s.is_pure_value_constructor()) } // A case is pure if its subject and all its branches are. // For example: // ```gleam // case 1 + 1 { // 0 -> 1 // _ -> 2 // } // ``` TypedExpr::Case { subjects, clauses, .. } => { subjects.iter().all(|s| s.is_pure_value_constructor()) && clauses.iter().all(|c| c.then.is_pure_value_constructor()) } // `panic`, `todo`, and placeholders are never considered pure value constructors, // we don't want to raise a warning for an unused value if it's one // of those. TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::Invalid { .. } => false, } } /// Returns the purity of the left hand side of a function call. For example: /// /// ```gleam /// io.println("Hello, world!") /// ``` /// /// Here, the left hand side is `io.println`, which is an impure function, /// so we would return `Purity::Impure`. /// /// This does not check whether an expression is pure on its own; for that /// see `is_pure_value_constructor`. /// pub fn called_function_purity(&self) -> Purity { match self { TypedExpr::Var { constructor, .. } => constructor.called_function_purity(), TypedExpr::ModuleSelect { constructor, .. } => constructor.called_function_purity(), TypedExpr::Fn { purity, .. } => *purity, // While we can infer the purity of some of these expressions, such // as `Case`, in this example: // ```gleam // case x { // True -> io.println // False -> function.identity // }("Hello") // ``` // // This kind of code is rare in real Gleam applications, and as this // system is just used for warnings, it is unlikely that supporting // them will provide any significant benefit to developer experience, // so we just return `Unknown` for simplicity. // TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Call { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Echo { .. } => Purity::Unknown, // The following expressions are all invalid on the left hand side // of a call expression: `10()` is not valid Gleam. Therefore, we // don't really care about any of these as they shouldn't appear in // well typed Gleam code, and so we can just return `Unknown`. TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::List { .. } | TypedExpr::BinOp { .. } | TypedExpr::Tuple { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::Invalid { .. } => Purity::Unknown, } } #[must_use] /// Returns true if the value is a literal record builder like /// `Wibble(1, 2)`, `module.Wobble("a")` /// pub fn is_record_literal(&self) -> bool { match self { TypedExpr::Call { fun, .. } => fun.is_record_literal(), TypedExpr::Var { constructor, .. } => constructor.variant.is_record(), TypedExpr::ModuleSelect { constructor: ModuleValueConstructor::Record { .. }, .. } => true, TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => false, } } /// Returns true if the expression is a record constructor function/record constructor /// with non-zero arity. /// pub fn is_record_constructor_function(&self) -> bool { match self { TypedExpr::Var { constructor: ValueConstructor { variant: ValueConstructorVariant::Record { arity, .. }, .. }, .. } => *arity > 0, TypedExpr::ModuleSelect { constructor: ModuleValueConstructor::Record { arity, .. }, .. } => *arity > 0, TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => false, } } /// If the given expression is a literal record builder, this will return /// index of the variant being built. /// pub fn variant_index(&self) -> Option { match self { TypedExpr::Call { fun, .. } => fun.variant_index(), TypedExpr::Var { constructor: ValueConstructor { variant: ValueConstructorVariant::Record { variant_index, .. }, .. }, .. } | TypedExpr::ModuleSelect { constructor: ModuleValueConstructor::Record { variant_index, .. }, .. } => Some(*variant_index), TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => None, } } #[must_use] /// If `self` is a record constructor, returns the number of arguments it /// needs to be called. Otherwise, returns `None`. /// pub fn record_constructor_arity(&self) -> Option { match self { TypedExpr::Call { fun, .. } => fun.record_constructor_arity(), TypedExpr::Var { constructor: ValueConstructor { variant: ValueConstructorVariant::Record { arity, .. }, .. }, .. } => Some(*arity), TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => None, } } pub fn var_constructor(&self) -> Option<(&ValueConstructor, &EcoString)> { if let TypedExpr::Var { constructor, name, .. } = self { Some((constructor, name)) } else { None } } #[must_use] pub(crate) fn is_panic(&self) -> bool { matches!(self, TypedExpr::Panic { .. }) } pub(crate) fn call_arguments(&self) -> Option<&Vec> { if let TypedExpr::Call { arguments, .. } = self { Some(arguments) } else { None } } pub(crate) fn fn_expression_body(&self) -> Option<&Vec1> { if let TypedExpr::Fn { body, .. } = self { Some(body) } else { None } } // If the expression is a fn or a block then returns the location of its // last element, otherwise it returns the location of the whole expression. pub fn last_location(&self) -> SrcSpan { match self { TypedExpr::Int { location, .. } | TypedExpr::Float { location, .. } | TypedExpr::String { location, .. } | TypedExpr::Var { location, .. } | TypedExpr::List { location, .. } | TypedExpr::Call { location, .. } | TypedExpr::BinOp { location, .. } | TypedExpr::Case { location, .. } | TypedExpr::RecordAccess { location, .. } | TypedExpr::PositionalAccess { location, .. } | TypedExpr::ModuleSelect { location, .. } | TypedExpr::Tuple { location, .. } | TypedExpr::TupleIndex { location, .. } | TypedExpr::Todo { location, .. } | TypedExpr::Panic { location, .. } | TypedExpr::BitArray { location, .. } | TypedExpr::RecordUpdate { location, .. } | TypedExpr::NegateBool { location, .. } | TypedExpr::NegateInt { location, .. } | TypedExpr::Invalid { location, .. } | TypedExpr::Echo { location, .. } | TypedExpr::Pipeline { location, .. } => *location, TypedExpr::Block { statements, .. } => statements.last().last_location(), TypedExpr::Fn { body, .. } => body.last().last_location(), } } pub fn field_map(&self) -> Option<&FieldMap> { match self { TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => None, TypedExpr::Var { constructor, .. } => constructor.field_map(), TypedExpr::ModuleSelect { constructor, .. } => match constructor { ModuleValueConstructor::Record { field_map, .. } | ModuleValueConstructor::Fn { field_map, .. } => field_map.as_ref(), ModuleValueConstructor::Constant { .. } => None, }, } } pub fn is_invalid(&self) -> bool { matches!(self, TypedExpr::Invalid { .. }) } /// Checks that two expressions are written in the same (ignoring /// whitespace). /// /// This is useful for the language server to know when it is possible to /// merge two blocks of code together because they are the same. /// Simply checking for equality of the AST nodes wouldn't work as those /// also contain the source location (meaning that two expression that look /// the same but are in different places would be considered different)! /// pub fn syntactically_eq(&self, other: &TypedExpr) -> bool { match (self, other) { (TypedExpr::Int { int_value: n, .. }, TypedExpr::Int { int_value: m, .. }) => n == m, (TypedExpr::Int { .. }, _) => false, (TypedExpr::Float { float_value: n, .. }, TypedExpr::Float { float_value: m, .. }) => { n == m } (TypedExpr::Float { .. }, _) => false, (TypedExpr::String { value, .. }, TypedExpr::String { value: other, .. }) => { value == other } (TypedExpr::String { .. }, _) => false, ( TypedExpr::Block { statements, .. }, TypedExpr::Block { statements: other, .. }, ) => pairwise_all(statements, other, |(one, other)| { one.syntactically_eq(other) }), (TypedExpr::Block { .. }, _) => false, ( TypedExpr::List { elements, tail, .. }, TypedExpr::List { elements: other_elements, tail: other_tail, .. }, ) => { let tails_are_equal = match (tail, other_tail) { (Some(one), Some(other)) => one.syntactically_eq(other), (None, Some(_)) | (Some(_), None) => false, (None, None) => true, }; tails_are_equal && pairwise_all(elements, other_elements, |(one, other)| { one.syntactically_eq(other) }) } (TypedExpr::List { .. }, _) => false, (TypedExpr::Var { name, .. }, TypedExpr::Var { name: other, .. }) => name == other, (TypedExpr::Var { .. }, _) => false, ( TypedExpr::Fn { arguments, body, .. }, TypedExpr::Fn { arguments: other_arguments, body: other_body, .. }, ) => { let arguments_are_equal = pairwise_all(arguments, other_arguments, |(one, other)| { one.get_variable_name() == other.get_variable_name() }); let bodies_are_equal = pairwise_all(body, other_body, |(one, other)| one.syntactically_eq(other)); arguments_are_equal && bodies_are_equal } (TypedExpr::Fn { .. }, _) => false, ( TypedExpr::Pipeline { first_value, assignments, finally, .. }, TypedExpr::Pipeline { first_value: other_first_value, assignments: other_assignments, finally: other_finally, .. }, ) => { first_value.value.syntactically_eq(&other_first_value.value) && pairwise_all(assignments, other_assignments, |(one, other)| { one.0.value.syntactically_eq(&other.0.value) }) && finally.syntactically_eq(other_finally) } (TypedExpr::Pipeline { .. }, _) => false, ( TypedExpr::Call { fun, arguments, .. }, TypedExpr::Call { fun: other_fun, arguments: other_arguments, .. }, ) => { fun.syntactically_eq(other_fun) && pairwise_all(arguments, other_arguments, |(one, other)| { one.label == other.label && one.value.syntactically_eq(&other.value) }) } (TypedExpr::Call { .. }, _) => false, ( TypedExpr::BinOp { name, left, right, .. }, TypedExpr::BinOp { name: other_name, left: other_left, right: other_right, .. }, ) => { name == other_name && left.syntactically_eq(other_left) && right.syntactically_eq(other_right) } (TypedExpr::BinOp { .. }, _) => false, ( TypedExpr::Case { subjects, clauses, .. }, TypedExpr::Case { subjects: other_subjects, clauses: other_clauses, .. }, ) => { pairwise_all(subjects, other_subjects, |(one, other)| { one.syntactically_eq(other) }) && pairwise_all(clauses, other_clauses, |(one, other)| { one.syntactically_eq(other) }) } (TypedExpr::Case { .. }, _) => false, ( TypedExpr::RecordAccess { label, record, .. }, TypedExpr::RecordAccess { label: other_label, record: other_record, .. }, ) => label == other_label && record.syntactically_eq(other_record), (TypedExpr::RecordAccess { .. }, _) => false, ( TypedExpr::ModuleSelect { label, module_alias, .. }, TypedExpr::ModuleSelect { label: other_label, module_alias: other_module_alias, .. }, ) => label == other_label && module_alias == other_module_alias, (TypedExpr::ModuleSelect { .. }, _) => false, ( TypedExpr::Tuple { elements, .. }, TypedExpr::Tuple { elements: other_elements, .. }, ) => pairwise_all(elements, other_elements, |(one, other)| { one.syntactically_eq(other) }), (TypedExpr::Tuple { .. }, _) => false, ( TypedExpr::TupleIndex { index, tuple, .. }, TypedExpr::TupleIndex { index: other_index, tuple: other_tuple, .. }, ) => index == other_index && tuple.syntactically_eq(other_tuple), (TypedExpr::TupleIndex { .. }, _) => false, ( TypedExpr::Todo { message, kind, .. }, TypedExpr::Todo { message: other_message, kind: other_kind, .. }, ) => { let messages_are_equal = match (message, other_message) { (Some(one), Some(other)) => one.syntactically_eq(other), (None, None) => true, (None, Some(_)) | (Some(_), None) => false, }; messages_are_equal && kind == other_kind } (TypedExpr::Todo { .. }, _) => false, ( TypedExpr::Panic { message, .. }, TypedExpr::Panic { message: message_other, .. }, ) => match (message, message_other) { (None, None) => true, (None, Some(_)) | (Some(_), None) => false, (Some(one), Some(other)) => one.syntactically_eq(other), }, (TypedExpr::Panic { .. }, _) => false, ( TypedExpr::Echo { expression, message, .. }, TypedExpr::Echo { expression: other_expression, message: other_message, .. }, ) => { let messages_are_equal = match (message, other_message) { (None, None) => true, (None, Some(_)) | (Some(_), None) => false, (Some(one), Some(other)) => one.syntactically_eq(other), }; let expressions_are_equal = match (expression, other_expression) { (None, None) => true, (None, Some(_)) | (Some(_), None) => false, (Some(one), Some(other)) => one.syntactically_eq(other), }; messages_are_equal && expressions_are_equal } (TypedExpr::Echo { .. }, _) => false, ( TypedExpr::BitArray { segments, .. }, TypedExpr::BitArray { segments: other_segments, .. }, ) => pairwise_all(segments, other_segments, |(one, other)| { one.syntactically_eq(other) }), (TypedExpr::BitArray { .. }, _) => false, ( TypedExpr::RecordUpdate { constructor, arguments, .. }, TypedExpr::RecordUpdate { constructor: other_constructor, arguments: other_arguments, .. }, ) => { constructor.syntactically_eq(other_constructor) && pairwise_all(arguments, other_arguments, |(one, other)| { one.label == other.label && one.value.syntactically_eq(&other.value) }) } (TypedExpr::RecordUpdate { .. }, _) => false, ( TypedExpr::NegateBool { value, .. }, TypedExpr::NegateBool { value: other_value, .. }, ) => value.syntactically_eq(other_value), (TypedExpr::NegateBool { .. }, _) => false, (TypedExpr::NegateInt { value: n, .. }, TypedExpr::NegateInt { value: m, .. }) => { n.syntactically_eq(m) } (TypedExpr::NegateInt { .. }, _) => false, (TypedExpr::PositionalAccess { .. }, _) => false, (TypedExpr::Invalid { .. }, _) => false, } } pub fn is_todo_with_no_message(&self) -> bool { matches!(self, TypedExpr::Todo { message: None, .. }) } } /// Checks that two slices have the same number of item and that the given /// predicate holds for all pairs of items. /// pub(crate) fn pairwise_all(one: &[A], other: &[A], function: impl Fn((&A, &A)) -> bool) -> bool { one.len() == other.len() && one.iter().zip(other).all(function) } impl<'a> From<&'a TypedExpr> for Located<'a> { fn from(expression: &'a TypedExpr) -> Self { Located::Expression { expression, position: ExpressionPosition::Expression, } } } impl HasLocation for TypedExpr { fn location(&self) -> SrcSpan { self.location() } } impl HasType for TypedExpr { fn type_(&self) -> Arc { self.type_() } } impl bit_array::GetLiteralValue for TypedExpr { fn as_int_literal(&self) -> Option { if let TypedExpr::Int { int_value, .. } = self { Some(int_value.clone()) } else { None } } } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum InvalidExpression { ModuleSelect { module_name: EcoString, label: EcoString, }, UnknownVariable { name: EcoString, }, } ================================================ FILE: compiler-core/src/ast/untyped.rs ================================================ use vec1::Vec1; use crate::parse::LiteralFloatValue; use super::*; #[derive(Debug, Clone, PartialEq, Eq)] pub enum UntypedExpr { Int { location: SrcSpan, value: EcoString, int_value: BigInt, }, Float { location: SrcSpan, value: EcoString, float_value: LiteralFloatValue, }, String { location: SrcSpan, value: EcoString, }, Block { location: SrcSpan, statements: Vec1, }, Var { location: SrcSpan, name: EcoString, }, // TODO: create new variant for captures specifically Fn { /// For anonymous functions, this is the location of the entire function including the end of the body. /// For named functions, this is the location of the function head. location: SrcSpan, kind: FunctionLiteralKind, /// The byte location of the end of the function head before the opening bracket end_of_head_byte_index: u32, arguments: Vec, body: Vec1, return_annotation: Option, }, List { location: SrcSpan, elements: Vec, tail: Option>, }, Call { location: SrcSpan, fun: Box, arguments: Vec>, }, BinOp { location: SrcSpan, name: BinOp, name_location: SrcSpan, left: Box, right: Box, }, PipeLine { expressions: Vec1, }, Case { location: SrcSpan, subjects: Vec, // None if the case expression is missing a body. clauses: Option>>, }, FieldAccess { // This is the location of the whole record and field // user.name // ^^^^^^^^^ location: SrcSpan, // This is the location of just the field access (ignoring the `.`) // user.name // ^^^^ label_location: SrcSpan, label: EcoString, container: Box, }, Tuple { location: SrcSpan, elements: Vec, }, TupleIndex { location: SrcSpan, index: u64, tuple: Box, }, Todo { kind: TodoKind, location: SrcSpan, message: Option>, }, Panic { location: SrcSpan, message: Option>, }, Echo { location: SrcSpan, /// This is the position where the echo keyword ends: /// ```gleam /// echo wibble /// // ^ ends here! /// ``` keyword_end: u32, expression: Option>, message: Option>, }, BitArray { location: SrcSpan, segments: Vec, }, RecordUpdate { location: SrcSpan, constructor: Box, record: RecordBeingUpdated, arguments: Vec, }, NegateBool { location: SrcSpan, value: Box, }, NegateInt { location: SrcSpan, value: Box, }, } impl UntypedExpr { pub fn location(&self) -> SrcSpan { match self { Self::PipeLine { expressions, .. } => expressions .first() .location() .merge(&expressions.last().location()), Self::Fn { location, .. } | Self::Var { location, .. } | Self::Int { location, .. } | Self::Todo { location, .. } | Self::Echo { location, .. } | Self::Case { location, .. } | Self::Call { location, .. } | Self::List { location, .. } | Self::Float { location, .. } | Self::Block { location, .. } | Self::BinOp { location, .. } | Self::Tuple { location, .. } | Self::Panic { location, .. } | Self::String { location, .. } | Self::BitArray { location, .. } | Self::NegateInt { location, .. } | Self::NegateBool { location, .. } | Self::TupleIndex { location, .. } | Self::FieldAccess { location, .. } | Self::RecordUpdate { location, .. } => *location, } } pub fn start_byte_index(&self) -> u32 { match self { Self::Block { location, .. } => location.start, Self::PipeLine { expressions, .. } => expressions.first().start_byte_index(), Self::Int { .. } | Self::Float { .. } | Self::String { .. } | Self::Var { .. } | Self::Fn { .. } | Self::List { .. } | Self::Call { .. } | Self::BinOp { .. } | Self::Case { .. } | Self::FieldAccess { .. } | Self::Tuple { .. } | Self::TupleIndex { .. } | Self::Todo { .. } | Self::Panic { .. } | Self::Echo { .. } | Self::BitArray { .. } | Self::RecordUpdate { .. } | Self::NegateBool { .. } | Self::NegateInt { .. } => self.location().start, } } pub fn bin_op_precedence(&self) -> u8 { match self { Self::BinOp { name, .. } => name.precedence(), Self::PipeLine { .. } => 5, Self::Int { .. } | Self::Float { .. } | Self::String { .. } | Self::Block { .. } | Self::Var { .. } | Self::Fn { .. } | Self::List { .. } | Self::Call { .. } | Self::Case { .. } | Self::FieldAccess { .. } | Self::Tuple { .. } | Self::TupleIndex { .. } | Self::Todo { .. } | Self::Panic { .. } | Self::Echo { .. } | Self::BitArray { .. } | Self::RecordUpdate { .. } | Self::NegateBool { .. } | Self::NegateInt { .. } => u8::MAX, } } pub fn bin_op_name(&self) -> Option<&BinOp> { if let UntypedExpr::BinOp { name, .. } = self { Some(name) } else { None } } pub fn can_have_multiple_per_line(&self) -> bool { match self { UntypedExpr::Int { .. } | UntypedExpr::Float { .. } | UntypedExpr::String { .. } | UntypedExpr::Var { .. } => true, UntypedExpr::NegateBool { value, .. } | UntypedExpr::NegateInt { value, .. } | UntypedExpr::FieldAccess { container: value, .. } => value.can_have_multiple_per_line(), UntypedExpr::Block { .. } | UntypedExpr::Fn { .. } | UntypedExpr::List { .. } | UntypedExpr::Call { .. } | UntypedExpr::BinOp { .. } | UntypedExpr::PipeLine { .. } | UntypedExpr::Case { .. } | UntypedExpr::Tuple { .. } | UntypedExpr::TupleIndex { .. } | UntypedExpr::Todo { .. } | UntypedExpr::Panic { .. } | UntypedExpr::Echo { .. } | UntypedExpr::BitArray { .. } | UntypedExpr::RecordUpdate { .. } => false, } } pub fn is_tuple(&self) -> bool { matches!(self, UntypedExpr::Tuple { .. }) } /// Returns `true` if the untyped expr is [`Call`]. /// /// [`Call`]: UntypedExpr::Call #[must_use] pub fn is_call(&self) -> bool { matches!(self, Self::Call { .. }) } #[must_use] pub fn is_binop(&self) -> bool { matches!(self, Self::BinOp { .. }) } #[must_use] pub fn is_pipeline(&self) -> bool { matches!(self, Self::PipeLine { .. }) } #[must_use] pub fn is_todo(&self) -> bool { matches!(self, Self::Todo { .. }) } #[must_use] pub fn is_panic(&self) -> bool { matches!(self, Self::Panic { .. }) } } impl HasLocation for UntypedExpr { fn location(&self) -> SrcSpan { self.location() } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum FunctionLiteralKind { Capture { hole: SrcSpan }, Anonymous { head: SrcSpan }, Use { location: SrcSpan }, } impl FunctionLiteralKind { pub fn is_capture(&self) -> bool { match self { FunctionLiteralKind::Capture { .. } => true, FunctionLiteralKind::Anonymous { .. } | FunctionLiteralKind::Use { .. } => false, } } } ================================================ FILE: compiler-core/src/ast/visit.rs ================================================ //! AST traversal routines, referenced from [`syn::visit`](https://docs.rs/syn/latest/syn/visit/index.html) //! //! Each method of the [`Visit`] trait can be overriden to customize the //! behaviour when visiting the corresponding type of AST node. By default, //! every method recursively visits the substructure of the node by using the //! right visitor method. //! //! # Example //! //! Suppose we would like to collect all function names in a module, //! we can do the following: //! //! ```no_run //! use gleam_core::ast::{TypedFunction, visit::{self, Visit}}; //! //! struct FnCollector<'ast> { //! functions: Vec<&'ast TypedFunction> //! } //! //! impl<'ast> Visit<'ast> for FnCollector<'ast> { //! fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { //! self.functions.push(fun); //! //! // Use the default behaviour to visit any nested functions //! visit::visit_typed_function(self, fun); //! } //! } //! //! fn print_all_module_functions() { //! let module = todo!("module"); //! let mut fn_collector = FnCollector { functions: vec![] }; //! //! // This will walk the AST and collect all functions //! fn_collector.visit_typed_module(module); //! //! // Print the collected functions //! println!("{:#?}", fn_collector.functions); //! } //! ``` use crate::{ analyse::Inferred, ast::{ BitArraySize, RecordBeingUpdated, TypedBitArraySize, TypedConstantBitArraySegment, TypedDefinitions, TypedImport, TypedTailPattern, TypedTypeAlias, typed::InvalidExpression, }, exhaustiveness::CompiledCase, parse::LiteralFloatValue, type_::{ FieldMap, ModuleValueConstructor, PatternConstructor, TypedCallArg, ValueConstructor, error::VariableOrigin, }, }; use std::sync::Arc; use ecow::EcoString; use num_bigint::BigInt; use vec1::Vec1; use crate::type_::Type; use super::{ AssignName, BinOp, BitArrayOption, CallArg, Pattern, PipelineAssignmentKind, RecordUpdateArg, SrcSpan, Statement, TodoKind, TypeAst, TypedArg, TypedAssert, TypedAssignment, TypedClause, TypedClauseGuard, TypedConstant, TypedCustomType, TypedExpr, TypedExprBitArraySegment, TypedFunction, TypedModule, TypedModuleConstant, TypedPattern, TypedPatternBitArraySegment, TypedPipelineAssignment, TypedStatement, TypedUse, untyped::FunctionLiteralKind, }; pub trait Visit<'ast> { fn visit_typed_module(&mut self, module: &'ast TypedModule) { visit_typed_module(self, module); } fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { visit_typed_function(self, fun); } fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) { visit_typed_module_constant(self, constant); } fn visit_typed_custom_type(&mut self, custom_type: &'ast TypedCustomType) { visit_typed_custom_type(self, custom_type); } fn visit_typed_type_alias(&mut self, type_alias: &'ast TypedTypeAlias) { visit_typed_type_alias(self, type_alias) } fn visit_typed_import(&mut self, import: &'ast TypedImport) { visit_typed_import(self, import) } fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) { visit_typed_expr(self, expr); } fn visit_typed_expr_echo( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, expression: &'ast Option>, message: &'ast Option>, ) { visit_typed_expr_echo(self, location, type_, expression, message); } fn visit_typed_expr_int( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, value: &'ast EcoString, ) { visit_typed_expr_int(self, location, type_, value); } fn visit_typed_expr_float( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, value: &'ast EcoString, ) { visit_typed_expr_float(self, location, type_, value); } fn visit_typed_expr_string( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, value: &'ast EcoString, ) { visit_typed_expr_string(self, location, type_, value); } fn visit_typed_expr_block( &mut self, location: &'ast SrcSpan, statements: &'ast [TypedStatement], ) { visit_typed_expr_block(self, location, statements); } fn visit_typed_expr_pipeline( &mut self, location: &'ast SrcSpan, first_value: &'ast TypedPipelineAssignment, assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], finally: &'ast TypedExpr, finally_kind: &'ast PipelineAssignmentKind, ) { visit_typed_expr_pipeline( self, location, first_value, assignments, finally, finally_kind, ); } fn visit_typed_expr_var( &mut self, location: &'ast SrcSpan, constructor: &'ast ValueConstructor, name: &'ast EcoString, ) { visit_typed_expr_var(self, location, constructor, name); } fn visit_typed_expr_fn( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, kind: &'ast FunctionLiteralKind, arguments: &'ast [TypedArg], body: &'ast Vec1, return_annotation: &'ast Option, ) { visit_typed_expr_fn( self, location, type_, kind, arguments, body, return_annotation, ); } fn visit_typed_expr_list( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, elements: &'ast [TypedExpr], tail: &'ast Option>, ) { visit_typed_expr_list(self, location, type_, elements, tail); } fn visit_typed_expr_call( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, fun: &'ast TypedExpr, arguments: &'ast [TypedCallArg], ) { visit_typed_expr_call(self, location, type_, fun, arguments); } fn visit_typed_expr_bin_op( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, name: &'ast BinOp, name_location: &'ast SrcSpan, left: &'ast TypedExpr, right: &'ast TypedExpr, ) { visit_typed_expr_bin_op(self, location, type_, name, name_location, left, right); } fn visit_typed_expr_case( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, subjects: &'ast [TypedExpr], clauses: &'ast [TypedClause], compiled_case: &'ast CompiledCase, ) { visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case); } #[allow(clippy::too_many_arguments)] fn visit_typed_expr_record_access( &mut self, location: &'ast SrcSpan, field_start: &'ast u32, type_: &'ast Arc, label: &'ast EcoString, index: &'ast u64, record: &'ast TypedExpr, documentation: &'ast Option, ) { visit_typed_expr_record_access( self, location, field_start, type_, label, index, record, documentation, ); } #[allow(clippy::too_many_arguments)] fn visit_typed_expr_module_select( &mut self, location: &'ast SrcSpan, field_start: &'ast u32, type_: &'ast Arc, label: &'ast EcoString, module_name: &'ast EcoString, module_alias: &'ast EcoString, constructor: &'ast ModuleValueConstructor, ) { visit_typed_expr_module_select( self, location, field_start, type_, label, module_name, module_alias, constructor, ); } fn visit_typed_expr_tuple( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, elements: &'ast [TypedExpr], ) { visit_typed_expr_tuple(self, location, type_, elements); } fn visit_typed_expr_tuple_index( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, index: &'ast u64, tuple: &'ast TypedExpr, ) { visit_typed_expr_tuple_index(self, location, type_, index, tuple); } fn visit_typed_expr_todo( &mut self, location: &'ast SrcSpan, message: &'ast Option>, kind: &'ast TodoKind, type_: &'ast Arc, ) { visit_typed_expr_todo(self, location, message, kind, type_); } fn visit_typed_expr_panic( &mut self, location: &'ast SrcSpan, message: &'ast Option>, type_: &'ast Arc, ) { visit_typed_expr_panic(self, location, message, type_); } fn visit_typed_expr_bit_array( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, segments: &'ast [TypedExprBitArraySegment], ) { visit_typed_expr_bit_array(self, location, type_, segments); } fn visit_typed_expr_record_update( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, record: &'ast Option>, constructor: &'ast TypedExpr, arguments: &'ast [TypedCallArg], ) { visit_typed_expr_record_update(self, location, type_, record, constructor, arguments); } fn visit_typed_expr_negate_bool(&mut self, location: &'ast SrcSpan, value: &'ast TypedExpr) { visit_typed_expr_negate_bool(self, location, value); } fn visit_typed_expr_negate_int(&mut self, location: &'ast SrcSpan, value: &'ast TypedExpr) { visit_typed_expr_negate_int(self, location, value) } fn visit_typed_expr_invalid( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, extra_information: &'ast Option, ) { visit_typed_expr_invalid(self, location, type_, extra_information); } fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) { visit_typed_statement(self, statement); } fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { visit_typed_assignment(self, assignment); } fn visit_typed_use(&mut self, use_: &'ast TypedUse) { visit_typed_use(self, use_); } fn visit_typed_assert(&mut self, assert: &'ast TypedAssert) { visit_typed_assert(self, assert); } fn visit_typed_pipeline_assignment(&mut self, assignment: &'ast TypedPipelineAssignment) { visit_typed_pipeline_assignment(self, assignment); } fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) { visit_typed_call_arg(self, arg); } fn visit_typed_clause(&mut self, clause: &'ast TypedClause) { visit_typed_clause(self, clause); } fn visit_typed_clause_guard(&mut self, guard: &'ast TypedClauseGuard) { visit_typed_clause_guard(self, guard); } fn visit_typed_clause_guard_var( &mut self, location: &'ast SrcSpan, name: &'ast EcoString, type_: &'ast Arc, definition_location: &'ast SrcSpan, origin: &'ast VariableOrigin, ) { visit_typed_clause_guard_var(self, location, name, type_, definition_location, origin); } fn visit_typed_clause_guard_tuple_index( &mut self, location: &'ast SrcSpan, index: &'ast u64, type_: &'ast Arc, tuple: &'ast TypedClauseGuard, ) { visit_typed_clause_guard_tuple_index(self, location, index, type_, tuple) } fn visit_typed_clause_guard_field_access( &mut self, label_location: &'ast SrcSpan, index: &'ast Option, label: &'ast EcoString, type_: &'ast Arc, container: &'ast TypedClauseGuard, ) { visit_typed_clause_guard_field_access(self, label_location, index, label, type_, container) } #[allow(clippy::too_many_arguments)] fn visit_typed_clause_guard_module_select( &mut self, location: &'ast SrcSpan, field_start: &'ast u32, definition_location: &'ast SrcSpan, type_: &'ast Arc, label: &'ast EcoString, module_name: &'ast EcoString, module_alias: &'ast EcoString, literal: &'ast TypedConstant, ) { visit_typed_clause_guard_module_select( self, location, field_start, definition_location, type_, label, module_name, module_alias, literal, ) } fn visit_typed_expr_bit_array_segment(&mut self, segment: &'ast TypedExprBitArraySegment) { visit_typed_expr_bit_array_segment(self, segment); } fn visit_typed_bit_array_option(&mut self, option: &'ast BitArrayOption) { visit_typed_bit_array_option(self, option); } fn visit_typed_pattern(&mut self, pattern: &'ast TypedPattern) { visit_typed_pattern(self, pattern); } fn visit_typed_pattern_int(&mut self, location: &'ast SrcSpan, value: &'ast EcoString) { visit_typed_pattern_int(self, location, value); } fn visit_typed_pattern_float(&mut self, location: &'ast SrcSpan, value: &'ast EcoString) { visit_typed_pattern_float(self, location, value); } fn visit_typed_pattern_string(&mut self, location: &'ast SrcSpan, value: &'ast EcoString) { visit_typed_pattern_string(self, location, value); } fn visit_typed_pattern_variable( &mut self, location: &'ast SrcSpan, name: &'ast EcoString, type_: &'ast Arc, origin: &'ast VariableOrigin, ) { visit_typed_pattern_variable(self, location, name, type_, origin); } fn visit_typed_pattern_bit_array_size(&mut self, size: &'ast TypedBitArraySize) { visit_typed_pattern_bit_array_size(self, size); } fn visit_typed_bit_array_size_int(&mut self, location: &'ast SrcSpan, value: &'ast EcoString) { visit_typed_bit_array_size_int(self, location, value) } fn visit_typed_bit_array_size_variable( &mut self, location: &'ast SrcSpan, name: &'ast EcoString, constructor: &'ast Option>, type_: &'ast Arc, ) { visit_typed_bit_array_size_variable(self, location, name, constructor, type_) } fn visit_typed_pattern_assign( &mut self, location: &'ast SrcSpan, name: &'ast EcoString, pattern: &'ast TypedPattern, ) { visit_typed_pattern_assign(self, location, name, pattern); } fn visit_typed_pattern_discard( &mut self, location: &'ast SrcSpan, name: &'ast EcoString, type_: &'ast Arc, ) { visit_typed_pattern_discard(self, location, name, type_) } fn visit_typed_pattern_list( &mut self, location: &'ast SrcSpan, elements: &'ast Vec, tail: &'ast Option>, type_: &'ast Arc, ) { visit_typed_pattern_list(self, location, elements, tail, type_); } #[allow(clippy::too_many_arguments)] fn visit_typed_pattern_constructor( &mut self, location: &'ast SrcSpan, name_location: &'ast SrcSpan, name: &'ast EcoString, arguments: &'ast Vec>, module: &'ast Option<(EcoString, SrcSpan)>, constructor: &'ast Inferred, spread: &'ast Option, type_: &'ast Arc, ) { visit_typed_pattern_constructor( self, location, name_location, name, arguments, module, constructor, spread, type_, ); } fn visit_typed_pattern_call_arg(&mut self, arg: &'ast CallArg) { visit_typed_pattern_call_arg(self, arg); } fn visit_typed_pattern_tuple( &mut self, location: &'ast SrcSpan, elements: &'ast Vec, ) { visit_typed_pattern_tuple(self, location, elements); } fn visit_typed_pattern_bit_array( &mut self, location: &'ast SrcSpan, segments: &'ast [TypedPatternBitArraySegment], ) { visit_typed_pattern_bit_array(self, location, segments); } fn visit_typed_pattern_bit_array_option(&mut self, option: &'ast BitArrayOption) { visit_typed_pattern_bit_array_option(self, option); } fn visit_typed_pattern_string_prefix( &mut self, location: &'ast SrcSpan, left_location: &'ast SrcSpan, left_side_assignment: &'ast Option<(EcoString, SrcSpan)>, right_location: &'ast SrcSpan, left_side_string: &'ast EcoString, right_side_assignment: &'ast AssignName, ) { visit_typed_pattern_string_prefix( self, location, left_location, left_side_assignment, right_location, left_side_string, right_side_assignment, ); } fn visit_typed_pattern_invalid(&mut self, location: &'ast SrcSpan, type_: &'ast Arc) { visit_typed_pattern_invalid(self, location, type_); } fn visit_type_ast( &mut self, node: &'ast TypeAst, // The type inferred for this annotation. // It might not always be possible to infer one in presence of type // errors, so this is optional! inferred_type: Option>, ) { visit_type_ast(self, node, inferred_type); } fn visit_type_ast_constructor( &mut self, location: &'ast SrcSpan, name_location: &'ast SrcSpan, module: &'ast Option<(EcoString, SrcSpan)>, name: &'ast EcoString, arguments: &'ast [TypeAst], inferred_arguments_types: Option>>, ) { visit_type_ast_constructor( self, location, name_location, module, name, arguments, inferred_arguments_types, ); } fn visit_type_ast_fn( &mut self, location: &'ast SrcSpan, arguments: &'ast [TypeAst], arguments_types: Option>>, return_: &'ast TypeAst, return_type: Option>, ) { visit_type_ast_fn( self, location, arguments, arguments_types, return_, return_type, ); } fn visit_type_ast_var(&mut self, location: &'ast SrcSpan, name: &'ast EcoString) { visit_type_ast_var(self, location, name); } fn visit_type_ast_tuple( &mut self, location: &'ast SrcSpan, elements: &'ast [TypeAst], elements_types: Option<&Vec>>, ) { visit_type_ast_tuple(self, location, elements, elements_types); } fn visit_type_ast_hole( &mut self, location: &'ast SrcSpan, name: &'ast EcoString, type_: Option>, ) { visit_type_ast_hole(self, location, name, type_); } fn visit_typed_constant(&mut self, constant: &'ast TypedConstant) { visit_typed_constant(self, constant); } fn visit_typed_constant_int( &mut self, location: &'ast SrcSpan, value: &'ast EcoString, int_value: &'ast BigInt, ) { visit_typed_constant_int(self, location, value, int_value); } fn visit_typed_constant_float( &mut self, location: &'ast SrcSpan, value: &'ast EcoString, float_value: &'ast LiteralFloatValue, ) { visit_typed_constant_float(self, location, value, float_value); } fn visit_typed_constant_string(&mut self, location: &'ast SrcSpan, value: &'ast EcoString) { visit_typed_constant_string(self, location, value); } fn visit_typed_constant_tuple( &mut self, location: &'ast SrcSpan, elements: &'ast Vec, type_: &'ast Arc, ) { visit_typed_constant_tuple(self, location, elements, type_); } fn visit_typed_constant_list( &mut self, location: &'ast SrcSpan, elements: &'ast Vec, type_: &'ast Arc, tail: &'ast Option>, ) { visit_typed_constant_list(self, location, elements, type_, tail); } #[allow(clippy::too_many_arguments)] fn visit_typed_constant_record( &mut self, location: &'ast SrcSpan, module: &'ast Option<(EcoString, SrcSpan)>, name: &'ast EcoString, arguments: &'ast Vec>, tag: &'ast EcoString, type_: &'ast Arc, field_map: &'ast Inferred, record_constructor: &'ast Option>, ) { visit_typed_constant_record( self, location, module, name, arguments, tag, type_, field_map, record_constructor, ) } #[allow(clippy::too_many_arguments)] fn visit_typed_constant_record_update( &mut self, location: &'ast SrcSpan, constructor_location: &'ast SrcSpan, module: &'ast Option<(EcoString, SrcSpan)>, name: &'ast EcoString, record: &'ast RecordBeingUpdated, arguments: &'ast [RecordUpdateArg], tag: &'ast EcoString, type_: &'ast Arc, field_map: &'ast Inferred, ) { visit_typed_constant_record_update( self, location, constructor_location, module, name, record, arguments, tag, type_, field_map, ) } fn visit_typed_constant_bit_array( &mut self, location: &'ast SrcSpan, segments: &'ast [TypedConstantBitArraySegment], ) { visit_typed_constant_bit_array(self, location, segments); } fn visit_typed_constant_var( &mut self, location: &'ast SrcSpan, module: &'ast Option<(EcoString, SrcSpan)>, name: &'ast EcoString, constructor: &'ast Option>, type_: &'ast Arc, ) { visit_typed_constant_var(self, location, module, name, constructor, type_); } fn visit_typed_constant_string_concatenation( &mut self, location: &'ast SrcSpan, left: &'ast TypedConstant, right: &'ast TypedConstant, ) { visit_typed_constant_string_concatenation(self, location, left, right); } fn visit_typed_constant_invalid( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, extra_information: &'ast Option, ) { visit_typed_constant_invalid(self, location, type_, extra_information); } } fn visit_typed_constant_invalid<'a, V: Visit<'a> + ?Sized>( _v: &mut V, _location: &'a SrcSpan, _type_: &'a Type, _extra_information: &'a Option, ) { // No further traversal needed for constant invalid expressions } fn visit_typed_constant_string_concatenation<'a, V: Visit<'a> + ?Sized>( v: &mut V, _location: &'a SrcSpan, left: &'a TypedConstant, right: &'a TypedConstant, ) { v.visit_typed_constant(left); v.visit_typed_constant(right); } pub fn visit_typed_constant_var<'a, V: Visit<'a> + ?Sized>( _v: &mut V, _location: &'a SrcSpan, _module: &'a Option<(EcoString, SrcSpan)>, _name: &'a EcoString, _constructor: &'a Option>, _type_: &'a Arc, ) { // No further traversal needed for constant vars } fn visit_typed_constant_bit_array<'a, V: Visit<'a> + ?Sized>( _v: &mut V, _location: &'a SrcSpan, _segments: &'a [TypedConstantBitArraySegment], ) { // TODO } #[allow(clippy::too_many_arguments)] pub fn visit_typed_constant_record<'a, V: Visit<'a> + ?Sized>( v: &mut V, _location: &'a SrcSpan, _module: &'a Option<(EcoString, SrcSpan)>, _name: &'a EcoString, arguments: &'a Vec>, _tag: &'a EcoString, _type_: &'a Arc, _field_map: &'a Inferred, _record_constructor: &'a Option>, ) { for argument in arguments { v.visit_typed_constant(&argument.value) } } #[allow(clippy::too_many_arguments)] pub fn visit_typed_constant_record_update<'a, V: Visit<'a> + ?Sized>( v: &mut V, _location: &'a SrcSpan, _constructor_location: &'a SrcSpan, _module: &'a Option<(EcoString, SrcSpan)>, _name: &'a EcoString, record: &'a RecordBeingUpdated, arguments: &'a [RecordUpdateArg], _tag: &'a EcoString, _type_: &'a Arc, _field_map: &'a Inferred, ) { v.visit_typed_constant(&record.base); for argument in arguments { v.visit_typed_constant(&argument.value); } } fn visit_typed_constant_list<'a, V: Visit<'a> + ?Sized>( v: &mut V, _location: &'a SrcSpan, elements: &'a Vec, _type_: &'a Arc, tail: &'a Option>, ) { for element in elements { v.visit_typed_constant(element) } if let Some(tail) = tail { v.visit_typed_constant(tail); } } fn visit_typed_constant_tuple<'a, V: Visit<'a> + ?Sized>( v: &mut V, _location: &'a SrcSpan, elements: &'a Vec, _type_: &'a Arc, ) { for element in elements { v.visit_typed_constant(element) } } fn visit_typed_constant_string<'a, V: Visit<'a> + ?Sized>( _v: &mut V, _location: &'a SrcSpan, _value: &'a EcoString, ) { // No further traversal needed for constant strings } fn visit_typed_constant_float<'a, V: Visit<'a> + ?Sized>( _v: &mut V, _location: &'a SrcSpan, _value: &'a EcoString, _float_value: &'a LiteralFloatValue, ) { // No further traversal needed for constant floats } fn visit_typed_constant_int<'a, V: Visit<'a> + ?Sized>( _v: &mut V, _location: &'a SrcSpan, _value: &'a EcoString, _int_value: &'a BigInt, ) { // No further traversal needed for constant ints } pub fn visit_typed_module<'a, V>(v: &mut V, module: &'a TypedModule) where V: Visit<'a> + ?Sized, { let TypedDefinitions { imports, constants, custom_types, type_aliases, functions, } = &module.definitions; for import in imports { v.visit_typed_import(import); } for constant in constants { v.visit_typed_module_constant(constant); } for custom_type in custom_types { v.visit_typed_custom_type(custom_type); } for type_alias in type_aliases { v.visit_typed_type_alias(type_alias); } for function in functions { v.visit_typed_function(function); } } pub fn visit_typed_function<'a, V>(v: &mut V, fun: &'a TypedFunction) where V: Visit<'a> + ?Sized, { for argument in fun.arguments.iter() { if let Some(annotation) = &argument.annotation { v.visit_type_ast(annotation, Some(argument.type_.clone())); } } if let Some(annotation) = &fun.return_annotation { v.visit_type_ast(annotation, Some(fun.return_type.clone())); } for statement in &fun.body { v.visit_typed_statement(statement); } } pub fn visit_type_ast<'a, V>(v: &mut V, node: &'a TypeAst, type_: Option>) where V: Visit<'a> + ?Sized, { match node { TypeAst::Constructor(super::TypeAstConstructor { location, name_location, arguments, module, name, start_parentheses: _, }) => { v.visit_type_ast_constructor( location, name_location, module, name, arguments, type_.and_then(|type_| type_.constructor_types()), ); } TypeAst::Fn(super::TypeAstFn { location, arguments, return_, }) => { let (arguments_types, return_type) = match type_.and_then(|type_| type_.fn_types()) { Some((arguments_types, return_type)) => (Some(arguments_types), Some(return_type)), None => (None, None), }; v.visit_type_ast_fn(location, arguments, arguments_types, return_, return_type); } TypeAst::Var(super::TypeAstVar { location, name }) => { v.visit_type_ast_var(location, name); } TypeAst::Tuple(super::TypeAstTuple { location, elements }) => { let elements_types = if let Some(Type::Tuple { elements }) = type_.as_deref() { Some(elements) } else { None }; v.visit_type_ast_tuple(location, elements, elements_types); } TypeAst::Hole(super::TypeAstHole { location, name }) => { v.visit_type_ast_hole(location, name, type_); } } } pub fn visit_type_ast_constructor<'a, V>( v: &mut V, _location: &'a SrcSpan, _name_location: &'a SrcSpan, _module: &'a Option<(EcoString, SrcSpan)>, _name: &'a EcoString, arguments: &'a [TypeAst], inferred_arguments_types: Option>>, ) where V: Visit<'a> + ?Sized, { for (i, argument) in arguments.iter().enumerate() { let argument_type = inferred_arguments_types .as_ref() .and_then(|types| types.get(i)); v.visit_type_ast(argument, argument_type.cloned()); } } pub fn visit_type_ast_fn<'a, V>( v: &mut V, _location: &'a SrcSpan, arguments: &'a [TypeAst], arguments_types: Option>>, return_: &'a TypeAst, return_type: Option>, ) where V: Visit<'a> + ?Sized, { for (i, argument) in arguments.iter().enumerate() { v.visit_type_ast( argument, arguments_types .as_ref() .and_then(|types| types.get(i)) .cloned(), ); } v.visit_type_ast(return_, return_type.clone()); } pub fn visit_type_ast_var<'a, V>(_v: &mut V, _location: &'a SrcSpan, _name: &'a EcoString) where V: Visit<'a> + ?Sized, { // No further traversal needed for variables } pub fn visit_type_ast_tuple<'a, V>( v: &mut V, _location: &'a SrcSpan, elements: &'a [TypeAst], elements_types: Option<&Vec>>, ) where V: Visit<'a> + ?Sized, { for (i, element) in elements.iter().enumerate() { let element_type = elements_types .as_ref() .and_then(|types| types.get(i)) .cloned(); v.visit_type_ast(element, element_type); } } pub fn visit_type_ast_hole<'a, V>( _v: &mut V, _location: &'a SrcSpan, _name: &'a EcoString, _type_: Option>, ) where V: Visit<'a> + ?Sized, { // No further traversal needed for holes } pub fn visit_typed_module_constant<'a, V>(v: &mut V, constant: &'a TypedModuleConstant) where V: Visit<'a> + ?Sized, { if let Some(annotation) = &constant.annotation { v.visit_type_ast(annotation, Some(constant.type_.clone())); } v.visit_typed_constant(&constant.value); } pub fn visit_typed_constant<'a, V: Visit<'a> + ?Sized>(v: &mut V, constant: &'a TypedConstant) { match constant { super::Constant::Int { location, value, int_value, } => v.visit_typed_constant_int(location, value, int_value), super::Constant::Float { location, value, float_value, } => v.visit_typed_constant_float(location, value, float_value), super::Constant::String { location, value } => { v.visit_typed_constant_string(location, value) } super::Constant::Tuple { location, elements, type_, } => v.visit_typed_constant_tuple(location, elements, type_), super::Constant::List { location, elements, type_, tail, } => v.visit_typed_constant_list(location, elements, type_, tail), super::Constant::Record { location, module, name, arguments, tag, type_, field_map, record_constructor, } => v.visit_typed_constant_record( location, module, name, arguments, tag, type_, field_map, record_constructor, ), super::Constant::RecordUpdate { location, constructor_location, module, name, record, arguments, tag, type_, field_map, } => v.visit_typed_constant_record_update( location, constructor_location, module, name, record, arguments, tag, type_, field_map, ), super::Constant::BitArray { location, segments } => { v.visit_typed_constant_bit_array(location, segments) } super::Constant::Var { location, module, name, constructor, type_, } => v.visit_typed_constant_var(location, module, name, constructor, type_), super::Constant::StringConcatenation { location, left, right, } => v.visit_typed_constant_string_concatenation(location, left, right), super::Constant::Invalid { location, type_, extra_information, } => v.visit_typed_constant_invalid(location, type_, extra_information), } } pub fn visit_typed_custom_type<'a, V>(v: &mut V, custom_type: &'a TypedCustomType) where V: Visit<'a> + ?Sized, { for record in &custom_type.constructors { for argument in &record.arguments { v.visit_type_ast(&argument.ast, Some(argument.type_.clone())); } } } pub fn visit_typed_type_alias<'a, V>(v: &mut V, type_alias: &'a TypedTypeAlias) where V: Visit<'a> + ?Sized, { v.visit_type_ast(&type_alias.type_ast, Some(type_alias.type_.clone())); } pub fn visit_typed_import<'a, V>(_v: &mut V, _import: &'a TypedImport) where V: Visit<'a> + ?Sized, { } pub fn visit_typed_expr<'a, V>(v: &mut V, node: &'a TypedExpr) where V: Visit<'a> + ?Sized, { match node { TypedExpr::Int { location, type_, value, int_value: _, } => v.visit_typed_expr_int(location, type_, value), TypedExpr::Float { location, type_, value, float_value: _, } => v.visit_typed_expr_float(location, type_, value), TypedExpr::String { location, type_, value, } => v.visit_typed_expr_string(location, type_, value), TypedExpr::Block { location, statements, } => v.visit_typed_expr_block(location, statements), TypedExpr::Pipeline { location, first_value, assignments, finally, finally_kind, } => v.visit_typed_expr_pipeline(location, first_value, assignments, finally, finally_kind), TypedExpr::Var { location, constructor, name, } => v.visit_typed_expr_var(location, constructor, name), TypedExpr::Fn { location, type_, kind, arguments, body, return_annotation, purity: _, } => v.visit_typed_expr_fn(location, type_, kind, arguments, body, return_annotation), TypedExpr::List { location, type_, elements, tail, } => v.visit_typed_expr_list(location, type_, elements, tail), TypedExpr::Call { location, type_, fun, arguments, } => v.visit_typed_expr_call(location, type_, fun, arguments), TypedExpr::BinOp { location, type_, name, name_location, left, right, } => v.visit_typed_expr_bin_op(location, type_, name, name_location, left, right), TypedExpr::Case { location, type_, subjects, clauses, compiled_case, } => v.visit_typed_expr_case(location, type_, subjects, clauses, compiled_case), TypedExpr::RecordAccess { location, field_start, type_, label, index, record, documentation, } => v.visit_typed_expr_record_access( location, field_start, type_, label, index, record, documentation, ), TypedExpr::ModuleSelect { location, field_start, type_, label, module_name, module_alias, constructor, } => v.visit_typed_expr_module_select( location, field_start, type_, label, module_name, module_alias, constructor, ), TypedExpr::Tuple { location, type_, elements, } => v.visit_typed_expr_tuple(location, type_, elements), TypedExpr::TupleIndex { location, type_, index, tuple, } => v.visit_typed_expr_tuple_index(location, type_, index, tuple), TypedExpr::Todo { location, message, kind, type_, } => v.visit_typed_expr_todo(location, message, kind, type_), TypedExpr::Panic { location, message, type_, } => v.visit_typed_expr_panic(location, message, type_), TypedExpr::BitArray { location, type_, segments, } => v.visit_typed_expr_bit_array(location, type_, segments), TypedExpr::RecordUpdate { location, type_, record_assignment, constructor, arguments, } => v.visit_typed_expr_record_update( location, type_, record_assignment, constructor, arguments, ), TypedExpr::NegateBool { location, value } => { v.visit_typed_expr_negate_bool(location, value) } TypedExpr::NegateInt { location, value } => v.visit_typed_expr_negate_int(location, value), TypedExpr::Invalid { location, type_, extra_information, } => v.visit_typed_expr_invalid(location, type_, extra_information), TypedExpr::Echo { location, expression, message, type_, } => v.visit_typed_expr_echo(location, type_, expression, message), TypedExpr::PositionalAccess { .. } => {} } } pub fn visit_typed_expr_int<'a, V>( _v: &mut V, _location: &'a SrcSpan, _type_: &'a Arc, _value: &'a EcoString, ) where V: Visit<'a> + ?Sized, { } pub fn visit_typed_expr_float<'a, V>( _v: &mut V, _location: &'a SrcSpan, _type_: &'a Arc, _value: &'a EcoString, ) where V: Visit<'a> + ?Sized, { } pub fn visit_typed_expr_string<'a, V>( _v: &mut V, _location: &'a SrcSpan, _type_: &'a Arc, _value: &'a EcoString, ) where V: Visit<'a> + ?Sized, { } pub fn visit_typed_expr_block<'a, V>( v: &mut V, _location: &'a SrcSpan, statements: &'a [TypedStatement], ) where V: Visit<'a> + ?Sized, { for statement in statements { v.visit_typed_statement(statement); } } pub fn visit_typed_expr_pipeline<'a, V>( v: &mut V, _location: &'a SrcSpan, first_value: &'a TypedPipelineAssignment, assignments: &'a [(TypedPipelineAssignment, PipelineAssignmentKind)], finally: &'a TypedExpr, _finally_kind: &'a PipelineAssignmentKind, ) where V: Visit<'a> + ?Sized, { v.visit_typed_pipeline_assignment(first_value); for (assignment, _kind) in assignments { v.visit_typed_pipeline_assignment(assignment); } v.visit_typed_expr(finally); } pub fn visit_typed_pipeline_assignment<'a, V>(v: &mut V, assignment: &'a TypedPipelineAssignment) where V: Visit<'a> + ?Sized, { v.visit_typed_expr(&assignment.value); } pub fn visit_typed_expr_var<'a, V>( _v: &mut V, _location: &'a SrcSpan, _constructor: &'a ValueConstructor, _name: &'a EcoString, ) where V: Visit<'a> + ?Sized, { /* TODO */ } pub fn visit_typed_expr_fn<'a, V>( v: &mut V, _location: &'a SrcSpan, type_: &'a Arc, _kind: &'a FunctionLiteralKind, arguments: &'a [TypedArg], body: &'a Vec1, return_annotation: &'a Option, ) where V: Visit<'a> + ?Sized, { for argument in arguments { if let Some(annotation) = &argument.annotation { v.visit_type_ast(annotation, Some(argument.type_.clone())); } } if let Some(return_) = return_annotation { v.visit_type_ast( return_, type_.fn_types().map(|(_, return_)| return_.clone()), ); } for statement in body { v.visit_typed_statement(statement); } } pub fn visit_typed_expr_list<'a, V>( v: &mut V, _location: &'a SrcSpan, _type_: &'a Arc, elements: &'a [TypedExpr], tail: &'a Option>, ) where V: Visit<'a> + ?Sized, { for element in elements { v.visit_typed_expr(element); } if let Some(tail) = tail { v.visit_typed_expr(tail); } } pub fn visit_typed_expr_call<'a, V>( v: &mut V, _location: &'a SrcSpan, _type_: &'a Arc, fun: &'a TypedExpr, arguments: &'a [TypedCallArg], ) where V: Visit<'a> + ?Sized, { v.visit_typed_expr(fun); for argument in arguments { v.visit_typed_call_arg(argument); } } pub fn visit_typed_expr_bin_op<'a, V>( v: &mut V, _location: &'a SrcSpan, _type_: &'a Arc, _name: &'a BinOp, _name_location: &'a SrcSpan, left: &'a TypedExpr, right: &'a TypedExpr, ) where V: Visit<'a> + ?Sized, { v.visit_typed_expr(left); v.visit_typed_expr(right); } pub fn visit_typed_expr_case<'a, V>( v: &mut V, _location: &'a SrcSpan, _type_: &'a Arc, subjects: &'a [TypedExpr], clauses: &'a [TypedClause], _compiled_case: &'a CompiledCase, ) where V: Visit<'a> + ?Sized, { for subject in subjects { v.visit_typed_expr(subject); } for clause in clauses { v.visit_typed_clause(clause); } } #[allow(clippy::too_many_arguments)] pub fn visit_typed_expr_record_access<'a, V>( v: &mut V, _location: &'a SrcSpan, _field_start: &'a u32, _type_: &'a Arc, _label: &'a EcoString, _index: &'a u64, record: &'a TypedExpr, _documentation: &'a Option, ) where V: Visit<'a> + ?Sized, { v.visit_typed_expr(record); } #[allow(clippy::too_many_arguments)] pub fn visit_typed_expr_module_select<'a, V>( _v: &mut V, _location: &'a SrcSpan, _field_start: &'a u32, _type_: &'a Arc, _label: &'a EcoString, _module_name: &'a EcoString, _module_alias: &'a EcoString, _constructor: &'a ModuleValueConstructor, ) where V: Visit<'a> + ?Sized, { /* TODO */ } pub fn visit_typed_expr_tuple<'a, V>( v: &mut V, _location: &'a SrcSpan, _type_: &'a Arc, elements: &'a [TypedExpr], ) where V: Visit<'a> + ?Sized, { for element in elements { v.visit_typed_expr(element); } } pub fn visit_typed_expr_tuple_index<'a, V>( v: &mut V, _location: &'a SrcSpan, _type_: &'a Arc, _index: &'a u64, tuple: &'a TypedExpr, ) where V: Visit<'a> + ?Sized, { v.visit_typed_expr(tuple); } pub fn visit_typed_expr_todo<'a, V>( v: &mut V, _location: &'a SrcSpan, message: &'a Option>, _kind: &'a TodoKind, _type_: &'a Arc, ) where V: Visit<'a> + ?Sized, { if let Some(message) = message { v.visit_typed_expr(message); } } pub fn visit_typed_expr_echo<'a, V>( v: &mut V, _location: &'a SrcSpan, _type_: &'a Arc, expression: &'a Option>, message: &'a Option>, ) where V: Visit<'a> + ?Sized, { if let Some(expression) = expression { v.visit_typed_expr(expression) } if let Some(message) = message { v.visit_typed_expr(message) } } pub fn visit_typed_expr_panic<'a, V>( v: &mut V, _location: &'a SrcSpan, message: &'a Option>, _type_: &'a Arc, ) where V: Visit<'a> + ?Sized, { if let Some(message) = message { v.visit_typed_expr(message); } } pub fn visit_typed_expr_bit_array<'a, V>( v: &mut V, _location: &'a SrcSpan, _type_: &'a Arc, segments: &'a [TypedExprBitArraySegment], ) where V: Visit<'a> + ?Sized, { for segment in segments { v.visit_typed_expr_bit_array_segment(segment); } } pub fn visit_typed_expr_record_update<'a, V>( v: &mut V, _location: &'a SrcSpan, _type_: &'a Arc, record: &'a Option>, constructor: &'a TypedExpr, arguments: &'a [TypedCallArg], ) where V: Visit<'a> + ?Sized, { v.visit_typed_expr(constructor); if let Some(record) = record { v.visit_typed_assignment(record); } for argument in arguments { v.visit_typed_call_arg(argument); } } pub fn visit_typed_expr_negate_bool<'a, V>(v: &mut V, _location: &'a SrcSpan, value: &'a TypedExpr) where V: Visit<'a> + ?Sized, { v.visit_typed_expr(value); } pub fn visit_typed_expr_negate_int<'a, V>(v: &mut V, _location: &'a SrcSpan, value: &'a TypedExpr) where V: Visit<'a> + ?Sized, { v.visit_typed_expr(value); } pub fn visit_typed_statement<'a, V>(v: &mut V, statement: &'a TypedStatement) where V: Visit<'a> + ?Sized, { match statement { Statement::Expression(expression) => v.visit_typed_expr(expression), Statement::Assignment(assignment) => v.visit_typed_assignment(assignment), Statement::Use(use_) => v.visit_typed_use(use_), Statement::Assert(assert) => v.visit_typed_assert(assert), } } pub fn visit_typed_assignment<'a, V>(v: &mut V, assignment: &'a TypedAssignment) where V: Visit<'a> + ?Sized, { if let Some(annotation) = &assignment.annotation { v.visit_type_ast(annotation, Some(assignment.type_())); } v.visit_typed_expr(&assignment.value); v.visit_typed_pattern(&assignment.pattern); } pub fn visit_typed_use<'a, V>(v: &mut V, use_: &'a TypedUse) where V: Visit<'a> + ?Sized, { v.visit_typed_expr(&use_.call); // TODO: We should also visit the typed patterns!! } pub fn visit_typed_assert<'a, V>(v: &mut V, assert: &'a TypedAssert) where V: Visit<'a> + ?Sized, { v.visit_typed_expr(&assert.value); if let Some(message) = &assert.message { v.visit_typed_expr(message); } } pub fn visit_typed_call_arg<'a, V>(v: &mut V, arg: &'a TypedCallArg) where V: Visit<'a> + ?Sized, { v.visit_typed_expr(&arg.value); } pub fn visit_typed_clause<'a, V>(v: &mut V, clause: &'a TypedClause) where V: Visit<'a> + ?Sized, { for pattern in clause.pattern.iter() { v.visit_typed_pattern(pattern); } for patterns in clause.alternative_patterns.iter() { for pattern in patterns { v.visit_typed_pattern(pattern); } } if let Some(guard) = &clause.guard { v.visit_typed_clause_guard(guard); } v.visit_typed_expr(&clause.then); } pub fn visit_typed_clause_guard<'a, V>(v: &mut V, guard: &'a TypedClauseGuard) where V: Visit<'a> + ?Sized, { match guard { super::ClauseGuard::BinaryOperator { left, right, .. } => { v.visit_typed_clause_guard(left); v.visit_typed_clause_guard(right); } super::ClauseGuard::Block { location: _, value } => v.visit_typed_clause_guard(value), super::ClauseGuard::Not { location: _, expression, } => v.visit_typed_clause_guard(expression), super::ClauseGuard::Var { location, type_, name, definition_location, origin, } => v.visit_typed_clause_guard_var(location, name, type_, definition_location, origin), super::ClauseGuard::TupleIndex { location, index, type_, tuple, } => v.visit_typed_clause_guard_tuple_index(location, index, type_, tuple), super::ClauseGuard::FieldAccess { label_location, index, label, type_, container, } => { v.visit_typed_clause_guard_field_access(label_location, index, label, type_, container) } super::ClauseGuard::ModuleSelect { location, field_start, definition_location, type_, label, module_name, module_alias, literal, } => v.visit_typed_clause_guard_module_select( location, field_start, definition_location, type_, label, module_name, module_alias, literal, ), super::ClauseGuard::Constant(constant) => v.visit_typed_constant(constant), } } pub fn visit_typed_clause_guard_var<'a, V>( _v: &mut V, _location: &'a SrcSpan, _name: &'a EcoString, _type_: &'a Arc, _definition_location: &'a SrcSpan, _origin: &'a VariableOrigin, ) where V: Visit<'a> + ?Sized, { } pub fn visit_typed_clause_guard_tuple_index<'a, V>( v: &mut V, _location: &'a SrcSpan, _index: &'a u64, _type_: &'a Arc, tuple: &'a TypedClauseGuard, ) where V: Visit<'a> + ?Sized, { v.visit_typed_clause_guard(tuple); } pub fn visit_typed_clause_guard_field_access<'a, V>( v: &mut V, _label_location: &'a SrcSpan, _index: &'a Option, _label: &'a EcoString, _type_: &'a Arc, container: &'a TypedClauseGuard, ) where V: Visit<'a> + ?Sized, { v.visit_typed_clause_guard(container); } #[allow(clippy::too_many_arguments)] pub fn visit_typed_clause_guard_module_select<'a, V>( _v: &mut V, _location: &'a SrcSpan, _field_start: &'a u32, _definition_location: &'a SrcSpan, _type_: &'a Arc, _label: &'a EcoString, _module_name: &'a EcoString, _module_alias: &'a EcoString, _literal: &'a TypedConstant, ) where V: Visit<'a> + ?Sized, { } pub fn visit_typed_expr_bit_array_segment<'a, V>(v: &mut V, segment: &'a TypedExprBitArraySegment) where V: Visit<'a> + ?Sized, { v.visit_typed_expr(&segment.value); for option in &segment.options { v.visit_typed_bit_array_option(option); } } pub fn visit_typed_bit_array_option<'a, V>(v: &mut V, option: &'a BitArrayOption) where V: Visit<'a> + ?Sized, { match option { BitArrayOption::Bytes { location: _ } => { /* TODO */ } BitArrayOption::Int { location: _ } => { /* TODO */ } BitArrayOption::Float { location: _ } => { /* TODO */ } BitArrayOption::Bits { location: _ } => { /* TODO */ } BitArrayOption::Utf8 { location: _ } => { /* TODO */ } BitArrayOption::Utf16 { location: _ } => { /* TODO */ } BitArrayOption::Utf32 { location: _ } => { /* TODO */ } BitArrayOption::Utf8Codepoint { location: _ } => { /* TODO */ } BitArrayOption::Utf16Codepoint { location: _ } => { /* TODO */ } BitArrayOption::Utf32Codepoint { location: _ } => { /* TODO */ } BitArrayOption::Signed { location: _ } => { /* TODO */ } BitArrayOption::Unsigned { location: _ } => { /* TODO */ } BitArrayOption::Big { location: _ } => { /* TODO */ } BitArrayOption::Little { location: _ } => { /* TODO */ } BitArrayOption::Native { location: _ } => { /* TODO */ } BitArrayOption::Size { location: _, value, short_form: _, } => { v.visit_typed_expr(value); } BitArrayOption::Unit { location: _, value: _, } => { /* TODO */ } } } pub fn visit_typed_pattern<'a, V>(v: &mut V, pattern: &'a TypedPattern) where V: Visit<'a> + ?Sized, { match pattern { Pattern::Int { location, value, int_value: _, } => v.visit_typed_pattern_int(location, value), Pattern::Float { location, value, float_value: _, } => v.visit_typed_pattern_float(location, value), Pattern::String { location, value } => v.visit_typed_pattern_string(location, value), Pattern::Variable { location, name, type_, origin, } => v.visit_typed_pattern_variable(location, name, type_, origin), Pattern::BitArraySize(size) => v.visit_typed_pattern_bit_array_size(size), Pattern::Assign { location, name, pattern, } => v.visit_typed_pattern_assign(location, name, pattern), Pattern::Discard { location, name, type_, } => v.visit_typed_pattern_discard(location, name, type_), Pattern::List { location, elements, tail, type_, } => v.visit_typed_pattern_list(location, elements, tail, type_), Pattern::Constructor { location, name_location, name, arguments, module, constructor, spread, type_, } => v.visit_typed_pattern_constructor( location, name_location, name, arguments, module, constructor, spread, type_, ), Pattern::Tuple { location, elements } => v.visit_typed_pattern_tuple(location, elements), Pattern::BitArray { location, segments } => { v.visit_typed_pattern_bit_array(location, segments) } Pattern::StringPrefix { location, left_location, left_side_assignment, right_location, left_side_string, right_side_assignment, } => v.visit_typed_pattern_string_prefix( location, left_location, left_side_assignment, right_location, left_side_string, right_side_assignment, ), Pattern::Invalid { location, type_ } => v.visit_typed_pattern_invalid(location, type_), } } fn visit_typed_pattern_int<'a, V>(_v: &mut V, _location: &'a SrcSpan, _value: &'a EcoString) where V: Visit<'a> + ?Sized, { } pub fn visit_typed_pattern_float<'a, V>(_v: &mut V, _location: &'a SrcSpan, _value: &'a EcoString) where V: Visit<'a> + ?Sized, { } pub fn visit_typed_pattern_string<'a, V>(_v: &mut V, _location: &'a SrcSpan, _value: &'a EcoString) where V: Visit<'a> + ?Sized, { } pub fn visit_typed_pattern_variable<'a, V>( _v: &mut V, _location: &'a SrcSpan, _name: &'a EcoString, _type_: &'a Arc, _origin: &'a VariableOrigin, ) where V: Visit<'a> + ?Sized, { } pub fn visit_typed_pattern_bit_array_size<'a, V>(v: &mut V, size: &'a TypedBitArraySize) where V: Visit<'a> + ?Sized, { match size { BitArraySize::Int { location, value, int_value: _, } => v.visit_typed_bit_array_size_int(location, value), BitArraySize::Variable { location, name, constructor, type_, } => v.visit_typed_bit_array_size_variable(location, name, constructor, type_), BitArraySize::BinaryOperator { left, right, .. } => { v.visit_typed_pattern_bit_array_size(left); v.visit_typed_pattern_bit_array_size(right); } BitArraySize::Block { inner, .. } => v.visit_typed_pattern_bit_array_size(inner), } } pub fn visit_typed_bit_array_size_int<'a, V>( _v: &mut V, _location: &'a SrcSpan, _value: &'a EcoString, ) where V: Visit<'a> + ?Sized, { } pub fn visit_typed_bit_array_size_variable<'a, V>( _v: &mut V, _location: &'a SrcSpan, _name: &'a EcoString, _constructor: &'a Option>, _type_: &'a Arc, ) where V: Visit<'a> + ?Sized, { } pub fn visit_typed_pattern_assign<'a, V>( v: &mut V, _location: &'a SrcSpan, _name: &'a EcoString, pattern: &'a TypedPattern, ) where V: Visit<'a> + ?Sized, { v.visit_typed_pattern(pattern); } pub fn visit_typed_pattern_discard<'a, V>( _v: &mut V, _location: &'a SrcSpan, _name: &'a EcoString, _type_: &'a Arc, ) where V: Visit<'a> + ?Sized, { } pub fn visit_typed_pattern_list<'a, V>( v: &mut V, _location: &'a SrcSpan, elements: &'a Vec, tail: &'a Option>, _type_: &'a Arc, ) where V: Visit<'a> + ?Sized, { for element in elements { v.visit_typed_pattern(element); } if let Some(tail) = tail { v.visit_typed_pattern(&tail.pattern); } } #[allow(clippy::too_many_arguments)] pub fn visit_typed_pattern_constructor<'a, V>( v: &mut V, _location: &'a SrcSpan, _name_location: &'a SrcSpan, _name: &'a EcoString, arguments: &'a Vec>, _module: &'a Option<(EcoString, SrcSpan)>, _constructor: &'a Inferred, _spread: &'a Option, _type_: &'a Arc, ) where V: Visit<'a> + ?Sized, { for argument in arguments { v.visit_typed_pattern_call_arg(argument); } } pub fn visit_typed_pattern_call_arg<'a, V>(v: &mut V, argument: &'a CallArg) where V: Visit<'a> + ?Sized, { v.visit_typed_pattern(&argument.value) } pub fn visit_typed_pattern_tuple<'a, V>( v: &mut V, _location: &'a SrcSpan, elements: &'a Vec, ) where V: Visit<'a> + ?Sized, { for element in elements { v.visit_typed_pattern(element); } } pub fn visit_typed_pattern_bit_array<'a, V>( v: &mut V, _location: &'a SrcSpan, segments: &'a [TypedPatternBitArraySegment], ) where V: Visit<'a> + ?Sized, { for segment in segments { v.visit_typed_pattern(&segment.value); for option in segment.options.iter() { v.visit_typed_pattern_bit_array_option(option); } } } pub fn visit_typed_pattern_bit_array_option<'a, V>( v: &mut V, option: &'a BitArrayOption, ) where V: Visit<'a> + ?Sized, { match option { BitArrayOption::Bytes { location: _ } => { /* TODO */ } BitArrayOption::Int { location: _ } => { /* TODO */ } BitArrayOption::Float { location: _ } => { /* TODO */ } BitArrayOption::Bits { location: _ } => { /* TODO */ } BitArrayOption::Utf8 { location: _ } => { /* TODO */ } BitArrayOption::Utf16 { location: _ } => { /* TODO */ } BitArrayOption::Utf32 { location: _ } => { /* TODO */ } BitArrayOption::Utf8Codepoint { location: _ } => { /* TODO */ } BitArrayOption::Utf16Codepoint { location: _ } => { /* TODO */ } BitArrayOption::Utf32Codepoint { location: _ } => { /* TODO */ } BitArrayOption::Signed { location: _ } => { /* TODO */ } BitArrayOption::Unsigned { location: _ } => { /* TODO */ } BitArrayOption::Big { location: _ } => { /* TODO */ } BitArrayOption::Little { location: _ } => { /* TODO */ } BitArrayOption::Native { location: _ } => { /* TODO */ } BitArrayOption::Size { location: _, value, short_form: _, } => { v.visit_typed_pattern(value); } BitArrayOption::Unit { location: _, value: _, } => { /* TODO */ } } } pub fn visit_typed_pattern_string_prefix<'a, V>( _v: &mut V, _location: &'a SrcSpan, _left_location: &'a SrcSpan, _left_side_assignment: &'a Option<(EcoString, SrcSpan)>, _right_location: &'a SrcSpan, _left_side_string: &'a EcoString, _right_side_assignment: &'a AssignName, ) where V: Visit<'a> + ?Sized, { } pub fn visit_typed_pattern_invalid<'a, V>(_v: &mut V, _location: &'a SrcSpan, _type_: &'a Arc) where V: Visit<'a> + ?Sized, { } pub fn visit_typed_expr_invalid<'a, V>( _v: &mut V, _location: &'a SrcSpan, _type_: &'a Arc, _extra_information: &'a Option, ) where V: Visit<'a> + ?Sized, { } ================================================ FILE: compiler-core/src/ast.rs ================================================ mod constant; mod typed; mod untyped; #[cfg(test)] mod tests; pub mod visit; pub use self::typed::{InvalidExpression, TypedExpr}; pub use self::untyped::{FunctionLiteralKind, UntypedExpr}; pub use self::constant::{Constant, TypedConstant, UntypedConstant}; use crate::analyse::Inferred; use crate::ast::typed::pairwise_all; use crate::bit_array; use crate::build::{ExpressionPosition, Located, Target, module_erlang_name}; use crate::exhaustiveness::CompiledCase; use crate::parse::{LiteralFloatValue, SpannedString}; use crate::type_::error::VariableOrigin; use crate::type_::expression::{Implementations, Purity}; use crate::type_::printer::Names; use crate::type_::{ self, Deprecation, HasType, ModuleValueConstructor, PatternConstructor, Type, TypedCallArg, ValueConstructor, ValueConstructorVariant, nil, }; use itertools::Itertools; use num_traits::Zero; use std::collections::HashSet; use std::sync::Arc; use ecow::EcoString; use num_bigint::{BigInt, Sign}; use num_traits::{One, ToPrimitive}; #[cfg(test)] use pretty_assertions::assert_eq; use vec1::Vec1; pub const PIPE_VARIABLE: &str = "_pipe"; pub const USE_ASSIGNMENT_VARIABLE: &str = "_use"; pub const RECORD_UPDATE_VARIABLE: &str = "_record"; pub const ASSERT_FAIL_VARIABLE: &str = "_assert_fail"; pub const ASSERT_SUBJECT_VARIABLE: &str = "_assert_subject"; pub const CAPTURE_VARIABLE: &str = "_capture"; pub const BLOCK_VARIABLE: &str = "_block"; pub trait HasLocation { fn location(&self) -> SrcSpan; } pub type UntypedModule = Module<(), Vec>; pub type TypedModule = Module; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Module { pub name: EcoString, pub documentation: Vec, pub type_info: Info, pub definitions: Definitions, pub names: Names, /// The source byte locations of definition that are unused. /// This is used in code generation to know when definitions can be safely omitted. pub unused_definition_positions: HashSet, } impl Module { pub fn erlang_name(&self) -> EcoString { module_erlang_name(&self.name) } } impl TypedModule { pub fn find_node(&self, byte_index: u32) -> Option> { let TypedDefinitions { imports, constants, custom_types, type_aliases, functions, } = &self.definitions; imports .iter() .find_map(|import| import.find_node(byte_index)) .or_else(|| (constants.iter()).find_map(|constant| constant.find_node(byte_index))) .or_else(|| (custom_types.iter()).find_map(|type_| type_.find_node(byte_index))) .or_else(|| (type_aliases.iter()).find_map(|alias| alias.find_node(byte_index))) .or_else(|| (functions.iter()).find_map(|function| function.find_node(byte_index))) } pub fn find_statement(&self, byte_index: u32) -> Option<&TypedStatement> { // Statements can only be found inside a module function, there's no // need to go over all the other module definitions. self.definitions .functions .iter() .find_map(|function| function.find_statement(byte_index)) } pub fn definitions_len(&self) -> usize { let TypedDefinitions { imports, constants, custom_types, type_aliases, functions, } = &self.definitions; imports.len() + constants.len() + custom_types.len() + type_aliases.len() + functions.len() } } #[derive(Debug)] pub struct TypedDefinitions { pub imports: Vec, pub constants: Vec, pub custom_types: Vec, pub type_aliases: Vec, pub functions: Vec, } /// The `@target(erlang)` and `@target(javascript)` attributes can be used to /// mark a definition as only being for a specific target. /// /// ```gleam /// const x: Int = 1 /// /// @target(erlang) /// pub fn main(a) { ...} /// ``` /// #[derive(Debug, Clone, PartialEq, Eq)] pub struct TargetedDefinition { pub definition: UntypedDefinition, pub target: Option, } impl TargetedDefinition { pub fn is_for(&self, target: Target) -> bool { self.target.map(|t| t == target).unwrap_or(true) } } impl UntypedModule { pub fn dependencies(&self, target: Target) -> Vec<(EcoString, SrcSpan)> { self.iter_definitions(target) .flat_map(|definition| match definition { Definition::Import(Import { module, location, .. }) => Some((module.clone(), *location)), Definition::Function(_) | Definition::TypeAlias(_) | Definition::CustomType(_) | Definition::ModuleConstant(_) => None, }) .collect() } pub fn iter_definitions(&self, target: Target) -> impl Iterator { self.definitions .iter() .filter(move |definition| definition.is_for(target)) .map(|definition| &definition.definition) } pub fn into_iter_definitions(self, target: Target) -> impl Iterator { self.definitions .into_iter() .filter(move |definition| definition.is_for(target)) .map(|definition| definition.definition) } } #[test] fn module_dependencies_test() { let parsed = crate::parse::parse_module( camino::Utf8PathBuf::from("test/path"), "import one @target(erlang) import two @target(javascript) import three import four", &crate::warning::WarningEmitter::null(), ) .expect("syntax error"); let module = parsed.module; assert_eq!( vec![ ("one".into(), SrcSpan::new(0, 10)), ("two".into(), SrcSpan::new(45, 55)), ("four".into(), SrcSpan::new(118, 129)), ], module.dependencies(Target::Erlang) ); } pub type TypedArg = Arg>; pub type UntypedArg = Arg<()>; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Arg { pub names: ArgNames, pub location: SrcSpan, pub annotation: Option, pub type_: T, } impl Arg { pub fn set_type(self, t: B) -> Arg { Arg { type_: t, names: self.names, location: self.location, annotation: self.annotation, } } pub fn get_variable_name(&self) -> Option<&EcoString> { self.names.get_variable_name() } pub fn is_capture_hole(&self) -> bool { match &self.names { ArgNames::Named { name, .. } if name == CAPTURE_VARIABLE => true, ArgNames::Discard { .. } | ArgNames::LabelledDiscard { .. } | ArgNames::Named { .. } | ArgNames::NamedLabelled { .. } => false, } } } impl TypedArg { pub fn find_node(&self, byte_index: u32) -> Option> { if self.location.contains(byte_index) { if let Some(annotation) = &self.annotation { return annotation .find_node(byte_index, self.type_.clone()) .or(Some(Located::Arg(self))); } Some(Located::Arg(self)) } else { None } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ArgNames { Discard { name: EcoString, location: SrcSpan, }, LabelledDiscard { label: EcoString, label_location: SrcSpan, name: EcoString, name_location: SrcSpan, }, Named { name: EcoString, location: SrcSpan, }, NamedLabelled { label: EcoString, label_location: SrcSpan, name: EcoString, name_location: SrcSpan, }, } impl ArgNames { pub fn get_label(&self) -> Option<&EcoString> { match self { ArgNames::Discard { .. } | ArgNames::Named { .. } => None, ArgNames::LabelledDiscard { label, .. } | ArgNames::NamedLabelled { label, .. } => { Some(label) } } } pub fn get_variable_name(&self) -> Option<&EcoString> { match self { ArgNames::Discard { .. } | ArgNames::LabelledDiscard { .. } => None, ArgNames::NamedLabelled { name, .. } | ArgNames::Named { name, .. } => Some(name), } } } pub type TypedRecordConstructor = RecordConstructor>; #[derive(Debug, Clone, PartialEq, Eq)] pub struct RecordConstructor { pub location: SrcSpan, pub name_location: SrcSpan, pub name: EcoString, pub arguments: Vec>, pub documentation: Option<(u32, EcoString)>, pub deprecation: Deprecation, } impl RecordConstructor { pub fn put_doc(&mut self, new_doc: (u32, EcoString)) { self.documentation = Some(new_doc); } } pub type TypedRecordConstructorArg = RecordConstructorArg>; #[derive(Debug, Clone, PartialEq, Eq)] pub struct RecordConstructorArg { pub label: Option, pub ast: TypeAst, pub location: SrcSpan, pub type_: T, pub doc: Option<(u32, EcoString)>, } impl RecordConstructorArg { pub fn put_doc(&mut self, new_doc: (u32, EcoString)) { self.doc = Some(new_doc); } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct TypeAstConstructor { pub location: SrcSpan, pub name_location: SrcSpan, pub module: Option<(EcoString, SrcSpan)>, pub name: EcoString, pub arguments: Vec, pub start_parentheses: Option, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct TypeAstFn { pub location: SrcSpan, pub arguments: Vec, pub return_: Box, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct TypeAstVar { pub location: SrcSpan, pub name: EcoString, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct TypeAstTuple { pub location: SrcSpan, pub elements: Vec, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct TypeAstHole { pub location: SrcSpan, pub name: EcoString, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum TypeAst { Constructor(TypeAstConstructor), Fn(TypeAstFn), Var(TypeAstVar), Tuple(TypeAstTuple), Hole(TypeAstHole), } impl TypeAst { pub fn location(&self) -> SrcSpan { match self { TypeAst::Fn(TypeAstFn { location, .. }) | TypeAst::Var(TypeAstVar { location, .. }) | TypeAst::Hole(TypeAstHole { location, .. }) | TypeAst::Tuple(TypeAstTuple { location, .. }) | TypeAst::Constructor(TypeAstConstructor { location, .. }) => *location, } } pub fn is_logically_equal(&self, other: &TypeAst) -> bool { match self { TypeAst::Constructor(TypeAstConstructor { module, name, arguments, location: _, name_location: _, start_parentheses: _, }) => match other { TypeAst::Constructor(TypeAstConstructor { module: o_module, name: o_name, arguments: o_arguments, location: _, name_location: _, start_parentheses: _, }) => { let module_name = |m: &Option<(EcoString, _)>| m.as_ref().map(|(m, _)| m.clone()); module_name(module) == module_name(o_module) && name == o_name && arguments.len() == o_arguments.len() && arguments .iter() .zip(o_arguments) .all(|a| a.0.is_logically_equal(a.1)) } TypeAst::Fn(_) | TypeAst::Var(_) | TypeAst::Tuple(_) | TypeAst::Hole(_) => false, }, TypeAst::Fn(TypeAstFn { arguments, return_, location: _, }) => match other { TypeAst::Fn(TypeAstFn { arguments: o_arguments, return_: o_return_, location: _, }) => { arguments.len() == o_arguments.len() && arguments .iter() .zip(o_arguments) .all(|a| a.0.is_logically_equal(a.1)) && return_.is_logically_equal(o_return_) } TypeAst::Constructor(_) | TypeAst::Var(_) | TypeAst::Tuple(_) | TypeAst::Hole(_) => false, }, TypeAst::Var(TypeAstVar { name, location: _ }) => match other { TypeAst::Var(TypeAstVar { name: o_name, location: _, }) => name == o_name, TypeAst::Constructor(_) | TypeAst::Fn(_) | TypeAst::Tuple(_) | TypeAst::Hole(_) => { false } }, TypeAst::Tuple(TypeAstTuple { elements, location: _, }) => match other { TypeAst::Tuple(TypeAstTuple { elements: other_elements, location: _, }) => { elements.len() == other_elements.len() && elements .iter() .zip(other_elements) .all(|a| a.0.is_logically_equal(a.1)) } TypeAst::Constructor(_) | TypeAst::Fn(_) | TypeAst::Var(_) | TypeAst::Hole(_) => { false } }, TypeAst::Hole(TypeAstHole { name, location: _ }) => match other { TypeAst::Hole(TypeAstHole { name: o_name, location: _, }) => name == o_name, TypeAst::Constructor(_) | TypeAst::Fn(_) | TypeAst::Var(_) | TypeAst::Tuple(_) => { false } }, } } pub fn find_node(&self, byte_index: u32, type_: Arc) -> Option> { if !self.location().contains(byte_index) { return None; } match self { TypeAst::Fn(TypeAstFn { arguments, return_, .. }) => type_ .fn_types() .and_then(|(arg_types, ret_type)| { if let Some(arg) = arguments .iter() .zip(arg_types) .find_map(|(arg, arg_type)| arg.find_node(byte_index, arg_type.clone())) { return Some(arg); } if let Some(ret) = return_.find_node(byte_index, ret_type) { return Some(ret); } None }) .or(Some(Located::Annotation { ast: self, type_ })), TypeAst::Constructor(TypeAstConstructor { arguments, module, .. }) => type_ .named_type_information() .and_then(|(module_name, _, arg_types)| { if let Some(arg) = arguments .iter() .zip(arg_types) .find_map(|(arg, arg_type)| arg.find_node(byte_index, arg_type.clone())) { return Some(arg); } if let Some((module_alias, location)) = module && location.contains(byte_index) { return Some(Located::ModuleName { location: *location, module_name, module_alias: module_alias.clone(), layer: Layer::Type, }); } None }) .or(Some(Located::Annotation { ast: self, type_ })), TypeAst::Tuple(TypeAstTuple { elements, .. }) => type_ .tuple_types() .and_then(|elem_types| { if let Some(e) = elements .iter() .zip(elem_types) .find_map(|(e, e_type)| e.find_node(byte_index, e_type.clone())) { return Some(e); } None }) .or(Some(Located::Annotation { ast: self, type_ })), TypeAst::Var(_) | TypeAst::Hole(_) => Some(Located::Annotation { ast: self, type_ }), } } /// Generates an annotation corresponding to the type. pub fn print(&self, buffer: &mut EcoString) { match &self { TypeAst::Var(var) => buffer.push_str(&var.name), TypeAst::Hole(hole) => buffer.push_str(&hole.name), TypeAst::Tuple(tuple) => { buffer.push_str("#("); for (i, element) in tuple.elements.iter().enumerate() { element.print(buffer); if i < tuple.elements.len() - 1 { buffer.push_str(", "); } } buffer.push(')') } TypeAst::Fn(func) => { buffer.push_str("fn("); for (i, argument) in func.arguments.iter().enumerate() { argument.print(buffer); if i < func.arguments.len() - 1 { buffer.push_str(", "); } } buffer.push(')'); buffer.push_str(" -> "); func.return_.print(buffer); } TypeAst::Constructor(constructor) => { if let Some((module, _)) = &constructor.module { buffer.push_str(module); buffer.push('.'); } buffer.push_str(&constructor.name); if !constructor.arguments.is_empty() { buffer.push('('); for (i, argument) in constructor.arguments.iter().enumerate() { argument.print(buffer); if i < constructor.arguments.len() - 1 { buffer.push_str(", "); } } buffer.push(')'); } } } } } #[test] fn type_ast_print_fn() { let mut buffer = EcoString::new(); let ast = TypeAst::Fn(TypeAstFn { location: SrcSpan { start: 1, end: 1 }, arguments: vec![ TypeAst::Var(TypeAstVar { location: SrcSpan { start: 1, end: 1 }, name: "String".into(), }), TypeAst::Var(TypeAstVar { location: SrcSpan { start: 1, end: 1 }, name: "Bool".into(), }), ], return_: Box::new(TypeAst::Var(TypeAstVar { location: SrcSpan { start: 1, end: 1 }, name: "Int".into(), })), }); ast.print(&mut buffer); assert_eq!(&buffer, "fn(String, Bool) -> Int") } #[test] fn type_ast_print_constructor() { let mut buffer = EcoString::new(); let ast = TypeAst::Constructor(TypeAstConstructor { name: "SomeType".into(), module: Some(("some_module".into(), SrcSpan { start: 1, end: 1 })), location: SrcSpan { start: 1, end: 1 }, name_location: SrcSpan { start: 1, end: 1 }, arguments: vec![ TypeAst::Var(TypeAstVar { location: SrcSpan { start: 1, end: 1 }, name: "String".into(), }), TypeAst::Var(TypeAstVar { location: SrcSpan { start: 1, end: 1 }, name: "Bool".into(), }), ], start_parentheses: Some(1), }); ast.print(&mut buffer); assert_eq!(&buffer, "some_module.SomeType(String, Bool)") } #[test] fn type_ast_print_tuple() { let mut buffer = EcoString::new(); let ast = TypeAst::Tuple(TypeAstTuple { location: SrcSpan { start: 1, end: 1 }, elements: vec![ TypeAst::Constructor(TypeAstConstructor { name: "SomeType".into(), module: Some(("some_module".into(), SrcSpan { start: 1, end: 1 })), location: SrcSpan { start: 1, end: 1 }, name_location: SrcSpan { start: 1, end: 1 }, arguments: vec![ TypeAst::Var(TypeAstVar { location: SrcSpan { start: 1, end: 1 }, name: "String".into(), }), TypeAst::Var(TypeAstVar { location: SrcSpan { start: 1, end: 1 }, name: "Bool".into(), }), ], start_parentheses: Some(1), }), TypeAst::Fn(TypeAstFn { location: SrcSpan { start: 1, end: 1 }, arguments: vec![ TypeAst::Var(TypeAstVar { location: SrcSpan { start: 1, end: 1 }, name: "String".into(), }), TypeAst::Var(TypeAstVar { location: SrcSpan { start: 1, end: 1 }, name: "Bool".into(), }), ], return_: Box::new(TypeAst::Var(TypeAstVar { location: SrcSpan { start: 1, end: 1 }, name: "Int".into(), })), }), ], }); ast.print(&mut buffer); assert_eq!( &buffer, "#(some_module.SomeType(String, Bool), fn(String, Bool) -> Int)" ) } #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum Publicity { Public, Private, Internal { attribute_location: Option }, } impl Publicity { pub fn is_private(&self) -> bool { match self { Self::Private => true, Self::Public | Self::Internal { .. } => false, } } pub fn is_internal(&self) -> bool { match self { Self::Internal { .. } => true, Self::Public | Self::Private => false, } } pub fn is_public(&self) -> bool { match self { Self::Public => true, Self::Internal { .. } | Self::Private => false, } } pub fn is_importable(&self) -> bool { match self { Self::Internal { .. } | Self::Public => true, Self::Private => false, } } } #[derive(Debug, Clone, PartialEq, Eq)] /// A function definition /// /// Note that an anonymous function will have `None` as the name field, while a /// named function will have `Some`. /// /// # Example(s) /// /// ```gleam /// // Public function /// pub fn wobble() -> String { ... } /// // Private function /// fn wibble(x: Int) -> Int { ... } /// // Anonymous function /// fn(x: Int) { ... } /// ``` pub struct Function { pub location: SrcSpan, pub body_start: Option, pub end_position: u32, pub name: Option, pub arguments: Vec>, pub body: Vec>, pub publicity: Publicity, pub deprecation: Deprecation, pub return_annotation: Option, pub return_type: T, pub documentation: Option<(u32, EcoString)>, pub external_erlang: Option<(EcoString, EcoString, SrcSpan)>, pub external_javascript: Option<(EcoString, EcoString, SrcSpan)>, pub implementations: Implementations, pub purity: Purity, } pub type TypedFunction = Function, TypedExpr>; pub type UntypedFunction = Function<(), UntypedExpr>; impl Function { pub fn full_location(&self) -> SrcSpan { SrcSpan::new(self.location.start, self.end_position) } } impl TypedFunction { pub fn find_node(&self, byte_index: u32) -> Option> { // Search for the corresponding node inside the function // only if the index falls within the function's full location. if !self.full_location().contains(byte_index) { return None; } if let Some(found) = self .body .iter() .find_map(|statement| statement.find_node(byte_index)) { return Some(found); } if let Some(found_arg) = self .arguments .iter() .find_map(|arg| arg.find_node(byte_index)) { return Some(found_arg); }; if let Some(found_statement) = self .body .iter() .find(|statement| statement.location().contains(byte_index)) { return Some(Located::Statement(found_statement)); }; // Check if location is within the return annotation. if let Some(located) = self .return_annotation .iter() .find_map(|annotation| annotation.find_node(byte_index, self.return_type.clone())) { return Some(located); }; // Note that the fn `.location` covers the function head, not // the entire statement. if self.location.contains(byte_index) { Some(Located::ModuleFunction(self)) } else if self.full_location().contains(byte_index) { Some(Located::FunctionBody(self)) } else { None } } pub fn find_statement(&self, byte_index: u32) -> Option<&TypedStatement> { if !self.full_location().contains(byte_index) { return None; } self.body .iter() .find_map(|statement| statement.find_statement(byte_index)) } pub fn main_function(&self) -> Option<&TypedFunction> { if let Some((_, name)) = &self.name && name == "main" { Some(self) } else { None } } } pub type UntypedImport = Import<()>; pub type TypedImport = Import; #[derive(Debug, Clone, PartialEq, Eq)] /// Import another Gleam module so the current module can use the types and /// values it defines. /// /// # Example(s) /// /// ```gleam /// import unix/cat /// // Import with alias /// import animal/cat as kitty /// ``` pub struct Import { pub documentation: Option, pub location: SrcSpan, pub module_location: SrcSpan, pub module: EcoString, pub as_name: Option<(AssignName, SrcSpan)>, pub unqualified_values: Vec, pub unqualified_types: Vec, pub package: PackageName, } impl Import { pub fn used_name(&self) -> Option { match self.as_name.as_ref() { Some((AssignName::Variable(name), _)) => Some(name.clone()), Some((AssignName::Discard(_), _)) => None, None => self.module.split('/').next_back().map(EcoString::from), } } pub(crate) fn alias_location(&self) -> Option { self.as_name.as_ref().map(|(_, location)| *location) } } impl TypedImport { pub fn find_node(&self, byte_index: u32) -> Option> { if !self.location.contains(byte_index) { return None; } if let Some(unqualified) = self .unqualified_values .iter() .find(|unqualified_value| unqualified_value.location.contains(byte_index)) { return Some(Located::UnqualifiedImport( crate::build::UnqualifiedImport { name: &unqualified.name, module: &self.module, is_type: false, location: &unqualified.location, }, )); } if let Some(unqualified) = self .unqualified_types .iter() .find(|unqualified_value| unqualified_value.location.contains(byte_index)) { return Some(Located::UnqualifiedImport( crate::build::UnqualifiedImport { name: &unqualified.name, module: &self.module, is_type: true, location: &unqualified.location, }, )); } Some(Located::ModuleImport(self)) } } pub type UntypedModuleConstant = ModuleConstant<(), ()>; pub type TypedModuleConstant = ModuleConstant, EcoString>; #[derive(Debug, Clone, PartialEq, Eq)] /// A certain fixed value that can be used in multiple places /// /// # Example(s) /// /// ```gleam /// pub const start_year = 2101 /// pub const end_year = 2111 /// ``` pub struct ModuleConstant { pub documentation: Option<(u32, EcoString)>, /// The location of the constant, starting at the "(pub) const" keywords and /// ending after the ": Type" annotation, or (without an annotation) after its name. pub location: SrcSpan, pub publicity: Publicity, pub name: EcoString, pub name_location: SrcSpan, pub annotation: Option, pub value: Box>, pub type_: T, pub deprecation: Deprecation, pub implementations: Implementations, } impl TypedModuleConstant { pub fn find_node(&self, byte_index: u32) -> Option> { // Check if location is within the annotation. if let Some(annotation) = &self.annotation && let Some(located) = annotation.find_node(byte_index, self.type_.clone()) { return Some(located); } if let Some(located) = self.value.find_node(byte_index) { return Some(located); } if self.location.contains(byte_index) { Some(Located::ModuleConstant(self)) } else { None } } } pub type UntypedCustomType = CustomType<()>; pub type TypedCustomType = CustomType>; #[derive(Debug, Clone, PartialEq, Eq)] /// A newly defined type with one or more constructors. /// Each variant of the custom type can contain different types, so the type is /// the product of the types contained by each variant. /// /// This might be called an algebraic data type (ADT) or tagged union in other /// languages and type systems. /// /// /// # Example(s) /// /// ```gleam /// pub type Cat { /// Cat(name: String, cuteness: Int) /// } /// ``` pub struct CustomType { pub location: SrcSpan, pub end_position: u32, pub name: EcoString, pub name_location: SrcSpan, pub publicity: Publicity, pub constructors: Vec>, pub documentation: Option<(u32, EcoString)>, pub deprecation: Deprecation, pub opaque: bool, /// The names of the type parameters. pub parameters: Vec, /// Once type checked this field will contain the type information for the /// type parameters. pub typed_parameters: Vec, pub external_erlang: Option<(EcoString, EcoString, SrcSpan)>, pub external_javascript: Option<(EcoString, EcoString, SrcSpan)>, } impl CustomType { /// The `location` field of a `CustomType` is only the location of `pub type /// TheName`. This method returns a `SrcSpan` that includes the entire type /// definition. pub fn full_location(&self) -> SrcSpan { SrcSpan::new(self.location.start, self.end_position) } } impl TypedCustomType { pub fn find_node(&self, byte_index: u32) -> Option> { // Check if location is within the type of one of the arguments of a constructor. if let Some(constructor) = self .constructors .iter() .find(|constructor| constructor.location.contains(byte_index)) { if let Some(annotation) = constructor .arguments .iter() .find(|arg| arg.location.contains(byte_index)) .and_then(|arg| arg.ast.find_node(byte_index, arg.type_.clone())) { return Some(annotation); } return Some(Located::VariantConstructorDefinition(constructor)); } // Note that the custom type `.location` covers the function // head, not the entire statement. if self.full_location().contains(byte_index) { Some(Located::ModuleCustomType(self)) } else { None } } } pub type UntypedTypeAlias = TypeAlias<()>; pub type TypedTypeAlias = TypeAlias>; #[derive(Debug, Clone, PartialEq, Eq)] /// A new name for an existing type /// /// # Example(s) /// /// ```gleam /// pub type Headers = /// List(#(String, String)) /// ``` pub struct TypeAlias { pub location: SrcSpan, pub alias: EcoString, pub name_location: SrcSpan, pub parameters: Vec, pub type_ast: TypeAst, pub type_: T, pub publicity: Publicity, pub documentation: Option<(u32, EcoString)>, pub deprecation: Deprecation, } impl TypedTypeAlias { pub fn find_node(&self, byte_index: u32) -> Option> { // Check if location is within the type being aliased. if let Some(located) = self.type_ast.find_node(byte_index, self.type_.clone()) { return Some(located); } if self.location.contains(byte_index) { Some(Located::ModuleTypeAlias(self)) } else { None } } } pub type UntypedDefinition = Definition<(), UntypedExpr, (), ()>; #[derive(Debug, Clone, PartialEq, Eq)] pub enum Definition { Function(Function), TypeAlias(TypeAlias), CustomType(CustomType), Import(Import), ModuleConstant(ModuleConstant), } impl Definition { pub fn location(&self) -> SrcSpan { match self { Definition::Function(Function { location, .. }) | Definition::Import(Import { location, .. }) | Definition::TypeAlias(TypeAlias { location, .. }) | Definition::CustomType(CustomType { location, .. }) | Definition::ModuleConstant(ModuleConstant { location, .. }) => *location, } } /// Returns `true` if the definition is [`Import`]. /// /// [`Import`]: Definition::Import #[must_use] pub fn is_import(&self) -> bool { matches!(self, Self::Import(..)) } /// Returns `true` if the module statement is [`Function`]. /// /// [`Function`]: ModuleStatement::Function #[must_use] pub fn is_function(&self) -> bool { matches!(self, Self::Function(..)) } /// Returns `true` if the module statement is [`CustomType`]. /// /// [`CustomType`]: ModuleStatement::CustomType #[must_use] pub fn is_custom_type(&self) -> bool { matches!(self, Self::CustomType(..)) } pub fn get_doc(&self) -> Option { match self { Definition::Import(Import { .. }) => None, Definition::Function(Function { documentation: doc, .. }) | Definition::TypeAlias(TypeAlias { documentation: doc, .. }) | Definition::CustomType(CustomType { documentation: doc, .. }) | Definition::ModuleConstant(ModuleConstant { documentation: doc, .. }) => doc.as_ref().map(|(_, doc)| doc.clone()), } } pub fn is_internal(&self) -> bool { match self { Definition::Function(Function { publicity, .. }) | Definition::CustomType(CustomType { publicity, .. }) | Definition::ModuleConstant(ModuleConstant { publicity, .. }) | Definition::TypeAlias(TypeAlias { publicity, .. }) => publicity.is_internal(), Definition::Import(_) => false, } } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct UnqualifiedImport { pub location: SrcSpan, /// The location excluding the potential `as ...` clause, or the `type` keyword pub imported_name_location: SrcSpan, pub name: EcoString, pub as_name: Option, } impl UnqualifiedImport { pub fn used_name(&self) -> &EcoString { self.as_name.as_ref().unwrap_or(&self.name) } } #[derive(Debug, Clone, PartialEq, Eq, Copy, Default, serde::Serialize, serde::Deserialize)] pub enum Layer { #[default] Value, Type, } impl Layer { /// Returns `true` if the layer is [`Value`]. pub fn is_value(&self) -> bool { matches!(self, Self::Value) } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BinOp { // Boolean logic And, Or, // Equality Eq, NotEq, // Order comparison LtInt, LtEqInt, LtFloat, LtEqFloat, GtEqInt, GtInt, GtEqFloat, GtFloat, // Maths AddInt, AddFloat, SubInt, SubFloat, MultInt, MultFloat, DivInt, DivFloat, RemainderInt, // Strings Concatenate, } #[derive(Clone, Copy, Debug, PartialEq)] pub enum OperatorKind { BooleanLogic, Equality, IntComparison, FLoatComparison, IntMath, FloatMath, StringConcatenation, } pub const PIPE_PRECEDENCE: u8 = 6; impl BinOp { pub fn precedence(&self) -> u8 { // Ensure that this matches the other precedence function for guards match self { Self::Or => 1, Self::And => 2, Self::Eq | Self::NotEq => 3, Self::LtInt | Self::LtEqInt | Self::LtFloat | Self::LtEqFloat | Self::GtEqInt | Self::GtInt | Self::GtEqFloat | Self::GtFloat => 4, Self::Concatenate => 5, // Pipe is 6 Self::AddInt | Self::AddFloat | Self::SubInt | Self::SubFloat => 7, Self::MultInt | Self::MultFloat | Self::DivInt | Self::DivFloat | Self::RemainderInt => 8, } } pub fn name(&self) -> &'static str { match self { Self::And => "&&", Self::Or => "||", Self::LtInt => "<", Self::LtEqInt => "<=", Self::LtFloat => "<.", Self::LtEqFloat => "<=.", Self::Eq => "==", Self::NotEq => "!=", Self::GtEqInt => ">=", Self::GtInt => ">", Self::GtEqFloat => ">=.", Self::GtFloat => ">.", Self::AddInt => "+", Self::AddFloat => "+.", Self::SubInt => "-", Self::SubFloat => "-.", Self::MultInt => "*", Self::MultFloat => "*.", Self::DivInt => "/", Self::DivFloat => "/.", Self::RemainderInt => "%", Self::Concatenate => "<>", } } pub fn operator_kind(&self) -> OperatorKind { match self { Self::Concatenate => OperatorKind::StringConcatenation, Self::Eq | Self::NotEq => OperatorKind::Equality, Self::And | Self::Or => OperatorKind::BooleanLogic, Self::LtInt | Self::LtEqInt | Self::GtEqInt | Self::GtInt => { OperatorKind::IntComparison } Self::LtFloat | Self::LtEqFloat | Self::GtEqFloat | Self::GtFloat => { OperatorKind::FLoatComparison } Self::AddInt | Self::SubInt | Self::MultInt | Self::RemainderInt | Self::DivInt => { OperatorKind::IntMath } Self::AddFloat | Self::SubFloat | Self::MultFloat | Self::DivFloat => { OperatorKind::FloatMath } } } pub fn can_be_grouped_with(&self, other: &BinOp) -> bool { self.operator_kind() == other.operator_kind() } pub fn is_float_operator(&self) -> bool { match self { BinOp::LtFloat | BinOp::LtEqFloat | BinOp::GtEqFloat | BinOp::GtFloat | BinOp::AddFloat | BinOp::SubFloat | BinOp::MultFloat | BinOp::DivFloat => true, BinOp::And | BinOp::Or | BinOp::Eq | BinOp::NotEq | BinOp::LtInt | BinOp::LtEqInt | BinOp::GtEqInt | BinOp::GtInt | BinOp::AddInt | BinOp::SubInt | BinOp::MultInt | BinOp::DivInt | BinOp::RemainderInt | BinOp::Concatenate => false, } } fn is_bool_operator(&self) -> bool { match self { BinOp::And | BinOp::Or => true, BinOp::Eq | BinOp::NotEq | BinOp::LtInt | BinOp::LtEqInt | BinOp::LtFloat | BinOp::LtEqFloat | BinOp::GtEqInt | BinOp::GtInt | BinOp::GtEqFloat | BinOp::GtFloat | BinOp::AddInt | BinOp::AddFloat | BinOp::SubInt | BinOp::SubFloat | BinOp::MultInt | BinOp::MultFloat | BinOp::DivInt | BinOp::DivFloat | BinOp::RemainderInt | BinOp::Concatenate => false, } } pub fn is_int_operator(&self) -> bool { match self { BinOp::LtInt | BinOp::LtEqInt | BinOp::GtEqInt | BinOp::GtInt | BinOp::AddInt | BinOp::SubInt | BinOp::MultInt | BinOp::DivInt | BinOp::RemainderInt => true, BinOp::And | BinOp::Or | BinOp::Eq | BinOp::NotEq | BinOp::LtFloat | BinOp::LtEqFloat | BinOp::GtEqFloat | BinOp::GtFloat | BinOp::AddFloat | BinOp::SubFloat | BinOp::MultFloat | BinOp::DivFloat | BinOp::Concatenate => false, } } pub fn float_equivalent(&self) -> Option { match self { BinOp::LtInt => Some(BinOp::LtFloat), BinOp::LtEqInt => Some(BinOp::LtEqFloat), BinOp::GtEqInt => Some(BinOp::GtEqFloat), BinOp::GtInt => Some(BinOp::GtFloat), BinOp::AddInt => Some(BinOp::AddFloat), BinOp::SubInt => Some(BinOp::SubFloat), BinOp::MultInt => Some(BinOp::MultFloat), BinOp::DivInt => Some(BinOp::DivFloat), BinOp::And | BinOp::Or | BinOp::Eq | BinOp::NotEq | BinOp::LtFloat | BinOp::LtEqFloat | BinOp::GtEqFloat | BinOp::GtFloat | BinOp::AddFloat | BinOp::SubFloat | BinOp::MultFloat | BinOp::DivFloat | BinOp::RemainderInt | BinOp::Concatenate => None, } } pub fn int_equivalent(&self) -> Option { match self { BinOp::LtFloat => Some(BinOp::LtInt), BinOp::LtEqFloat => Some(BinOp::LtEqInt), BinOp::GtEqFloat => Some(BinOp::GtEqInt), BinOp::GtFloat => Some(BinOp::GtInt), BinOp::AddFloat => Some(BinOp::AddInt), BinOp::SubFloat => Some(BinOp::SubInt), BinOp::MultFloat => Some(BinOp::MultInt), BinOp::DivFloat => Some(BinOp::DivInt), BinOp::And | BinOp::Or | BinOp::Eq | BinOp::NotEq | BinOp::LtInt | BinOp::LtEqInt | BinOp::GtEqInt | BinOp::GtInt | BinOp::AddInt | BinOp::SubInt | BinOp::MultInt | BinOp::DivInt | BinOp::RemainderInt | BinOp::Concatenate => None, } } } #[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)] pub struct CallArg { pub label: Option, pub location: SrcSpan, pub value: A, pub implicit: Option, } #[derive(Debug, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)] pub enum ImplicitCallArgOrigin { /// The implicit callback argument passed as the last argument to the /// function on the right hand side of `use`. /// Use, /// An argument added by the compiler when rewriting a pipe `left |> right`. /// Pipe, /// An argument added by the compiler to fill in all the missing fields of a /// record that are being ignored with the `..` syntax. /// PatternFieldSpread, /// An argument used to fill in the missing args when a function on the /// right hand side of `use` is being called with the wrong arity. /// IncorrectArityUse, /// An argument added by the compiler to fill in the missing args when using /// the record update synax. /// RecordUpdate, } impl CallArg { #[must_use] pub fn is_implicit(&self) -> bool { self.implicit.is_some() } #[must_use] pub fn is_use_implicit_callback(&self) -> bool { match self.implicit { Some(ImplicitCallArgOrigin::Use | ImplicitCallArgOrigin::IncorrectArityUse) => true, Some(_) | None => false, } } } impl CallArg { pub fn find_node<'a>( &'a self, byte_index: u32, called_function: &'a TypedExpr, function_arguments: &'a [TypedCallArg], ) -> Option> { match (self.implicit, &self.value) { // If a call argument is the implicit use callback then we don't // want to look at its arguments and body but we don't want to // return the whole anonymous function if anything else doesn't // match. // // In addition, if the callback is invalid because it couldn't be // typed, we don't want to return it as it would make it hard for // the LSP to give any suggestions on the use function being typed. // (Some(ImplicitCallArgOrigin::Use), TypedExpr::Invalid { .. }) => None, // So the code below is exactly the same as // `TypedExpr::Fn{}.find_node()` except we do not return self as a // fallback. // ( Some(ImplicitCallArgOrigin::Use), TypedExpr::Fn { arguments, body, .. }, ) => arguments .iter() .find_map(|argument| argument.find_node(byte_index)) .or_else(|| body.iter().find_map(|s| s.find_node(byte_index))), // In all other cases we're happy with the default behaviour. // _ => match self.value.find_node(byte_index) { Some(Located::Expression { expression, .. }) // This is only possibly a label if we are at the end of the expression // (so not in the middle like `[abc|]`) and if this argument doesn't // already have a label. if byte_index == self.value.location().end && self.label.is_none() => { Some(Located::Expression { expression, position: ExpressionPosition::ArgumentOrLabel { called_function, function_arguments, }, }) } Some(located) => Some(located), None => { if self.location.contains(byte_index) && self.label.is_some() { Some(Located::Label(self.location, self.value.type_())) } else { None } } }, } } pub fn find_statement(&self, byte_index: u32) -> Option<&TypedStatement> { match (self.implicit, &self.value) { (Some(ImplicitCallArgOrigin::Use), TypedExpr::Invalid { .. }) => None, (Some(ImplicitCallArgOrigin::Use), TypedExpr::Fn { body, .. }) => { body.iter().find_map(|s| s.find_statement(byte_index)) } _ => self.value.find_statement(byte_index), } } pub fn is_capture_hole(&self) -> bool { match &self.value { TypedExpr::Var { name, .. } => name == CAPTURE_VARIABLE, TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => false, } } } impl CallArg { pub fn find_node(&self, byte_index: u32) -> Option> { match self.value.find_node(byte_index) { Some(located) => Some(located), _ => { if self.location.contains(byte_index) && self.label.is_some() { Some(Located::Label(self.location, self.value.type_())) } else { None } } } } } impl CallArg { pub fn find_node(&self, byte_index: u32) -> Option> { match self.value.find_node(byte_index) { Some(located) => Some(located), _ => { if self.location.contains(byte_index) && self.label.is_some() { Some(Located::Label(self.location, self.value.type_())) } else { None } } } } } impl CallArg { pub fn is_capture_hole(&self) -> bool { match &self.value { UntypedExpr::Var { name, .. } => name == CAPTURE_VARIABLE, UntypedExpr::Int { .. } | UntypedExpr::Float { .. } | UntypedExpr::String { .. } | UntypedExpr::Block { .. } | UntypedExpr::Fn { .. } | UntypedExpr::List { .. } | UntypedExpr::Call { .. } | UntypedExpr::BinOp { .. } | UntypedExpr::PipeLine { .. } | UntypedExpr::Case { .. } | UntypedExpr::FieldAccess { .. } | UntypedExpr::Tuple { .. } | UntypedExpr::TupleIndex { .. } | UntypedExpr::Todo { .. } | UntypedExpr::Panic { .. } | UntypedExpr::Echo { .. } | UntypedExpr::BitArray { .. } | UntypedExpr::RecordUpdate { .. } | UntypedExpr::NegateBool { .. } | UntypedExpr::NegateInt { .. } => false, } } } impl CallArg where T: HasLocation, { #[must_use] pub fn uses_label_shorthand(&self) -> bool { self.label_shorthand_name().is_some() } /// If the call arg is defined using a label shorthand, this will return the /// label name. /// pub fn label_shorthand_name(&self) -> Option<&EcoString> { if !self.is_implicit() && self.location == self.value.location() { self.label.as_ref() } else { None } } } impl HasLocation for CallArg { fn location(&self) -> SrcSpan { self.location } } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct RecordBeingUpdated { pub base: Box, pub location: SrcSpan, } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct RecordUpdateArg { pub label: EcoString, pub location: SrcSpan, pub value: A, } pub type UntypedRecordUpdateArg = RecordUpdateArg; impl HasLocation for RecordUpdateArg { fn location(&self) -> SrcSpan { self.location } } impl RecordUpdateArg { #[must_use] pub fn uses_label_shorthand(&self) -> bool { self.value.location() == self.location } } pub type MultiPattern = Vec>; pub type UntypedMultiPattern = MultiPattern<()>; pub type TypedMultiPattern = MultiPattern>; pub type TypedClause = Clause, EcoString>; pub type UntypedClause = Clause; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Clause { pub location: SrcSpan, pub pattern: MultiPattern, pub alternative_patterns: Vec>, pub guard: Option>, pub then: Expr, } impl Clause { pub fn pattern_count(&self) -> usize { 1 + self.alternative_patterns.len() } } impl TypedClause { pub fn location(&self) -> SrcSpan { SrcSpan { start: self .pattern .first() .map(|p| p.location().start) .unwrap_or_default(), end: self.then.location().end, } } pub fn find_node(&self, byte_index: u32) -> Option> { self.pattern .iter() .find_map(|p| p.find_node(byte_index)) .or_else(|| { self.alternative_patterns .iter() .flat_map(|p| p.iter()) .find_map(|p| p.find_node(byte_index)) }) .or_else(|| { self.guard .as_ref() .and_then(|guard| guard.find_node(byte_index)) }) .or_else(|| self.then.find_node(byte_index)) } pub fn pattern_location(&self) -> SrcSpan { let start = self.pattern.first().map(|pattern| pattern.location().start); let end = if let Some(last_pattern) = self .alternative_patterns .last() .and_then(|patterns| patterns.last()) { Some(last_pattern.location().end) } else { self.pattern.last().map(|pattern| pattern.location().end) }; SrcSpan::new(start.unwrap_or_default(), end.unwrap_or_default()) } /// If the branch is rebuilding exactly one of the matched subjects and /// returning it, this will return the index of that subject. /// /// For example: /// - `n -> n`, `1 -> 1`, `Ok(1) -> Ok(1)` all return `Some(0)` /// - `"a", n -> n`, `n, m if n == m -> a` all return `Some(1)` /// - `_ -> 1`, `Ok(1), _ -> Ok(2)` all return `None` /// ``` /// pub fn returned_subject(&self) -> Option { // The pattern must not have any alternative patterns. if !self.alternative_patterns.is_empty() { return None; } self.pattern .iter() .find_position(|pattern| pattern_and_expression_are_the_same(pattern, &self.then)) .map(|(position, _)| position) } /// This returns the names of all the variables bound in this case clause. /// For example if we had `#(a, b) | c` this will return "a", "b", and "c". pub fn bound_variables(&self) -> impl Iterator { std::iter::once(&self.pattern) .chain(&self.alternative_patterns) .flatten() .flat_map(|pattern| pattern.bound_variables()) } fn syntactically_eq(&self, other: &Self) -> bool { let patterns_are_equal = pairwise_all(&self.pattern, &other.pattern, |(one, other)| { one.syntactically_eq(other) }); let alternatives_are_equal = pairwise_all( &self.alternative_patterns, &other.alternative_patterns, |(patterns_one, patterns_other)| { pairwise_all(patterns_one, patterns_other, |(one, other)| -> bool { one.syntactically_eq(other) }) }, ); let guards_are_equal = match (&self.guard, &other.guard) { (None, None) => true, (None, Some(_)) | (Some(_), None) => false, (Some(one), Some(other)) => one.syntactically_eq(other), }; patterns_are_equal && alternatives_are_equal && guards_are_equal && self.then.syntactically_eq(&other.then) } } /// Returns true if a pattern and an expression are the same: that is the expression /// would be building the exact matched value back. /// For example, if I had a branch like this: /// /// ```gleam /// [a, b, c] -> [a, b, c] /// ``` /// /// The pattern and the expression would indeed be the same. However, if I had /// something like this: /// /// ```gleam /// [first, ..rest] -> [first] /// ``` /// /// They wouldn't be the same! I'm not building back exactly the value the /// pattern can match on. /// fn pattern_and_expression_are_the_same(pattern: &TypedPattern, expression: &TypedExpr) -> bool { match (pattern, expression) { // A pattern could be the same as a block if the block is wrapping just // a single expression that is the same as the pattern itself! (pattern, TypedExpr::Block { statements, .. }) if statements.len() == 1 => { match statements.first() { Statement::Assignment(_) | Statement::Use(_) | Statement::Assert(_) => false, Statement::Expression(expression) => { pattern_and_expression_are_the_same(pattern, expression) } } } // If the block has many statements then it can never be the same as a // pattern. (_, TypedExpr::Block { .. }) => false, // A pattern and an expression are the same if they're a simple variable // with exactly the same name: `x -> x`, `a -> a` ( TypedPattern::Variable { name: pattern_var, .. }, TypedExpr::Var { name: body_var, .. }, ) => pattern_var == body_var, (TypedPattern::Variable { .. }, _) => false, // Floats, Ints, and Strings are the same if they are exactly the same // literal. // `1 -> 1` // `1.1 -> 1.1` // `"wibble" -> "wibble"` ( TypedPattern::Float { float_value: pattern_value, .. }, TypedExpr::Float { float_value, .. }, ) => pattern_value == float_value, (TypedPattern::Float { .. }, _) => false, ( TypedPattern::Int { int_value: pattern_value, .. }, TypedExpr::Int { int_value, .. }, ) => pattern_value == int_value, (TypedPattern::Int { .. }, _) => false, ( TypedPattern::String { value: pattern_value, .. }, TypedExpr::String { value, .. }, ) => pattern_value == value, (TypedPattern::String { .. }, _) => false, // A string prefix is equivalent to building the string back: // `"wibble" <> wobble -> "wibble" <> wobble` // `"wibble" as a <> wobble -> a <> wobble` ( TypedPattern::StringPrefix { left_side_assignment, left_side_string, right_side_assignment, .. }, TypedExpr::BinOp { name: BinOp::Concatenate, left, right, .. }, ) => { let left_side_matches = match (left_side_assignment, left_side_string, left.as_ref()) { (_, left_side_string, TypedExpr::String { value, .. }) => value == left_side_string, (Some((left_side_name, _)), _, TypedExpr::Var { name, .. }) => { left_side_name == name } (_, _, _) => false, }; let right_side_matches = match (right_side_assignment, right.as_ref()) { (AssignName::Variable(right_side_name), TypedExpr::Var { name, .. }) => { name == right_side_name } (AssignName::Variable(_) | AssignName::Discard(_), _) => false, }; left_side_matches && right_side_matches } (TypedPattern::StringPrefix { .. }, _) => false, // Two tuples where each element is equivalent to the other: // `#(a, 1, "wibble") -> #(a, 1, "wibble")` // `#(a, b) -> #(a, b)` ( TypedPattern::Tuple { elements: pattern_elements, .. }, TypedExpr::Tuple { elements, .. }, ) => { pattern_elements.len() == elements.len() && pattern_elements .iter() .zip(elements) .all(|(pattern, expression)| { pattern_and_expression_are_the_same(pattern, expression) }) } (TypedPattern::Tuple { .. }, _) => false, // Two lists are the same if each element is equivalent to the other: // `[] -> []` // `[a, b] -> [a, b]` // `[1, ..rest] -> [1, ..rest]` ( TypedPattern::List { elements: pattern_elements, tail: pattern_tail, .. }, TypedExpr::List { elements, tail, .. }, ) => { let tails_are_the_same = match (pattern_tail, tail) { (None, None) => true, (None, Some(_)) | (Some(_), None) => false, (Some(tail_pattern), Some(tail_expression)) => { pattern_and_expression_are_the_same(&tail_pattern.pattern, tail_expression) } }; tails_are_the_same && pattern_elements.len() == elements.len() && pattern_elements .iter() .zip(elements) .all(|(pattern, expression)| { pattern_and_expression_are_the_same(pattern, expression) }) } (TypedPattern::List { .. }, _) => false, // Two constructors are the same if the expression is building exactly // the same value being matched on (regardless of qualification). // `Ok(a) -> Ok(a)` // `Ok(1) -> Ok(1)` // `Wibble(a, b, c) -> Wibble(a, b, c)` // `Ok(a) -> gleam.Ok(a)` // `gleam.Ok(1) -> Ok(1)` ( TypedPattern::Constructor { constructor: Inferred::Known(PatternConstructor { module: pattern_module, name: pattern_name, .. }), arguments: pattern_arguments, spread: None, .. }, TypedExpr::Call { fun, arguments, .. }, ) => match fun.as_ref() { TypedExpr::Var { constructor: ValueConstructor { variant: ValueConstructorVariant::Record { name, module, .. }, .. }, .. } | TypedExpr::ModuleSelect { constructor: ModuleValueConstructor::Record { name, .. }, module_name: module, .. } => { pattern_module == module && pattern_name == name && pattern_arguments.len() == arguments.len() && pattern_arguments .iter() .zip(arguments) .all(|(pattern, expression)| { pattern_and_expression_are_the_same(&pattern.value, &expression.value) }) } TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => false, }, // A pattern for a constructor with no arguments: // `Nil -> Nil` // `gleam.Nil -> Nil` // `Nil -> gleam.Nil` // `Wibble -> Wibble` ( TypedPattern::Constructor { constructor: Inferred::Known(PatternConstructor { module: pattern_module, name: pattern_name, .. }), arguments: pattern_arguments, spread: None, .. }, TypedExpr::Var { constructor: ValueConstructor { variant: ValueConstructorVariant::Record { name, module, .. }, .. }, .. } | TypedExpr::ModuleSelect { constructor: ModuleValueConstructor::Record { name, .. }, module_name: module, .. }, ) => pattern_module == module && pattern_name == name && pattern_arguments.is_empty(), (TypedPattern::Constructor { .. }, _) => false, // An assignment is the same if the corresponding expression is a // variable with the same name, or if the inner pattern is the same: // `Ok(1) as a -> a` // `Ok(1) as a -> Ok(1)` ( TypedPattern::Assign { name: pattern_name, .. }, TypedExpr::Var { name, .. }, ) => pattern_name == name, (TypedPattern::Assign { pattern, .. }, expression) => { pattern_and_expression_are_the_same(pattern, expression) } // Bit arrays are trickier as they can use existing variables in their // pattern and shadow existing variables so for now we just ignore // those. (TypedPattern::BitArray { .. } | TypedPattern::BitArraySize { .. }, _) => false, // A discard is never the same as an expression, same goes for an // invalid pattern: there's no way to check if it matches an expression! (TypedPattern::Discard { .. } | TypedPattern::Invalid { .. }, _) => false, } } pub type UntypedClauseGuard = ClauseGuard<(), ()>; pub type TypedClauseGuard = ClauseGuard, EcoString>; #[derive(Debug, Clone, PartialEq, Eq)] pub enum ClauseGuard { Block { location: SrcSpan, value: Box>, }, BinaryOperator { location: SrcSpan, operator: BinOp, left: Box, right: Box, }, Not { location: SrcSpan, expression: Box, }, Var { location: SrcSpan, type_: Type, name: EcoString, definition_location: SrcSpan, origin: VariableOrigin, }, TupleIndex { location: SrcSpan, index: u64, type_: Type, tuple: Box, }, FieldAccess { label_location: SrcSpan, index: Option, label: EcoString, type_: Type, container: Box, }, ModuleSelect { location: SrcSpan, field_start: u32, definition_location: SrcSpan, type_: Type, label: EcoString, module_name: EcoString, module_alias: EcoString, literal: Constant, }, Constant(Constant), } impl ClauseGuard { pub fn location(&self) -> SrcSpan { match self { ClauseGuard::Constant(constant) => constant.location(), ClauseGuard::BinaryOperator { location, .. } | ClauseGuard::Not { location, .. } | ClauseGuard::Var { location, .. } | ClauseGuard::TupleIndex { location, .. } | ClauseGuard::ModuleSelect { location, .. } | ClauseGuard::Block { location, .. } => *location, ClauseGuard::FieldAccess { label_location, container, .. } => container.location().merge(label_location), } } pub fn precedence(&self) -> u8 { // Ensure that this matches the other precedence function for guards match self.bin_op_name() { Some(name) => name.precedence(), None => u8::MAX, } } pub fn bin_op_name(&self) -> Option { match self { ClauseGuard::BinaryOperator { operator, .. } => Some(*operator), ClauseGuard::Constant(_) | ClauseGuard::Var { .. } | ClauseGuard::Not { .. } | ClauseGuard::TupleIndex { .. } | ClauseGuard::FieldAccess { .. } | ClauseGuard::ModuleSelect { .. } | ClauseGuard::Block { .. } => None, } } } impl TypedClauseGuard { pub fn type_(&self) -> Arc { match self { ClauseGuard::Var { type_, .. } => type_.clone(), ClauseGuard::TupleIndex { type_, .. } => type_.clone(), ClauseGuard::FieldAccess { type_, .. } => type_.clone(), ClauseGuard::ModuleSelect { type_, .. } => type_.clone(), ClauseGuard::Constant(constant) => constant.type_(), ClauseGuard::Block { value, .. } => value.type_(), ClauseGuard::Not { .. } => type_::bool(), ClauseGuard::BinaryOperator { operator, .. } => match operator { BinOp::AddInt | BinOp::SubInt | BinOp::MultInt | BinOp::DivInt | BinOp::RemainderInt => type_::int(), BinOp::AddFloat | BinOp::SubFloat | BinOp::MultFloat | BinOp::DivFloat => { type_::float() } BinOp::Concatenate => type_::string(), BinOp::Or | BinOp::And | BinOp::Eq | BinOp::NotEq | BinOp::GtInt | BinOp::GtEqInt | BinOp::LtInt | BinOp::LtEqInt | BinOp::GtFloat | BinOp::GtEqFloat | BinOp::LtFloat | BinOp::LtEqFloat => type_::bool(), }, } } pub fn find_node(&self, byte_index: u32) -> Option> { if !self.location().contains(byte_index) { return None; } match self { ClauseGuard::ModuleSelect { location, module_name, module_alias, .. } => { let module_span = SrcSpan::new(location.start, location.start + (module_alias.len() as u32)); if module_span.contains(byte_index) { Some(Located::ModuleName { location: module_span, module_name: module_name.clone(), module_alias: module_alias.clone(), layer: Layer::Value, }) } else { Some(Located::ClauseGuard(self)) } } ClauseGuard::BinaryOperator { left, right, .. } => left .find_node(byte_index) .or_else(|| right.find_node(byte_index)), ClauseGuard::Not { expression: value, .. } | ClauseGuard::TupleIndex { tuple: value, .. } | ClauseGuard::FieldAccess { container: value, .. } | ClauseGuard::Block { value, .. } => value.find_node(byte_index), ClauseGuard::Constant(constant) => constant.find_node(byte_index), ClauseGuard::Var { .. } => Some(Located::ClauseGuard(self)), } } pub(crate) fn referenced_variables(&self) -> im::HashSet<&EcoString> { match self { ClauseGuard::Var { name, .. } => im::hashset![name], ClauseGuard::Block { value, .. } => value.referenced_variables(), ClauseGuard::Not { expression, .. } => expression.referenced_variables(), ClauseGuard::TupleIndex { tuple, .. } => tuple.referenced_variables(), ClauseGuard::FieldAccess { container, .. } => container.referenced_variables(), ClauseGuard::Constant(constant) => constant.referenced_variables(), ClauseGuard::ModuleSelect { .. } => im::HashSet::new(), ClauseGuard::BinaryOperator { left, right, .. } => left .referenced_variables() .union(right.referenced_variables()), } } fn syntactically_eq(&self, other: &Self) -> bool { match (self, other) { ( ClauseGuard::Block { value, .. }, ClauseGuard::Block { value: other_value, .. }, ) => value.syntactically_eq(other_value), (ClauseGuard::Block { .. }, _) => false, ( ClauseGuard::BinaryOperator { left, right, .. }, ClauseGuard::BinaryOperator { left: other_left, right: other_right, .. }, ) => left.syntactically_eq(other_left) && right.syntactically_eq(other_right), (ClauseGuard::BinaryOperator { .. }, _) => false, ( ClauseGuard::Not { expression, .. }, ClauseGuard::Not { expression: other_expression, .. }, ) => expression.syntactically_eq(other_expression), (ClauseGuard::Not { .. }, _) => false, ( ClauseGuard::Var { name, .. }, ClauseGuard::Var { name: other_name, .. }, ) => name == other_name, (ClauseGuard::Var { .. }, _) => false, ( ClauseGuard::TupleIndex { index, tuple, .. }, ClauseGuard::TupleIndex { index: other_index, tuple: other_tuple, .. }, ) => index == other_index && tuple.syntactically_eq(other_tuple), (ClauseGuard::TupleIndex { .. }, _) => false, ( ClauseGuard::FieldAccess { label, container, .. }, ClauseGuard::FieldAccess { label: other_label, container: other_container, .. }, ) => label == other_label && container.syntactically_eq(other_container), (ClauseGuard::FieldAccess { .. }, _) => false, ( ClauseGuard::ModuleSelect { label, module_alias, .. }, ClauseGuard::ModuleSelect { label: other_label, module_alias: other_module_alias, .. }, ) => label == other_label && module_alias == other_module_alias, (ClauseGuard::ModuleSelect { .. }, _) => false, (ClauseGuard::Constant(one), ClauseGuard::Constant(other)) => { one.syntactically_eq(other) } (ClauseGuard::Constant(_), _) => false, } } pub fn definition_location(&self) -> Option { match self { ClauseGuard::Block { .. } | ClauseGuard::BinaryOperator { .. } | ClauseGuard::Not { .. } | ClauseGuard::TupleIndex { .. } | ClauseGuard::FieldAccess { .. } => None, ClauseGuard::Constant(constant) => constant.definition_location(), ClauseGuard::Var { definition_location, .. } => Some(DefinitionLocation { module: None, span: *definition_location, }), ClauseGuard::ModuleSelect { module_name, definition_location, .. } => Some(DefinitionLocation { module: Some(module_name.clone()), span: *definition_location, }), } } } #[derive( Debug, PartialEq, Eq, PartialOrd, Ord, Default, Clone, Copy, serde::Serialize, serde::Deserialize, Hash, )] pub struct SrcSpan { pub start: u32, pub end: u32, } impl SrcSpan { pub fn new(start: u32, end: u32) -> Self { Self { start, end } } pub fn contains(&self, byte_index: u32) -> bool { byte_index >= self.start && byte_index <= self.end } pub fn contains_span(&self, span: SrcSpan) -> bool { self.contains(span.start) && self.contains(span.end) } /// Merges two spans into a new one that starts at the start of the smaller /// one and ends at the end of the bigger one. For example: /// /// ```txt /// wibble wobble /// ─┬──── ─┬──── /// │ ╰─ one span /// ╰─ the other span /// ─┬────────────── /// ╰─ the span you get by merging the two /// ``` pub fn merge(&self, with: &SrcSpan) -> SrcSpan { Self { start: self.start.min(with.start), end: self.end.max(with.end), } } pub fn is_empty(&self) -> bool { self.len() == 0 } pub fn len(&self) -> usize { (self.end - self.start) as usize } } #[derive(Debug, PartialEq, Eq, Clone)] pub struct DefinitionLocation { pub module: Option, pub span: SrcSpan, } pub type UntypedPattern = Pattern<()>; pub type TypedPattern = Pattern>; #[derive(Debug, Clone, PartialEq, Eq)] pub enum Pattern { Int { location: SrcSpan, value: EcoString, int_value: BigInt, }, Float { location: SrcSpan, value: EcoString, float_value: LiteralFloatValue, }, String { location: SrcSpan, value: EcoString, }, /// The creation of a variable. /// e.g. `assert [this_is_a_var, .._] = x` Variable { location: SrcSpan, name: EcoString, type_: Type, origin: VariableOrigin, }, /// The specified size of a bit array. This can either be a literal integer, /// a reference to a variable, or a maths expression. /// e.g. `let assert <> = x` BitArraySize(BitArraySize), /// A name given to a sub-pattern using the `as` keyword. /// e.g. `assert #(1, [_, _] as the_list) = x` Assign { name: EcoString, location: SrcSpan, pattern: Box, }, /// A pattern that binds to any value but does not assign a variable. /// Always starts with an underscore. Discard { name: EcoString, location: SrcSpan, type_: Type, }, List { location: SrcSpan, elements: Vec, tail: Option>>, /// The type of the list, so this is going to be `List(something)`. /// type_: Type, }, /// The constructor for a custom type. Starts with an uppercase letter. Constructor { location: SrcSpan, name_location: SrcSpan, name: EcoString, arguments: Vec>, module: Option<(EcoString, SrcSpan)>, constructor: Inferred, spread: Option, type_: Type, }, Tuple { location: SrcSpan, elements: Vec, }, BitArray { location: SrcSpan, segments: Vec>, }, // "prefix" <> variable StringPrefix { location: SrcSpan, left_location: SrcSpan, left_side_assignment: Option<(EcoString, SrcSpan)>, right_location: SrcSpan, left_side_string: EcoString, /// The variable on the right hand side of the `<>`. right_side_assignment: AssignName, }, /// A placeholder pattern used to allow module analysis to continue /// even when there are type errors. Should never end up in generated code. Invalid { location: SrcSpan, type_: Type, }, } pub type TypedBitArraySize = BitArraySize>; #[derive(Debug, Clone, PartialEq, Eq)] pub enum BitArraySize { Int { location: SrcSpan, value: EcoString, int_value: BigInt, }, Variable { location: SrcSpan, name: EcoString, constructor: Option>, type_: Type, }, BinaryOperator { location: SrcSpan, operator: IntOperator, left: Box, right: Box, }, Block { location: SrcSpan, inner: Box, }, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] pub enum IntOperator { Add, Subtract, Multiply, Divide, Remainder, } impl IntOperator { pub fn precedence(&self) -> u8 { match self { Self::Add | Self::Subtract => 7, Self::Multiply | Self::Divide | Self::Remainder => 8, } } pub fn to_bin_op(&self) -> BinOp { match self { IntOperator::Add => BinOp::AddInt, IntOperator::Subtract => BinOp::SubInt, IntOperator::Multiply => BinOp::MultInt, IntOperator::Divide => BinOp::DivInt, IntOperator::Remainder => BinOp::RemainderInt, } } } impl BitArraySize { pub fn location(&self) -> SrcSpan { match self { BitArraySize::Int { location, .. } | BitArraySize::Variable { location, .. } | BitArraySize::BinaryOperator { location, .. } | BitArraySize::Block { location, .. } => *location, } } pub fn non_zero_compile_time_number(&self) -> bool { match self { BitArraySize::Int { int_value, .. } => !int_value.is_zero(), BitArraySize::Block { inner, .. } => inner.non_zero_compile_time_number(), BitArraySize::Variable { .. } | BitArraySize::BinaryOperator { .. } => false, } } fn syntactically_eq(&self, other: &Self) -> bool { match (self, other) { (BitArraySize::Int { int_value: n, .. }, BitArraySize::Int { int_value: m, .. }) => { n == m } (BitArraySize::Int { .. }, _) => false, ( BitArraySize::Variable { name, .. }, BitArraySize::Variable { name: other_name, .. }, ) => name == other_name, (BitArraySize::Variable { .. }, _) => false, ( BitArraySize::BinaryOperator { operator, left, right, .. }, BitArraySize::BinaryOperator { operator: other_operator, left: other_left, right: other_right, .. }, ) => { operator == other_operator && left.syntactically_eq(other_left) && right.syntactically_eq(other_right) } (BitArraySize::BinaryOperator { .. }, _) => false, ( BitArraySize::Block { inner, .. }, BitArraySize::Block { inner: other_inner, .. }, ) => inner.syntactically_eq(other_inner), (BitArraySize::Block { .. }, _) => false, } } } pub type TypedTailPattern = TailPattern>; pub type UntypedTailPattern = TailPattern<()>; /// The pattern one can use to match on the rest of a list: /// #[derive(Debug, Clone, PartialEq, Eq)] pub struct TailPattern { /// The entire location of the pattern, covering the `..` as well. /// pub location: SrcSpan, /// The name assigned to the rest of the list being matched: /// /// ```gleam /// [wibble, ..] /// // ^^ no name /// /// [wibble, ..rest] /// // ^^^^^^ a variable name /// /// [wibble, .._rest] /// // ^^^^^^^ a discarded name /// ``` /// pub pattern: Pattern, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum AssignName { Variable(EcoString), Discard(EcoString), } impl AssignName { pub fn name(&self) -> &EcoString { match self { AssignName::Variable(name) | AssignName::Discard(name) => name, } } pub fn to_arg_names(self, location: SrcSpan) -> ArgNames { match self { AssignName::Variable(name) => ArgNames::Named { name, location }, AssignName::Discard(name) => ArgNames::Discard { name, location }, } } pub fn assigned_name(&self) -> Option<&str> { match self { AssignName::Variable(name) => Some(name), AssignName::Discard(_) => None, } } } impl Pattern { pub fn location(&self) -> SrcSpan { match self { Pattern::Assign { pattern, location, .. } => SrcSpan::new(pattern.location().start, location.end), Pattern::Int { location, .. } | Pattern::Variable { location, .. } | Pattern::List { location, .. } | Pattern::Float { location, .. } | Pattern::Discard { location, .. } | Pattern::String { location, .. } | Pattern::Tuple { location, .. } | Pattern::Constructor { location, .. } | Pattern::StringPrefix { location, .. } | Pattern::BitArray { location, .. } | Pattern::Invalid { location, .. } => *location, Pattern::BitArraySize(size) => size.location(), } } /// Returns `true` if the pattern is [`Discard`]. /// /// [`Discard`]: Pattern::Discard #[must_use] pub fn is_discard(&self) -> bool { matches!(self, Self::Discard { .. }) } #[must_use] pub fn is_variable(&self) -> bool { matches!(self, Pattern::Variable { .. }) } #[must_use] pub fn is_string(&self) -> bool { matches!(self, Self::String { .. }) } } impl TypedPattern { fn syntactically_eq(&self, other: &Self) -> bool { match (self, other) { (Pattern::Int { int_value: n, .. }, Pattern::Int { int_value: m, .. }) => n == m, (Pattern::Int { .. }, _) => false, (Pattern::Float { float_value: n, .. }, Pattern::Float { float_value: m, .. }) => { n == m } (Pattern::Float { .. }, _) => false, ( Pattern::String { value, .. }, Pattern::String { value: other_value, .. }, ) => value == other_value, (Pattern::String { .. }, _) => false, ( Pattern::Variable { name, .. }, Pattern::Variable { name: other_name, .. }, ) => name == other_name, (Pattern::Variable { .. }, _) => false, (Pattern::BitArraySize(one), Pattern::BitArraySize(other)) => { one.syntactically_eq(other) } (Pattern::BitArraySize(..), _) => false, ( Pattern::Assign { name, pattern, .. }, Pattern::Assign { name: other_name, pattern: other_pattern, .. }, ) => name == other_name && pattern.syntactically_eq(other_pattern), (Pattern::Assign { .. }, _) => false, ( Pattern::Discard { name, .. }, Pattern::Discard { name: other_name, .. }, ) => name == other_name, (Pattern::Discard { .. }, _) => false, ( Pattern::List { elements, tail, .. }, Pattern::List { elements: other_elements, tail: other_tail, .. }, ) => { let tails_are_equal = match (tail, other_tail) { (None, None) => true, (None, Some(_)) | (Some(_), None) => false, (Some(one), Some(other)) => one.pattern.syntactically_eq(&other.pattern), }; tails_are_equal && pairwise_all(elements, other_elements, |(one, other)| { one.syntactically_eq(other) }) } (Pattern::List { .. }, _) => false, ( Pattern::Constructor { name, arguments, module, .. }, Pattern::Constructor { name: other_name, arguments: other_arguments, module: other_module, .. }, ) => { let modules_are_equal = match (module, other_module) { (None, None) => true, (None, Some(_)) | (Some(_), None) => false, (Some((one, _)), Some((other, _))) => one == other, }; modules_are_equal && name == other_name && pairwise_all(arguments, other_arguments, |(one, other)| { one.label == other.label && one.value.syntactically_eq(&other.value) }) } (Pattern::Constructor { .. }, _) => false, ( Pattern::Tuple { elements, .. }, Pattern::Tuple { elements: other_elements, .. }, ) => pairwise_all(elements, other_elements, |(one, other)| { one.syntactically_eq(other) }), (Pattern::Tuple { .. }, _) => false, ( Pattern::BitArray { segments, .. }, Pattern::BitArray { segments: other_segments, .. }, ) => pairwise_all(segments, other_segments, |(one, other)| { one.syntactically_eq(other) }), (Pattern::BitArray { .. }, _) => false, ( Pattern::StringPrefix { left_side_assignment, left_side_string, right_side_assignment, .. }, Pattern::StringPrefix { left_side_assignment: other_left_side_assignment, left_side_string: other_left_side_string, right_side_assignment: other_right_side_assignment, .. }, ) => { let left_side_assignments_are_equal = match (left_side_assignment, other_left_side_assignment) { (None, None) => true, (None, Some(_)) | (Some(_), None) => false, (Some((one, _)), Some((other, _))) => one == other, }; let right_side_assignments_are_equal = match (right_side_assignment, other_right_side_assignment) { (AssignName::Variable(one), AssignName::Variable(other)) => one == other, (AssignName::Variable(_), AssignName::Discard(_)) => false, (AssignName::Discard(one), AssignName::Discard(other)) => one == other, (AssignName::Discard(_), AssignName::Variable(_)) => false, }; left_side_string == other_left_side_string && left_side_assignments_are_equal && right_side_assignments_are_equal } (Pattern::StringPrefix { .. }, _) => false, (Pattern::Invalid { .. }, _) => false, } } } /// A variable bound inside a pattern. #[derive(Debug, Clone)] pub struct BoundVariable { pub name: BoundVariableName, pub location: SrcSpan, pub type_: Arc, } #[derive(Debug, Clone)] pub enum BoundVariableName { /// A record's labelled field introduced with the shorthand syntax. ShorthandLabel { name: EcoString }, ListTail { name: EcoString, /// The location of the whole tail, from the `..` prefix until the end of the variable. tail_location: SrcSpan, }, /// Any other variable name. Regular { name: EcoString }, } impl BoundVariable { pub fn name(&self) -> EcoString { match &self.name { BoundVariableName::ShorthandLabel { name } | BoundVariableName::ListTail { name, .. } | BoundVariableName::Regular { name } => name.clone(), } } } impl TypedPattern { pub fn definition_location(&self) -> Option { match self { Pattern::Int { .. } | Pattern::Float { .. } | Pattern::String { .. } | Pattern::Variable { .. } | Pattern::BitArraySize { .. } | Pattern::Assign { .. } | Pattern::Discard { .. } | Pattern::List { .. } | Pattern::Tuple { .. } | Pattern::BitArray { .. } | Pattern::StringPrefix { .. } | Pattern::Invalid { .. } => None, Pattern::Constructor { constructor, .. } => constructor.definition_location(), } } pub fn get_documentation(&self) -> Option<&str> { match self { Pattern::Int { .. } | Pattern::Float { .. } | Pattern::String { .. } | Pattern::Variable { .. } | Pattern::BitArraySize { .. } | Pattern::Assign { .. } | Pattern::Discard { .. } | Pattern::List { .. } | Pattern::Tuple { .. } | Pattern::BitArray { .. } | Pattern::StringPrefix { .. } | Pattern::Invalid { .. } => None, Pattern::Constructor { constructor, .. } => constructor.get_documentation(), } } pub fn type_(&self) -> Arc { match self { Pattern::Int { .. } => type_::int(), Pattern::Float { .. } => type_::float(), Pattern::String { .. } => type_::string(), Pattern::BitArray { .. } => type_::bit_array(), Pattern::StringPrefix { .. } => type_::string(), Pattern::Variable { type_, .. } | Pattern::List { type_, .. } | Pattern::Constructor { type_, .. } | Pattern::Invalid { type_, .. } => type_.clone(), Pattern::Assign { pattern, .. } => pattern.type_(), // Bit array sizes should always be integers Pattern::BitArraySize(_) => type_::int(), Pattern::Discard { type_, .. } => type_.clone(), Pattern::Tuple { elements, .. } => { type_::tuple(elements.iter().map(|p| p.type_()).collect()) } } } fn find_node(&self, byte_index: u32) -> Option> { if !self.location().contains(byte_index) { return None; } if let Pattern::Variable { name, .. } = self { // For pipes the pattern can't be pointed to if name.as_str().eq(PIPE_VARIABLE) { return None; } } match self { Pattern::Int { .. } | Pattern::Float { .. } | Pattern::String { .. } | Pattern::Variable { .. } | Pattern::BitArraySize { .. } | Pattern::Discard { .. } | Pattern::Invalid { .. } => Some(Located::Pattern(self)), Pattern::StringPrefix { left_side_assignment, right_side_assignment, right_location, .. } => { // Handle the prefix alias: "prefix" as name if let Some((name, left_side_assignment_location)) = left_side_assignment && left_side_assignment_location.contains(byte_index) { return Some(Located::StringPrefixPatternVariable { location: *left_side_assignment_location, name, }); } // Handle the suffix: <> name if let AssignName::Variable(name) = right_side_assignment && right_location.contains(byte_index) { return Some(Located::StringPrefixPatternVariable { location: *right_location, name, }); } Some(Located::Pattern(self)) } Pattern::Assign { pattern, .. } => pattern .find_node(byte_index) .or_else(|| Some(Located::Pattern(self))), Pattern::Constructor { module, spread, arguments, constructor, .. } => { if let Some((module_alias, module_location)) = module && let Inferred::Known(constructor) = constructor && module_location.contains(byte_index) { Some(Located::ModuleName { location: *module_location, module_name: constructor.module.clone(), module_alias: module_alias.clone(), layer: Layer::Value, }) } else if let Some(spread_location) = spread && spread_location.contains(byte_index) { Some(Located::PatternSpread { spread_location: *spread_location, pattern: self, }) } else { arguments .iter() .find_map(|argument| argument.find_node(byte_index)) } } Pattern::List { elements, tail, .. } => elements .iter() .find_map(|element| element.find_node(byte_index)) .or_else(|| { tail.as_ref() .and_then(|tail| tail.pattern.find_node(byte_index)) }), Pattern::Tuple { elements, .. } => elements .iter() .find_map(|element| element.find_node(byte_index)), Pattern::BitArray { segments, .. } => segments .iter() .find_map(|segment| segment.find_node(byte_index)) .or(Some(Located::Pattern(self))), } .or(Some(Located::Pattern(self))) } /// If the pattern is a `Constructor` with a spread, it returns a tuple with /// all the ignored fields. Split in unlabelled and labelled ones. /// pub fn unused_arguments(&self) -> Option { let TypedPattern::Constructor { arguments, spread: Some(_), .. } = self else { return None; }; let mut positional = vec![]; let mut labelled = vec![]; for argument in arguments { // We only want to display the arguments that were ignored using `..`. // Any argument ignored that way is marked as implicit, so if it is // not implicit we just ignore it. if !argument.is_implicit() { continue; } let type_ = argument.value.type_(); match &argument.label { Some(label) => labelled.push((label.clone(), type_)), None => positional.push(type_), } } Some(PatternUnusedArguments { positional, labelled, }) } /// Whether the pattern always matches. For example, a tuple or simple /// variable assignment always match and can never fail. #[must_use] pub fn always_matches(&self) -> bool { match self { Pattern::Variable { .. } | Pattern::Discard { .. } => true, Pattern::Assign { pattern, .. } => pattern.always_matches(), Pattern::Tuple { elements, .. } => { elements.iter().all(|element| element.always_matches()) } Pattern::Int { .. } | Pattern::Float { .. } | Pattern::String { .. } | Pattern::BitArraySize { .. } | Pattern::List { .. } | Pattern::Constructor { .. } | Pattern::BitArray { .. } | Pattern::StringPrefix { .. } | Pattern::Invalid { .. } => false, } } pub fn bound_variables(&self) -> Vec { let mut variables = Vec::new(); self.collect_bound_variables(&mut variables); variables } fn collect_bound_variables(&self, variables: &mut Vec) { match self { Pattern::Int { .. } | Pattern::Float { .. } | Pattern::String { .. } | Pattern::Discard { .. } | Pattern::Invalid { .. } => {} Pattern::Variable { name, location, type_, .. } => variables.push(BoundVariable { name: BoundVariableName::Regular { name: name.clone() }, location: *location, type_: type_.clone(), }), Pattern::BitArraySize { .. } => {} Pattern::Assign { name, pattern, location, } => { variables.push(BoundVariable { name: BoundVariableName::Regular { name: name.clone() }, location: *location, type_: pattern.type_(), }); pattern.collect_bound_variables(variables); } Pattern::List { elements, tail, type_, .. } => { for element in elements { element.collect_bound_variables(variables); } if let Some(tail) = tail && let Pattern::Variable { name, location, .. } = tail.pattern.to_owned() { variables.push(BoundVariable { name: BoundVariableName::ListTail { name, tail_location: tail.location, }, location, type_: type_.clone(), }) }; } Pattern::Constructor { arguments, .. } => { for argument in arguments { if let Some(name) = argument.label_shorthand_name() { variables.push(BoundVariable { name: BoundVariableName::ShorthandLabel { name: name.clone() }, location: argument.location, type_: argument.value.type_(), }) } else { argument.value.collect_bound_variables(variables); } } } Pattern::Tuple { elements, .. } => { for element in elements { element.collect_bound_variables(variables); } } Pattern::BitArray { segments, .. } => { for segment in segments { segment.value.collect_bound_variables(variables); } } Pattern::StringPrefix { left_side_assignment, right_side_assignment, right_location, .. } => { if let Some((name, location)) = left_side_assignment { variables.push(BoundVariable { name: BoundVariableName::Regular { name: name.clone() }, location: *location, type_: type_::string(), }); } match right_side_assignment { AssignName::Variable(name) => variables.push(BoundVariable { name: BoundVariableName::Regular { name: name.clone() }, location: *right_location, type_: type_::string(), }), AssignName::Discard(_) => {} } } } } } #[derive(Debug, Default)] pub struct PatternUnusedArguments { pub positional: Vec>, pub labelled: Vec<(EcoString, Arc)>, } impl HasLocation for Pattern { fn location(&self) -> SrcSpan { self.location() } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum AssignmentKind { /// let x = ... Let, /// This is a let assignment generated by the compiler for intermediate variables /// needed by record updates and `use`. /// Like a regular `Let` assignment this can never fail. /// Generated, /// let assert x = ... Assert { /// The src byte span of the `let assert` /// /// ```gleam /// let assert Wibble = todo /// ^^^^^^^^^^ /// ``` location: SrcSpan, /// The byte index of the start of `assert` /// /// ```gleam /// let assert Wibble = todo /// ^ /// ``` assert_keyword_start: u32, /// The message given to the assertion: /// /// ```gleam /// let asset Ok(a) = something() as "This will never fail" /// ^^^^^^^^^^^^^^^^^^^^^^ /// ``` message: Option, }, } impl AssignmentKind { /// Returns `true` if the assignment kind is [`Assert`]. /// /// [`Assert`]: AssignmentKind::Assert #[must_use] pub fn is_assert(&self) -> bool { match self { Self::Assert { .. } => true, Self::Let | Self::Generated => false, } } } // BitArrays pub type UntypedExprBitArraySegment = BitArraySegment; pub type TypedExprBitArraySegment = BitArraySegment>; pub type UntypedConstantBitArraySegment = BitArraySegment; pub type TypedConstantBitArraySegment = BitArraySegment>; pub type UntypedPatternBitArraySegment = BitArraySegment; pub type TypedPatternBitArraySegment = BitArraySegment>; #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct BitArraySegment { pub location: SrcSpan, pub value: Box, pub options: Vec>, pub type_: Type, } #[derive(Debug, PartialEq, Eq, Copy, Clone, Hash, serde::Serialize, serde::Deserialize)] pub enum Endianness { Big, Little, } impl Endianness { pub fn is_big(&self) -> bool { *self == Endianness::Big } } impl HasLocation for BitArraySegment { fn location(&self) -> SrcSpan { self.location } } impl BitArraySegment, Type> { /// Returns the value of the pattern unwrapping any assign pattern. /// pub fn value_unwrapping_assign(&self) -> &Pattern { match self.value.as_ref() { Pattern::Assign { pattern, .. } => pattern, Pattern::Int { .. } | Pattern::Float { .. } | Pattern::String { .. } | Pattern::Variable { .. } | Pattern::BitArraySize { .. } | Pattern::Discard { .. } | Pattern::List { .. } | Pattern::Constructor { .. } | Pattern::Tuple { .. } | Pattern::BitArray { .. } | Pattern::StringPrefix { .. } | Pattern::Invalid { .. } => self.value.as_ref(), } } } impl BitArraySegment { #[must_use] pub fn has_native_option(&self) -> bool { self.options .iter() .any(|x| matches!(x, BitArrayOption::Native { .. })) } #[must_use] pub fn has_utf16_codepoint_option(&self) -> bool { self.options .iter() .any(|x| matches!(x, BitArrayOption::Utf16Codepoint { .. })) } #[must_use] pub fn has_utf32_codepoint_option(&self) -> bool { self.options .iter() .any(|x| matches!(x, BitArrayOption::Utf32Codepoint { .. })) } #[must_use] pub fn has_utf16_option(&self) -> bool { self.options .iter() .any(|x| matches!(x, BitArrayOption::Utf16 { .. })) } #[must_use] pub fn has_utf32_option(&self) -> bool { self.options .iter() .any(|x| matches!(x, BitArrayOption::Utf32 { .. })) } pub fn endianness(&self) -> Endianness { if self .options .iter() .any(|x| matches!(x, BitArrayOption::Little { .. })) { Endianness::Little } else { Endianness::Big } } pub(crate) fn signed(&self) -> bool { self.options .iter() .any(|x| matches!(x, BitArrayOption::Signed { .. })) } pub fn size(&self) -> Option<&Value> { self.options.iter().find_map(|x| match x { BitArrayOption::Size { value, .. } => Some(value.as_ref()), BitArrayOption::Bytes { .. } | BitArrayOption::Int { .. } | BitArrayOption::Float { .. } | BitArrayOption::Bits { .. } | BitArrayOption::Utf8 { .. } | BitArrayOption::Utf16 { .. } | BitArrayOption::Utf32 { .. } | BitArrayOption::Utf8Codepoint { .. } | BitArrayOption::Utf16Codepoint { .. } | BitArrayOption::Utf32Codepoint { .. } | BitArrayOption::Signed { .. } | BitArrayOption::Unsigned { .. } | BitArrayOption::Big { .. } | BitArrayOption::Little { .. } | BitArrayOption::Native { .. } | BitArrayOption::Unit { .. } => None, }) } pub fn unit(&self) -> u8 { self.options .iter() .find_map(|option| match option { BitArrayOption::Unit { value, .. } => Some(*value), BitArrayOption::Bytes { .. } => Some(8), BitArrayOption::Int { .. } | BitArrayOption::Float { .. } | BitArrayOption::Bits { .. } | BitArrayOption::Utf8 { .. } | BitArrayOption::Utf16 { .. } | BitArrayOption::Utf32 { .. } | BitArrayOption::Utf8Codepoint { .. } | BitArrayOption::Utf16Codepoint { .. } | BitArrayOption::Utf32Codepoint { .. } | BitArrayOption::Signed { .. } | BitArrayOption::Unsigned { .. } | BitArrayOption::Big { .. } | BitArrayOption::Little { .. } | BitArrayOption::Native { .. } | BitArrayOption::Size { .. } => None, }) .unwrap_or(1) } pub(crate) fn has_bits_option(&self) -> bool { self.options .iter() .any(|option| matches!(option, BitArrayOption::Bits { .. })) } pub(crate) fn has_bytes_option(&self) -> bool { self.options .iter() .any(|option| matches!(option, BitArrayOption::Bytes { .. })) } } impl BitArraySegment { #[must_use] pub(crate) fn has_type_option(&self) -> bool { self.options.iter().any(|option| option.is_type_option()) } } impl TypedExprBitArraySegment { pub fn find_node(&self, byte_index: u32) -> Option> { self.value.find_node(byte_index) } fn syntactically_eq(&self, other: &Self) -> bool { self.value.syntactically_eq(&other.value) && pairwise_all(&self.options, &other.options, |(option, other_option)| { option.syntactically_eq(other_option, |size, other_size| { size.syntactically_eq(other_size) }) }) } } impl BitArraySegment> where TypedValue: HasType + HasLocation + Clone + bit_array::GetLiteralValue, { pub fn check_for_truncated_value(&self) -> Option { // Both the size and the value must be two compile-time known constants. let segment_bits = self.bits_size()?.to_i64()?; let literal_value = self.value.as_int_literal()?; if segment_bits <= 0 { return None; } let safe_range = match literal_value.sign() { Sign::NoSign => return None, Sign::Minus => { (-(BigInt::one() << (segment_bits - 1))) ..((BigInt::one() << (segment_bits - 1)) - 1) } Sign::Plus => BigInt::ZERO..(BigInt::one() << segment_bits), }; if !safe_range.contains(&literal_value) { Some(BitArraySegmentTruncation { truncated_value: literal_value.clone(), truncated_into: truncate(&literal_value, segment_bits), value_location: self.value.location(), segment_bits, }) } else { None } } /// If the segment size is a compile-time known constant this returns the /// segment size in bits, taking the segment's unit into consideration! /// fn bits_size(&self) -> Option { let size = match self.size() { None if self.type_.is_int() => 8.into(), None => 64.into(), Some(value) => value.as_int_literal()?, }; let unit = self.unit(); Some(size * unit) } } /// As Björn said, when a value is smaller than the segment's size it will be /// truncated, only taking the first `n` bits: /// /// > It will be silently truncated. In general, when storing value an integer /// > `I` into a segment of size `N`, the actual value stored will be /// > `I band ((1 bsl N) - 1)`. /// /// /// /// Thank you Björn! /// fn truncate(literal_value: &BigInt, segment_bits: i64) -> BigInt { literal_value & ((BigInt::one() << segment_bits) - BigInt::one()) } #[derive(serde::Deserialize, serde::Serialize, Eq, PartialEq, Clone, Debug)] pub struct BitArraySegmentTruncation { /// The value that would end up being truncated. pub truncated_value: BigInt, /// What the value would be truncated into. pub truncated_into: BigInt, /// The span of the segment's value being truncated. pub value_location: SrcSpan, /// The size of the segment. pub segment_bits: i64, } impl TypedPatternBitArraySegment { pub fn find_node(&self, byte_index: u32) -> Option> { self.value.find_node(byte_index).or_else(|| { self.options .iter() .find_map(|option| option.find_node(byte_index)) }) } fn syntactically_eq(&self, other: &Self) -> bool { self.value.syntactically_eq(&other.value) && pairwise_all(&self.options, &other.options, |(option, other_option)| { option.syntactically_eq(other_option, |size, other_size| { size.syntactically_eq(other_size) }) }) } } impl TypedConstantBitArraySegment { pub fn find_node(&self, byte_index: u32) -> Option> { self.value.find_node(byte_index).or_else(|| { self.options .iter() .find_map(|option| option.find_node(byte_index)) }) } fn syntactically_eq(&self, other: &Self) -> bool { self.value.syntactically_eq(&other.value) && pairwise_all(&self.options, &other.options, |(option, other_option)| { option.syntactically_eq(other_option, |size, other_size| { size.syntactically_eq(other_size) }) }) } } pub type TypedConstantBitArraySegmentOption = BitArrayOption; #[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)] pub enum BitArrayOption { Bytes { location: SrcSpan, }, Int { location: SrcSpan, }, Float { location: SrcSpan, }, Bits { location: SrcSpan, }, Utf8 { location: SrcSpan, }, Utf16 { location: SrcSpan, }, Utf32 { location: SrcSpan, }, Utf8Codepoint { location: SrcSpan, }, Utf16Codepoint { location: SrcSpan, }, Utf32Codepoint { location: SrcSpan, }, Signed { location: SrcSpan, }, Unsigned { location: SrcSpan, }, Big { location: SrcSpan, }, Little { location: SrcSpan, }, Native { location: SrcSpan, }, Size { location: SrcSpan, value: Box, short_form: bool, }, Unit { location: SrcSpan, value: u8, }, } impl BitArrayOption { pub fn value(&self) -> Option<&A> { match self { BitArrayOption::Size { value, .. } => Some(value), BitArrayOption::Bytes { .. } | BitArrayOption::Int { .. } | BitArrayOption::Float { .. } | BitArrayOption::Bits { .. } | BitArrayOption::Utf8 { .. } | BitArrayOption::Utf16 { .. } | BitArrayOption::Utf32 { .. } | BitArrayOption::Utf8Codepoint { .. } | BitArrayOption::Utf16Codepoint { .. } | BitArrayOption::Utf32Codepoint { .. } | BitArrayOption::Signed { .. } | BitArrayOption::Unsigned { .. } | BitArrayOption::Big { .. } | BitArrayOption::Little { .. } | BitArrayOption::Native { .. } | BitArrayOption::Unit { .. } => None, } } pub fn location(&self) -> SrcSpan { match self { BitArrayOption::Bytes { location } | BitArrayOption::Int { location } | BitArrayOption::Float { location } | BitArrayOption::Bits { location } | BitArrayOption::Utf8 { location } | BitArrayOption::Utf16 { location } | BitArrayOption::Utf32 { location } | BitArrayOption::Utf8Codepoint { location } | BitArrayOption::Utf16Codepoint { location } | BitArrayOption::Utf32Codepoint { location } | BitArrayOption::Signed { location } | BitArrayOption::Unsigned { location } | BitArrayOption::Big { location } | BitArrayOption::Little { location } | BitArrayOption::Native { location } | BitArrayOption::Size { location, .. } | BitArrayOption::Unit { location, .. } => *location, } } pub fn label(&self) -> EcoString { match self { BitArrayOption::Bytes { .. } => "bytes".into(), BitArrayOption::Int { .. } => "int".into(), BitArrayOption::Float { .. } => "float".into(), BitArrayOption::Bits { .. } => "bits".into(), BitArrayOption::Utf8 { .. } => "utf8".into(), BitArrayOption::Utf16 { .. } => "utf16".into(), BitArrayOption::Utf32 { .. } => "utf32".into(), BitArrayOption::Utf8Codepoint { .. } => "utf8_codepoint".into(), BitArrayOption::Utf16Codepoint { .. } => "utf16_codepoint".into(), BitArrayOption::Utf32Codepoint { .. } => "utf32_codepoint".into(), BitArrayOption::Signed { .. } => "signed".into(), BitArrayOption::Unsigned { .. } => "unsigned".into(), BitArrayOption::Big { .. } => "big".into(), BitArrayOption::Little { .. } => "little".into(), BitArrayOption::Native { .. } => "native".into(), BitArrayOption::Size { .. } => "size".into(), BitArrayOption::Unit { .. } => "unit".into(), } } fn is_type_option(&self) -> bool { match self { BitArrayOption::Bytes { .. } | BitArrayOption::Int { .. } | BitArrayOption::Float { .. } | BitArrayOption::Bits { .. } | BitArrayOption::Utf8 { .. } | BitArrayOption::Utf16 { .. } | BitArrayOption::Utf32 { .. } | BitArrayOption::Utf8Codepoint { .. } | BitArrayOption::Utf16Codepoint { .. } | BitArrayOption::Utf32Codepoint { .. } => true, BitArrayOption::Signed { .. } | BitArrayOption::Unsigned { .. } | BitArrayOption::Big { .. } | BitArrayOption::Little { .. } | BitArrayOption::Native { .. } | BitArrayOption::Size { .. } | BitArrayOption::Unit { .. } => false, } } fn syntactically_eq(&self, other: &Self, compare_sizes: impl Fn(&A, &A) -> bool) -> bool { match (self, other) { (BitArrayOption::Bytes { .. }, BitArrayOption::Bytes { .. }) => true, (BitArrayOption::Bytes { .. }, _) => false, (BitArrayOption::Int { .. }, BitArrayOption::Int { .. }) => true, (BitArrayOption::Int { .. }, _) => false, (BitArrayOption::Float { .. }, BitArrayOption::Float { .. }) => true, (BitArrayOption::Float { .. }, _) => false, (BitArrayOption::Bits { .. }, BitArrayOption::Bits { .. }) => true, (BitArrayOption::Bits { .. }, _) => false, (BitArrayOption::Utf8 { .. }, BitArrayOption::Utf8 { .. }) => true, (BitArrayOption::Utf8 { .. }, _) => false, (BitArrayOption::Utf16 { .. }, BitArrayOption::Utf16 { .. }) => true, (BitArrayOption::Utf16 { .. }, _) => false, (BitArrayOption::Utf32 { .. }, BitArrayOption::Utf32 { .. }) => true, (BitArrayOption::Utf32 { .. }, _) => false, (BitArrayOption::Utf8Codepoint { .. }, BitArrayOption::Utf8Codepoint { .. }) => true, (BitArrayOption::Utf8Codepoint { .. }, _) => false, (BitArrayOption::Utf16Codepoint { .. }, BitArrayOption::Utf16Codepoint { .. }) => true, (BitArrayOption::Utf16Codepoint { .. }, _) => false, (BitArrayOption::Utf32Codepoint { .. }, BitArrayOption::Utf32Codepoint { .. }) => true, (BitArrayOption::Utf32Codepoint { .. }, _) => false, (BitArrayOption::Signed { .. }, BitArrayOption::Signed { .. }) => true, (BitArrayOption::Signed { .. }, _) => false, (BitArrayOption::Unsigned { .. }, BitArrayOption::Unsigned { .. }) => true, (BitArrayOption::Unsigned { .. }, _) => false, (BitArrayOption::Big { .. }, BitArrayOption::Big { .. }) => true, (BitArrayOption::Big { .. }, _) => false, (BitArrayOption::Little { .. }, BitArrayOption::Little { .. }) => true, (BitArrayOption::Little { .. }, _) => false, (BitArrayOption::Native { .. }, BitArrayOption::Native { .. }) => true, (BitArrayOption::Native { .. }, _) => false, ( BitArrayOption::Unit { value, .. }, BitArrayOption::Unit { value: other_value, .. }, ) => value == other_value, (BitArrayOption::Unit { .. }, _) => false, ( BitArrayOption::Size { value, short_form, .. }, BitArrayOption::Size { value: other_value, short_form: other_short_form, .. }, ) => short_form == other_short_form && compare_sizes(value, other_value), (BitArrayOption::Size { .. }, _) => false, } } } impl BitArrayOption { fn referenced_variables(&self) -> im::HashSet<&EcoString> { match self { BitArrayOption::Bytes { .. } | BitArrayOption::Int { .. } | BitArrayOption::Float { .. } | BitArrayOption::Bits { .. } | BitArrayOption::Utf8 { .. } | BitArrayOption::Utf16 { .. } | BitArrayOption::Utf32 { .. } | BitArrayOption::Utf8Codepoint { .. } | BitArrayOption::Utf16Codepoint { .. } | BitArrayOption::Utf32Codepoint { .. } | BitArrayOption::Signed { .. } | BitArrayOption::Unsigned { .. } | BitArrayOption::Big { .. } | BitArrayOption::Little { .. } | BitArrayOption::Unit { .. } | BitArrayOption::Native { .. } => im::hashset![], BitArrayOption::Size { value, .. } => value.referenced_variables(), } } } impl BitArrayOption { pub fn find_node(&self, byte_index: u32) -> Option> { match self { BitArrayOption::Bytes { .. } | BitArrayOption::Int { .. } | BitArrayOption::Float { .. } | BitArrayOption::Bits { .. } | BitArrayOption::Utf8 { .. } | BitArrayOption::Utf16 { .. } | BitArrayOption::Utf32 { .. } | BitArrayOption::Utf8Codepoint { .. } | BitArrayOption::Utf16Codepoint { .. } | BitArrayOption::Utf32Codepoint { .. } | BitArrayOption::Signed { .. } | BitArrayOption::Unsigned { .. } | BitArrayOption::Big { .. } | BitArrayOption::Little { .. } | BitArrayOption::Native { .. } | BitArrayOption::Unit { .. } => None, BitArrayOption::Size { value, .. } => value.find_node(byte_index), } } } impl BitArrayOption { pub fn find_node(&self, byte_index: u32) -> Option> { match self { BitArrayOption::Bytes { .. } | BitArrayOption::Int { .. } | BitArrayOption::Float { .. } | BitArrayOption::Bits { .. } | BitArrayOption::Utf8 { .. } | BitArrayOption::Utf16 { .. } | BitArrayOption::Utf32 { .. } | BitArrayOption::Utf8Codepoint { .. } | BitArrayOption::Utf16Codepoint { .. } | BitArrayOption::Utf32Codepoint { .. } | BitArrayOption::Signed { .. } | BitArrayOption::Unsigned { .. } | BitArrayOption::Big { .. } | BitArrayOption::Little { .. } | BitArrayOption::Native { .. } | BitArrayOption::Unit { .. } => None, BitArrayOption::Size { value, .. } => value.find_node(byte_index), } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum TodoKind { Keyword, EmptyFunction { function_location: SrcSpan }, IncompleteUse, EmptyBlock, } #[derive(Debug, Default)] pub struct GroupedDefinitions { pub functions: Vec, pub constants: Vec, pub custom_types: Vec, pub imports: Vec, pub type_aliases: Vec, } impl GroupedDefinitions { pub fn new(definitions: impl IntoIterator) -> Self { let mut this = Self::default(); for definition in definitions { this.add(definition) } this } pub fn len(&self) -> usize { let Self { custom_types, functions, constants, imports, type_aliases, } = self; functions.len() + constants.len() + imports.len() + custom_types.len() + type_aliases.len() } fn add(&mut self, statement: UntypedDefinition) { match statement { Definition::Import(import) => self.imports.push(import), Definition::Function(function) => self.functions.push(function), Definition::TypeAlias(type_alias) => self.type_aliases.push(type_alias), Definition::CustomType(custom_type) => self.custom_types.push(custom_type), Definition::ModuleConstant(constant) => self.constants.push(constant), } } } /// A statement with in a function body. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Statement { /// A bare expression that is not assigned to any variable. Expression(ExpressionT), /// Assigning an expression to variables using a pattern. Assignment(Box>), /// A `use` expression. Use(Use), /// A bool assertion. Assert(Assert), } pub type UntypedUse = Use<(), UntypedExpr>; pub type TypedUse = Use, TypedExpr>; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Use { /// In an untyped use this is the expression with the untyped code of the /// callback function. /// /// In a typed use this is the typed function call the use expression /// desugars to. /// pub call: Box, /// This is the location of the whole use line, starting from the `use` /// keyword and ending with the function call on the right hand side of /// `<-`. /// /// ```gleam /// use a <- result.try(result) /// ^^^^^^^^^^^^^^^^^^^^^^^^^^^ /// ``` /// pub location: SrcSpan, /// This is the location of the expression on the right hand side of the use /// arrow. /// /// ```gleam /// use a <- result.try(result) /// ^^^^^^^^^^^^^^^^^^ /// ``` /// pub right_hand_side_location: SrcSpan, /// This is the SrcSpan of the patterns you find on the left hand side of /// `<-` in a use expression. /// /// ```gleam /// use pattern1, pattern2 <- todo /// ^^^^^^^^^^^^^^^^^^ /// ``` /// /// In case there's no patterns it will be corresponding to the SrcSpan of /// the `use` keyword itself. /// pub assignments_location: SrcSpan, /// The patterns on the left hand side of `<-` in a use expression. /// pub assignments: Vec>, } pub type UntypedUseAssignment = UseAssignment<()>; pub type TypedUseAssignment = UseAssignment>; #[derive(Debug, Clone, PartialEq, Eq)] pub struct UseAssignment { pub location: SrcSpan, pub pattern: Pattern, pub annotation: Option, } impl TypedUse { pub fn find_node(&self, byte_index: u32) -> Option> { for assignment in self.assignments.iter() { if let Some(found) = assignment.pattern.find_node(byte_index) { return Some(found); } if let Some(found) = assignment .annotation .as_ref() .and_then(|annotation| annotation.find_node(byte_index, assignment.pattern.type_())) { return Some(found); } } self.call.find_node(byte_index) } pub fn callback_arguments(&self) -> Option<&Vec> { let TypedExpr::Call { arguments, .. } = self.call.as_ref() else { return None; }; let callback = arguments.iter().last()?; let TypedExpr::Fn { arguments, .. } = &callback.value else { // The expression might be invalid so we have to return a None here return None; }; Some(arguments) } } pub type TypedStatement = Statement, TypedExpr>; pub type UntypedStatement = Statement<(), UntypedExpr>; impl Statement { /// Returns `true` if the statement is [`Expression`]. /// /// [`Expression`]: Statement::Expression #[must_use] pub fn is_expression(&self) -> bool { matches!(self, Self::Expression(..)) } #[must_use] pub fn is_use(&self) -> bool { matches!(self, Self::Use(_)) } } impl UntypedStatement { pub fn location(&self) -> SrcSpan { match self { Statement::Expression(expression) => expression.location(), Statement::Assignment(assignment) => assignment.location, Statement::Use(use_) => use_.location.merge(&use_.call.location()), Statement::Assert(assert) => assert.location, } } pub fn start_byte_index(&self) -> u32 { match self { Statement::Expression(expression) => expression.start_byte_index(), Statement::Assignment(assignment) => assignment.location.start, Statement::Use(use_) => use_.location.start, Statement::Assert(assert) => assert.location.start, } } } impl TypedStatement { pub fn is_println(&self) -> bool { match self { Statement::Expression(e) => e.is_println(), Statement::Assignment(_) => false, Statement::Use(_) => false, Statement::Assert(_) => false, } } pub fn location(&self) -> SrcSpan { match self { Statement::Expression(expression) => expression.location(), Statement::Assignment(assignment) => assignment.location, // A use statement covers the entire block: `use_.location` covers // just the use's first line and not what comes after it. Statement::Use(use_) => use_.location.merge(&use_.call.location()), Statement::Assert(assert) => assert.location, } } /// Returns the location of the last element of a statement. This means that /// if the statement is a use you'll get the location of the last item at /// the end of its block. pub fn last_location(&self) -> SrcSpan { match self { Statement::Expression(expression) => expression.last_location(), Statement::Assignment(assignment) => assignment.value.last_location(), Statement::Use(use_) => use_.call.last_location(), Statement::Assert(assert) => assert.value.last_location(), } } pub fn type_(&self) -> Arc { match self { Statement::Expression(expression) => expression.type_(), Statement::Assignment(assignment) => assignment.type_(), Statement::Use(use_) => use_.call.type_(), Statement::Assert(_) => nil(), } } pub fn definition_location(&self) -> Option { match self { Statement::Expression(expression) => expression.definition_location(), Statement::Assignment(_) => None, Statement::Use(use_) => use_.call.definition_location(), Statement::Assert(_) => None, } } pub fn find_node(&self, byte_index: u32) -> Option> { match self { Statement::Use(use_) => use_.find_node(byte_index), Statement::Expression(expression) => expression.find_node(byte_index), Statement::Assignment(assignment) => assignment.find_node(byte_index).or_else(|| { if assignment.location.contains(byte_index) { Some(Located::Statement(self)) } else { None } }), Statement::Assert(assert) => assert.find_node(byte_index).or_else(|| { if assert.location.contains(byte_index) { Some(Located::Statement(self)) } else { None } }), } } pub fn find_statement(&self, byte_index: u32) -> Option<&TypedStatement> { match self { Statement::Use(use_) => use_.call.find_statement(byte_index), Statement::Expression(expression) => expression.find_statement(byte_index), Statement::Assignment(assignment) => { assignment.value.find_statement(byte_index).or_else(|| { if assignment.location.contains(byte_index) { Some(self) } else { None } }) } Statement::Assert(assert) => assert.value.find_statement(byte_index).or_else(|| { if assert.location.contains(byte_index) { Some(self) } else { None } }), } } pub fn type_defining_location(&self) -> SrcSpan { match self { Statement::Expression(expression) => expression.type_defining_location(), Statement::Assignment(assignment) => assignment.location, Statement::Use(use_) => use_.location, Statement::Assert(assert) => assert.location, } } fn is_pure_value_constructor(&self) -> bool { match self { Statement::Expression(expression) => expression.is_pure_value_constructor(), Statement::Assignment(assignment) => { // A let assert is not considered a pure value constructor // as it could crash the program! !assignment.kind.is_assert() && assignment.value.is_pure_value_constructor() } Statement::Use(Use { call, .. }) => call.is_pure_value_constructor(), // Assert statements by definition are not pure Statement::Assert(_) => false, } } fn syntactically_eq(&self, other: &Self) -> bool { match (self, other) { (Statement::Expression(one), Statement::Expression(other)) => { one.syntactically_eq(other) } (Statement::Expression(_), _) => false, (Statement::Assignment(one), Statement::Assignment(other)) => { one.pattern.syntactically_eq(&other.pattern) && one.value.syntactically_eq(&other.value) } (Statement::Assignment(_), _) => false, (Statement::Use(one), Statement::Use(other)) => one.call.syntactically_eq(&other.call), (Statement::Use(_), _) => false, (Statement::Assert(one), Statement::Assert(other)) => { let messages_are_equal = match (&one.message, &other.message) { (None, None) => true, (None, Some(_)) | (Some(_), None) => false, (Some(one), Some(other)) => one.syntactically_eq(other), }; messages_are_equal && one.value.syntactically_eq(&other.value) } (Statement::Assert(_), _) => false, } } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct Assignment { pub location: SrcSpan, pub value: ExpressionT, pub pattern: Pattern, pub kind: AssignmentKind, pub compiled_case: CompiledCase, /// This will be true for assignments that are automatically generated by /// the compiler. pub annotation: Option, } pub type TypedAssignment = Assignment, TypedExpr>; pub type UntypedAssignment = Assignment<(), UntypedExpr>; impl TypedAssignment { pub fn find_node(&self, byte_index: u32) -> Option> { if let Some(annotation) = &self.annotation && let Some(l) = annotation.find_node(byte_index, self.pattern.type_()) { return Some(l); } self.pattern .find_node(byte_index) .or_else(|| self.value.find_node(byte_index)) } pub fn type_(&self) -> Arc { self.value.type_() } } pub type TypedAssert = Assert; pub type UntypedAssert = Assert; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Assert { pub location: SrcSpan, pub value: Expression, pub message: Option, } impl TypedAssert { pub fn find_node(&self, byte_index: u32) -> Option> { if let Some(found) = self.value.find_node(byte_index) { return Some(found); } if let Some(message) = &self.message && let Some(found) = message.find_node(byte_index) { return Some(found); } None } } /// A pipeline is desugared to a series of assignments: /// /// ```gleam /// wibble |> wobble |> woo /// ``` /// /// Becomes: /// /// ```erl /// Pipe1 = wibble /// Pipe2 = wobble(Pipe1) /// woo(Pipe2) /// ``` /// /// This represents one of such assignments once the pipeline has been desugared /// and each step has been typed. /// /// > We're not using a more general `TypedAssignment` node since that has much /// > more informations to carry around. This one is limited since we know it /// > will always be in the form `VarName = `, with no patterns on the /// > left hand side of the assignment. /// > Being more constrained simplifies code generation for pipelines! /// #[derive(Debug, Clone, PartialEq, Eq)] pub struct TypedPipelineAssignment { pub location: SrcSpan, pub name: EcoString, pub value: Box, } impl TypedPipelineAssignment { pub fn find_node(&self, byte_index: u32) -> Option> { self.value.find_node(byte_index) } pub fn find_statement(&self, byte_index: u32) -> Option<&TypedStatement> { self.value.find_statement(byte_index) } pub fn type_(&self) -> Arc { self.value.type_() } } /// The kind of desugaring that might take place when rewriting a pipeline to /// regular assignments. /// #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PipelineAssignmentKind { /// In case `a |> b(c)` is desugared to `b(a, c)`. FirstArgument { /// The location of the second argument of the call, in case there's any: /// - `a |> b(c, d)`: here it's `Some` wrapping the location of `c`. /// - `a |> b()`: here it's `None`. second_argument: Option, }, /// In case there's an explicit hole and `a |> b(_, c)` is desugared to /// `b(a, c)`. Hole { hole: SrcSpan }, /// In case `a |> b(c)` is desugared to `b(c)(a)` FunctionCall, /// In case there's an echo in the middle of a pipeline `a |> echo` Echo, } ================================================ FILE: compiler-core/src/ast_folder.rs ================================================ use ecow::EcoString; use itertools::Itertools; use num_bigint::BigInt; use vec1::Vec1; use crate::{ analyse::Inferred, ast::{ Assert, AssignName, Assignment, BinOp, BitArraySize, CallArg, Constant, Definition, FunctionLiteralKind, InvalidExpression, Pattern, RecordBeingUpdated, RecordUpdateArg, SrcSpan, Statement, TailPattern, TargetedDefinition, TodoKind, TypeAst, TypeAstConstructor, TypeAstFn, TypeAstHole, TypeAstTuple, TypeAstVar, UntypedArg, UntypedAssert, UntypedAssignment, UntypedClause, UntypedConstant, UntypedConstantBitArraySegment, UntypedCustomType, UntypedDefinition, UntypedExpr, UntypedExprBitArraySegment, UntypedFunction, UntypedImport, UntypedModule, UntypedModuleConstant, UntypedPattern, UntypedPatternBitArraySegment, UntypedRecordUpdateArg, UntypedStatement, UntypedTailPattern, UntypedTypeAlias, UntypedUse, UntypedUseAssignment, Use, UseAssignment, }, build::Target, parse::LiteralFloatValue, type_::error::VariableOrigin, }; #[allow(dead_code)] pub trait UntypedModuleFolder: TypeAstFolder + UntypedExprFolder { /// You probably don't want to override this method. fn fold_module(&mut self, mut module: UntypedModule) -> UntypedModule { module.definitions = module .definitions .into_iter() .map(|definition| { let TargetedDefinition { definition, target } = definition; match definition { Definition::Function(function) => { let function = self.fold_function_definition(function, target); let definition = self.walk_function_definition(function); TargetedDefinition { definition, target } } Definition::TypeAlias(type_alias) => { let type_alias = self.fold_type_alias(type_alias, target); let definition = self.walk_type_alias(type_alias); TargetedDefinition { definition, target } } Definition::CustomType(custom_type) => { let custom_type = self.fold_custom_type(custom_type, target); let definition = self.walk_custom_type(custom_type); TargetedDefinition { definition, target } } Definition::Import(import) => { let import = self.fold_import(import, target); let definition = self.walk_import(import); TargetedDefinition { definition, target } } Definition::ModuleConstant(constant) => { let constant = self.fold_module_constant(constant, target); let definition = self.walk_module_constant(constant); TargetedDefinition { definition, target } } } }) .collect(); module } /// You probably don't want to override this method. fn walk_function_definition(&mut self, mut function: UntypedFunction) -> UntypedDefinition { function.body = function .body .into_iter() .map(|statement| self.fold_statement(statement)) .collect_vec(); function.return_annotation = function .return_annotation .map(|type_| self.fold_type(type_)); function.arguments = function .arguments .into_iter() .map(|mut argument| { argument.annotation = argument.annotation.map(|type_| self.fold_type(type_)); argument }) .collect(); Definition::Function(function) } /// You probably don't want to override this method. fn walk_type_alias(&mut self, mut type_alias: UntypedTypeAlias) -> UntypedDefinition { type_alias.type_ast = self.fold_type(type_alias.type_ast); Definition::TypeAlias(type_alias) } /// You probably don't want to override this method. fn walk_custom_type(&mut self, mut custom_type: UntypedCustomType) -> UntypedDefinition { custom_type.constructors = custom_type .constructors .into_iter() .map(|mut constructor| { constructor.arguments = constructor .arguments .into_iter() .map(|mut argument| { argument.ast = self.fold_type(argument.ast); argument }) .collect(); constructor }) .collect(); Definition::CustomType(custom_type) } /// You probably don't want to override this method. fn walk_import(&mut self, i: UntypedImport) -> UntypedDefinition { Definition::Import(i) } /// You probably don't want to override this method. fn walk_module_constant(&mut self, mut constant: UntypedModuleConstant) -> UntypedDefinition { constant.annotation = constant.annotation.map(|type_| self.fold_type(type_)); constant.value = Box::new(self.fold_constant(*constant.value)); Definition::ModuleConstant(constant) } fn fold_function_definition( &mut self, function: UntypedFunction, _target: Option, ) -> UntypedFunction { function } fn fold_type_alias( &mut self, function: UntypedTypeAlias, _target: Option, ) -> UntypedTypeAlias { function } fn fold_custom_type( &mut self, custom_type: UntypedCustomType, _target: Option, ) -> UntypedCustomType { custom_type } fn fold_import(&mut self, import: UntypedImport, _target: Option) -> UntypedImport { import } fn fold_module_constant( &mut self, constant: UntypedModuleConstant, _target: Option, ) -> UntypedModuleConstant { constant } } #[allow(dead_code)] pub trait TypeAstFolder { /// Visit a node and potentially replace it with another node using the /// `fold_*` methods. Afterwards, the `walk` method is called on the new /// node to continue traversing. /// /// You probably don't want to override this method. fn fold_type(&mut self, type_: TypeAst) -> TypeAst { let type_ = self.update_type(type_); self.walk_type(type_) } /// You probably don't want to override this method. fn update_type(&mut self, type_: TypeAst) -> TypeAst { match type_ { TypeAst::Constructor(constructor) => self.fold_type_constructor(constructor), TypeAst::Fn(fn_) => self.fold_type_fn(fn_), TypeAst::Var(var) => self.fold_type_var(var), TypeAst::Tuple(tuple) => self.fold_type_tuple(tuple), TypeAst::Hole(hole) => self.fold_type_hole(hole), } } /// You probably don't want to override this method. fn walk_type(&mut self, type_: TypeAst) -> TypeAst { match type_ { TypeAst::Constructor(mut constructor) => { constructor.arguments = self.fold_all_types(constructor.arguments); TypeAst::Constructor(constructor) } TypeAst::Fn(mut fn_) => { fn_.arguments = self.fold_all_types(fn_.arguments); fn_.return_ = Box::new(self.fold_type(*fn_.return_)); TypeAst::Fn(fn_) } TypeAst::Tuple(mut tuple) => { tuple.elements = self.fold_all_types(tuple.elements); TypeAst::Tuple(tuple) } TypeAst::Var(_) | TypeAst::Hole(_) => type_, } } /// You probably don't want to override this method. fn fold_all_types(&mut self, types: Vec) -> Vec { types .into_iter() .map(|type_| self.fold_type(type_)) .collect() } fn fold_type_constructor(&mut self, constructor: TypeAstConstructor) -> TypeAst { TypeAst::Constructor(constructor) } fn fold_type_fn(&mut self, function: TypeAstFn) -> TypeAst { TypeAst::Fn(function) } fn fold_type_tuple(&mut self, tuple: TypeAstTuple) -> TypeAst { TypeAst::Tuple(tuple) } fn fold_type_var(&mut self, var: TypeAstVar) -> TypeAst { TypeAst::Var(var) } fn fold_type_hole(&mut self, hole: TypeAstHole) -> TypeAst { TypeAst::Hole(hole) } } #[allow(dead_code)] pub trait UntypedExprFolder: TypeAstFolder + UntypedConstantFolder + PatternFolder { /// Visit a node and potentially replace it with another node using the /// `fold_*` methods. Afterwards, the `walk` method is called on the new /// node to continue traversing. /// /// You probably don't want to override this method. fn fold_expr(&mut self, expression: UntypedExpr) -> UntypedExpr { let expression = self.update_expr(expression); self.walk_expr(expression) } /// You probably don't want to override this method. fn update_expr(&mut self, expression: UntypedExpr) -> UntypedExpr { match expression { UntypedExpr::Var { location, name } => self.fold_var(location, name), UntypedExpr::Int { location, value, int_value, } => self.fold_int(location, value, int_value), UntypedExpr::Float { location, value, float_value, } => self.fold_float(location, value, float_value), UntypedExpr::String { location, value } => self.fold_string(location, value), UntypedExpr::Block { location, statements, } => self.fold_block(location, statements), UntypedExpr::Fn { location, end_of_head_byte_index, kind, arguments, body, return_annotation, } => self.fold_fn( location, end_of_head_byte_index, kind, arguments, body, return_annotation, ), UntypedExpr::List { location, elements, tail, } => self.fold_list(location, elements, tail), UntypedExpr::Call { location, fun, arguments, } => self.fold_call(location, fun, arguments), UntypedExpr::BinOp { location, name, name_location, left, right, } => self.fold_bin_op(location, name, name_location, left, right), UntypedExpr::PipeLine { expressions } => self.fold_pipe_line(expressions), UntypedExpr::Case { location, subjects, clauses, } => self.fold_case(location, subjects, clauses), UntypedExpr::FieldAccess { location, label_location, label, container, } => self.fold_field_access(location, label_location, label, container), UntypedExpr::Tuple { location, elements } => self.fold_tuple(location, elements), UntypedExpr::TupleIndex { location, index, tuple, } => self.fold_tuple_index(location, index, tuple), UntypedExpr::Todo { kind, location, message, } => self.fold_todo(kind, location, message), UntypedExpr::Echo { location, keyword_end, expression, message, } => self.fold_echo(location, keyword_end, expression, message), UntypedExpr::Panic { location, message } => self.fold_panic(location, message), UntypedExpr::BitArray { location, segments } => self.fold_bit_array(location, segments), UntypedExpr::RecordUpdate { location, constructor, record, arguments, } => self.fold_record_update(location, constructor, record, arguments), UntypedExpr::NegateBool { location, value } => self.fold_negate_bool(location, value), UntypedExpr::NegateInt { location, value } => self.fold_negate_int(location, value), } } /// You probably don't want to override this method. fn walk_expr(&mut self, expression: UntypedExpr) -> UntypedExpr { match expression { UntypedExpr::Int { .. } | UntypedExpr::Var { .. } | UntypedExpr::Float { .. } | UntypedExpr::String { .. } | UntypedExpr::NegateInt { .. } | UntypedExpr::NegateBool { .. } => expression, UntypedExpr::Todo { kind, location, message, } => UntypedExpr::Todo { kind, location, message: message.map(|msg_expr| Box::new(self.fold_expr(*msg_expr))), }, UntypedExpr::Panic { location, message } => UntypedExpr::Panic { location, message: message.map(|msg_expr| Box::new(self.fold_expr(*msg_expr))), }, UntypedExpr::Echo { location, expression, keyword_end, message, } => UntypedExpr::Echo { location, keyword_end, expression: expression.map(|expression| Box::new(self.fold_expr(*expression))), message: message.map(|message| Box::new(self.fold_expr(*message))), }, UntypedExpr::Block { location, statements, } => { let statements = statements.mapped(|s| self.fold_statement(s)); UntypedExpr::Block { location, statements, } } UntypedExpr::Fn { location, kind, end_of_head_byte_index, arguments, body, return_annotation, } => { let arguments = arguments.into_iter().map(|a| self.fold_arg(a)).collect(); let return_annotation = return_annotation.map(|type_| self.fold_type(type_)); let body = body.mapped(|s| self.fold_statement(s)); UntypedExpr::Fn { location, end_of_head_byte_index, kind, arguments, body, return_annotation, } } UntypedExpr::List { location, elements, tail, } => { let elements = elements .into_iter() .map(|element| self.fold_expr(element)) .collect(); let tail = tail.map(|e| Box::new(self.fold_expr(*e))); UntypedExpr::List { location, elements, tail, } } UntypedExpr::Call { location, fun, arguments, } => { let fun = Box::new(self.fold_expr(*fun)); let arguments = arguments .into_iter() .map(|mut a| { a.value = self.fold_expr(a.value); a }) .collect(); UntypedExpr::Call { location, fun, arguments, } } UntypedExpr::BinOp { location, name, name_location, left, right, } => { let left = Box::new(self.fold_expr(*left)); let right = Box::new(self.fold_expr(*right)); UntypedExpr::BinOp { location, name, name_location, left, right, } } UntypedExpr::PipeLine { expressions } => { let expressions = expressions.mapped(|e| self.fold_expr(e)); UntypedExpr::PipeLine { expressions } } UntypedExpr::Case { location, subjects, clauses, } => { let subjects = subjects.into_iter().map(|e| self.fold_expr(e)).collect(); let clauses = clauses.map(|clauses| { clauses .into_iter() .map(|mut c| { c.pattern = c .pattern .into_iter() .map(|p| self.fold_pattern(p)) .collect(); c.alternative_patterns = c .alternative_patterns .into_iter() .map(|p| p.into_iter().map(|p| self.fold_pattern(p)).collect()) .collect(); c.then = self.fold_expr(c.then); c }) .collect() }); UntypedExpr::Case { location, subjects, clauses, } } UntypedExpr::FieldAccess { location, label_location, label, container, } => { let container = Box::new(self.fold_expr(*container)); UntypedExpr::FieldAccess { location, label_location, label, container, } } UntypedExpr::Tuple { location, elements } => { let elements = elements .into_iter() .map(|element| self.fold_expr(element)) .collect(); UntypedExpr::Tuple { location, elements } } UntypedExpr::TupleIndex { location, index, tuple, } => { let tuple = Box::new(self.fold_expr(*tuple)); UntypedExpr::TupleIndex { location, index, tuple, } } UntypedExpr::BitArray { location, segments } => { let segments = segments .into_iter() .map(|mut s| { s.value = Box::new(self.fold_expr(*s.value)); s }) .collect(); UntypedExpr::BitArray { location, segments } } UntypedExpr::RecordUpdate { location, constructor, record, arguments, } => { let constructor = Box::new(self.fold_expr(*constructor)); let arguments = arguments .into_iter() .map(|mut a| { a.value = self.fold_expr(a.value); a }) .collect(); UntypedExpr::RecordUpdate { location, constructor, record, arguments, } } } } /// You probably don't want to override this method. fn fold_arg(&mut self, arg: UntypedArg) -> UntypedArg { let UntypedArg { location, names, annotation, type_, } = arg; let annotation = annotation.map(|type_| self.fold_type(type_)); UntypedArg { location, names, annotation, type_, } } /// You probably don't want to override this method. fn fold_statement(&mut self, statement: UntypedStatement) -> UntypedStatement { let statement = self.update_statement(statement); self.walk_statement(statement) } /// You probably don't want to override this method. fn update_statement(&mut self, statement: UntypedStatement) -> UntypedStatement { match statement { Statement::Expression(expression) => Statement::Expression(expression), Statement::Assignment(assignment) => { Statement::Assignment(Box::new(self.fold_assignment(*assignment))) } Statement::Use(use_) => Statement::Use(self.fold_use(use_)), Statement::Assert(assert) => Statement::Assert(self.fold_assert(assert)), } } /// You probably don't want to override this method. fn walk_statement(&mut self, statement: UntypedStatement) -> UntypedStatement { match statement { Statement::Expression(expression) => Statement::Expression(self.fold_expr(expression)), Statement::Assignment(assignment) => { let Assignment { location, value, pattern, kind, annotation, compiled_case, } = *assignment; let pattern = self.fold_pattern(pattern); let annotation = annotation.map(|type_| self.fold_type(type_)); let value = self.fold_expr(value); Statement::Assignment(Box::new(Assignment { location, value, pattern, kind, annotation, compiled_case, })) } Statement::Use(Use { location, right_hand_side_location, assignments_location, call, assignments, }) => { let assignments = assignments .into_iter() .map(|assignment| { let mut use_ = self.fold_use_assignment(assignment); use_.pattern = self.fold_pattern(use_.pattern); use_ }) .collect(); let call = Box::new(self.fold_expr(*call)); Statement::Use(Use { location, right_hand_side_location, assignments_location, call, assignments, }) } Statement::Assert(Assert { location, value, message, }) => { let value = self.fold_expr(value); let message = message.map(|message| self.fold_expr(message)); Statement::Assert(Assert { location, value, message, }) } } } /// You probably don't want to override this method. fn fold_use_assignment(&mut self, use_: UntypedUseAssignment) -> UntypedUseAssignment { let UseAssignment { location, pattern, annotation, } = use_; let annotation = annotation.map(|type_| self.fold_type(type_)); UseAssignment { location, pattern, annotation, } } fn fold_int(&mut self, location: SrcSpan, value: EcoString, int_value: BigInt) -> UntypedExpr { UntypedExpr::Int { location, value, int_value, } } fn fold_float( &mut self, location: SrcSpan, value: EcoString, float_value: LiteralFloatValue, ) -> UntypedExpr { UntypedExpr::Float { location, value, float_value, } } fn fold_string(&mut self, location: SrcSpan, value: EcoString) -> UntypedExpr { UntypedExpr::String { location, value } } fn fold_block(&mut self, location: SrcSpan, statements: Vec1) -> UntypedExpr { UntypedExpr::Block { location, statements, } } fn fold_var(&mut self, location: SrcSpan, name: EcoString) -> UntypedExpr { UntypedExpr::Var { location, name } } fn fold_fn( &mut self, location: SrcSpan, end_of_head_byte_index: u32, kind: FunctionLiteralKind, arguments: Vec, body: Vec1, return_annotation: Option, ) -> UntypedExpr { UntypedExpr::Fn { location, end_of_head_byte_index, kind, arguments, body, return_annotation, } } fn fold_list( &mut self, location: SrcSpan, elements: Vec, tail: Option>, ) -> UntypedExpr { UntypedExpr::List { location, elements, tail, } } fn fold_call( &mut self, location: SrcSpan, fun: Box, arguments: Vec>, ) -> UntypedExpr { UntypedExpr::Call { location, fun, arguments, } } fn fold_bin_op( &mut self, location: SrcSpan, name: BinOp, name_location: SrcSpan, left: Box, right: Box, ) -> UntypedExpr { UntypedExpr::BinOp { location, name, name_location, left, right, } } fn fold_pipe_line(&mut self, expressions: Vec1) -> UntypedExpr { UntypedExpr::PipeLine { expressions } } fn fold_case( &mut self, location: SrcSpan, subjects: Vec, clauses: Option>, ) -> UntypedExpr { UntypedExpr::Case { location, subjects, clauses, } } fn fold_field_access( &mut self, location: SrcSpan, label_location: SrcSpan, label: EcoString, container: Box, ) -> UntypedExpr { UntypedExpr::FieldAccess { location, label_location, label, container, } } fn fold_tuple(&mut self, location: SrcSpan, elements: Vec) -> UntypedExpr { UntypedExpr::Tuple { location, elements } } fn fold_tuple_index( &mut self, location: SrcSpan, index: u64, tuple: Box, ) -> UntypedExpr { UntypedExpr::TupleIndex { location, index, tuple, } } fn fold_todo( &mut self, kind: TodoKind, location: SrcSpan, message: Option>, ) -> UntypedExpr { UntypedExpr::Todo { kind, location, message, } } fn fold_echo( &mut self, location: SrcSpan, keyword_end: u32, expression: Option>, message: Option>, ) -> UntypedExpr { UntypedExpr::Echo { location, keyword_end, expression, message, } } fn fold_panic(&mut self, location: SrcSpan, message: Option>) -> UntypedExpr { UntypedExpr::Panic { location, message } } fn fold_bit_array( &mut self, location: SrcSpan, segments: Vec, ) -> UntypedExpr { UntypedExpr::BitArray { location, segments } } fn fold_record_update( &mut self, location: SrcSpan, constructor: Box, record: RecordBeingUpdated, arguments: Vec, ) -> UntypedExpr { UntypedExpr::RecordUpdate { location, constructor, record, arguments, } } fn fold_negate_bool(&mut self, location: SrcSpan, value: Box) -> UntypedExpr { UntypedExpr::NegateBool { location, value } } fn fold_negate_int(&mut self, location: SrcSpan, value: Box) -> UntypedExpr { UntypedExpr::NegateInt { location, value } } fn fold_assignment(&mut self, assignment: UntypedAssignment) -> UntypedAssignment { assignment } fn fold_use(&mut self, use_: UntypedUse) -> UntypedUse { use_ } fn fold_assert(&mut self, assert: UntypedAssert) -> UntypedAssert { assert } } #[allow(dead_code)] pub trait UntypedConstantFolder { /// You probably don't want to override this method. fn fold_constant(&mut self, constant: UntypedConstant) -> UntypedConstant { let constant = self.update_constant(constant); self.walk_constant(constant) } /// You probably don't want to override this method. fn update_constant(&mut self, constant: UntypedConstant) -> UntypedConstant { match constant { Constant::Int { location, value, int_value, } => self.fold_constant_int(location, value, int_value), Constant::Float { location, value, float_value, } => self.fold_constant_float(location, value, float_value), Constant::String { location, value } => self.fold_constant_string(location, value), Constant::Tuple { location, elements, type_: (), } => self.fold_constant_tuple(location, elements), Constant::List { location, elements, type_: (), tail, } => self.fold_constant_list(location, elements, tail), Constant::Record { location, module, name, arguments, tag: (), type_: (), field_map: _, record_constructor: _, } => self.fold_constant_record(location, module, name, arguments), Constant::RecordUpdate { location, constructor_location, module, name, record, arguments, tag: (), type_: (), field_map: _, } => self.fold_constant_record_update( location, constructor_location, module, name, record, arguments, ), Constant::BitArray { location, segments } => { self.fold_constant_bit_array(location, segments) } Constant::Var { location, module, name, constructor: _, type_: (), } => self.fold_constant_var(location, module, name), Constant::StringConcatenation { location, left, right, } => self.fold_constant_string_concatenation(location, left, right), Constant::Invalid { location, type_: (), extra_information, } => self.fold_constant_invalid(location, extra_information), } } fn fold_constant_int( &mut self, location: SrcSpan, value: EcoString, int_value: BigInt, ) -> UntypedConstant { Constant::Int { location, value, int_value, } } fn fold_constant_float( &mut self, location: SrcSpan, value: EcoString, float_value: LiteralFloatValue, ) -> UntypedConstant { Constant::Float { location, value, float_value, } } fn fold_constant_string(&mut self, location: SrcSpan, value: EcoString) -> UntypedConstant { Constant::String { location, value } } fn fold_constant_tuple( &mut self, location: SrcSpan, elements: Vec, ) -> UntypedConstant { Constant::Tuple { location, elements, type_: (), } } fn fold_constant_list( &mut self, location: SrcSpan, elements: Vec, tail: Option>, ) -> UntypedConstant { Constant::List { location, elements, type_: (), tail, } } fn fold_constant_record( &mut self, location: SrcSpan, module: Option<(EcoString, SrcSpan)>, name: EcoString, arguments: Vec>, ) -> UntypedConstant { Constant::Record { location, module, name, arguments, tag: (), type_: (), field_map: Inferred::Unknown, record_constructor: None, } } fn fold_constant_record_update( &mut self, location: SrcSpan, constructor_location: SrcSpan, module: Option<(EcoString, SrcSpan)>, name: EcoString, record: RecordBeingUpdated, arguments: Vec>, ) -> UntypedConstant { Constant::RecordUpdate { location, constructor_location, module, name, record, arguments, tag: (), type_: (), field_map: Inferred::Unknown, } } fn fold_constant_bit_array( &mut self, location: SrcSpan, segments: Vec, ) -> UntypedConstant { Constant::BitArray { location, segments } } fn fold_constant_var( &mut self, location: SrcSpan, module: Option<(EcoString, SrcSpan)>, name: EcoString, ) -> UntypedConstant { Constant::Var { location, module, name, constructor: None, type_: (), } } fn fold_constant_string_concatenation( &mut self, location: SrcSpan, left: Box, right: Box, ) -> UntypedConstant { Constant::StringConcatenation { location, left, right, } } fn fold_constant_invalid( &mut self, location: SrcSpan, extra_information: Option, ) -> UntypedConstant { Constant::Invalid { location, type_: (), extra_information, } } /// You probably don't want to override this method. fn walk_constant(&mut self, constant: UntypedConstant) -> UntypedConstant { match constant { Constant::Var { .. } | Constant::Int { .. } | Constant::Float { .. } | Constant::String { .. } | Constant::Tuple { .. } | Constant::Invalid { .. } => constant, Constant::List { location, elements, type_, tail, } => { let elements = elements .into_iter() .map(|element| self.fold_constant(element)) .collect(); let tail = tail.map(|tail| Box::new(self.fold_constant(*tail))); Constant::List { location, elements, type_, tail, } } Constant::Record { location, module, name, arguments, tag, type_, field_map, record_constructor, } => { let arguments = arguments .into_iter() .map(|mut argument| { argument.value = self.fold_constant(argument.value); argument }) .collect(); Constant::Record { location, module, name, arguments, tag, type_, field_map, record_constructor, } } Constant::RecordUpdate { location, constructor_location, module, name, record, arguments, tag, type_, field_map, } => { let record = RecordBeingUpdated { base: Box::new(self.fold_constant(*record.base)), location: record.location, }; let arguments = arguments .into_iter() .map(|argument| RecordUpdateArg { label: argument.label, location: argument.location, value: self.fold_constant(argument.value), }) .collect(); Constant::RecordUpdate { location, constructor_location, module, name, record, arguments, tag, type_, field_map, } } Constant::BitArray { location, segments } => { let segments = segments .into_iter() .map(|mut segment| { segment.value = Box::new(self.fold_constant(*segment.value)); segment }) .collect(); Constant::BitArray { location, segments } } Constant::StringConcatenation { location, left, right, } => { let left = Box::new(self.fold_constant(*left)); let right = Box::new(self.fold_constant(*right)); Constant::StringConcatenation { location, left, right, } } } } } #[allow(dead_code)] pub trait PatternFolder { /// You probably don't want to override this method. fn fold_pattern(&mut self, pattern: UntypedPattern) -> UntypedPattern { let pattern = self.update_pattern(pattern); self.walk_pattern(pattern) } /// You probably don't want to override this method. fn update_pattern(&mut self, pattern: UntypedPattern) -> UntypedPattern { match pattern { Pattern::Int { location, value, int_value, } => self.fold_pattern_int(location, value, int_value), Pattern::Float { location, value, float_value, } => self.fold_pattern_float(location, value, float_value), Pattern::String { location, value } => self.fold_pattern_string(location, value), Pattern::Variable { location, name, type_: (), origin, } => self.fold_pattern_var(location, name, origin), Pattern::BitArraySize(size) => self.fold_pattern_bit_array_size(size), Pattern::Assign { name, location, pattern, } => self.fold_pattern_assign(name, location, pattern), Pattern::Discard { name, location, type_: (), } => self.fold_pattern_discard(name, location), Pattern::List { location, elements, tail, type_: (), } => self.fold_pattern_list(location, elements, tail), Pattern::Constructor { location, name_location, name, arguments, module, spread, constructor: _, type_: (), } => self.fold_pattern_constructor( location, name_location, name, arguments, module, spread, ), Pattern::Tuple { location, elements } => self.fold_pattern_tuple(location, elements), Pattern::BitArray { location, segments } => { self.fold_pattern_bit_array(location, segments) } Pattern::StringPrefix { location, left_location, left_side_assignment, right_location, left_side_string, right_side_assignment, } => self.fold_pattern_string_prefix( location, left_location, left_side_assignment, right_location, left_side_string, right_side_assignment, ), Pattern::Invalid { location, .. } => self.fold_pattern_invalid(location), } } fn fold_pattern_int( &mut self, location: SrcSpan, value: EcoString, int_value: BigInt, ) -> UntypedPattern { Pattern::Int { location, value, int_value, } } fn fold_pattern_float( &mut self, location: SrcSpan, value: EcoString, float_value: LiteralFloatValue, ) -> UntypedPattern { Pattern::Float { location, value, float_value, } } fn fold_pattern_string(&mut self, location: SrcSpan, value: EcoString) -> UntypedPattern { Pattern::String { location, value } } fn fold_pattern_var( &mut self, location: SrcSpan, name: EcoString, origin: VariableOrigin, ) -> UntypedPattern { Pattern::Variable { location, name, type_: (), origin, } } fn fold_pattern_bit_array_size(&mut self, size: BitArraySize<()>) -> UntypedPattern { Pattern::BitArraySize(self.fold_bit_array_size(size)) } fn fold_bit_array_size(&mut self, size: BitArraySize<()>) -> BitArraySize<()> { match size { BitArraySize::Int { location, value, int_value, } => self.fold_bit_array_size_int(location, value, int_value), BitArraySize::Variable { location, name, .. } => { self.fold_bit_array_size_variable(location, name) } BitArraySize::BinaryOperator { location, operator, left, right, } => BitArraySize::BinaryOperator { location, operator, left: Box::new(self.fold_bit_array_size(*left)), right: Box::new(self.fold_bit_array_size(*right)), }, BitArraySize::Block { location, inner } => BitArraySize::Block { location, inner: Box::new(self.fold_bit_array_size(*inner)), }, } } fn fold_bit_array_size_int( &mut self, location: SrcSpan, value: EcoString, int_value: BigInt, ) -> BitArraySize<()> { BitArraySize::Int { location, value, int_value, } } fn fold_bit_array_size_variable( &mut self, location: SrcSpan, name: EcoString, ) -> BitArraySize<()> { BitArraySize::Variable { location, name, constructor: None, type_: (), } } fn fold_pattern_assign( &mut self, name: EcoString, location: SrcSpan, pattern: Box, ) -> UntypedPattern { Pattern::Assign { name, location, pattern, } } fn fold_pattern_discard(&mut self, name: EcoString, location: SrcSpan) -> UntypedPattern { Pattern::Discard { name, location, type_: (), } } fn fold_pattern_list( &mut self, location: SrcSpan, elements: Vec, tail: Option>, ) -> UntypedPattern { Pattern::List { location, elements, tail, type_: (), } } fn fold_pattern_constructor( &mut self, location: SrcSpan, name_location: SrcSpan, name: EcoString, arguments: Vec>, module: Option<(EcoString, SrcSpan)>, spread: Option, ) -> UntypedPattern { Pattern::Constructor { location, name_location, name, arguments, module, constructor: Inferred::Unknown, spread, type_: (), } } fn fold_pattern_tuple( &mut self, location: SrcSpan, elements: Vec, ) -> UntypedPattern { Pattern::Tuple { location, elements } } fn fold_pattern_bit_array( &mut self, location: SrcSpan, segments: Vec, ) -> UntypedPattern { Pattern::BitArray { location, segments } } fn fold_pattern_string_prefix( &mut self, location: SrcSpan, left_location: SrcSpan, left_side_assignment: Option<(EcoString, SrcSpan)>, right_location: SrcSpan, left_side_string: EcoString, right_side_assignment: AssignName, ) -> UntypedPattern { Pattern::StringPrefix { location, left_location, left_side_assignment, right_location, left_side_string, right_side_assignment, } } fn fold_pattern_invalid(&mut self, location: SrcSpan) -> UntypedPattern { Pattern::Invalid { location, type_: (), } } /// You probably don't want to override this method. fn walk_pattern(&mut self, pattern: UntypedPattern) -> UntypedPattern { match pattern { Pattern::Int { .. } | Pattern::Variable { .. } | Pattern::Float { .. } | Pattern::String { .. } | Pattern::Discard { .. } | Pattern::BitArraySize { .. } | Pattern::StringPrefix { .. } | Pattern::Invalid { .. } => pattern, Pattern::Assign { name, location, pattern, } => { let pattern = Box::new(self.fold_pattern(*pattern)); Pattern::Assign { name, location, pattern, } } Pattern::List { location, elements, tail, type_, } => { let elements = elements .into_iter() .map(|pattern| self.fold_pattern(pattern)) .collect(); let tail = tail.map(|tail_pattern| { Box::new(TailPattern { location: tail_pattern.location, pattern: self.fold_pattern(tail_pattern.pattern), }) }); Pattern::List { location, elements, tail, type_, } } Pattern::Constructor { location, name_location, name, arguments, module, constructor, spread, type_, } => { let arguments = arguments .into_iter() .map(|mut argument| { argument.value = self.fold_pattern(argument.value); argument }) .collect(); Pattern::Constructor { location, name_location, name, arguments, module, constructor, spread, type_, } } Pattern::Tuple { location, elements } => { let elements = elements .into_iter() .map(|pattern| self.fold_pattern(pattern)) .collect(); Pattern::Tuple { location, elements } } Pattern::BitArray { location, segments } => { let segments = segments .into_iter() .map(|mut segment| { segment.value = Box::new(self.fold_pattern(*segment.value)); segment }) .collect(); Pattern::BitArray { location, segments } } } } } ================================================ FILE: compiler-core/src/bit_array.rs ================================================ use ecow::EcoString; use num_bigint::BigInt; use crate::ast::{self, BitArrayOption, SrcSpan}; use crate::build::Target; use crate::type_::Type; use std::sync::Arc; // // Public Interface // pub fn type_options_for_value( input_options: &[BitArrayOption], target: Target, ) -> Result, Error> where TypedValue: GetLiteralValue, { type_options(input_options, TypeOptionsMode::Expression, false, target) } pub fn type_options_for_pattern( input_options: &[BitArrayOption], must_have_size: bool, target: Target, ) -> Result, Error> where TypedValue: GetLiteralValue, { type_options( input_options, TypeOptionsMode::Pattern, must_have_size, target, ) } struct SegmentOptionCategories<'a, T> { type_: Option<&'a BitArrayOption>, signed: Option<&'a BitArrayOption>, endian: Option<&'a BitArrayOption>, unit: Option<&'a BitArrayOption>, size: Option<&'a BitArrayOption>, } impl SegmentOptionCategories<'_, T> { fn new() -> Self { SegmentOptionCategories { type_: None, signed: None, endian: None, unit: None, size: None, } } fn segment_type(&self) -> Arc { use BitArrayOption::*; let default = Int { location: SrcSpan::default(), }; match self.type_.unwrap_or(&default) { Int { .. } => crate::type_::int(), Float { .. } => crate::type_::float(), Utf8 { .. } | Utf16 { .. } | Utf32 { .. } => crate::type_::string(), Bytes { .. } | Bits { .. } => crate::type_::bit_array(), Utf8Codepoint { .. } | Utf16Codepoint { .. } | Utf32Codepoint { .. } => { crate::type_::utf_codepoint() } Signed { .. } | Unsigned { .. } | Big { .. } | Little { .. } | Native { .. } | Size { .. } | Unit { .. } => panic!("Tried to type a non type kind BitArray option."), } } } #[derive(Debug, PartialEq, Eq)] /// Whether we're typing options for a bit array segment that's part of a pattern /// or an expression. /// enum TypeOptionsMode { Expression, Pattern, } fn type_options( input_options: &[BitArrayOption], mode: TypeOptionsMode, must_have_size: bool, target: Target, ) -> Result, Error> where TypedValue: GetLiteralValue, { use BitArrayOption::*; let mut categories = SegmentOptionCategories::new(); // Basic category checking for option in input_options { match option { Utf8Codepoint { .. } | Utf16Codepoint { .. } | Utf32Codepoint { .. } if mode == TypeOptionsMode::Pattern && target == Target::JavaScript => { return err( ErrorType::OptionNotSupportedForTarget { target, option: UnsupportedOption::UtfCodepointPattern, }, option.location(), ); } Bytes { .. } | Int { .. } | Float { .. } | Bits { .. } | Utf8 { .. } | Utf16 { .. } | Utf32 { .. } | Utf8Codepoint { .. } | Utf16Codepoint { .. } | Utf32Codepoint { .. } => { if let Some(previous) = categories.type_ { return err( ErrorType::ConflictingTypeOptions { existing_type: previous.label(), }, option.location(), ); } else { categories.type_ = Some(option); } } Signed { .. } | Unsigned { .. } => { if let Some(previous) = categories.signed { return err( ErrorType::ConflictingSignednessOptions { existing_signed: previous.label(), }, option.location(), ); } else { categories.signed = Some(option); } } Native { .. } if target == Target::JavaScript => { return err( ErrorType::OptionNotSupportedForTarget { target, option: UnsupportedOption::NativeEndianness, }, option.location(), ); } Big { .. } | Little { .. } | Native { .. } => { if let Some(previous) = categories.endian { return err( ErrorType::ConflictingEndiannessOptions { existing_endianness: previous.label(), }, option.location(), ); } else { categories.endian = Some(option); } } Size { .. } => { if categories.size.is_some() { return err(ErrorType::ConflictingSizeOptions, option.location()); } else { categories.size = Some(option); } } Unit { .. } => { if categories.unit.is_some() { return err(ErrorType::ConflictingUnitOptions, option.location()); } else { categories.unit = Some(option); } } }; } // Some options are not allowed in value mode if mode == TypeOptionsMode::Expression { match categories { SegmentOptionCategories { signed: Some(opt), .. } | SegmentOptionCategories { type_: Some(opt @ Bytes { .. }), .. } => return err(ErrorType::OptionNotAllowedInValue, opt.location()), _ => (), } } // All but the last segment in a pattern must have an exact size if must_have_size && let SegmentOptionCategories { type_: Some(opt @ (Bytes { .. } | Bits { .. })), size: None, .. } = categories { return err(ErrorType::SegmentMustHaveSize, opt.location()); } // Endianness is only valid for int, utf16, utf16_codepoint, utf32, // utf32_codepoint and float match categories { SegmentOptionCategories { type_: None | Some( Int { .. } | Utf16 { .. } | Utf32 { .. } | Utf16Codepoint { .. } | Utf32Codepoint { .. } | Float { .. }, ), .. } => {} SegmentOptionCategories { endian: Some(endian), .. } => return err(ErrorType::InvalidEndianness, endian.location()), _ => {} } // signed and unsigned can only be used with int types match categories { SegmentOptionCategories { type_: None | Some(Int { .. }), .. } => {} SegmentOptionCategories { type_: Some(opt), signed: Some(sign), .. } => { return err( ErrorType::SignednessUsedOnNonInt { type_: opt.label() }, sign.location(), ); } _ => {} } // utf8, utf16, utf32 exclude unit and size match categories { SegmentOptionCategories { type_: Some(type_), unit: Some(unit), .. } if is_unicode(type_) => { return err( ErrorType::TypeDoesNotAllowUnit { type_: type_.label(), }, unit.location(), ); } SegmentOptionCategories { type_: Some(type_), size: Some(size), .. } if is_unicode(type_) => { return err( ErrorType::TypeDoesNotAllowSize { type_: type_.label(), }, size.location(), ); } _ => {} } // if unit specified, size must be specified if let SegmentOptionCategories { unit: Some(unit), size: None, .. } = categories { return err(ErrorType::UnitMustHaveSize, unit.location()); } // float only 16/32/64 if let SegmentOptionCategories { type_: Some(Float { .. }), size: Some(size), .. } = categories && let Some(abox) = size.value() { match abox.as_int_literal() { None => (), Some(value) if value == 16.into() || value == 32.into() || value == 64.into() => (), _ => return err(ErrorType::FloatWithSize, size.location()), } } // Segment patterns with a zero or negative constant size must be rejected, // we know they will never match! // A negative size is still allowed in expressions as it will just result // in an empty segment. if let (Some(size @ Size { value, .. }), TypeOptionsMode::Pattern) = (categories.size, mode) { match value.as_int_literal() { Some(n) if n <= BigInt::ZERO => { return err(ErrorType::ConstantSizeNotPositive, size.location()); } Some(_) | None => (), } } Ok(categories.segment_type()) } pub trait GetLiteralValue { fn as_int_literal(&self) -> Option; } impl GetLiteralValue for ast::TypedPattern { fn as_int_literal(&self) -> Option { match self { ast::Pattern::Int { int_value, .. } | ast::Pattern::BitArraySize(ast::BitArraySize::Int { int_value, .. }) => { Some(int_value.clone()) } ast::Pattern::Float { .. } | ast::Pattern::String { .. } | ast::Pattern::Variable { .. } | ast::Pattern::BitArraySize(_) | ast::Pattern::Assign { .. } | ast::Pattern::Discard { .. } | ast::Pattern::List { .. } | ast::Pattern::Constructor { .. } | ast::Pattern::Tuple { .. } | ast::Pattern::BitArray { .. } | ast::Pattern::StringPrefix { .. } | ast::Pattern::Invalid { .. } => None, } } } fn is_unicode(opt: &BitArrayOption) -> bool { use BitArrayOption::*; matches!( opt, Utf8 { .. } | Utf16 { .. } | Utf32 { .. } | Utf8Codepoint { .. } | Utf16Codepoint { .. } | Utf32Codepoint { .. } ) } fn err(error: ErrorType, location: SrcSpan) -> Result { Err(Error { location, error }) } #[derive(Debug)] pub struct Error { pub location: SrcSpan, pub error: ErrorType, } #[derive(Debug, PartialEq, Eq, Clone)] pub enum ErrorType { ConflictingEndiannessOptions { existing_endianness: EcoString, }, ConflictingSignednessOptions { existing_signed: EcoString, }, ConflictingSizeOptions, ConflictingTypeOptions { existing_type: EcoString, }, ConflictingUnitOptions, FloatWithSize, InvalidEndianness, OptionNotAllowedInValue, SegmentMustHaveSize, SignednessUsedOnNonInt { type_: EcoString, }, TypeDoesNotAllowSize { type_: EcoString, }, TypeDoesNotAllowUnit { type_: EcoString, }, UnitMustHaveSize, VariableUtfSegmentInPattern, ConstantSizeNotPositive, OptionNotSupportedForTarget { target: Target, option: UnsupportedOption, }, } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum UnsupportedOption { UtfCodepointPattern, NativeEndianness, } ================================================ FILE: compiler-core/src/build/elixir_libraries.rs ================================================ use crate::{ Error, error::ShellCommandFailureReason, io::{Command, CommandExecutor, FileSystemReader, FileSystemWriter, Stdio}, }; use camino::Utf8PathBuf; #[cfg(not(target_os = "windows"))] const ELIXIR_EXECUTABLE: &str = "elixir"; #[cfg(target_os = "windows")] const ELIXIR_EXECUTABLE: &str = "elixir.bat"; // These Elixir core libs will be loaded with the current project const ELIXIR_LIBS: [&str; 4] = ["eex", "elixir", "logger", "mix"]; pub struct ElixirLibraries<'a, IO> { io: &'a IO, build_dir: &'a Utf8PathBuf, subprocess_stdio: Stdio, } impl<'a, IO> ElixirLibraries<'a, IO> { fn new(io: &'a IO, build_dir: &'a Utf8PathBuf, subprocess_stdio: Stdio) -> Self { Self { io, build_dir, subprocess_stdio, } } } impl<'a, IO> ElixirLibraries<'a, IO> where IO: CommandExecutor + FileSystemReader + FileSystemWriter + Clone, { pub fn make_available( io: &'a IO, build_dir: &'a Utf8PathBuf, subprocess_stdio: Stdio, ) -> Result<(), Error> { let it = Self::new(io, build_dir, subprocess_stdio); let result = it.run(); if result.is_err() { it.cleanup(); } result } fn cleanup(&self) { self.io .delete_file(&self.paths_cache_path()) .expect("deleting paths cache in cleanup"); } fn paths_cache_filename(&self) -> &'static str { "gleam_elixir_paths" } fn paths_cache_path(&self) -> Utf8PathBuf { self.build_dir.join(self.paths_cache_filename()) } fn run(&self) -> Result<(), Error> { // The pathfinder is a file in build/{target}/erlang // It contains the full path for each Elixir core lib we need, new-line delimited // The pathfinder saves us from repeatedly loading Elixir to get this info let mut update_links = false; let cache = self.paths_cache_path(); if !self.io.is_file(&cache) { // The pathfinder must be written // Any existing core lib links will get updated update_links = true; // TODO: test let env = vec![("TERM".to_string(), "dumb".to_string())]; // Prepare the libs for Erlang's code:lib_dir function let elixir_atoms: Vec = ELIXIR_LIBS.iter().map(|lib| format!(":{}", lib)).collect(); // Use Elixir to find its core lib paths and write the pathfinder file let args = vec![ "--eval".to_string(), format!( ":ok = File.write(~s({}), [{}] |> Stream.map(fn(lib) -> lib |> :code.lib_dir |> Path.expand end) |> Enum.join(~s(\\n)))", self.paths_cache_filename(), elixir_atoms.join(", "), ), ]; tracing::debug!("writing_elixir_paths_to_build"); let status = self.io.exec(Command { program: ELIXIR_EXECUTABLE.into(), args, env, cwd: Some(self.build_dir.clone()), stdio: self.subprocess_stdio, })?; if status != 0 { return Err(Error::ShellCommand { program: "elixir".into(), reason: ShellCommandFailureReason::Unknown, }); } } // Each pathfinder line is a system path for an Elixir core library let read_pathfinder = self.io.read(&cache)?; for lib_path in read_pathfinder.split('\n') { let source = Utf8PathBuf::from(lib_path); let name = source.as_path().file_name().expect(&format!( "Unexpanded path in {}", self.paths_cache_filename() )); let dest = self.build_dir.join(name); let ebin = dest.join("ebin"); if !update_links || self.io.is_directory(&ebin) { // Either links don't need updating // Or this library is already linked continue; } // TODO: unit test if self.io.is_directory(&dest) { // Delete the existing link self.io.delete_directory(&dest)?; } tracing::debug!("linking_{}_to_build", name,); self.io.symlink_dir(&source, &dest)?; } Ok(()) } } ================================================ FILE: compiler-core/src/build/module_loader/tests.rs ================================================ use super::*; use crate::{ build::SourceFingerprint, io::{FileSystemWriter, memory::InMemoryFileSystem}, line_numbers::LineNumbers, }; use std::time::Duration; #[test] fn no_cache_present() { let name = "package".into(); let artefact = Utf8Path::new("/artefact"); let fs = InMemoryFileSystem::new(); let warnings = WarningEmitter::null(); let incomplete_modules = HashSet::new(); let loader = make_loader(&warnings, &name, &fs, artefact, &incomplete_modules); fs.write(&Utf8Path::new("/src/main.gleam"), "const x = 1") .unwrap(); let file = GleamFile::new("/src".into(), "/src/main.gleam".into()); let result = loader.load(file).unwrap(); assert!(result.is_new()); } #[test] fn cache_present_and_fresh() { let name = "package".into(); let artefact = Utf8Path::new("/artefact"); let fs = InMemoryFileSystem::new(); let warnings = WarningEmitter::null(); let incomplete_modules = HashSet::new(); let loader = make_loader(&warnings, &name, &fs, artefact, &incomplete_modules); // The mtime of the source is older than that of the cache write_src(&fs, TEST_SOURCE_1, "/src/main.gleam", 0); write_cache(&fs, TEST_SOURCE_1, "/artefact/main.cache_meta", 1, false); let file = GleamFile::new("/src".into(), "/src/main.gleam".into()); let result = loader.load(file).unwrap(); assert!(result.is_cached()); } #[test] fn cache_present_and_stale() { let name = "package".into(); let artefact = Utf8Path::new("/artefact"); let fs = InMemoryFileSystem::new(); let warnings = WarningEmitter::null(); let incomplete_modules = HashSet::new(); let loader = make_loader(&warnings, &name, &fs, artefact, &incomplete_modules); // The mtime of the source is newer than that of the cache write_src(&fs, TEST_SOURCE_2, "/src/main.gleam", 2); write_cache(&fs, TEST_SOURCE_1, "/artefact/main.cache_meta", 1, false); let file = GleamFile::new("/src".into(), "/src/main.gleam".into()); let result = loader.load(file).unwrap(); assert!(result.is_new()); } #[test] fn cache_present_and_stale_but_source_is_the_same() { let name = "package".into(); let artefact = Utf8Path::new("/artefact"); let fs = InMemoryFileSystem::new(); let warnings = WarningEmitter::null(); let incomplete_modules = HashSet::new(); let loader = make_loader(&warnings, &name, &fs, artefact, &incomplete_modules); // The mtime of the source is newer than that of the cache write_src(&fs, TEST_SOURCE_1, "/src/main.gleam", 2); write_cache(&fs, TEST_SOURCE_1, "/artefact/main.cache_meta", 1, false); let file = GleamFile::new("/src".into(), "/src/main.gleam".into()); let result = loader.load(file).unwrap(); assert!(result.is_cached()); } #[test] fn cache_present_and_stale_source_is_the_same_lsp_mode() { let name = "package".into(); let artefact = Utf8Path::new("/artefact"); let fs = InMemoryFileSystem::new(); let warnings = WarningEmitter::null(); let incomplete_modules = HashSet::new(); let mut loader = make_loader(&warnings, &name, &fs, artefact, &incomplete_modules); loader.mode = Mode::Lsp; // The mtime of the source is newer than that of the cache write_src(&fs, TEST_SOURCE_1, "/src/main.gleam", 2); write_cache(&fs, TEST_SOURCE_1, "/artefact/main.cache_meta", 1, false); let file = GleamFile::new("/src".into(), "/src/main.gleam".into()); let result = loader.load(file).unwrap(); assert!(result.is_cached()); } #[test] fn cache_present_and_stale_source_is_the_same_lsp_mode_and_invalidated() { let name = "package".into(); let artefact = Utf8Path::new("/artefact"); let fs = InMemoryFileSystem::new(); let warnings = WarningEmitter::null(); let mut incomplete_modules = HashSet::new(); let _ = incomplete_modules.insert("main".into()); let mut loader = make_loader(&warnings, &name, &fs, artefact, &incomplete_modules); loader.mode = Mode::Lsp; // The mtime of the source is newer than that of the cache write_src(&fs, TEST_SOURCE_1, "/src/main.gleam", 2); write_cache(&fs, TEST_SOURCE_1, "/artefact/main.cache_meta", 1, false); let file = GleamFile::new("/src".into(), "/src/main.gleam".into()); let result = loader.load(file).unwrap(); assert!(result.is_new()); } #[test] fn cache_present_without_codegen_when_required() { let name = "package".into(); let artefact = Utf8Path::new("/artefact"); let fs = InMemoryFileSystem::new(); let warnings = WarningEmitter::null(); let incomplete_modules = HashSet::new(); let mut loader = make_loader(&warnings, &name, &fs, artefact, &incomplete_modules); loader.codegen = CodegenRequired::Yes; // The mtime of the cache is newer than that of the source write_src(&fs, TEST_SOURCE_1, "/src/main.gleam", 0); write_cache(&fs, TEST_SOURCE_1, "/artefact/main.cache_meta", 1, false); let file = GleamFile::new("/src".into(), "/src/main.gleam".into()); let result = loader.load(file).unwrap(); assert!(result.is_new()); } #[test] fn cache_present_with_codegen_when_required() { let name = "package".into(); let artefact = Utf8Path::new("/artefact"); let fs = InMemoryFileSystem::new(); let warnings = WarningEmitter::null(); let incomplete_modules = HashSet::new(); let mut loader = make_loader(&warnings, &name, &fs, artefact, &incomplete_modules); loader.codegen = CodegenRequired::Yes; // The mtime of the cache is newer than that of the source write_src(&fs, TEST_SOURCE_1, "/src/main.gleam", 0); write_cache(&fs, TEST_SOURCE_1, "/artefact/main.cache_meta", 1, true); let file = GleamFile::new("/src".into(), "/src/main.gleam".into()); let result = loader.load(file).unwrap(); assert!(result.is_cached()); } #[test] fn cache_present_without_codegen_when_not_required() { let name = "package".into(); let artefact = Utf8Path::new("/artefact"); let fs = InMemoryFileSystem::new(); let warnings = WarningEmitter::null(); let incomplete_modules = HashSet::new(); let mut loader = make_loader(&warnings, &name, &fs, artefact, &incomplete_modules); loader.codegen = CodegenRequired::No; // The mtime of the cache is newer than that of the source write_src(&fs, TEST_SOURCE_1, "/src/main.gleam", 0); write_cache(&fs, TEST_SOURCE_1, "/artefact/main.cache_meta", 1, false); let file = GleamFile::new("/src".into(), "/src/main.gleam".into()); let result = loader.load(file).unwrap(); assert!(result.is_cached()); } const TEST_SOURCE_1: &'static str = "const x = 1"; const TEST_SOURCE_2: &'static str = "const x = 2"; fn write_cache( fs: &InMemoryFileSystem, source: &str, path: &str, seconds: u64, codegen_performed: bool, ) { let line_numbers = LineNumbers::new(source); let cache_metadata = CacheMetadata { mtime: SystemTime::UNIX_EPOCH + Duration::from_secs(seconds), codegen_performed, dependencies: vec![], fingerprint: SourceFingerprint::new(source), line_numbers, }; let path = Utf8Path::new(path); fs.write_bytes(&path, &cache_metadata.to_binary()).unwrap(); } fn write_src(fs: &InMemoryFileSystem, source: &str, path: &str, seconds: u64) { let path = Utf8Path::new(path); fs.write(&path, source).unwrap(); fs.set_modification_time(&path, SystemTime::UNIX_EPOCH + Duration::from_secs(seconds)); } fn make_loader<'a>( warnings: &'a WarningEmitter, package_name: &'a EcoString, fs: &InMemoryFileSystem, artefact: &'a Utf8Path, incomplete_modules: &'a HashSet, ) -> ModuleLoader<'a, InMemoryFileSystem> { ModuleLoader { warnings, io: fs.clone(), mode: Mode::Dev, target: Target::Erlang, codegen: CodegenRequired::No, package_name, artefact_directory: &artefact, origin: Origin::Src, incomplete_modules, } } ================================================ FILE: compiler-core/src/build/module_loader.rs ================================================ #[cfg(test)] mod tests; use std::{collections::HashSet, time::SystemTime}; use camino::{Utf8Path, Utf8PathBuf}; use ecow::EcoString; use serde::{Deserialize, Serialize}; use super::{ Mode, Origin, SourceFingerprint, Target, package_compiler::{CacheMetadata, CachedModule, Input, UncompiledModule}, package_loader::{CodegenRequired, GleamFile}, }; use crate::{ Error, Result, error::{FileIoAction, FileKind}, io::{CommandExecutor, FileSystemReader, FileSystemWriter}, warning::{TypeWarningEmitter, WarningEmitter}, }; #[derive(Debug)] pub(crate) struct ModuleLoader<'a, IO> { pub io: IO, pub warnings: &'a WarningEmitter, pub mode: Mode, pub target: Target, pub codegen: CodegenRequired, pub package_name: &'a EcoString, pub artefact_directory: &'a Utf8Path, pub origin: Origin, /// The set of modules that have had partial compilation done since the last /// successful compilation. pub incomplete_modules: &'a HashSet, } impl<'a, IO> ModuleLoader<'a, IO> where IO: FileSystemReader + FileSystemWriter + CommandExecutor + Clone, { /// Load a module from the given path. /// /// If the module has been compiled before and the source file has not been /// changed since then, load the precompiled data instead. /// /// Whether the module has changed or not is determined by comparing the /// modification time of the source file with the value recorded in the /// `.timestamp` file in the artefact directory. pub fn load(&self, file: GleamFile) -> Result { let name = file.module_name.clone(); let source_mtime = self.io.modification_time(&file.path)?; let read_source = |name| self.read_source(file.path.clone(), name, source_mtime); let meta = match self.read_cache_metadata(&file)? { Some(meta) => meta, None => return read_source(name).map(Input::New), }; // The cache currently does not contain enough data to perform codegen, // so if codegen is required in this compiler run then we must check // that codegen has already been performed before using a cache. if self.codegen.is_required() && !meta.codegen_performed { tracing::debug!(?name, "codegen_required_cache_insufficient"); return read_source(name).map(Input::New); } // If the timestamp of the source is newer than the cache entry and // the hash of the source differs from the one in the cache entry, // then we need to recompile. if meta.mtime < source_mtime { let source_module = read_source(name.clone())?; if meta.fingerprint != SourceFingerprint::new(&source_module.code) { tracing::debug!(?name, "cache_stale"); return Ok(Input::New(source_module)); } else if self.mode == Mode::Lsp && self.incomplete_modules.contains(&name) { // Since the lsp can have valid but incorrect intermediate code states between // successful compilations, we need to invalidate the cache even if the fingerprint matches tracing::debug!(?name, "cache_stale for lsp"); return Ok(Input::New(source_module)); } } Ok(Input::Cached(self.cached(file, meta))) } /// Read the cache metadata file from the artefact directory for the given /// source file. If the file does not exist, return `None`. fn read_cache_metadata(&self, source_file: &GleamFile) -> Result> { let meta_path = source_file.cache_files(&self.artefact_directory).meta_path; if !self.io.is_file(&meta_path) { return Ok(None); } let binary = self.io.read_bytes(&meta_path)?; let cache_metadata = CacheMetadata::from_binary(&binary).map_err(|e| -> Error { Error::FileIo { action: FileIoAction::Parse, kind: FileKind::File, path: meta_path, err: Some(e), } })?; Ok(Some(cache_metadata)) } fn read_source( &self, path: Utf8PathBuf, name: EcoString, mtime: SystemTime, ) -> Result { read_source( self.io.clone(), self.target, self.origin, path, name, self.package_name.clone(), mtime, self.warnings.clone(), ) } fn cached(&self, file: GleamFile, meta: CacheMetadata) -> CachedModule { CachedModule { dependencies: meta.dependencies, source_path: file.path, origin: self.origin, name: file.module_name, line_numbers: meta.line_numbers, } } } pub(crate) fn read_source( io: IO, target: Target, origin: Origin, path: Utf8PathBuf, name: EcoString, package_name: EcoString, mtime: SystemTime, emitter: WarningEmitter, ) -> Result where IO: FileSystemReader + FileSystemWriter + CommandExecutor + Clone, { let code: EcoString = io.read(&path)?.into(); let parsed = crate::parse::parse_module(path.clone(), &code, &emitter).map_err(|error| { Error::Parse { path: path.clone(), src: code.clone(), error: Box::new(error), } })?; let mut ast = parsed.module; let extra = parsed.extra; let dependencies = ast.dependencies(target); ast.name = name.clone(); let module = UncompiledModule { package: package_name, dependencies, origin, extra, mtime, path, name, code, ast, }; Ok(module) } ================================================ FILE: compiler-core/src/build/native_file_copier/tests.rs ================================================ use super::NativeFileCopier; use crate::{ build::{native_file_copier::CopiedNativeFiles, package_compiler::CheckModuleConflicts}, io::{FileSystemWriter, memory::InMemoryFileSystem}, }; use std::{ collections::HashMap, sync::OnceLock, time::{Duration, SystemTime, UNIX_EPOCH}, }; use camino::{Utf8Path, Utf8PathBuf}; fn root() -> &'static Utf8PathBuf { static ROOT: OnceLock = OnceLock::new(); ROOT.get_or_init(|| Utf8PathBuf::from("/").to_owned()) } fn root_out() -> &'static Utf8PathBuf { static OUT: OnceLock = OnceLock::new(); OUT.get_or_init(|| Utf8PathBuf::from("/out")) } #[test] fn javascript_files_are_copied_from_src() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/src/wibble.js"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(!copied.any_elixir); assert!(copied.to_compile.is_empty()); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/src/wibble.js"), "1".into()), (Utf8PathBuf::from("/out/wibble.js"), "1".into()) ]), fs.into_contents(), ); } #[test] fn javascript_files_are_copied_from_test() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/test/wibble.js"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(!copied.any_elixir); assert!(copied.to_compile.is_empty()); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/test/wibble.js"), "1".into()), (Utf8PathBuf::from("/out/wibble.js"), "1".into()) ]), fs.into_contents(), ); } #[test] fn javascript_files_are_copied_from_dev() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/dev/wibble.js"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(!copied.any_elixir); assert!(copied.to_compile.is_empty()); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/dev/wibble.js"), "1".into()), (Utf8PathBuf::from("/out/wibble.js"), "1".into()) ]), fs.into_contents(), ); } #[test] fn mjavascript_files_are_copied_from_src() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/src/wibble.mjs"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(!copied.any_elixir); assert!(copied.to_compile.is_empty()); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/src/wibble.mjs"), "1".into()), (Utf8PathBuf::from("/out/wibble.mjs"), "1".into()) ]), fs.into_contents(), ); } #[test] fn mjavascript_files_are_copied_from_test() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/test/wibble.mjs"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(!copied.any_elixir); assert!(copied.to_compile.is_empty()); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/test/wibble.mjs"), "1".into()), (Utf8PathBuf::from("/out/wibble.mjs"), "1".into()) ]), fs.into_contents(), ); } #[test] fn mjavascript_files_are_copied_from_dev() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/dev/wibble.mjs"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(!copied.any_elixir); assert!(copied.to_compile.is_empty()); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/dev/wibble.mjs"), "1".into()), (Utf8PathBuf::from("/out/wibble.mjs"), "1".into()) ]), fs.into_contents(), ); } #[test] fn cjavascript_files_are_copied_from_src() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/src/wibble.cjs"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(!copied.any_elixir); assert!(copied.to_compile.is_empty()); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/src/wibble.cjs"), "1".into()), (Utf8PathBuf::from("/out/wibble.cjs"), "1".into()) ]), fs.into_contents(), ); } #[test] fn cjavascript_files_are_copied_from_test() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/test/wibble.cjs"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(!copied.any_elixir); assert!(copied.to_compile.is_empty()); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/test/wibble.cjs"), "1".into()), (Utf8PathBuf::from("/out/wibble.cjs"), "1".into()) ]), fs.into_contents(), ); } #[test] fn cjavascript_files_are_copied_from_dev() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/dev/wibble.cjs"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(!copied.any_elixir); assert!(copied.to_compile.is_empty()); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/dev/wibble.cjs"), "1".into()), (Utf8PathBuf::from("/out/wibble.cjs"), "1".into()) ]), fs.into_contents(), ); } #[test] fn typescript_files_are_copied_from_src() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/src/wibble.ts"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(!copied.any_elixir); assert!(copied.to_compile.is_empty()); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/src/wibble.ts"), "1".into()), (Utf8PathBuf::from("/out/wibble.ts"), "1".into()) ]), fs.into_contents(), ); } #[test] fn typescript_files_are_copied_from_test() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/test/wibble.ts"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(!copied.any_elixir); assert!(copied.to_compile.is_empty()); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/test/wibble.ts"), "1".into()), (Utf8PathBuf::from("/out/wibble.ts"), "1".into()) ]), fs.into_contents(), ); } #[test] fn typescript_files_are_copied_from_dev() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/dev/wibble.ts"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(!copied.any_elixir); assert!(copied.to_compile.is_empty()); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/dev/wibble.ts"), "1".into()), (Utf8PathBuf::from("/out/wibble.ts"), "1".into()) ]), fs.into_contents(), ); } #[test] fn all_javascript_files_are_copied_from_src_subfolders() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/src/abc/def/wibble.mjs"), "1") .unwrap(); fs.write(&Utf8Path::new("/src/abc/ghi/wibble.js"), "2") .unwrap(); fs.write(&Utf8Path::new("/src/abc/jkl/wibble.cjs"), "3") .unwrap(); fs.write(&Utf8Path::new("/src/def/wobble.ts"), "4").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(!copied.any_elixir); assert!(copied.to_compile.is_empty()); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/src/abc/def/wibble.mjs"), "1".into()), (Utf8PathBuf::from("/out/abc/def/wibble.mjs"), "1".into()), (Utf8PathBuf::from("/src/abc/ghi/wibble.js"), "2".into()), (Utf8PathBuf::from("/out/abc/ghi/wibble.js"), "2".into()), (Utf8PathBuf::from("/src/abc/jkl/wibble.cjs"), "3".into()), (Utf8PathBuf::from("/out/abc/jkl/wibble.cjs"), "3".into()), (Utf8PathBuf::from("/src/def/wobble.ts"), "4".into()), (Utf8PathBuf::from("/out/def/wobble.ts"), "4".into()) ]), fs.into_contents(), ); } #[test] fn all_javascript_files_are_copied_from_test_subfolders() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/test/abc/def/wibble.mjs"), "1") .unwrap(); fs.write(&Utf8Path::new("/test/abc/ghi/wibble.js"), "2") .unwrap(); fs.write(&Utf8Path::new("/test/abc/jkl/wibble.cjs"), "3") .unwrap(); fs.write(&Utf8Path::new("/test/def/wobble.ts"), "4") .unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(!copied.any_elixir); assert!(copied.to_compile.is_empty()); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/test/abc/def/wibble.mjs"), "1".into()), (Utf8PathBuf::from("/out/abc/def/wibble.mjs"), "1".into()), (Utf8PathBuf::from("/test/abc/ghi/wibble.js"), "2".into()), (Utf8PathBuf::from("/out/abc/ghi/wibble.js"), "2".into()), (Utf8PathBuf::from("/test/abc/jkl/wibble.cjs"), "3".into()), (Utf8PathBuf::from("/out/abc/jkl/wibble.cjs"), "3".into()), (Utf8PathBuf::from("/test/def/wobble.ts"), "4".into()), (Utf8PathBuf::from("/out/def/wobble.ts"), "4".into()) ]), fs.into_contents(), ); } #[test] fn all_javascript_files_are_copied_from_dev_subfolders() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/dev/abc/def/wibble.mjs"), "1") .unwrap(); fs.write(&Utf8Path::new("/dev/abc/ghi/wibble.js"), "2") .unwrap(); fs.write(&Utf8Path::new("/dev/abc/jkl/wibble.cjs"), "3") .unwrap(); fs.write(&Utf8Path::new("/dev/def/wobble.ts"), "4").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(!copied.any_elixir); assert!(copied.to_compile.is_empty()); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/dev/abc/def/wibble.mjs"), "1".into()), (Utf8PathBuf::from("/out/abc/def/wibble.mjs"), "1".into()), (Utf8PathBuf::from("/dev/abc/ghi/wibble.js"), "2".into()), (Utf8PathBuf::from("/out/abc/ghi/wibble.js"), "2".into()), (Utf8PathBuf::from("/dev/abc/jkl/wibble.cjs"), "3".into()), (Utf8PathBuf::from("/out/abc/jkl/wibble.cjs"), "3".into()), (Utf8PathBuf::from("/dev/def/wobble.ts"), "4".into()), (Utf8PathBuf::from("/out/def/wobble.ts"), "4".into()) ]), fs.into_contents(), ); } #[test] fn erlang_header_files_are_copied_from_src() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/src/wibble.hrl"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(!copied.any_elixir); assert!(copied.to_compile.is_empty()); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/src/wibble.hrl"), "1".into()), (Utf8PathBuf::from("/out/wibble.hrl"), "1".into()) ]), fs.into_contents(), ); } #[test] fn erlang_header_files_are_copied_from_test() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/test/wibble.hrl"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(!copied.any_elixir); assert!(copied.to_compile.is_empty()); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/test/wibble.hrl"), "1".into()), (Utf8PathBuf::from("/out/wibble.hrl"), "1".into()) ]), fs.into_contents(), ); } #[test] fn erlang_header_files_are_copied_from_dev() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/dev/wibble.hrl"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(!copied.any_elixir); assert!(copied.to_compile.is_empty()); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/dev/wibble.hrl"), "1".into()), (Utf8PathBuf::from("/out/wibble.hrl"), "1".into()) ]), fs.into_contents(), ); } #[test] fn erlang_files_are_copied_from_src() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/src/wibble.erl"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(!copied.any_elixir); assert_eq!(copied.to_compile, vec![Utf8PathBuf::from("wibble.erl")]); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/src/wibble.erl"), "1".into()), (Utf8PathBuf::from("/out/wibble.erl"), "1".into()) ]), fs.into_contents(), ); } #[test] fn erlang_files_are_copied_from_test() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/test/wibble.erl"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(!copied.any_elixir); assert_eq!(copied.to_compile, vec![Utf8PathBuf::from("wibble.erl")]); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/test/wibble.erl"), "1".into()), (Utf8PathBuf::from("/out/wibble.erl"), "1".into()) ]), fs.into_contents(), ); } #[test] fn erlang_files_are_copied_from_dev() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/dev/wibble.erl"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(!copied.any_elixir); assert_eq!(copied.to_compile, vec![Utf8PathBuf::from("wibble.erl")]); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/dev/wibble.erl"), "1".into()), (Utf8PathBuf::from("/out/wibble.erl"), "1".into()) ]), fs.into_contents(), ); } #[test] fn elixir_files_are_copied_from_src() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/src/wibble.ex"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(copied.any_elixir); assert_eq!(copied.to_compile, vec![Utf8PathBuf::from("wibble.ex")]); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/src/wibble.ex"), "1".into()), (Utf8PathBuf::from("/out/wibble.ex"), "1".into()) ]), fs.into_contents(), ); } #[test] fn elixir_files_are_copied_from_test() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/test/wibble.ex"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(copied.any_elixir); assert_eq!(copied.to_compile, vec![Utf8PathBuf::from("wibble.ex")]); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/test/wibble.ex"), "1".into()), (Utf8PathBuf::from("/out/wibble.ex"), "1".into()) ]), fs.into_contents(), ); } #[test] fn elixir_files_are_copied_from_dev() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/dev/wibble.ex"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(copied.any_elixir); assert_eq!(copied.to_compile, vec![Utf8PathBuf::from("wibble.ex")]); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/dev/wibble.ex"), "1".into()), (Utf8PathBuf::from("/out/wibble.ex"), "1".into()) ]), fs.into_contents(), ); } #[test] fn all_erlang_files_are_copied_from_src_subfolders() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/src/abc/def/wibble.erl"), "1") .unwrap(); fs.write(&Utf8Path::new("/src/abc/ghi/wibble_header.hrl"), "2") .unwrap(); fs.write(&Utf8Path::new("/src/def/wobble.ex"), "3").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(copied.any_elixir); assert_eq!( copied.to_compile, vec![ Utf8PathBuf::from("abc/def/wibble.erl"), Utf8PathBuf::from("def/wobble.ex") ] ); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/src/abc/def/wibble.erl"), "1".into()), (Utf8PathBuf::from("/out/abc/def/wibble.erl"), "1".into()), ( Utf8PathBuf::from("/src/abc/ghi/wibble_header.hrl"), "2".into() ), ( Utf8PathBuf::from("/out/abc/ghi/wibble_header.hrl"), "2".into() ), (Utf8PathBuf::from("/src/def/wobble.ex"), "3".into()), (Utf8PathBuf::from("/out/def/wobble.ex"), "3".into()) ]), fs.into_contents(), ); } #[test] fn all_erlang_files_are_copied_from_test_subfolders() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/test/abc/def/wibble.erl"), "1") .unwrap(); fs.write(&Utf8Path::new("/test/abc/ghi/wibble_header.hrl"), "2") .unwrap(); fs.write(&Utf8Path::new("/test/def/wobble.ex"), "3") .unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(copied.any_elixir); assert_eq!( copied.to_compile, vec![ Utf8PathBuf::from("abc/def/wibble.erl"), Utf8PathBuf::from("def/wobble.ex") ] ); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/test/abc/def/wibble.erl"), "1".into()), (Utf8PathBuf::from("/out/abc/def/wibble.erl"), "1".into()), ( Utf8PathBuf::from("/test/abc/ghi/wibble_header.hrl"), "2".into() ), ( Utf8PathBuf::from("/out/abc/ghi/wibble_header.hrl"), "2".into() ), (Utf8PathBuf::from("/test/def/wobble.ex"), "3".into()), (Utf8PathBuf::from("/out/def/wobble.ex"), "3".into()) ]), fs.into_contents(), ); } #[test] fn all_erlang_files_are_copied_from_dev_subfolders() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/dev/abc/def/wibble.erl"), "1") .unwrap(); fs.write(&Utf8Path::new("/dev/abc/ghi/wibble_header.hrl"), "2") .unwrap(); fs.write(&Utf8Path::new("/dev/def/wobble.ex"), "3").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(copied.any_elixir); assert_eq!( copied.to_compile, vec![ Utf8PathBuf::from("abc/def/wibble.erl"), Utf8PathBuf::from("def/wobble.ex") ] ); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/dev/abc/def/wibble.erl"), "1".into()), (Utf8PathBuf::from("/out/abc/def/wibble.erl"), "1".into()), ( Utf8PathBuf::from("/dev/abc/ghi/wibble_header.hrl"), "2".into() ), ( Utf8PathBuf::from("/out/abc/ghi/wibble_header.hrl"), "2".into() ), (Utf8PathBuf::from("/dev/def/wobble.ex"), "3".into()), (Utf8PathBuf::from("/out/def/wobble.ex"), "3".into()) ]), fs.into_contents(), ); } #[test] fn other_files_are_ignored() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/src/wibble.cpp"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(!copied.any_elixir); assert!(copied.to_compile.is_empty()); assert_eq!( HashMap::from([(Utf8PathBuf::from("/src/wibble.cpp"), "1".into())]), fs.into_contents(), ); } #[test] fn files_do_not_get_copied_if_there_already_is_a_new_version() { let fs = InMemoryFileSystem::new(); let out = Utf8Path::new("/out/wibble.mjs"); let src = Utf8Path::new("/src/wibble.mjs"); fs.write(&out, "in-out").unwrap(); fs.write(&src, "in-src").unwrap(); fs.set_modification_time(&out, UNIX_EPOCH + Duration::from_secs(1)); fs.set_modification_time(&src, UNIX_EPOCH); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(!copied.any_elixir); assert!(copied.to_compile.is_empty()); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/src/wibble.mjs"), "in-src".into()), (Utf8PathBuf::from("/out/wibble.mjs"), "in-out".into()) ]), fs.into_contents(), ); } #[test] fn files_get_copied_if_the_previously_copied_version_is_older() { let fs = InMemoryFileSystem::new(); let out = Utf8Path::new("/out/wibble.mjs"); let src = Utf8Path::new("/src/wibble.mjs"); fs.write(&out, "in-out").unwrap(); fs.write(&src, "in-src").unwrap(); fs.set_modification_time(&out, UNIX_EPOCH); fs.set_modification_time(&src, UNIX_EPOCH + Duration::from_secs(1)); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); let copied = copier.run().unwrap(); assert!(!copied.any_elixir); assert!(copied.to_compile.is_empty()); assert_eq!( HashMap::from([ (Utf8PathBuf::from("/src/wibble.mjs"), "in-src".into()), (Utf8PathBuf::from("/out/wibble.mjs"), "in-src".into()) ]), fs.into_contents(), ); } #[test] fn duplicate_native_files_result_in_an_error() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/src/wibble.mjs"), "1").unwrap(); fs.write(&Utf8Path::new("/test/wibble.mjs"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); assert!(copier.run().is_err()); } #[test] fn conflicting_erlang_modules_in_src_result_in_an_error() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/src/a/b/c/wibble.erl"), "1") .unwrap(); fs.write(&Utf8Path::new("/src/e/f/wibble.erl"), "1") .unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); assert!(copier.run().is_err()); } #[test] fn conflicting_erlang_modules_in_src_and_test_result_in_an_error() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/src/a/b/c/wibble.erl"), "1") .unwrap(); fs.write(&Utf8Path::new("/test/e/f/wibble.erl"), "1") .unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); assert!(copier.run().is_err()); } #[test] fn conflicting_erlang_modules_in_src_and_dev_result_in_an_error() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/src/a/b/c/wibble.erl"), "1") .unwrap(); fs.write(&Utf8Path::new("/dev/e/f/wibble.erl"), "1") .unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); assert!(copier.run().is_err()); } #[test] fn conflicting_erlang_modules_in_dev_and_test_result_in_an_error() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/dev/a/b/c/wibble.erl"), "1") .unwrap(); fs.write(&Utf8Path::new("/test/e/f/wibble.erl"), "1") .unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); assert!(copier.run().is_err()); } #[test] fn conflicting_gleam_and_javascript_modules_result_in_an_error() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/src/wibble.gleam"), "1").unwrap(); fs.write(&Utf8Path::new("/src/wibble.mjs"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); assert!(copier.run().is_err()); } #[test] fn differently_nested_gleam_and_javascript_modules_with_same_name_are_ok() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/src/a/b/c/wibble.gleam"), "1") .unwrap(); fs.write(&Utf8Path::new("/src/d/e/wibble.mjs"), "1") .unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); assert!(copier.run().is_ok()); } #[test] fn conflicting_gleam_and_erlang_modules_result_in_an_error() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/src/wibble.gleam"), "1").unwrap(); fs.write(&Utf8Path::new("/src/wibble.erl"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); assert!(copier.run().is_err()); } #[test] fn conflicting_nested_gleam_and_erlang_modules_result_in_an_error() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/src/wibble/wobble.gleam"), "1") .unwrap(); fs.write(&Utf8Path::new("/src/wibble@wobble.erl"), "1") .unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); assert!(copier.run().is_err()); } #[test] fn conflicting_nested_gleam_file_does_not_conflict_with_root_erlang_file() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/src/wibble/wobble.gleam"), "1") .unwrap(); fs.write(&Utf8Path::new("/src/wobble.erl"), "1").unwrap(); let copier = NativeFileCopier::new(fs.clone(), root(), root_out(), CheckModuleConflicts::Check); assert!(copier.run().is_ok()); } #[test] fn conflicting_gleam_and_erlang_modules_produce_no_error_in_dependency() { let fs = InMemoryFileSystem::new(); fs.write(&Utf8Path::new("/src/wibble.gleam"), "1").unwrap(); fs.write(&Utf8Path::new("/src/wibble.erl"), "1").unwrap(); let copier = NativeFileCopier::new( fs.clone(), root(), root_out(), CheckModuleConflicts::DoNotCheck, ); assert!(copier.run().is_ok()); } ================================================ FILE: compiler-core/src/build/native_file_copier.rs ================================================ #[cfg(test)] mod tests; use std::collections::{HashMap, HashSet}; use camino::{Utf8Path, Utf8PathBuf}; use ecow::{EcoString, eco_format}; use crate::{ Error, Result, io::{DirWalker, FileSystemReader, FileSystemWriter}, paths::ProjectPaths, }; use super::package_compiler::CheckModuleConflicts; #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct CopiedNativeFiles { pub any_elixir: bool, pub to_compile: Vec, } pub(crate) struct NativeFileCopier<'a, IO> { io: IO, paths: ProjectPaths, destination_dir: &'a Utf8Path, seen_native_files: HashSet, seen_modules: HashMap, to_compile: Vec, elixir_files_copied: bool, check_module_conflicts: CheckModuleConflicts, } impl<'a, IO> NativeFileCopier<'a, IO> where IO: FileSystemReader + FileSystemWriter + Clone, { pub(crate) fn new( io: IO, root: &'a Utf8Path, out: &'a Utf8Path, check_module_conflicts: CheckModuleConflicts, ) -> Self { Self { io, paths: ProjectPaths::new(root.into()), destination_dir: out, to_compile: Vec::new(), seen_native_files: HashSet::new(), seen_modules: HashMap::new(), elixir_files_copied: false, check_module_conflicts, } } /// Copy native files from the given directory to the build directory. /// /// Errors if any duplicate files are found. /// /// Returns a list of files that need to be compiled (Elixir and Erlang). /// pub fn run(mut self) -> Result { self.io.mkdir(&self.destination_dir)?; let src = self.paths.src_directory(); self.copy_files(&src)?; let test = self.paths.test_directory(); if self.io.is_directory(&test) { self.copy_files(&test)?; } let dev = self.paths.dev_directory(); if self.io.is_directory(&dev) { self.copy_files(&dev)?; } // Sort for deterministic output self.to_compile.sort_unstable(); Ok(CopiedNativeFiles { to_compile: self.to_compile, any_elixir: self.elixir_files_copied, }) } fn copy_files(&mut self, src_root: &Utf8Path) -> Result<()> { let mut dir_walker = DirWalker::new(src_root.to_path_buf()); while let Some(path) = dir_walker.next_file(&self.io)? { self.copy(path, &src_root)?; } Ok(()) } fn copy(&mut self, file: Utf8PathBuf, src_root: &Utf8Path) -> Result<()> { let extension = file.extension().unwrap_or_default(); let relative_path = file .strip_prefix(src_root) .expect("copy_native_files strip prefix") .to_path_buf(); // No need to run duplicate native file checks for .gleam files, but we // still need to check for conflicting `.gleam` and `.mjs` files, so we // add a special case for `.gleam`. if extension == "gleam" { self.check_for_conflicting_javascript_modules(&relative_path)?; self.check_for_conflicting_erlang_modules(&relative_path)?; return Ok(()); } // Skip unknown file formats that are not supported native files if !crate::io::is_native_file_extension(extension) { return Ok(()); } let destination = self.destination_dir.join(&relative_path); // Check that this native file was not already copied self.check_for_duplicate(&relative_path)?; // Check for JavaScript modules conflicting between each other within // the same relative path. We need to do this as '.gleam' files can // also cause a conflict, despite not being native files, as they are // compiled to `.mjs`. self.check_for_conflicting_javascript_modules(&relative_path)?; // Check for Erlang modules conflicting between each other anywhere in // the tree. self.check_for_conflicting_erlang_modules(&relative_path)?; // If the source file's mtime is older than the destination file's mtime // then it has not changed and as such does not need to be copied. // // This makes no practical difference for JavaScript etc files, but for // Erlang and Elixir files it mean we can skip compiling them. if self.io.is_file(&destination) && self.io.modification_time(&file)? <= self.io.modification_time(&destination)? { tracing::debug!(?file, "skipping_unchanged_native_file_unchanged"); return Ok(()); } tracing::debug!(?file, "copying_native_file"); // Ensure destination exists (subdir might not exist yet in the output) if let Some(parent) = destination.parent() { self.io.mkdir(parent)?; } self.io.copy(&file, &destination)?; self.elixir_files_copied = self.elixir_files_copied || extension == "ex"; // BEAM native modules need to be compiled if matches!(extension, "erl" | "ex") { _ = self.to_compile.push(relative_path.clone()); } Ok(()) } fn check_for_duplicate(&mut self, relative_path: &Utf8PathBuf) -> Result<(), Error> { if !self.seen_native_files.insert(relative_path.clone()) { return Err(Error::DuplicateSourceFile { file: relative_path.to_string(), }); } Ok(()) } /// Gleam files are compiled to `.mjs` files, which must not conflict with /// an FFI `.mjs` file with the same name, so we check for this case here. fn check_for_conflicting_javascript_modules( &mut self, relative_path: &Utf8PathBuf, ) -> Result<(), Error> { let mjs_path = match relative_path.extension() { Some("gleam") => eco_format!("{}", relative_path.with_extension("mjs")), Some("mjs") => eco_format!("{}", relative_path), _ => return Ok(()), }; // Insert the full relative `.mjs` path in `seen_modules` as there is // no conflict if two `.mjs` files have the same name but are in // different subpaths, unlike Erlang files. let existing = self .seen_modules .insert(mjs_path.clone(), relative_path.clone()); // If there was no already existing one then there's no problem. let Some(existing) = existing else { return Ok(()); }; let existing_is_gleam = existing.extension() == Some("gleam"); if existing_is_gleam || relative_path.extension() == Some("gleam") { let (gleam_file, native_file) = if existing_is_gleam { (&existing, relative_path) } else { (relative_path, &existing) }; return Err(Error::ClashingGleamModuleAndNativeFileName { module: eco_format!("{}", gleam_file.with_extension("")), gleam_file: gleam_file.clone(), native_file: native_file.clone(), }); } // The only way for two `.mjs` files to clash is by having // the exact same path. assert_eq!(&existing, relative_path); return Err(Error::DuplicateSourceFile { file: existing.to_string(), }); } /// Erlang module files cannot have the same name regardless of their /// relative positions within the project. Ensure we raise an error if the /// user attempts to create `.erl` files with the same name. fn check_for_conflicting_erlang_modules( &mut self, relative_path: &Utf8PathBuf, ) -> Result<(), Error> { let erlang_module_name = match relative_path.extension() { Some("erl") => { eco_format!("{}", relative_path.file_name().expect("path has file name")) } Some("gleam") if self.check_module_conflicts.should_check() => relative_path .with_extension("erl") .as_str() .replace("/", "@") .into(), _ => return Ok(()), }; // Insert just the `.erl` module filename in `seen_modules` instead of // its full relative path, because `.erl` files with the same name // cause a conflict when targetting Erlang regardless of subpath. if let Some(existing) = self .seen_modules .insert(erlang_module_name, relative_path.clone()) { let existing_is_gleam = existing.extension() == Some("gleam"); if existing_is_gleam || relative_path.extension() == Some("gleam") { let (gleam_file, native_file) = if existing_is_gleam { (&existing, relative_path) } else { (relative_path, &existing) }; return Err(Error::ClashingGleamModuleAndNativeFileName { module: eco_format!("{}", gleam_file.with_extension("")), gleam_file: gleam_file.clone(), native_file: native_file.clone(), }); } return Err(Error::DuplicateNativeErlangModule { module: eco_format!("{}", relative_path.file_stem().expect("path has file stem")), first: existing, second: relative_path.clone(), }); } Ok(()) } } ================================================ FILE: compiler-core/src/build/package_compiler/snapshots/gleam_core__build__package_compiler__tests__different_packages_defining_duplicate_module.snap ================================================ --- source: compiler-core/src/build/package_compiler/tests.rs expression: output --- error: Duplicate module The module `a_module` is defined multiple times. It is first defined by the package dep1 It is defined a second time by the package a_package ================================================ FILE: compiler-core/src/build/package_compiler/snapshots/gleam_core__build__package_compiler__tests__same_package_defining_duplicate_module.snap ================================================ --- source: compiler-core/src/build/package_compiler/tests.rs expression: output --- error: Duplicate module The module `a_module` is defined multiple times. It is first defined at a_package/a_module.gleam It is defined a second time at /src/a_module.gleam ================================================ FILE: compiler-core/src/build/package_compiler/tests.rs ================================================ use std::collections::HashSet; use camino::Utf8Path; use ecow::EcoString; use crate::{ Error, build::{ self, NullTelemetry, Outcome, PackageCompiler, StaleTracker, Target, TargetCodegenConfiguration, Telemetry, package_compiler::Compiled, }, config::PackageConfig, error::DefinedModuleOrigin, io::{FileSystemWriter, memory::InMemoryFileSystem}, uid::UniqueIdGenerator, warning::WarningEmitter, }; fn compile_modules( package_name: &str, module: &str, existing_modules: Vec<(&str, &str)>, ) -> Outcome { let mut fs = InMemoryFileSystem::new(); fs.write( Utf8Path::new(&format!("/src/{module}.gleam")), "pub fn main() -> Nil { Nil }", ) .expect("write module"); let mut config = PackageConfig::default(); config.name = package_name.into(); let compiler = PackageCompiler::new( &config, build::Mode::Dev, Utf8Path::new("/"), Utf8Path::new("/out"), Utf8Path::new("/lib"), &TargetCodegenConfiguration::Erlang { app_file: None }, UniqueIdGenerator::new(), fs, ); let mut already_defined_modules = existing_modules .into_iter() .map(|(package_name, module_name)| { ( EcoString::from(module_name), DefinedModuleOrigin { package_name: EcoString::from(package_name), path: Utf8Path::new(&format!("{package_name}/{module_name}.gleam")) .to_path_buf(), }, ) }) .collect(); compiler.compile( &WarningEmitter::null(), &mut im::HashMap::new(), &mut already_defined_modules, &mut StaleTracker::default(), &mut HashSet::new(), &NullTelemetry, ) } #[test] pub fn different_packages_defining_duplicate_module() { let output = compile_modules("a_package", "a_module", vec![("dep1", "a_module")]) .into_result() .expect_err("should produce an error") .pretty_string(); insta::assert_snapshot!(insta::internals::AutoName, output); } #[test] pub fn same_package_defining_duplicate_module() { let output = compile_modules("a_package", "a_module", vec![("a_package", "a_module")]) .into_result() .expect_err("should produce an error") .pretty_string(); insta::assert_snapshot!(insta::internals::AutoName, output); } ================================================ FILE: compiler-core/src/build/package_compiler.rs ================================================ #[cfg(test)] mod tests; use crate::analyse::{ModuleAnalyzerConstructor, TargetSupport}; use crate::build::package_loader::CacheFiles; use crate::error::DefinedModuleOrigin; use crate::io::files_with_extension; use crate::line_numbers::{self, LineNumbers}; use crate::type_::PRELUDE_MODULE_NAME; use crate::{ Error, Result, Warning, ast::{SrcSpan, TypedModule, UntypedModule}, build::{ Mode, Module, Origin, Outcome, Package, SourceFingerprint, Target, elixir_libraries::ElixirLibraries, native_file_copier::NativeFileCopier, package_loader::{CodegenRequired, PackageLoader, StaleTracker}, }, codegen::{Erlang, ErlangApp, JavaScript, TypeScriptDeclarations}, config::PackageConfig, dep_tree, error, io::{BeamCompiler, CommandExecutor, FileSystemReader, FileSystemWriter, Stdio}, parse::extra::ModuleExtra, paths, type_, uid::UniqueIdGenerator, warning::{TypeWarningEmitter, WarningEmitter}, }; use crate::{inline, metadata}; use askama::Template; use ecow::EcoString; use std::collections::HashSet; use std::{collections::HashMap, fmt::write, time::SystemTime}; use vec1::Vec1; use camino::{Utf8Path, Utf8PathBuf}; use super::{ErlangAppCodegenConfiguration, TargetCodegenConfiguration, Telemetry}; #[derive(Debug)] pub struct Compiled { /// The modules which were just compiled pub modules: Vec, /// The names of all cached modules, which are not present in the `modules` field. pub cached_module_names: Vec, } #[derive(Debug)] pub struct PackageCompiler<'a, IO> { pub io: IO, pub out: &'a Utf8Path, pub lib: &'a Utf8Path, pub root: &'a Utf8Path, pub mode: Mode, pub target: &'a TargetCodegenConfiguration, pub config: &'a PackageConfig, pub ids: UniqueIdGenerator, pub write_metadata: bool, pub perform_codegen: bool, /// If set to false the compiler won't load and analyse any of the package's /// modules and always succeed compilation returning no compile modules. /// /// Code generation is still carried out so that a root package will have an /// entry point nonetheless. /// pub compile_modules: bool, pub write_entrypoint: bool, pub copy_native_files: bool, pub compile_beam_bytecode: bool, pub subprocess_stdio: Stdio, pub target_support: TargetSupport, pub cached_warnings: CachedWarnings, pub check_module_conflicts: CheckModuleConflicts, } impl<'a, IO> PackageCompiler<'a, IO> where IO: FileSystemReader + FileSystemWriter + CommandExecutor + BeamCompiler + Clone, { pub fn new( config: &'a PackageConfig, mode: Mode, root: &'a Utf8Path, out: &'a Utf8Path, lib: &'a Utf8Path, target: &'a TargetCodegenConfiguration, ids: UniqueIdGenerator, io: IO, ) -> Self { Self { io, ids, out, lib, root, mode, config, target, write_metadata: true, perform_codegen: true, compile_modules: true, write_entrypoint: false, copy_native_files: true, compile_beam_bytecode: true, subprocess_stdio: Stdio::Inherit, target_support: TargetSupport::NotEnforced, cached_warnings: CachedWarnings::Ignore, check_module_conflicts: CheckModuleConflicts::DoNotCheck, } } /// Compile the package. /// Returns a list of modules that were compiled. Any modules that were read /// from the cache will not be returned. // TODO: return the cached modules. pub fn compile( mut self, warnings: &WarningEmitter, existing_modules: &mut im::HashMap, already_defined_modules: &mut im::HashMap, stale_modules: &mut StaleTracker, incomplete_modules: &mut HashSet, telemetry: &dyn Telemetry, ) -> Outcome { let span = tracing::info_span!("compile", package = %self.config.name.as_str()); let _enter = span.enter(); // Ensure that the package is compatible with this version of Gleam if let Err(e) = self.config.check_gleam_compatibility() { return e.into(); } let artefact_directory = self.out.join(paths::ARTEFACT_DIRECTORY_NAME); let codegen_required = if self.perform_codegen { CodegenRequired::Yes } else { CodegenRequired::No }; let loader = PackageLoader::new( self.io.clone(), self.ids.clone(), self.mode, self.root, self.cached_warnings, warnings, codegen_required, &artefact_directory, self.target.target(), &self.config.name, stale_modules, already_defined_modules, incomplete_modules, ); let loaded = if self.compile_modules { match loader.run() { Ok(loaded) => loaded, Err(error) => return error.into(), } } else { Loaded::empty() }; let mut cached_module_names = Vec::new(); // Load the cached modules that have previously been compiled for module in loaded.cached.into_iter() { // Emit any cached warnings. // Note that `self.cached_warnings` is set to `Ignore` (such as for // dependency packages) then this field will not be populated. if let Err(e) = self.emit_warnings(warnings, &module) { return e.into(); } cached_module_names.push(module.name.clone()); // Register the cached module so its type information etc can be // used for compiling futher modules. _ = existing_modules.insert(module.name.clone(), module); } if !loaded.to_compile.is_empty() { // Print that work is being done if self.perform_codegen { telemetry.compiling_package(&self.config.name); } else { telemetry.checking_package(&self.config.name) } } // Type check the modules that are new or have changed tracing::info!(count=%loaded.to_compile.len(), "analysing_modules"); let outcome = analyse( &self.config, self.target.target(), self.mode, &self.ids, loaded.to_compile, existing_modules, warnings, self.target_support, incomplete_modules, ); let mut modules = match outcome { Outcome::Ok(modules) => modules, Outcome::PartialFailure(modules, error) => { return Outcome::PartialFailure( Compiled { modules, cached_module_names, }, error, ); } Outcome::TotalFailure(error) => return Outcome::TotalFailure(error), }; tracing::debug!("performing_code_generation"); // Inlining is currently disabled. See // https://github.com/gleam-lang/gleam/pull/5010 for information. // let modules = if self.perform_codegen { // modules // .into_iter() // .map(|mut module| { // module.ast = inline::module(module.ast, &existing_modules); // module // }) // .collect() // } else { // modules // }; if let Err(error) = self.perform_codegen(&modules) { return error.into(); } if let Err(error) = self.encode_and_write_metadata(&mut modules) { return error.into(); } Outcome::Ok(Compiled { modules, cached_module_names, }) } fn compile_erlang_to_beam( &mut self, modules: &HashSet, ) -> Result, Error> { if modules.is_empty() { tracing::debug!("no_erlang_to_compile"); return Ok(Vec::new()); } tracing::debug!("compiling_erlang"); self.io .compile_beam(self.out, self.lib, modules, self.subprocess_stdio) .map(|modules| modules.iter().map(|str| EcoString::from(str)).collect()) } fn copy_project_native_files( &mut self, destination_dir: &Utf8Path, to_compile_modules: &mut HashSet, ) -> Result<(), Error> { tracing::debug!("copying_native_source_files"); // TODO: unit test let priv_source = self.root.join("priv"); let priv_build = self.out.join("priv"); if self.io.is_directory(&priv_source) && !self.io.is_directory(&priv_build) { tracing::debug!("linking_priv_to_build"); self.io.symlink_dir(&priv_source, &priv_build)?; } let copier = NativeFileCopier::new( self.io.clone(), self.root.clone(), destination_dir, self.check_module_conflicts, ); let copied = copier.run()?; to_compile_modules.extend(copied.to_compile.into_iter()); // If there are any Elixir files then we need to locate Elixir // installed on this system for use in compilation. if copied.any_elixir { ElixirLibraries::make_available( &self.io, &self.lib.to_path_buf(), self.subprocess_stdio, )?; } Ok(()) } fn encode_and_write_metadata(&mut self, modules: &mut [Module]) -> Result<()> { if !self.write_metadata { tracing::debug!("package_metadata_writing_disabled"); return Ok(()); } if modules.is_empty() { return Ok(()); } let artefact_dir = self.out.join(paths::ARTEFACT_DIRECTORY_NAME); tracing::debug!("writing_module_caches"); for module in modules { let cache_files = CacheFiles::new(&artefact_dir, &module.name); let result = if self.cached_warnings.should_use() { metadata::encode(&module.ast.type_info) } else { // Dependency packages don't get warnings persisted as the // programmer doesn't want to be told every time about warnings they // cannot fix directly. let warnings = std::mem::take(&mut module.ast.type_info.warnings); let result = metadata::encode(&module.ast.type_info); module.ast.type_info.warnings = warnings; result }; // Write cache file let bytes = result.expect("Failed to serialise module cache"); self.io.write_bytes(&cache_files.cache_path, &bytes)?; // Write cache metadata let info = CacheMetadata { mtime: module.mtime, codegen_performed: self.perform_codegen, dependencies: module.dependencies.clone(), fingerprint: SourceFingerprint::new(&module.code), line_numbers: module.ast.type_info.line_numbers.clone(), }; self.io .write_bytes(&cache_files.meta_path, &info.to_binary())?; } Ok(()) } fn perform_codegen(&mut self, modules: &[Module]) -> Result<()> { if !self.perform_codegen { tracing::debug!("skipping_codegen"); return Ok(()); } match self.target { TargetCodegenConfiguration::JavaScript { emit_typescript_definitions, prelude_location, } => self.perform_javascript_codegen( modules, *emit_typescript_definitions, prelude_location, ), TargetCodegenConfiguration::Erlang { app_file } => { self.perform_erlang_codegen(modules, app_file.as_ref()) } } } fn perform_erlang_codegen( &mut self, modules: &[Module], app_file_config: Option<&ErlangAppCodegenConfiguration>, ) -> Result<(), Error> { let mut written = HashSet::new(); let build_dir = self.out.join(paths::ARTEFACT_DIRECTORY_NAME); let include_dir = self.out.join("include"); let io = self.io.clone(); io.mkdir(&build_dir)?; if self.copy_native_files { self.copy_project_native_files(&build_dir, &mut written)?; } else { tracing::debug!("skipping_native_file_copying"); } if self.compile_beam_bytecode && self.write_entrypoint { self.render_erlang_entrypoint_module(&build_dir, &mut written)?; } else { tracing::debug!("skipping_entrypoint_generation"); } // NOTE: This must come after `copy_project_native_files` to ensure that // we overwrite any precompiled Erlang that was included in the Hex // package. Otherwise we will build the potentially outdated precompiled // version and not the newly compiled version. Erlang::new(&build_dir, &include_dir).render(io.clone(), modules, self.root)?; let native_modules: Vec = if self.compile_beam_bytecode { written.extend(modules.iter().map(Module::compiled_erlang_path)); self.compile_erlang_to_beam(&written)? } else { tracing::debug!("skipping_erlang_bytecode_compilation"); Vec::new() }; if let Some(config) = app_file_config { ErlangApp::new(&self.out.join("ebin"), config).render( io, &self.config, modules, native_modules, )?; } Ok(()) } fn perform_javascript_codegen( &mut self, modules: &[Module], typescript: bool, prelude_location: &Utf8Path, ) -> Result<(), Error> { let mut written = HashSet::new(); let typescript = if typescript { TypeScriptDeclarations::Emit } else { TypeScriptDeclarations::None }; JavaScript::new(&self.out, typescript, prelude_location, &self.root).render( &self.io, modules, self.stdlib_package(), )?; if self.copy_native_files { self.copy_project_native_files(&self.out, &mut written)?; } else { tracing::debug!("skipping_native_file_copying"); } Ok(()) } fn render_erlang_entrypoint_module( &mut self, out: &Utf8Path, modules_to_compile: &mut HashSet, ) -> Result<(), Error> { let name = format!("{name}@@main.erl", name = self.config.name); let path = out.join(&name); // If the entrypoint module has already been created then we don't need // to write and compile it again. if self.io.is_file(&path) { tracing::debug!("erlang_entrypoint_already_exists"); return Ok(()); } let template = ErlangEntrypointModule { application: &self.config.name, }; let module = template.render().expect("Erlang entrypoint rendering"); self.io.write(&path, &module)?; let _ = modules_to_compile.insert(name.into()); tracing::debug!("erlang_entrypoint_written"); Ok(()) } fn emit_warnings( &self, warnings: &WarningEmitter, module: &type_::ModuleInterface, ) -> Result<()> { for warning in &module.warnings { let src = self.io.read(&module.src_path)?; warnings.emit(Warning::Type { path: module.src_path.clone(), src: src.into(), warning: warning.clone(), }); } Ok(()) } fn stdlib_package(&self) -> StdlibPackage { if self.config.dependencies.contains_key("gleam_stdlib") || self.config.dev_dependencies.contains_key("gleam_stdlib") { StdlibPackage::Present } else { StdlibPackage::Missing } } } #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum StdlibPackage { Present, Missing, } fn analyse( package_config: &PackageConfig, target: Target, mode: Mode, ids: &UniqueIdGenerator, mut parsed_modules: Vec, module_types: &mut im::HashMap, warnings: &WarningEmitter, target_support: TargetSupport, incomplete_modules: &mut HashSet, ) -> Outcome, Error> { let mut modules = Vec::with_capacity(parsed_modules.len() + 1); let direct_dependencies = package_config.dependencies_for(mode).expect("Package deps"); let dev_dependencies = package_config.dev_dependencies.keys().cloned().collect(); // Insert the prelude // DUPE: preludeinsertion // TODO: Currently we do this here and also in the tests. It would be better // to have one place where we create all this required state for use in each // place. let _ = module_types.insert(PRELUDE_MODULE_NAME.into(), type_::build_prelude(ids)); for UncompiledModule { name, code, ast, path, mtime, origin, package, dependencies, extra, } in parsed_modules { tracing::debug!(module = ?name, "Type checking"); let line_numbers = LineNumbers::new(&code); let analysis = crate::analyse::ModuleAnalyzerConstructor { target, ids, origin, importable_modules: module_types, warnings: &TypeWarningEmitter::new(path.clone(), code.clone(), warnings.clone()), direct_dependencies: &direct_dependencies, dev_dependencies: &dev_dependencies, target_support, package_config, } .infer_module(ast, line_numbers, path.clone()); match analysis { Outcome::Ok(ast) => { // Module has compiled successfully. Make sure it isn't marked as incomplete. let _ = incomplete_modules.remove(&name.clone()); let mut module = Module { dependencies, origin, extra, mtime, name, code, ast, input_path: path, }; module.attach_doc_and_module_comments(); // Register the types from this module so they can be imported into // other modules. let _ = module_types.insert(module.name.clone(), module.ast.type_info.clone()); // Check for empty modules and emit warning // Only emit the empty module warning if the module has no definitions at all. // Modules with only private definitions already emit their own warnings. if module_types .get(&module.name) .map(|interface| interface.values.is_empty() && interface.types.is_empty()) .unwrap_or(false) { warnings.emit(crate::warning::Warning::EmptyModule { path: module.input_path.clone(), name: module.name.clone(), }); } // Register the successfully type checked module data so that it can be // used for code generation and in the language server. modules.push(module); } Outcome::PartialFailure(ast, errors) => { let error = Error::Type { names: Box::new(ast.names.clone()), path: path.clone(), src: code.clone(), errors, }; // Mark as incomplete so that this module isn't reloaded from cache. let _ = incomplete_modules.insert(name.clone()); // Register the partially type checked module data so that it can be // used in the language server. let mut module = Module { dependencies, origin, extra, mtime, name, code, ast, input_path: path, }; module.attach_doc_and_module_comments(); let _ = module_types.insert(module.ast.name.clone(), module.ast.type_info.clone()); modules.push(module); // WARNING: This cannot be used for code generation as the code has errors. return Outcome::PartialFailure(modules, error); } Outcome::TotalFailure(errors) => { return Outcome::TotalFailure(Error::Type { names: Default::default(), path: path.clone(), src: code.clone(), errors, }); } }; } Outcome::Ok(modules) } #[derive(Debug)] pub(crate) enum Input { New(UncompiledModule), Cached(CachedModule), } impl Input { pub fn name(&self) -> &EcoString { match self { Input::New(m) => &m.name, Input::Cached(m) => &m.name, } } pub fn source_path(&self) -> &Utf8Path { match self { Input::New(m) => &m.path, Input::Cached(m) => &m.source_path, } } pub fn dependencies(&self) -> Vec { match self { Input::New(m) => m.dependencies.iter().map(|(n, _)| n.clone()).collect(), Input::Cached(m) => m.dependencies.iter().map(|(n, _)| n.clone()).collect(), } } /// Returns `true` if the input is [`New`]. /// /// [`New`]: Input::New #[must_use] pub(crate) fn is_new(&self) -> bool { matches!(self, Self::New(..)) } /// Returns `true` if the input is [`Cached`]. /// /// [`Cached`]: Input::Cached #[must_use] pub(crate) fn is_cached(&self) -> bool { matches!(self, Self::Cached(..)) } } #[derive(Debug)] pub(crate) struct CachedModule { pub name: EcoString, pub origin: Origin, pub dependencies: Vec<(EcoString, SrcSpan)>, pub source_path: Utf8PathBuf, pub line_numbers: LineNumbers, } #[derive(Debug, serde::Serialize, serde::Deserialize)] pub(crate) struct CacheMetadata { pub mtime: SystemTime, pub codegen_performed: bool, pub dependencies: Vec<(EcoString, SrcSpan)>, pub fingerprint: SourceFingerprint, pub line_numbers: LineNumbers, } impl CacheMetadata { pub fn to_binary(&self) -> Vec { bincode::serde::encode_to_vec(self, bincode::config::legacy()) .expect("Serializing cache info") } pub fn from_binary(bytes: &[u8]) -> Result { match bincode::serde::decode_from_slice(bytes, bincode::config::legacy()) { Ok((data, _)) => Ok(data), Err(e) => Err(e.to_string()), } } } #[derive(Debug, Default, PartialEq, Eq)] pub(crate) struct Loaded { pub to_compile: Vec, pub cached: Vec, } impl Loaded { fn empty() -> Self { Self { to_compile: vec![], cached: vec![], } } } #[derive(Debug, PartialEq, Eq)] pub(crate) struct UncompiledModule { pub path: Utf8PathBuf, pub name: EcoString, pub code: EcoString, pub mtime: SystemTime, pub origin: Origin, pub package: EcoString, pub dependencies: Vec<(EcoString, SrcSpan)>, pub ast: UntypedModule, pub extra: ModuleExtra, } #[derive(Template)] #[template(path = "gleam@@main.erl", escape = "none")] struct ErlangEntrypointModule<'a> { application: &'a str, } #[derive(Debug, Clone, Copy)] pub enum CachedWarnings { Use, Ignore, } impl CachedWarnings { pub(crate) fn should_use(&self) -> bool { match self { CachedWarnings::Use => true, CachedWarnings::Ignore => false, } } } #[derive(Debug, Clone, Copy)] pub enum CheckModuleConflicts { Check, DoNotCheck, } impl CheckModuleConflicts { pub(crate) fn should_check(&self) -> bool { match self { CheckModuleConflicts::Check => true, CheckModuleConflicts::DoNotCheck => false, } } } ================================================ FILE: compiler-core/src/build/package_loader/tests.rs ================================================ use ecow::{EcoString, eco_format}; use hexpm::version::Version; use super::*; use crate::{ Warning, build::SourceFingerprint, io::{FileSystemWriter, memory::InMemoryFileSystem}, line_numbers, parse::extra::ModuleExtra, warning::NullWarningEmitterIO, }; use std::time::Duration; #[derive(Debug)] struct LoaderTestOutput { to_compile: Vec, cached: Vec, warnings: Vec, } const TEST_SOURCE_1: &'static str = "const x = 1"; const TEST_SOURCE_2: &'static str = "const x = 2"; fn write_src(fs: &InMemoryFileSystem, path: &str, seconds: u64, src: &str) { let path = Utf8Path::new(path); fs.write(&path, src).unwrap(); fs.set_modification_time(&path, SystemTime::UNIX_EPOCH + Duration::from_secs(seconds)); } fn write_cache( fs: &InMemoryFileSystem, name: &str, seconds: u64, deps: Vec<(EcoString, SrcSpan)>, src: &str, ) { let line_numbers = line_numbers::LineNumbers::new(src); let mtime = SystemTime::UNIX_EPOCH + Duration::from_secs(seconds); let cache_metadata = CacheMetadata { mtime, codegen_performed: true, dependencies: deps, fingerprint: SourceFingerprint::new(src), line_numbers: line_numbers.clone(), }; let artefact_name = name.replace("/", "@"); let path = Utf8Path::new("/artefact").join(format!("{artefact_name}.cache_meta")); fs.write_bytes(&path, &cache_metadata.to_binary()).unwrap(); let cache = crate::type_::ModuleInterface { name: name.into(), origin: Origin::Src, package: "my_package".into(), types: Default::default(), types_value_constructors: Default::default(), values: Default::default(), accessors: Default::default(), line_numbers: line_numbers.clone(), is_internal: false, src_path: Utf8PathBuf::from(format!("/src/{}.gleam", name)), warnings: vec![], minimum_required_version: Version::new(0, 1, 0), type_aliases: Default::default(), documentation: Default::default(), contains_echo: false, references: Default::default(), inline_functions: Default::default(), }; let path = Utf8Path::new("/artefact").join(format!("{artefact_name}.cache")); fs.write_bytes(&path, &metadata::encode(&cache).unwrap()) .unwrap(); } fn run_loader(fs: InMemoryFileSystem, root: &Utf8Path, artefact: &Utf8Path) -> LoaderTestOutput { let mut defined = im::HashMap::new(); let ids = UniqueIdGenerator::new(); let (emitter, warnings) = WarningEmitter::vector(); let loader = PackageLoader { io: fs.clone(), ids, mode: Mode::Dev, paths: ProjectPaths::new(root.into()), warnings: &emitter, codegen: CodegenRequired::Yes, artefact_directory: &artefact, package_name: &"my_package".into(), target: Target::JavaScript, stale_modules: &mut StaleTracker::default(), already_defined_modules: &mut defined, incomplete_modules: &mut HashSet::new(), cached_warnings: CachedWarnings::Ignore, }; let loaded = loader.run().unwrap(); LoaderTestOutput { to_compile: loaded.to_compile.into_iter().map(|m| m.name).collect(), cached: loaded.cached.into_iter().map(|m| m.name).collect(), warnings: warnings.take(), } } #[test] fn no_modules() { let fs = InMemoryFileSystem::new(); let root = Utf8Path::new("/"); let artefact = Utf8Path::new("/artefact"); let loaded = run_loader(fs, root, artefact); assert!(loaded.to_compile.is_empty()); assert!(loaded.cached.is_empty()); } #[test] fn one_src_module() { let fs = InMemoryFileSystem::new(); let root = Utf8Path::new("/"); let artefact = Utf8Path::new("/artefact"); write_src(&fs, "/src/main.gleam", 0, "const x = 1"); let loaded = run_loader(fs, root, artefact); assert_eq!(loaded.to_compile, vec![EcoString::from("main")]); assert!(loaded.cached.is_empty()); } #[test] fn one_test_module() { let fs = InMemoryFileSystem::new(); let root = Utf8Path::new("/"); let artefact = Utf8Path::new("/artefact"); write_src(&fs, "/test/main.gleam", 0, "const x = 1"); let loaded = run_loader(fs, root, artefact); assert_eq!(loaded.to_compile, vec![EcoString::from("main")]); assert!(loaded.cached.is_empty()); } #[test] fn one_dev_module() { let fs = InMemoryFileSystem::new(); let root = Utf8Path::new("/"); let artefact = Utf8Path::new("/artefact"); write_src(&fs, "/dev/main.gleam", 0, "const x = 1"); let loaded = run_loader(fs, root, artefact); assert_eq!(loaded.to_compile, vec![EcoString::from("main")]); assert!(loaded.cached.is_empty()); } #[test] fn importing() { let fs = InMemoryFileSystem::new(); let root = Utf8Path::new("/"); let artefact = Utf8Path::new("/artefact"); write_src(&fs, "/src/three.gleam", 0, "import two"); write_src(&fs, "/src/one.gleam", 0, ""); write_src(&fs, "/src/two.gleam", 0, "import one"); let loaded = run_loader(fs, root, artefact); assert_eq!( loaded.to_compile, vec![ EcoString::from("one"), EcoString::from("two"), EcoString::from("three") ] ); assert!(loaded.cached.is_empty()); } #[test] fn reading_cache() { let fs = InMemoryFileSystem::new(); let root = Utf8Path::new("/"); let artefact = Utf8Path::new("/artefact"); write_src(&fs, "/src/one.gleam", 0, TEST_SOURCE_1); write_cache(&fs, "one", 0, vec![], TEST_SOURCE_1); let loaded = run_loader(fs, root, artefact); assert!(loaded.to_compile.is_empty()); assert_eq!(loaded.cached, vec![EcoString::from("one")]); } #[test] fn module_is_stale_if_cache_older() { let fs = InMemoryFileSystem::new(); let root = Utf8Path::new("/"); let artefact = Utf8Path::new("/artefact"); write_src(&fs, "/src/one.gleam", 1, TEST_SOURCE_2); write_cache(&fs, "one", 0, vec![], TEST_SOURCE_1); let loaded = run_loader(fs, root, artefact); assert_eq!(loaded.to_compile, vec![EcoString::from("one")]); assert!(loaded.cached.is_empty()); } #[test] fn module_is_stale_if_deps_are_stale() { let fs = InMemoryFileSystem::new(); let root = Utf8Path::new("/"); let artefact = Utf8Path::new("/artefact"); // Cache is stale write_src(&fs, "/src/one.gleam", 1, TEST_SOURCE_2); write_cache(&fs, "one", 0, vec![], TEST_SOURCE_1); // Cache is fresh but dep is stale write_src(&fs, "/src/two.gleam", 1, "import one"); write_cache( &fs, "two", 2, vec![(EcoString::from("one"), SrcSpan { start: 0, end: 0 })], "import one", ); // Cache is fresh write_src(&fs, "/src/three.gleam", 1, TEST_SOURCE_1); write_cache(&fs, "three", 2, vec![], TEST_SOURCE_1); let loaded = run_loader(fs, root, artefact); assert_eq!( loaded.to_compile, vec![EcoString::from("one"), EcoString::from("two")] ); assert_eq!(loaded.cached, vec![EcoString::from("three")]); } #[test] fn module_is_stale_if_deps_removed() { let fs = InMemoryFileSystem::new(); let root = Utf8Path::new("/"); let artefact = Utf8Path::new("/artefact"); // Source is removed, cache is present write_cache(&fs, "nested/one", 0, vec![], TEST_SOURCE_1); // Cache is fresh but dep is removed write_src(&fs, "/src/two.gleam", 1, "import one"); write_cache( &fs, "two", 2, vec![(EcoString::from("nested/one"), SrcSpan { start: 0, end: 0 })], "import nested/one", ); let loaded = run_loader(fs, root, artefact); assert_eq!(loaded.to_compile, vec![EcoString::from("two")]); } #[test] fn module_continues_to_be_stale_if_deps_get_updated() { let fs = InMemoryFileSystem::new(); let root = Utf8Path::new("/"); let artefact = Utf8Path::new("/artefact"); // Cache is stale write_src(&fs, "/src/one.gleam", 1, TEST_SOURCE_2); write_cache(&fs, "one", 0, vec![], TEST_SOURCE_1); // Cache is fresh but dep is stale write_src(&fs, "/src/two.gleam", 1, "import one"); write_cache( &fs, "two", 2, vec![(EcoString::from("one"), SrcSpan { start: 0, end: 0 })], "import one", ); // Cache is fresh write_src(&fs, "/src/three.gleam", 1, TEST_SOURCE_1); write_cache(&fs, "three", 2, vec![], TEST_SOURCE_1); let _loaded1 = run_loader(fs.clone(), root, artefact); // update the dependency write_cache(&fs, "one", 3, vec![], TEST_SOURCE_2); let loaded2 = run_loader(fs, root, artefact); assert_eq!(loaded2.to_compile, vec![EcoString::from("two")]); assert_eq!( loaded2.cached, vec![EcoString::from("one"), EcoString::from("three")] ); } #[test] fn invalid_module_name() { let fs = InMemoryFileSystem::new(); let root = Utf8Path::new("/"); let artefact = Utf8Path::new("/artefact"); // Cache is stale write_src(&fs, "/src/One.gleam", 1, TEST_SOURCE_2); let loaded = run_loader(fs, root, artefact); assert!(loaded.to_compile.is_empty()); assert!(loaded.cached.is_empty()); assert_eq!( loaded.warnings, vec![Warning::InvalidSource { path: Utf8PathBuf::from("/src/One.gleam"), }], ); } #[test] fn invalid_nested_module_name() { let fs = InMemoryFileSystem::new(); let root = Utf8Path::new("/"); let artefact = Utf8Path::new("/artefact"); // Cache is stale write_src(&fs, "/src/1/one.gleam", 1, TEST_SOURCE_2); let loaded = run_loader(fs, root, artefact); assert!(loaded.to_compile.is_empty()); assert!(loaded.cached.is_empty()); assert_eq!( loaded.warnings, vec![Warning::InvalidSource { path: Utf8PathBuf::from("/src/1/one.gleam"), }], ); } #[test] fn invalid_module_name_in_test() { let fs = InMemoryFileSystem::new(); let root = Utf8Path::new("/"); let artefact = Utf8Path::new("/artefact"); // Cache is stale write_src(&fs, "/test/One.gleam", 1, TEST_SOURCE_2); let loaded = run_loader(fs, root, artefact); assert!(loaded.to_compile.is_empty()); assert!(loaded.cached.is_empty()); assert_eq!( loaded.warnings, vec![Warning::InvalidSource { path: Utf8PathBuf::from("/test/One.gleam"), }], ); } #[test] fn invalid_nested_module_name_in_test() { let fs = InMemoryFileSystem::new(); let root = Utf8Path::new("/"); let artefact = Utf8Path::new("/artefact"); // Cache is stale write_src(&fs, "/test/1/one.gleam", 1, TEST_SOURCE_2); let loaded = run_loader(fs, root, artefact); assert!(loaded.to_compile.is_empty()); assert!(loaded.cached.is_empty()); assert_eq!( loaded.warnings, vec![Warning::InvalidSource { path: Utf8PathBuf::from("/test/1/one.gleam"), }], ); } #[test] fn invalid_module_name_in_dev() { let fs = InMemoryFileSystem::new(); let root = Utf8Path::new("/"); let artefact = Utf8Path::new("/artefact"); // Cache is stale write_src(&fs, "/dev/One.gleam", 1, TEST_SOURCE_2); let loaded = run_loader(fs, root, artefact); assert!(loaded.to_compile.is_empty()); assert!(loaded.cached.is_empty()); assert_eq!( loaded.warnings, vec![Warning::InvalidSource { path: Utf8PathBuf::from("/dev/One.gleam"), }], ); } #[test] fn invalid_nested_module_name_in_dev() { let fs = InMemoryFileSystem::new(); let root = Utf8Path::new("/"); let artefact = Utf8Path::new("/artefact"); // Cache is stale write_src(&fs, "/dev/1/one.gleam", 1, TEST_SOURCE_2); let loaded = run_loader(fs, root, artefact); assert!(loaded.to_compile.is_empty()); assert!(loaded.cached.is_empty()); assert_eq!( loaded.warnings, vec![Warning::InvalidSource { path: Utf8PathBuf::from("/dev/1/one.gleam"), }], ); } #[test] fn cache_files_are_removed_when_source_removed() { let fs = InMemoryFileSystem::new(); let root = Utf8Path::new("/"); let artefact = Utf8Path::new("/artefact"); // Source is removed, cache is present write_cache(&fs, "nested/one", 0, vec![], TEST_SOURCE_1); _ = run_loader(fs.clone(), root, artefact); assert_eq!(fs.files().len(), 0); } ================================================ FILE: compiler-core/src/build/package_loader.rs ================================================ #[cfg(test)] mod tests; use std::{ collections::{HashMap, HashSet}, time::{Duration, SystemTime}, }; use camino::{Utf8Path, Utf8PathBuf}; // TODO: emit warnings for cached modules even if they are not compiled again. use ecow::EcoString; use itertools::Itertools; use vec1::Vec1; use crate::{ Error, Result, ast::SrcSpan, build::{Module, Origin, module_loader::ModuleLoader}, config::PackageConfig, dep_tree, error::{DefinedModuleOrigin, FileIoAction, FileKind, ImportCycleLocationDetails}, io::{self, CommandExecutor, FileSystemReader, FileSystemWriter, files_with_extension}, metadata, paths::ProjectPaths, type_, uid::UniqueIdGenerator, warning::WarningEmitter, }; use super::{ Mode, Target, module_loader::read_source, package_compiler::{ CacheMetadata, CachedModule, CachedWarnings, Input, Loaded, UncompiledModule, }, }; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CodegenRequired { Yes, No, } impl CodegenRequired { /// Returns `true` if the codegen required is [`Yes`]. /// /// [`Yes`]: CodegenRequired::Yes #[must_use] pub fn is_required(&self) -> bool { matches!(self, Self::Yes) } } #[derive(Debug)] pub struct PackageLoader<'a, IO> { io: IO, ids: UniqueIdGenerator, mode: Mode, paths: ProjectPaths, warnings: &'a WarningEmitter, codegen: CodegenRequired, artefact_directory: &'a Utf8Path, package_name: &'a EcoString, target: Target, stale_modules: &'a mut StaleTracker, already_defined_modules: &'a mut im::HashMap, incomplete_modules: &'a HashSet, cached_warnings: CachedWarnings, } impl<'a, IO> PackageLoader<'a, IO> where IO: FileSystemWriter + FileSystemReader + CommandExecutor + Clone, { pub(crate) fn new( io: IO, ids: UniqueIdGenerator, mode: Mode, root: &'a Utf8Path, cached_warnings: CachedWarnings, warnings: &'a WarningEmitter, codegen: CodegenRequired, artefact_directory: &'a Utf8Path, target: Target, package_name: &'a EcoString, stale_modules: &'a mut StaleTracker, already_defined_modules: &'a mut im::HashMap, incomplete_modules: &'a HashSet, ) -> Self { Self { io, ids, mode, paths: ProjectPaths::new(root.into()), warnings, codegen, target, package_name, cached_warnings, artefact_directory, stale_modules, already_defined_modules, incomplete_modules, } } pub(crate) fn run(mut self) -> Result { // First read the source files. This will use the `ModuleLoader`, which // will check the mtimes and hashes of sources and caches to determine // which should be loaded. let mut inputs = self.read_sources_and_caches()?; // Check for any removed modules, by looking at cache files that don't exist in inputs. // Delete the cache files for removed modules and mark them as stale // to trigger refreshing dependent modules. for module in CacheFiles::modules_with_meta_files(&self.io, &self.artefact_directory) { if (!inputs.contains_key(&module)) { tracing::debug!(%module, "module_removed"); CacheFiles::new(&self.artefact_directory, &module).delete(&self.io)?; self.stale_modules.add(module); } } // Determine order in which modules are to be processed let mut dep_location_map = HashMap::new(); let deps = inputs .values() .map(|(_, module)| { let name = module.name().clone(); let _ = dep_location_map.insert(name.clone(), module); (name, module.dependencies()) }) // Making sure that the module order is deterministic, to prevent different // compilations of the same project compiling in different orders. This could impact // any bugged outcomes, though not any where the compiler is working correctly, so it's // mostly to aid debugging. .sorted_by(|(a, _), (b, _)| a.cmp(b)) .collect(); let sequence = dep_tree::toposort_deps(deps) .map_err(|error| self.convert_deps_tree_error(error, dep_location_map))?; // Now that we have loaded sources and caches we check to see if any of // the caches need to be invalidated because their dependencies have // changed. let mut loaded = Loaded::default(); for name in sequence { let (_, input) = inputs .remove(&name) .expect("Getting parsed module for name"); match input { // A new uncached module is to be compiled Input::New(module) => { tracing::debug!(module = %module.name, "new_module_to_be_compiled"); self.stale_modules.add(module.name.clone()); loaded.to_compile.push(module); } // A cached module with dependencies that are stale must be // recompiled as the changes in the dependencies may have affect // the output, making the cache invalid. Input::Cached(info) if self.stale_modules.includes_any(&info.dependencies) => { tracing::debug!(module = %info.name, "stale_module_to_be_compiled"); self.stale_modules.add(info.name.clone()); let module = self.load_stale_module(info)?; loaded.to_compile.push(module); } // A cached module with no stale dependencies can be used as-is // and does not need to be recompiled. Input::Cached(info) => { tracing::debug!(module = %info.name, "module_to_load_from_cache"); let module = self.load_cached_module(info)?; loaded.cached.push(module); } } } Ok(loaded) } fn load_cached_module(&self, info: CachedModule) -> Result { let cache_files = CacheFiles::new(&self.artefact_directory, &info.name); let bytes = self.io.read_bytes(&cache_files.cache_path)?; let mut module = match metadata::decode(bytes.as_slice(), self.ids.clone()) { Ok(module) => module, Err(e) => { return Err(Error::FileIo { kind: FileKind::File, action: FileIoAction::Parse, path: cache_files.cache_path, err: Some(e.to_string()), }); } }; // Discard warnings if they should not be cached if !self.cached_warnings.should_use() { module.warnings.clear(); } Ok(module) } fn read_sources_and_caches( &mut self, ) -> Result> { let span = tracing::info_span!("load"); let _enter = span.enter(); let mut inputs = Inputs::new(self.package_name.clone(), self.already_defined_modules); let src = self.paths.src_directory(); let mut loader = ModuleLoader { io: self.io.clone(), warnings: self.warnings, mode: self.mode, target: self.target, codegen: self.codegen, package_name: self.package_name, artefact_directory: self.artefact_directory, origin: Origin::Src, incomplete_modules: self.incomplete_modules, }; // Src for file in GleamFile::iterate_files_in_directory(&self.io, &src) { match file { Ok(file) => { let input = loader.load(file)?; inputs.insert(input)?; } Err(warning) => self.warnings.emit(warning), } } // Test and dev if self.mode.includes_dev_code() { let test = self.paths.test_directory(); loader.origin = Origin::Test; for file in GleamFile::iterate_files_in_directory(&self.io, &test) { match file { Ok(file) => { let input = loader.load(file)?; inputs.insert(input)?; } Err(warning) => self.warnings.emit(warning), } } let dev = self.paths.dev_directory(); loader.origin = Origin::Dev; for file in GleamFile::iterate_files_in_directory(&self.io, &dev) { match file { Ok(file) => { let input = loader.load(file)?; inputs.insert(input)?; } Err(warning) => self.warnings.emit(warning), } } } // If we are compiling for Erlang then modules all live in a single // namespace. If we were to name a module the same as a module that // is included in the standard Erlang distribution then this new // Gleam module would overwrite the existing Erlang one, likely // resulting in cryptic errors. // This would most commonly happen for modules like "user" and // "code". Emit an error so this never happens. if self.target.is_erlang() { for (_, input) in inputs.collection.values() { ensure_gleam_module_does_not_overwrite_standard_erlang_module(&input)?; } } Ok(inputs.collection) } fn load_stale_module(&self, cached: CachedModule) -> Result { let mtime = self.io.modification_time(&cached.source_path)?; // We need to delete any existing cache files for this module. // While we figured it out this time because the module has stale dependencies, // next time the dependencies might no longer be stale, but we still need to be able to tell // that this module needs to be recompiled until it successfully compiles at least once. // This can happen if the stale dependency includes breaking changes. CacheFiles::new(&self.artefact_directory, &cached.name).delete(&self.io)?; read_source( self.io.clone(), self.target, cached.origin, cached.source_path, cached.name, self.package_name.clone(), mtime, self.warnings.clone(), ) } fn convert_deps_tree_error( &self, e: dep_tree::Error, dep_location_map: HashMap, ) -> Error { match e { dep_tree::Error::Cycle(modules) => { let modules = modules .iter() .enumerate() .map(|(i, module)| { // cycles are in order of reference so get next in list or loop back to first let index_of_imported = if i == 0 { modules.len() - 1 } else { i - 1 }; let imported_module = modules .get(index_of_imported) .expect("importing module must exist"); let input = dep_location_map.get(module).expect("dependency must exist"); let location = match input { Input::New(module) => { let (_, location) = module .dependencies .iter() .find(|d| &d.0 == imported_module) .expect("import must exist for there to be a cycle"); ImportCycleLocationDetails { location: *location, path: module.path.clone(), src: module.code.clone(), } } Input::Cached(cached_module) => { let (_, location) = cached_module .dependencies .iter() .find(|d| &d.0 == imported_module) .expect("import must exist for there to be a cycle"); let src = self .io .read(&cached_module.source_path) .expect("failed to read source") .into(); ImportCycleLocationDetails { location: *location, path: cached_module.source_path.clone(), src, } } }; (module.clone(), location) }) .collect_vec(); Error::ImportCycle { modules: Vec1::try_from(modules) .expect("at least 1 module must exist in cycle"), } } } } } fn ensure_gleam_module_does_not_overwrite_standard_erlang_module(input: &Input) -> Result<()> { // We only need to check uncached modules as it's not possible for these // to have compiled successfully. let Input::New(input) = input else { return Ok(()); }; // These names were got with this Erlang // // ```erl // file:write_file("names.txt", lists:join("\n",lists:map(fun(T) -> erlang:element(1, T) end, code:all_available()))). // ``` // match input.name.as_str() { "alarm_handler" | "application" | "application_controller" | "application_master" | "application_starter" | "appmon_info" | "argparse" | "array" | "asn1_db" | "asn1ct" | "asn1ct_check" | "asn1ct_constructed_ber_bin_v2" | "asn1ct_constructed_per" | "asn1ct_eval_ext" | "asn1ct_func" | "asn1ct_gen" | "asn1ct_gen_ber_bin_v2" | "asn1ct_gen_check" | "asn1ct_gen_jer" | "asn1ct_gen_per" | "asn1ct_imm" | "asn1ct_name" | "asn1ct_parser2" | "asn1ct_pretty_format" | "asn1ct_rtt" | "asn1ct_table" | "asn1ct_tok" | "asn1ct_value" | "asn1rt_nif" | "atomics" | "auth" | "base64" | "beam_a" | "beam_asm" | "beam_block" | "beam_bounds" | "beam_call_types" | "beam_clean" | "beam_dict" | "beam_digraph" | "beam_disasm" | "beam_flatten" | "beam_jump" | "beam_kernel_to_ssa" | "beam_lib" | "beam_listing" | "beam_opcodes" | "beam_ssa" | "beam_ssa_alias" | "beam_ssa_bc_size" | "beam_ssa_bool" | "beam_ssa_bsm" | "beam_ssa_check" | "beam_ssa_codegen" | "beam_ssa_dead" | "beam_ssa_lint" | "beam_ssa_opt" | "beam_ssa_pp" | "beam_ssa_pre_codegen" | "beam_ssa_private_append" | "beam_ssa_recv" | "beam_ssa_share" | "beam_ssa_throw" | "beam_ssa_type" | "beam_trim" | "beam_types" | "beam_utils" | "beam_validator" | "beam_z" | "binary" | "c" | "calendar" | "cdv_atom_cb" | "cdv_bin_cb" | "cdv_detail_wx" | "cdv_dist_cb" | "cdv_ets_cb" | "cdv_fun_cb" | "cdv_gen_cb" | "cdv_html_wx" | "cdv_info_wx" | "cdv_int_tab_cb" | "cdv_mem_cb" | "cdv_mod_cb" | "cdv_multi_wx" | "cdv_persistent_cb" | "cdv_port_cb" | "cdv_proc_cb" | "cdv_sched_cb" | "cdv_table_wx" | "cdv_term_cb" | "cdv_timer_cb" | "cdv_virtual_list_wx" | "cdv_wx" | "cerl" | "cerl_clauses" | "cerl_inline" | "cerl_prettypr" | "cerl_trees" | "code" | "code_server" | "compile" | "core_lib" | "core_lint" | "core_parse" | "core_pp" | "core_scan" | "counters" | "cover" | "cprof" | "cpu_sup" | "crashdump_viewer" | "crypto" | "crypto_ec_curves" | "ct" | "ct_config" | "ct_config_plain" | "ct_config_xml" | "ct_conn_log_h" | "ct_cover" | "ct_default_gl" | "ct_event" | "ct_framework" | "ct_ftp" | "ct_gen_conn" | "ct_groups" | "ct_hooks" | "ct_hooks_lock" | "ct_logs" | "ct_make" | "ct_master" | "ct_master_event" | "ct_master_logs" | "ct_master_status" | "ct_netconfc" | "ct_property_test" | "ct_release_test" | "ct_repeat" | "ct_rpc" | "ct_run" | "ct_slave" | "ct_snmp" | "ct_ssh" | "ct_suite" | "ct_telnet" | "ct_telnet_client" | "ct_testspec" | "ct_util" | "cth_conn_log" | "cth_log_redirect" | "cth_surefire" | "dbg" | "dbg_debugged" | "dbg_icmd" | "dbg_idb" | "dbg_ieval" | "dbg_iload" | "dbg_iserver" | "dbg_istk" | "dbg_wx_break" | "dbg_wx_break_win" | "dbg_wx_code" | "dbg_wx_filedialog_win" | "dbg_wx_interpret" | "dbg_wx_mon" | "dbg_wx_mon_win" | "dbg_wx_settings" | "dbg_wx_src_view" | "dbg_wx_trace" | "dbg_wx_trace_win" | "dbg_wx_view" | "dbg_wx_win" | "dbg_wx_winman" | "debugger" | "dets" | "dets_server" | "dets_sup" | "dets_utils" | "dets_v9" | "dialyzer" | "dialyzer_analysis_callgraph" | "dialyzer_behaviours" | "dialyzer_callgraph" | "dialyzer_cl" | "dialyzer_cl_parse" | "dialyzer_clean_core" | "dialyzer_codeserver" | "dialyzer_contracts" | "dialyzer_coordinator" | "dialyzer_cplt" | "dialyzer_dataflow" | "dialyzer_dep" | "dialyzer_dot" | "dialyzer_explanation" | "dialyzer_gui_wx" | "dialyzer_incremental" | "dialyzer_iplt" | "dialyzer_options" | "dialyzer_plt" | "dialyzer_succ_typings" | "dialyzer_timing" | "dialyzer_typegraph" | "dialyzer_typesig" | "dialyzer_utils" | "dialyzer_worker" | "diameter" | "diameter_app" | "diameter_callback" | "diameter_capx" | "diameter_codec" | "diameter_codegen" | "diameter_config" | "diameter_config_sup" | "diameter_dbg" | "diameter_dict_parser" | "diameter_dict_scanner" | "diameter_dict_util" | "diameter_dist" | "diameter_etcp" | "diameter_etcp_sup" | "diameter_exprecs" | "diameter_gen" | "diameter_gen_acct_rfc6733" | "diameter_gen_base_accounting" | "diameter_gen_base_rfc3588" | "diameter_gen_base_rfc6733" | "diameter_gen_doic_rfc7683" | "diameter_gen_relay" | "diameter_info" | "diameter_lib" | "diameter_make" | "diameter_misc_sup" | "diameter_peer" | "diameter_peer_fsm" | "diameter_peer_fsm_sup" | "diameter_reg" | "diameter_sctp" | "diameter_sctp_sup" | "diameter_service" | "diameter_service_sup" | "diameter_session" | "diameter_stats" | "diameter_sup" | "diameter_sync" | "diameter_tcp" | "diameter_tcp_sup" | "diameter_traffic" | "diameter_transport" | "diameter_transport_sup" | "diameter_types" | "diameter_watchdog" | "diameter_watchdog_sup" | "dict" | "digraph" | "digraph_utils" | "disk_log" | "disk_log_1" | "disk_log_server" | "disk_log_sup" | "disksup" | "dist_ac" | "dist_util" | "docgen_edoc_xml_cb" | "docgen_otp_specs" | "docgen_xmerl_xml_cb" | "docgen_xml_to_chunk" | "dtls_connection" | "dtls_connection_sup" | "dtls_gen_connection" | "dtls_handshake" | "dtls_listener_sup" | "dtls_packet_demux" | "dtls_record" | "dtls_server_session_cache_sup" | "dtls_server_sup" | "dtls_socket" | "dtls_sup" | "dtls_v1" | "dyntrace" | "edlin" | "edlin_context" | "edlin_expand" | "edlin_key" | "edlin_type_suggestion" | "edoc" | "edoc_cli" | "edoc_data" | "edoc_doclet" | "edoc_doclet_chunks" | "edoc_extract" | "edoc_layout" | "edoc_layout_chunks" | "edoc_lib" | "edoc_macros" | "edoc_parser" | "edoc_refs" | "edoc_report" | "edoc_run" | "edoc_scanner" | "edoc_specs" | "edoc_tags" | "edoc_types" | "edoc_wiki" | "eldap" | "epp" | "epp_dodger" | "eprof" | "erl2html2" | "erl_abstract_code" | "erl_anno" | "erl_bif_types" | "erl_bifs" | "erl_bits" | "erl_boot_server" | "erl_comment_scan" | "erl_compile" | "erl_compile_server" | "erl_ddll" | "erl_distribution" | "erl_epmd" | "erl_error" | "erl_erts_errors" | "erl_eval" | "erl_expand_records" | "erl_features" | "erl_init" | "erl_internal" | "erl_kernel_errors" | "erl_lint" | "erl_parse" | "erl_posix_msg" | "erl_pp" | "erl_prettypr" | "erl_prim_loader" | "erl_recomment" | "erl_reply" | "erl_scan" | "erl_signal_handler" | "erl_stdlib_errors" | "erl_syntax" | "erl_syntax_lib" | "erl_tar" | "erl_tracer" | "erl_types" | "erlang" | "erlsrv" | "erpc" | "error_handler" | "error_logger" | "error_logger_file_h" | "error_logger_tty_h" | "erts_alloc_config" | "erts_code_purger" | "erts_debug" | "erts_dirty_process_signal_handler" | "erts_internal" | "erts_literal_area_collector" | "escript" | "et" | "et_collector" | "et_selector" | "et_viewer" | "et_wx_contents_viewer" | "et_wx_viewer" | "etop" | "etop_tr" | "etop_txt" | "ets" | "eunit" | "eunit_autoexport" | "eunit_data" | "eunit_lib" | "eunit_listener" | "eunit_proc" | "eunit_serial" | "eunit_server" | "eunit_striptests" | "eunit_surefire" | "eunit_test" | "eunit_tests" | "eunit_tty" | "eval_bits" | "file" | "file_io_server" | "file_server" | "file_sorter" | "filelib" | "filename" | "format_lib_supp" | "fprof" | "ftp" | "ftp_app" | "ftp_internal" | "ftp_progress" | "ftp_response" | "ftp_sup" | "gb_sets" | "gb_trees" | "gen" | "gen_event" | "gen_fsm" | "gen_sctp" | "gen_server" | "gen_statem" | "gen_tcp" | "gen_tcp_socket" | "gen_udp" | "gen_udp_socket" | "gl" | "global" | "global_group" | "global_search" | "glu" | "group" | "group_history" | "heart" | "http_chunk" | "http_request" | "http_response" | "http_transport" | "http_uri" | "http_util" | "httpc" | "httpc_cookie" | "httpc_handler" | "httpc_handler_sup" | "httpc_manager" | "httpc_profile_sup" | "httpc_request" | "httpc_response" | "httpc_sup" | "httpd" | "httpd_acceptor" | "httpd_acceptor_sup" | "httpd_cgi" | "httpd_conf" | "httpd_connection_sup" | "httpd_custom" | "httpd_custom_api" | "httpd_esi" | "httpd_example" | "httpd_file" | "httpd_instance_sup" | "httpd_log" | "httpd_logger" | "httpd_manager" | "httpd_misc_sup" | "httpd_request" | "httpd_request_handler" | "httpd_response" | "httpd_script_env" | "httpd_socket" | "httpd_sup" | "httpd_util" | "i" | "inet" | "inet6_sctp" | "inet6_tcp" | "inet6_tcp_dist" | "inet6_tls_dist" | "inet6_udp" | "inet_config" | "inet_db" | "inet_dns" | "inet_epmd_dist" | "inet_epmd_socket" | "inet_gethost_native" | "inet_hosts" | "inet_parse" | "inet_res" | "inet_sctp" | "inet_tcp" | "inet_tcp_dist" | "inet_tls_dist" | "inet_udp" | "inets" | "inets_app" | "inets_lib" | "inets_service" | "inets_sup" | "inets_trace" | "init" | "instrument" | "int" | "io" | "io_lib" | "io_lib_format" | "io_lib_fread" | "io_lib_pretty" | "json" | "kernel" | "kernel_config" | "kernel_refc" | "lcnt" | "leex" | "lists" | "local_tcp" | "local_udp" | "log_mf_h" | "logger" | "logger_backend" | "logger_config" | "logger_disk_log_h" | "logger_filters" | "logger_formatter" | "logger_h_common" | "logger_handler_watcher" | "logger_olp" | "logger_proxy" | "logger_server" | "logger_simple_h" | "logger_std_h" | "logger_sup" | "make" | "maps" | "math" | "megaco" | "megaco_ber_encoder" | "megaco_ber_media_gateway_control_v1" | "megaco_ber_media_gateway_control_v2" | "megaco_ber_media_gateway_control_v3" | "megaco_binary_encoder" | "megaco_binary_encoder_lib" | "megaco_binary_name_resolver_v1" | "megaco_binary_name_resolver_v2" | "megaco_binary_name_resolver_v3" | "megaco_binary_term_id" | "megaco_binary_term_id_gen" | "megaco_binary_transformer_v1" | "megaco_binary_transformer_v2" | "megaco_binary_transformer_v3" | "megaco_compact_text_encoder" | "megaco_compact_text_encoder_v1" | "megaco_compact_text_encoder_v2" | "megaco_compact_text_encoder_v3" | "megaco_config" | "megaco_config_misc" | "megaco_digit_map" | "megaco_edist_compress" | "megaco_encoder" | "megaco_erl_dist_encoder" | "megaco_erl_dist_encoder_mc" | "megaco_filter" | "megaco_flex_scanner" | "megaco_flex_scanner_handler" | "megaco_messenger" | "megaco_messenger_misc" | "megaco_misc_sup" | "megaco_monitor" | "megaco_per_encoder" | "megaco_per_media_gateway_control_v1" | "megaco_per_media_gateway_control_v2" | "megaco_per_media_gateway_control_v3" | "megaco_pretty_text_encoder" | "megaco_pretty_text_encoder_v1" | "megaco_pretty_text_encoder_v2" | "megaco_pretty_text_encoder_v3" | "megaco_sdp" | "megaco_stats" | "megaco_sup" | "megaco_tcp" | "megaco_tcp_accept" | "megaco_tcp_accept_sup" | "megaco_tcp_connection" | "megaco_tcp_connection_sup" | "megaco_tcp_sup" | "megaco_text_mini_decoder" | "megaco_text_mini_parser" | "megaco_text_parser_v1" | "megaco_text_parser_v2" | "megaco_text_parser_v3" | "megaco_text_scanner" | "megaco_timer" | "megaco_trans_sender" | "megaco_trans_sup" | "megaco_transport" | "megaco_udp" | "megaco_udp_server" | "megaco_udp_sup" | "megaco_user" | "megaco_user_default" | "memsup" | "merl" | "merl_transform" | "misc_supp" | "mnesia" | "mnesia_app" | "mnesia_backend_type" | "mnesia_backup" | "mnesia_bup" | "mnesia_checkpoint" | "mnesia_checkpoint_sup" | "mnesia_controller" | "mnesia_dumper" | "mnesia_event" | "mnesia_ext_sup" | "mnesia_frag" | "mnesia_frag_hash" | "mnesia_index" | "mnesia_kernel_sup" | "mnesia_late_loader" | "mnesia_lib" | "mnesia_loader" | "mnesia_locker" | "mnesia_log" | "mnesia_monitor" | "mnesia_recover" | "mnesia_registry" | "mnesia_rpc" | "mnesia_schema" | "mnesia_snmp_hook" | "mnesia_sp" | "mnesia_subscr" | "mnesia_sup" | "mnesia_text" | "mnesia_tm" | "mod_actions" | "mod_alias" | "mod_auth" | "mod_auth_dets" | "mod_auth_mnesia" | "mod_auth_plain" | "mod_auth_server" | "mod_cgi" | "mod_dir" | "mod_disk_log" | "mod_esi" | "mod_get" | "mod_head" | "mod_log" | "mod_range" | "mod_responsecontrol" | "mod_security" | "mod_security_server" | "mod_trace" | "ms_transform" | "msacc" | "net" | "net_adm" | "net_kernel" | "nteventlog" | "observer" | "observer_alloc_wx" | "observer_app_wx" | "observer_backend" | "observer_html_lib" | "observer_lib" | "observer_perf_wx" | "observer_port_wx" | "observer_pro_wx" | "observer_procinfo" | "observer_sock_wx" | "observer_sys_wx" | "observer_trace_wx" | "observer_traceoptions_wx" | "observer_tv_table" | "observer_tv_wx" | "observer_wx" | "orddict" | "ordsets" | "os" | "os_mon" | "os_mon_mib" | "os_mon_sysinfo" | "os_sup" | "otp_internal" | "peer" | "persistent_term" | "pg" | "pg2" | "pool" | "prettypr" | "prim_buffer" | "prim_eval" | "prim_file" | "prim_inet" | "prim_net" | "prim_socket" | "prim_tty" | "prim_zip" | "proc_lib" | "proplists" | "pubkey_cert" | "pubkey_cert_records" | "pubkey_crl" | "pubkey_ocsp" | "pubkey_os_cacerts" | "pubkey_pbe" | "pubkey_pem" | "pubkey_policy_tree" | "pubkey_ssh" | "public_key" | "qlc" | "qlc_pt" | "queue" | "ram_file" | "rand" | "random" | "raw_file_io" | "raw_file_io_compressed" | "raw_file_io_deflate" | "raw_file_io_delayed" | "raw_file_io_inflate" | "raw_file_io_list" | "rb" | "rb_format_supp" | "re" | "rec_env" | "release_handler" | "release_handler_1" | "reltool" | "reltool_app_win" | "reltool_fgraph" | "reltool_fgraph_win" | "reltool_mod_win" | "reltool_server" | "reltool_sys_win" | "reltool_target" | "reltool_utils" | "rpc" | "runtime_tools" | "runtime_tools_sup" | "sasl" | "sasl_report" | "sasl_report_file_h" | "sasl_report_tty_h" | "scheduler" | "seq_trace" | "sets" | "shell" | "shell_default" | "shell_docs" | "slave" | "snmp" | "snmp_app" | "snmp_app_sup" | "snmp_community_mib" | "snmp_conf" | "snmp_config" | "snmp_framework_mib" | "snmp_generic" | "snmp_generic_mnesia" | "snmp_index" | "snmp_log" | "snmp_mini_mib" | "snmp_misc" | "snmp_note_store" | "snmp_notification_mib" | "snmp_pdus" | "snmp_shadow_table" | "snmp_standard_mib" | "snmp_target_mib" | "snmp_user_based_sm_mib" | "snmp_usm" | "snmp_verbosity" | "snmp_view_based_acm_mib" | "snmpa" | "snmpa_acm" | "snmpa_agent" | "snmpa_agent_sup" | "snmpa_app" | "snmpa_authentication_service" | "snmpa_conf" | "snmpa_discovery_handler" | "snmpa_discovery_handler_default" | "snmpa_error" | "snmpa_error_io" | "snmpa_error_logger" | "snmpa_error_report" | "snmpa_get" | "snmpa_get_lib" | "snmpa_get_mechanism" | "snmpa_local_db" | "snmpa_mib" | "snmpa_mib_data" | "snmpa_mib_data_tttn" | "snmpa_mib_lib" | "snmpa_mib_storage" | "snmpa_mib_storage_dets" | "snmpa_mib_storage_ets" | "snmpa_mib_storage_mnesia" | "snmpa_misc_sup" | "snmpa_mpd" | "snmpa_net_if" | "snmpa_net_if_filter" | "snmpa_network_interface" | "snmpa_network_interface_filter" | "snmpa_notification_delivery_info_receiver" | "snmpa_notification_filter" | "snmpa_set" | "snmpa_set_lib" | "snmpa_set_mechanism" | "snmpa_supervisor" | "snmpa_svbl" | "snmpa_symbolic_store" | "snmpa_target_cache" | "snmpa_trap" | "snmpa_usm" | "snmpa_vacm" | "snmpc" | "snmpc_lib" | "snmpc_mib_gram" | "snmpc_mib_to_hrl" | "snmpc_misc" | "snmpc_tok" | "snmpm" | "snmpm_conf" | "snmpm_config" | "snmpm_misc_sup" | "snmpm_mpd" | "snmpm_net_if" | "snmpm_net_if_filter" | "snmpm_net_if_mt" | "snmpm_network_interface" | "snmpm_network_interface_filter" | "snmpm_server" | "snmpm_server_sup" | "snmpm_supervisor" | "snmpm_user" | "snmpm_user_default" | "snmpm_user_old" | "snmpm_usm" | "socket" | "socket_registry" | "sofs" | "ssh" | "ssh_acceptor" | "ssh_acceptor_sup" | "ssh_agent" | "ssh_app" | "ssh_auth" | "ssh_bits" | "ssh_channel" | "ssh_channel_sup" | "ssh_cli" | "ssh_client_channel" | "ssh_client_key_api" | "ssh_connection" | "ssh_connection_handler" | "ssh_daemon_channel" | "ssh_dbg" | "ssh_file" | "ssh_fsm_kexinit" | "ssh_fsm_userauth_client" | "ssh_fsm_userauth_server" | "ssh_info" | "ssh_io" | "ssh_lib" | "ssh_message" | "ssh_no_io" | "ssh_options" | "ssh_server_channel" | "ssh_server_key_api" | "ssh_sftp" | "ssh_sftpd" | "ssh_sftpd_file" | "ssh_sftpd_file_api" | "ssh_shell" | "ssh_subsystem_sup" | "ssh_system_sup" | "ssh_tcpip_forward_acceptor" | "ssh_tcpip_forward_acceptor_sup" | "ssh_tcpip_forward_client" | "ssh_tcpip_forward_srv" | "ssh_transport" | "ssh_xfer" | "ssl" | "ssl_admin_sup" | "ssl_alert" | "ssl_app" | "ssl_certificate" | "ssl_cipher" | "ssl_cipher_format" | "ssl_client_session_cache_db" | "ssl_config" | "ssl_connection_sup" | "ssl_crl" | "ssl_crl_cache" | "ssl_crl_cache_api" | "ssl_crl_hash_dir" | "ssl_dh_groups" | "ssl_dist_admin_sup" | "ssl_dist_connection_sup" | "ssl_dist_sup" | "ssl_gen_statem" | "ssl_handshake" | "ssl_listen_tracker_sup" | "ssl_logger" | "ssl_manager" | "ssl_pem_cache" | "ssl_pkix_db" | "ssl_record" | "ssl_server_session_cache" | "ssl_server_session_cache_db" | "ssl_server_session_cache_sup" | "ssl_session" | "ssl_session_cache_api" | "ssl_srp_primes" | "ssl_sup" | "ssl_trace" | "ssl_upgrade_server_session_cache_sup" | "standard_error" | "string" | "supervisor" | "supervisor_bridge" | "sys" | "sys_core_alias" | "sys_core_bsm" | "sys_core_fold" | "sys_core_fold_lists" | "sys_core_inline" | "sys_core_prepare" | "sys_messages" | "sys_pre_attributes" | "system_information" | "systools" | "systools_lib" | "systools_make" | "systools_rc" | "systools_relup" | "tags" | "test_server" | "test_server_ctrl" | "test_server_gl" | "test_server_io" | "test_server_node" | "test_server_sup" | "tftp" | "tftp_app" | "tftp_binary" | "tftp_engine" | "tftp_file" | "tftp_lib" | "tftp_logger" | "tftp_sup" | "timer" | "tls_bloom_filter" | "tls_client_connection_1_3" | "tls_client_ticket_store" | "tls_connection" | "tls_connection_sup" | "tls_dist_server_sup" | "tls_dist_sup" | "tls_dtls_connection" | "tls_dyn_connection_sup" | "tls_gen_connection" | "tls_gen_connection_1_3" | "tls_handshake" | "tls_handshake_1_3" | "tls_record" | "tls_record_1_3" | "tls_sender" | "tls_server_connection_1_3" | "tls_server_session_ticket" | "tls_server_session_ticket_sup" | "tls_server_sup" | "tls_socket" | "tls_sup" | "tls_v1" | "ttb" | "ttb_autostart" | "ttb_et" | "typer" | "typer_core" | "unicode" | "unicode_util" | "unix_telnet" | "uri_string" | "user_drv" | "user_sup" | "v3_core" | "v3_kernel" | "v3_kernel_pp" | "win32reg" | "wrap_log_reader" | "wx" | "wxAcceleratorEntry" | "wxAcceleratorTable" | "wxActivateEvent" | "wxArtProvider" | "wxAuiDockArt" | "wxAuiManager" | "wxAuiManagerEvent" | "wxAuiNotebook" | "wxAuiNotebookEvent" | "wxAuiPaneInfo" | "wxAuiSimpleTabArt" | "wxAuiTabArt" | "wxBitmap" | "wxBitmapButton" | "wxBitmapDataObject" | "wxBookCtrlBase" | "wxBookCtrlEvent" | "wxBoxSizer" | "wxBrush" | "wxBufferedDC" | "wxBufferedPaintDC" | "wxButton" | "wxCalendarCtrl" | "wxCalendarDateAttr" | "wxCalendarEvent" | "wxCaret" | "wxCheckBox" | "wxCheckListBox" | "wxChildFocusEvent" | "wxChoice" | "wxChoicebook" | "wxClientDC" | "wxClipboard" | "wxClipboardTextEvent" | "wxCloseEvent" | "wxColourData" | "wxColourDialog" | "wxColourPickerCtrl" | "wxColourPickerEvent" | "wxComboBox" | "wxCommandEvent" | "wxContextMenuEvent" | "wxControl" | "wxControlWithItems" | "wxCursor" | "wxDC" | "wxDCOverlay" | "wxDataObject" | "wxDateEvent" | "wxDatePickerCtrl" | "wxDialog" | "wxDirDialog" | "wxDirPickerCtrl" | "wxDisplay" | "wxDisplayChangedEvent" | "wxDropFilesEvent" | "wxEraseEvent" | "wxEvent" | "wxEvtHandler" | "wxFileDataObject" | "wxFileDialog" | "wxFileDirPickerEvent" | "wxFilePickerCtrl" | "wxFindReplaceData" | "wxFindReplaceDialog" | "wxFlexGridSizer" | "wxFocusEvent" | "wxFont" | "wxFontData" | "wxFontDialog" | "wxFontPickerCtrl" | "wxFontPickerEvent" | "wxFrame" | "wxGBSizerItem" | "wxGCDC" | "wxGLCanvas" | "wxGLContext" | "wxGauge" | "wxGenericDirCtrl" | "wxGraphicsBrush" | "wxGraphicsContext" | "wxGraphicsFont" | "wxGraphicsGradientStops" | "wxGraphicsMatrix" | "wxGraphicsObject" | "wxGraphicsPath" | "wxGraphicsPen" | "wxGraphicsRenderer" | "wxGrid" | "wxGridBagSizer" | "wxGridCellAttr" | "wxGridCellBoolEditor" | "wxGridCellBoolRenderer" | "wxGridCellChoiceEditor" | "wxGridCellEditor" | "wxGridCellFloatEditor" | "wxGridCellFloatRenderer" | "wxGridCellNumberEditor" | "wxGridCellNumberRenderer" | "wxGridCellRenderer" | "wxGridCellStringRenderer" | "wxGridCellTextEditor" | "wxGridEvent" | "wxGridSizer" | "wxHelpEvent" | "wxHtmlEasyPrinting" | "wxHtmlLinkEvent" | "wxHtmlWindow" | "wxIcon" | "wxIconBundle" | "wxIconizeEvent" | "wxIdleEvent" | "wxImage" | "wxImageList" | "wxInitDialogEvent" | "wxJoystickEvent" | "wxKeyEvent" | "wxLayoutAlgorithm" | "wxListBox" | "wxListCtrl" | "wxListEvent" | "wxListItem" | "wxListItemAttr" | "wxListView" | "wxListbook" | "wxLocale" | "wxLogNull" | "wxMDIChildFrame" | "wxMDIClientWindow" | "wxMDIParentFrame" | "wxMask" | "wxMaximizeEvent" | "wxMemoryDC" | "wxMenu" | "wxMenuBar" | "wxMenuEvent" | "wxMenuItem" | "wxMessageDialog" | "wxMiniFrame" | "wxMirrorDC" | "wxMouseCaptureChangedEvent" | "wxMouseCaptureLostEvent" | "wxMouseEvent" | "wxMoveEvent" | "wxMultiChoiceDialog" | "wxNavigationKeyEvent" | "wxNotebook" | "wxNotificationMessage" | "wxNotifyEvent" | "wxOverlay" | "wxPageSetupDialog" | "wxPageSetupDialogData" | "wxPaintDC" | "wxPaintEvent" | "wxPalette" | "wxPaletteChangedEvent" | "wxPanel" | "wxPasswordEntryDialog" | "wxPen" | "wxPickerBase" | "wxPopupTransientWindow" | "wxPopupWindow" | "wxPostScriptDC" | "wxPreviewCanvas" | "wxPreviewControlBar" | "wxPreviewFrame" | "wxPrintData" | "wxPrintDialog" | "wxPrintDialogData" | "wxPrintPreview" | "wxPrinter" | "wxPrintout" | "wxProgressDialog" | "wxQueryNewPaletteEvent" | "wxRadioBox" | "wxRadioButton" | "wxRegion" | "wxSashEvent" | "wxSashLayoutWindow" | "wxSashWindow" | "wxScreenDC" | "wxScrollBar" | "wxScrollEvent" | "wxScrollWinEvent" | "wxScrolledWindow" | "wxSetCursorEvent" | "wxShowEvent" | "wxSingleChoiceDialog" | "wxSizeEvent" | "wxSizer" | "wxSizerFlags" | "wxSizerItem" | "wxSlider" | "wxSpinButton" | "wxSpinCtrl" | "wxSpinEvent" | "wxSplashScreen" | "wxSplitterEvent" | "wxSplitterWindow" | "wxStaticBitmap" | "wxStaticBox" | "wxStaticBoxSizer" | "wxStaticLine" | "wxStaticText" | "wxStatusBar" | "wxStdDialogButtonSizer" | "wxStyledTextCtrl" | "wxStyledTextEvent" | "wxSysColourChangedEvent" | "wxSystemOptions" | "wxSystemSettings" | "wxTaskBarIcon" | "wxTaskBarIconEvent" | "wxTextAttr" | "wxTextCtrl" | "wxTextDataObject" | "wxTextEntryDialog" | "wxToggleButton" | "wxToolBar" | "wxToolTip" | "wxToolbook" | "wxTopLevelWindow" | "wxTreeCtrl" | "wxTreeEvent" | "wxTreebook" | "wxUpdateUIEvent" | "wxWebView" | "wxWebViewEvent" | "wxWindow" | "wxWindowCreateEvent" | "wxWindowDC" | "wxWindowDestroyEvent" | "wxXmlResource" | "wx_misc" | "wx_object" | "wxe_master" | "wxe_server" | "wxe_util" | "xmerl" | "xmerl_b64Bin" | "xmerl_b64Bin_scan" | "xmerl_eventp" | "xmerl_html" | "xmerl_lib" | "xmerl_otpsgml" | "xmerl_regexp" | "xmerl_sax_old_dom" | "xmerl_sax_parser" | "xmerl_sax_parser_latin1" | "xmerl_sax_parser_list" | "xmerl_sax_parser_utf16be" | "xmerl_sax_parser_utf16le" | "xmerl_sax_parser_utf8" | "xmerl_sax_simple_dom" | "xmerl_scan" | "xmerl_sgml" | "xmerl_simple" | "xmerl_text" | "xmerl_ucs" | "xmerl_uri" | "xmerl_validate" | "xmerl_xlate" | "xmerl_xml" | "xmerl_xpath" | "xmerl_xpath_lib" | "xmerl_xpath_parse" | "xmerl_xpath_pred" | "xmerl_xpath_scan" | "xmerl_xs" | "xmerl_xsd" | "xmerl_xsd_type" | "xref" | "xref_base" | "xref_compiler" | "xref_parser" | "xref_reader" | "xref_scanner" | "xref_utils" | "yecc" | "yeccparser" | "yeccscan" | "zip" | "zlib" => (), _ => return Ok(()), } Err(Error::GleamModuleWouldOverwriteStandardErlangModule { name: input.name.clone(), path: input.path.to_owned(), }) } #[derive(Debug, Default)] pub struct StaleTracker(HashSet); impl StaleTracker { fn add(&mut self, name: EcoString) { _ = self.0.insert(name); } fn includes_any(&self, names: &[(EcoString, SrcSpan)]) -> bool { names.iter().any(|n| self.0.contains(n.0.as_str())) } pub fn empty(&mut self) { let _ = self.0.drain(); // Clears the set but retains allocated memory } pub fn is_empty(&self) -> bool { self.0.is_empty() } } #[derive(Debug)] pub struct Inputs<'a> { /// The name of the package for which we're loading the inputs. package: EcoString, collection: HashMap, already_defined_modules: &'a mut im::HashMap, } impl<'a> Inputs<'a> { fn new( package: EcoString, already_defined_modules: &'a mut im::HashMap, ) -> Self { Self { package, collection: Default::default(), already_defined_modules, } } /// Insert a module into the hashmap. If there is already a module with the /// same name then an error is returned. fn insert(&mut self, input: Input) -> Result<()> { let name = input.name().clone(); let origin = DefinedModuleOrigin { package_name: self.package.clone(), path: input.source_path().to_path_buf(), }; if let Some(first) = self .already_defined_modules .insert(name.clone(), origin.clone()) { return Err(Error::DuplicateModule { module: name.clone(), first, second: origin, }); } if let Some((first, _)) = self .collection .insert(name.clone(), (origin.clone(), input)) { return Err(Error::DuplicateModule { module: name, first, second: origin, }); } Ok(()) } } /// A Gleam source file (`.gleam`) and the module name deduced from it pub struct GleamFile { pub path: Utf8PathBuf, pub module_name: EcoString, } impl GleamFile { pub fn new(dir: &Utf8Path, path: Utf8PathBuf) -> Self { Self { module_name: Self::module_name(&path, &dir), path, } } /// Iterates over Gleam source files (`.gleam`) in a certain directory. /// Symlinks are followed. /// If the there is a .gleam file with a path that would be an /// invalid module name it should not be loaded. For example, if it /// has a uppercase letter in it. pub fn iterate_files_in_directory<'b>( io: &'b impl FileSystemReader, dir: &'b Utf8Path, ) -> impl Iterator> + 'b { tracing::trace!("gleam_source_files {:?}", dir); files_with_extension(io, dir, "gleam").map(move |path| { if (Self::is_gleam_path(&path, &dir)) { Ok(Self::new(dir, path)) } else { Err(crate::Warning::InvalidSource { path }) } }) } pub fn cache_files(&self, artefact_directory: &Utf8Path) -> CacheFiles { CacheFiles::new(artefact_directory, &self.module_name) } fn module_name(path: &Utf8Path, dir: &Utf8Path) -> EcoString { // /path/to/project/_build/default/lib/the_package/src/my/module.gleam // my/module.gleam let mut module_path = path .strip_prefix(dir) .expect("Stripping package prefix from module path") .to_path_buf(); // my/module let _ = module_path.set_extension(""); // Stringify let name = module_path.to_string(); // normalise windows paths name.replace("\\", "/").into() } fn is_gleam_path(path: &Utf8Path, dir: &Utf8Path) -> bool { use regex::Regex; use std::cell::OnceCell; const RE: OnceCell = OnceCell::new(); RE.get_or_init(|| { Regex::new(&format!( "^({module}{slash})*{module}\\.gleam$", module = "[a-z][_a-z0-9]*", slash = "(/|\\\\)", )) .expect("is_gleam_path() RE regex") }) .is_match( path.strip_prefix(dir) .expect("is_gleam_path(): strip_prefix") .as_str(), ) } } /// The collection of cache files paths related to a module. /// These files are not guaranteed to exist. pub struct CacheFiles { pub cache_path: Utf8PathBuf, pub meta_path: Utf8PathBuf, } impl CacheFiles { pub fn new(artefact_directory: &Utf8Path, module_name: &EcoString) -> Self { let file_name = module_name.replace("/", "@"); let cache_path = artefact_directory .join(file_name.as_str()) .with_extension("cache"); let meta_path = artefact_directory .join(file_name.as_str()) .with_extension("cache_meta"); Self { cache_path, meta_path, } } pub fn delete(&self, io: &dyn io::FileSystemWriter) -> Result<()> { io.delete_file(&self.cache_path)?; io.delete_file(&self.meta_path) } /// Iterates over `.cache_meta` files in the given directory, /// and returns the respective module names. /// Symlinks are followed. pub fn modules_with_meta_files<'a>( io: &'a impl FileSystemReader, dir: &'a Utf8Path, ) -> impl Iterator + 'a { tracing::trace!("CacheFiles::modules_with_meta_files {:?}", dir); files_with_extension(io, dir, "cache_meta").map(move |path| Self::module_name(&dir, &path)) } fn module_name(dir: &Utf8Path, path: &Utf8Path) -> EcoString { // /path/to/artefact/dir/my@module.cache_meta // my@module.cache_meta let mut module_path = path .strip_prefix(dir) .expect("Stripping package prefix from module path") .to_path_buf(); // my@module let _ = module_path.set_extension(""); // my/module module_path.to_string().replace("@", "/").into() } } ================================================ FILE: compiler-core/src/build/project_compiler.rs ================================================ use crate::{ Error, Result, Warning, analyse::TargetSupport, build::{ Mode, Module, Origin, Package, Target, package_compiler::{self, PackageCompiler}, package_loader::StaleTracker, project_compiler, telemetry::Telemetry, }, codegen::{self, ErlangApp}, config::PackageConfig, dep_tree, error::{DefinedModuleOrigin, FileIoAction, FileKind, ShellCommandFailureReason}, io::{BeamCompiler, Command, CommandExecutor, FileSystemReader, FileSystemWriter, Stdio}, manifest::{ManifestPackage, ManifestPackageSource}, metadata, paths::{self, ProjectPaths}, type_::{self, ModuleFunction}, uid::UniqueIdGenerator, version::COMPILER_VERSION, warning::{self, WarningEmitter, WarningEmitterIO}, }; use ecow::EcoString; use hexpm::version::Version; use itertools::Itertools; use pubgrub::Range; use std::{ cmp, collections::{HashMap, HashSet}, fmt::Write, io::BufReader, rc::Rc, sync::Arc, time::Instant, }; use super::{ Codegen, Compile, ErlangAppCodegenConfiguration, Outcome, elixir_libraries::ElixirLibraries, package_compiler::{CachedWarnings, CheckModuleConflicts, Compiled}, }; use camino::{Utf8Path, Utf8PathBuf}; // On Windows we have to call rebar3 via a little wrapper script. // #[cfg(not(target_os = "windows"))] const REBAR_EXECUTABLE: &str = "rebar3"; #[cfg(target_os = "windows")] const REBAR_EXECUTABLE: &str = "rebar3.cmd"; #[cfg(not(target_os = "windows"))] const ELIXIR_EXECUTABLE: &str = "elixir"; #[cfg(target_os = "windows")] const ELIXIR_EXECUTABLE: &str = "elixir.bat"; #[derive(Debug)] pub struct Options { pub mode: Mode, pub target: Option, pub compile: Compile, pub codegen: Codegen, pub warnings_as_errors: bool, pub root_target_support: TargetSupport, pub no_print_progress: bool, } #[derive(Debug)] pub struct Built { pub root_package: Package, pub module_interfaces: im::HashMap, compiled_dependency_modules: Vec, } impl Built { pub fn get_main_function( &self, module: &EcoString, target: Target, ) -> Result { match self.module_interfaces.get(module) { Some(module_data) => module_data.get_main_function(target), None => Err(Error::ModuleDoesNotExist { module: module.clone(), suggestion: None, }), } } pub fn minimum_required_version(&self) -> Version { self.module_interfaces .values() .map(|interface| &interface.minimum_required_version) .reduce(|one_version, other_version| cmp::max(one_version, other_version)) .map(|minimum_required_version| minimum_required_version.clone()) .unwrap_or(Version::new(0, 1, 0)) } } #[derive(Debug)] pub struct ProjectCompiler { // The gleam.toml config for the root package of the project pub config: PackageConfig, pub packages: HashMap, importable_modules: im::HashMap, pub(crate) defined_modules: im::HashMap, stale_modules: StaleTracker, /// The set of modules that have had partial compilation done since the last /// successful compilation. incomplete_modules: HashSet, warnings: WarningEmitter, telemetry: &'static dyn Telemetry, options: Options, paths: ProjectPaths, ids: UniqueIdGenerator, pub io: IO, /// We may want to silence subprocess stdout if we are running in LSP mode. /// The language server talks over stdio so printing would break that. pub subprocess_stdio: Stdio, } // TODO: test that tests cannot be imported into src // TODO: test that dep cycles are not allowed between packages impl ProjectCompiler where IO: CommandExecutor + FileSystemWriter + FileSystemReader + BeamCompiler + Clone, { pub fn new( config: PackageConfig, options: Options, packages: Vec, telemetry: &'static dyn Telemetry, warning_emitter: Rc, paths: ProjectPaths, io: IO, ) -> Self { let packages = packages .into_iter() .map(|p| (p.name.to_string(), p)) .collect(); Self { importable_modules: im::HashMap::new(), defined_modules: im::HashMap::new(), stale_modules: StaleTracker::default(), incomplete_modules: HashSet::new(), ids: UniqueIdGenerator::new(), warnings: WarningEmitter::new(warning_emitter), subprocess_stdio: Stdio::Inherit, telemetry, packages, options, config, paths, io, } } pub fn mode(&self) -> Mode { self.options.mode } pub fn target(&self) -> Target { self.options.target.unwrap_or(self.config.target) } pub fn reset_state_for_new_compile_run(&mut self) { // We make sure the stale module tracker is empty before we start, to // avoid mistakenly thinking a module is stale due to outdated state // from a previous build. A ProjectCompiler instance is re-used by the // LSP engine so state could be reused if we don't reset it. self.stale_modules.empty(); /// We also clear the defined modules, otherwise the language server /// would start throwing errors for modules defined twice when compiling /// a second time! self.defined_modules.clear(); } fn retain_only_production_packages(&mut self) { let mut production = HashSet::new(); let mut queue: Vec<_> = self.config.dependencies.keys().collect(); while let Some(name) = queue.pop() { if production.insert(name.clone()) && let Some(pkg) = self.packages.get(name.as_str()) { queue.extend(pkg.requirements.iter()); } } self.packages .retain(|name, _| production.contains(name.as_str())); } /// Compiles all packages in the project and returns the compiled /// information from the root package pub fn compile(mut self) -> Result { self.reset_state_for_new_compile_run(); // In production mode, skip dev-only dependencies entirely so they // are never compiled. if self.mode() == Mode::Prod { self.retain_only_production_packages(); } // Each package may specify a Gleam version that it supports, so we // verify that this version is appropriate. self.check_gleam_version()?; // The JavaScript target requires a prelude module to be written. self.write_prelude()?; // Dependencies are compiled first. let compiled_dependency_modules = self.compile_dependencies()?; // We reset the warning count as we don't want to fail the build if a // dependency has warnings, only if the root package does. self.warnings.reset_count(); let root_package = self.compile_root_package().into_result()?; // TODO: test if self.options.warnings_as_errors && self.warnings.count() > 0 { return Err(Error::ForbiddenWarnings { count: self.warnings.count(), }); } Ok(Built { root_package, module_interfaces: self.importable_modules, compiled_dependency_modules, }) } pub fn compile_root_package(&mut self) -> Outcome { let config = self.config.clone(); self.compile_gleam_package(&config, true, self.paths.root().to_path_buf()) .map( |Compiled { modules, cached_module_names, }| Package { config, modules, cached_module_names, }, ) } /// Checks that version file found in the build directory matches the /// current version of gleam. If not, we will clear the build directory /// before continuing. This will ensure that upgrading gleam will not leave /// one with confusing or hard to debug states. pub fn check_gleam_version(&self) -> Result<(), Error> { let build_path = self .paths .build_directory_for_target(self.mode(), self.target()); let version_path = self.paths.build_gleam_version(self.mode(), self.target()); if self.io.is_file(&version_path) { let version = self.io.read(&version_path)?; if version == COMPILER_VERSION { return Ok(()); } } // Either file is missing our the versions do not match. Time to rebuild tracing::info!("removing_build_state_from_different_gleam_version"); self.io.delete_directory(&build_path)?; // Recreate build directory with new updated version file self.io.mkdir(&build_path)?; self.io .write(&version_path, COMPILER_VERSION) .map_err(|e| Error::FileIo { action: FileIoAction::WriteTo, kind: FileKind::File, path: version_path, err: Some(e.to_string()), }) } pub fn compile_dependencies(&mut self) -> Result, Error> { assert!( self.stale_modules.is_empty(), "The project compiler stale tracker was not emptied from the previous compilation" ); let sequence = order_packages(&self.packages)?; let mut modules = vec![]; for name in sequence { let compiled = self.load_cache_or_compile_package(&name)?; modules.extend(compiled); } Ok(modules) } fn write_prelude(&self) -> Result<()> { // Only the JavaScript target has a prelude to write. if !self.target().is_javascript() { return Ok(()); } let build = self .paths .build_directory_for_target(self.mode(), self.target()); // Write the JavaScript prelude let path = build.join("prelude.mjs"); if !self.io.is_file(&path) { self.io.write(&path, crate::javascript::PRELUDE)?; } // Write the TypeScript prelude, if asked for if self.config.javascript.typescript_declarations { let path = build.join("prelude.d.mts"); if !self.io.is_file(&path) { self.io.write(&path, crate::javascript::PRELUDE_TS_DEF)?; } } Ok(()) } fn load_cache_or_compile_package(&mut self, name: &str) -> Result, Error> { // TODO: We could remove this clone if we split out the compilation of // packages into their own classes and then only mutate self after we no // longer need to have the package borrowed from self.packages. let package = self.packages.get(name).expect("Missing package").clone(); let result = match usable_build_tools(&package)?.as_slice() { &[BuildTool::Gleam] => self.compile_gleam_dep_package(&package), &[BuildTool::Rebar3] => self.compile_rebar3_dep_package(&package).map(|_| vec![]), &[BuildTool::Mix] => self.compile_mix_dep_package(&package).map(|_| vec![]), &[BuildTool::Mix, BuildTool::Rebar3] => self .compile_mix_dep_package(&package) .or_else(|_| self.compile_rebar3_dep_package(&package)) .map(|_| vec![]), _ => { return Err(Error::UnsupportedBuildTool { package: package.name.to_string(), build_tools: package.build_tools.clone(), }); } }; // TODO: test. This one is not covered by the integration tests. if result.is_err() { tracing::debug!(package=%name, "removing_failed_build"); let path = self.paths.build_directory_for_package( self.mode(), self.target(), package.application_name(), ); self.io.delete_directory(&path)?; } result } // TODO: extract and unit test fn compile_rebar3_dep_package(&mut self, package: &ManifestPackage) -> Result<(), Error> { let application_name = package.application_name(); let package_name = &package.name; let mode = self.mode(); let target = self.target(); let package_build = self .paths .build_directory_for_package(mode, target, application_name); // TODO: test if self.io.is_directory(&package_build) { tracing::debug!(%package_name, "using_precompiled_rebar3_package"); return Ok(()); } // TODO: test if !self.options.codegen.should_codegen(false) { tracing::debug!(%package_name, "skipping_rebar3_build_as_codegen_disabled"); return Ok(()); } // TODO: test if target != Target::Erlang { tracing::debug!(%package_name, "skipping_rebar3_build_for_non_erlang_target"); return Ok(()); } // Print that work is being done self.telemetry.compiling_package(package_name); let package = self.paths.build_packages_package(package_name); let build_packages = self.paths.build_directory_for_target(mode, target); let ebins = self.paths.build_packages_ebins_glob(mode, target); let rebar3_path = |path: &Utf8Path| format!("../{}", path); tracing::debug!("copying_package_to_build"); self.io.mkdir(&package_build)?; self.io.copy_dir(&package, &package_build)?; let env = vec![ ("ERL_LIBS".to_string(), "../*/ebin".to_string()), ( "REBAR_BARE_COMPILER_OUTPUT_DIR".to_string(), package_build.to_string(), ), ("REBAR_PROFILE".to_string(), "prod".to_string()), ("REBAR_SKIP_PROJECT_PLUGINS".to_string(), "true".to_string()), ("TERM".to_string(), "dumb".to_string()), ]; let args = vec![ "bare".into(), "compile".into(), "--paths".into(), "../*/ebin".into(), ]; let status = self.io.exec(Command { program: REBAR_EXECUTABLE.into(), args, env, cwd: Some(package_build), stdio: self.subprocess_stdio, })?; if status == 0 { Ok(()) } else { Err(Error::ShellCommand { program: "rebar3".into(), reason: ShellCommandFailureReason::Unknown, }) } } fn compile_mix_dep_package(&mut self, package: &ManifestPackage) -> Result<(), Error> { let application_name = package.application_name(); let package_name = &package.name; let mode = self.mode(); let target = self.target(); let mix_target = "prod"; let dest = self .paths .build_directory_for_package(mode, target, application_name); // TODO: test if self.io.is_directory(&dest) { tracing::debug!(%package_name, "using_precompiled_mix_package"); return Ok(()); } // TODO: test if !self.options.codegen.should_codegen(false) { tracing::debug!(%package_name, "skipping_mix_build_as_codegen_disabled"); return Ok(()); } // TODO: test if target != Target::Erlang { tracing::debug!(%package_name, "skipping_mix_build_for_non_erlang_target"); return Ok(()); } // Print that work is being done self.telemetry.compiling_package(package_name); let build_dir = self.paths.build_directory_for_target(mode, target); let project_dir = self.paths.build_packages_package(package_name); let mix_build_dir = project_dir.join("_build").join(mix_target); let mix_build_lib_dir = mix_build_dir.join("lib"); let up = paths::unnest(&project_dir); let mix_path = |path: &Utf8Path| up.join(path).to_string(); let ebins = self.paths.build_packages_ebins_glob(mode, target); // Elixir core libs must be loaded ElixirLibraries::make_available(&self.io, &build_dir, self.subprocess_stdio)?; // Prevent Mix.Compilers.ApplicationTracer warnings // mix would make this if it didn't exist, but we make it anyway as // we need to link the compiled dependencies into there self.io.mkdir(&mix_build_lib_dir)?; let deps = &package.requirements; for dep in deps { // TODO: unit test let dep_source = build_dir.join(dep.as_str()); let dep_dest = mix_build_lib_dir.join(dep.as_str()); if self.io.is_directory(&dep_source) && !self.io.is_directory(&dep_dest) { tracing::debug!("linking_{}_to_build", dep); self.io.symlink_dir(&dep_source, &dep_dest)?; } } let env = vec![ ("MIX_BUILD_PATH".to_string(), mix_path(&mix_build_dir)), ("MIX_ENV".to_string(), mix_target.to_string()), ("MIX_QUIET".to_string(), "1".to_string()), ("TERM".to_string(), "dumb".to_string()), ]; let args = vec![ "-pa".to_string(), mix_path(&ebins), "-S".to_string(), "mix".to_string(), "compile".to_string(), "--no-deps-check".to_string(), "--no-load-deps".to_string(), "--no-protocol-consolidation".to_string(), ]; let status = self.io.exec(Command { program: ELIXIR_EXECUTABLE.into(), args, env, cwd: Some(project_dir), stdio: self.subprocess_stdio, })?; if status == 0 { // TODO: unit test let source = mix_build_dir.join("lib").join(application_name.as_str()); if self.io.is_directory(&source) && !self.io.is_directory(&dest) { tracing::debug!("linking_{}_to_build", application_name); self.io.symlink_dir(&source, &dest)?; } Ok(()) } else { Err(Error::ShellCommand { program: "mix".into(), reason: ShellCommandFailureReason::Unknown, }) } } fn compile_gleam_dep_package( &mut self, package: &ManifestPackage, ) -> Result, Error> { // TODO: Test let package_root = match &package.source { // If the path is relative it is relative to the root of the // project, not to the current working directory. The language server // could have the working directory and the project root in different // places. ManifestPackageSource::Local { path } if path.is_relative() => { self.io.canonicalise(&self.paths.root().join(path))? } // If the path is absolute we can use it as-is. ManifestPackageSource::Local { path } => path.clone(), // Hex and Git packages are downloaded into the project's build // directory. ManifestPackageSource::Git { .. } | ManifestPackageSource::Hex { .. } => { self.paths.build_packages_package(&package.name) } }; let config_path = package_root.join("gleam.toml"); let config = PackageConfig::read(config_path, &self.io)?; self.compile_gleam_package(&config, false, package_root) .into_result() .map(|compiled| compiled.modules) } fn compile_gleam_package( &mut self, config: &PackageConfig, is_root: bool, root_path: Utf8PathBuf, ) -> Outcome { let out_path = self.paths .build_directory_for_package(self.mode(), self.target(), &config.name); let lib_path = self .paths .build_directory_for_target(self.mode(), self.target()); let mode = if is_root { self.mode() } else { Mode::Prod }; let target = match self.target() { Target::Erlang => { let package_name_overrides = self .packages .values() .flat_map(|p| { let overriden = p.otp_app.as_ref()?; Some((p.name.clone(), overriden.clone())) }) .collect(); super::TargetCodegenConfiguration::Erlang { app_file: Some(ErlangAppCodegenConfiguration { include_dev_deps: is_root && self.mode().includes_dev_dependencies(), package_name_overrides, }), } } Target::JavaScript => super::TargetCodegenConfiguration::JavaScript { emit_typescript_definitions: self.config.javascript.typescript_declarations, // This path is relative to each package output directory prelude_location: Utf8PathBuf::from("../prelude.mjs"), }, }; let mut compiler = PackageCompiler::new( config, mode, &root_path, &out_path, &lib_path, &target, self.ids.clone(), self.io.clone(), ); compiler.write_metadata = true; compiler.write_entrypoint = is_root; compiler.perform_codegen = self.options.codegen.should_codegen(is_root); compiler.compile_beam_bytecode = self.options.codegen.should_codegen(is_root); compiler.compile_modules = !(self.options.compile == Compile::DepsOnly && is_root); compiler.subprocess_stdio = self.subprocess_stdio; compiler.target_support = if is_root { // When compiling the root package it is context specific as to whether we need to // enforce that all functions have an implementation for the current target. // Typically we do, but if we are using `gleam run -m $module` to run a module that // belongs to a dependency we don't need to enforce this as we don't want to fail // compilation. It's impossible for a dependecy module to call functions from the root // package, so it's OK if they could not be compiled. self.options.root_target_support } else { // When compiling dependencies we don't enforce that all functions have an // implementation for the current target. It is OK if they have APIs that are // unaccessible so long as they are not used by the root package. TargetSupport::NotEnforced }; if is_root { compiler.cached_warnings = CachedWarnings::Use; // We only check for conflicting Gleam files if this is the root // package, since Hex packages are bundled with the Gleam source files // and compiled Erlang files next to each other. compiler.check_module_conflicts = CheckModuleConflicts::Check; } else { compiler.cached_warnings = CachedWarnings::Ignore; compiler.check_module_conflicts = CheckModuleConflicts::DoNotCheck; }; // Compile project to Erlang or JavaScript source code compiler.compile( &mut self.warnings, &mut self.importable_modules, &mut self.defined_modules, &mut self.stale_modules, &mut self.incomplete_modules, self.telemetry, ) } } impl ProjectCompiler { pub fn get_importable_modules(&self) -> &im::HashMap { &self.importable_modules } } fn order_packages(packages: &HashMap) -> Result, Error> { dep_tree::toposort_deps( packages .values() // Making sure that the package order is deterministic, to prevent different // compilations of the same project compiling in different orders. This could impact // any bugged outcomes, though not any where the compiler is working correctly, so it's // mostly to aid debugging. .sorted_by(|a, b| a.name.cmp(&b.name)) .map(|package| { ( package.name.as_str().into(), package .requirements .iter() .map(|r| EcoString::from(r.as_ref())) .collect(), ) }) .collect(), ) .map_err(convert_deps_tree_error) } fn convert_deps_tree_error(e: dep_tree::Error) -> Error { match e { dep_tree::Error::Cycle(packages) => Error::PackageCycle { packages }, } } #[derive(Debug, PartialEq, Clone, Copy)] pub(crate) enum BuildTool { Gleam, Rebar3, Mix, } /// Determine the build tool we should use to build this package pub(crate) fn usable_build_tools(package: &ManifestPackage) -> Result, Error> { let mut rebar3_present = false; let mut mix_present = false; for tool in &package.build_tools { match tool.as_str() { "gleam" => return Ok(vec![BuildTool::Gleam]), "rebar" => rebar3_present = true, "rebar3" => rebar3_present = true, "mix" => mix_present = true, _ => (), } } if mix_present && rebar3_present { return Ok(vec![BuildTool::Mix, BuildTool::Rebar3]); } else if mix_present { return Ok(vec![BuildTool::Mix]); } else if rebar3_present { return Ok(vec![BuildTool::Rebar3]); } Err(Error::UnsupportedBuildTool { package: package.name.to_string(), build_tools: package.build_tools.clone(), }) } ================================================ FILE: compiler-core/src/build/telemetry.rs ================================================ use std::{ fmt::Debug, time::{Duration, Instant}, }; use crate::{ Warning, manifest::{PackageChanges, Resolved}, }; pub trait Telemetry: Debug { fn waiting_for_build_directory_lock(&self); fn running(&self, name: &str); fn resolving_package_versions(&self); fn resolved_package_versions(&self, changes: &PackageChanges); fn downloading_package(&self, name: &str); fn packages_downloaded(&self, start: Instant, count: usize); fn compiled_package(&self, duration: Duration); fn compiling_package(&self, name: &str); fn checked_package(&self, duration: Duration); fn checking_package(&self, name: &str); } #[derive(Debug, Clone, Copy)] pub struct NullTelemetry; impl Telemetry for NullTelemetry { fn waiting_for_build_directory_lock(&self) {} fn running(&self, name: &str) {} fn resolving_package_versions(&self) {} fn downloading_package(&self, _name: &str) {} fn compiled_package(&self, _duration: Duration) {} fn compiling_package(&self, _name: &str) {} fn checked_package(&self, _duration: Duration) {} fn checking_package(&self, _name: &str) {} fn packages_downloaded(&self, _start: Instant, _count: usize) {} fn resolved_package_versions(&self, _changes: &PackageChanges) {} } ================================================ FILE: compiler-core/src/build/tests.rs ================================================ use crate::{Error, manifest::ManifestPackage}; use super::project_compiler::{BuildTool, usable_build_tools}; #[test] fn usable_build_tool_unknown() { assert_eq!( usable_build_tools(&ManifestPackage::default().with_build_tools(&["unknown"])), Err(Error::UnsupportedBuildTool { package: "".into(), build_tools: vec!["unknown".into()], }) ) } #[test] fn usable_build_tool_none() { assert_eq!( usable_build_tools(&ManifestPackage::default()), Err(Error::UnsupportedBuildTool { package: "".into(), build_tools: vec![], }) ) } #[test] fn usable_build_tool_only_mix() { assert_eq!( usable_build_tools(&ManifestPackage::default().with_build_tools(&["mix"])), Ok(vec![BuildTool::Mix]) ) } #[test] fn usable_build_tool_only_rebar3() { assert_eq!( usable_build_tools(&ManifestPackage::default().with_build_tools(&["rebar3"])), Ok(vec![BuildTool::Rebar3]) ) } #[test] fn usable_build_tool_only_gleam() { assert_eq!( usable_build_tools(&ManifestPackage::default().with_build_tools(&["gleam"])), Ok(vec![BuildTool::Gleam]) ) } #[test] fn usable_build_tool_mix_then_rebar3() { assert_eq!( usable_build_tools(&ManifestPackage::default().with_build_tools(&["mix", "rebar3"])), Ok(vec![BuildTool::Mix, BuildTool::Rebar3]) ) } ================================================ FILE: compiler-core/src/build.rs ================================================ #![allow(warnings)] mod elixir_libraries; mod module_loader; mod native_file_copier; pub mod package_compiler; mod package_loader; mod project_compiler; mod telemetry; #[cfg(test)] mod tests; pub use self::package_compiler::PackageCompiler; pub use self::package_loader::StaleTracker; pub use self::project_compiler::{Built, Options, ProjectCompiler}; pub use self::telemetry::{NullTelemetry, Telemetry}; use crate::ast::{ self, CallArg, CustomType, DefinitionLocation, TypeAst, TypedArg, TypedClauseGuard, TypedConstant, TypedCustomType, TypedDefinitions, TypedExpr, TypedFunction, TypedImport, TypedModuleConstant, TypedPattern, TypedRecordConstructor, TypedStatement, TypedTypeAlias, }; use crate::type_::{Type, TypedCallArg}; use crate::{ ast::{Definition, SrcSpan, TypedModule}, config::{self, PackageConfig}, erlang, error::{Error, FileIoAction, FileKind}, io::OutputFile, parse::extra::{Comment, ModuleExtra}, type_, }; use camino::Utf8PathBuf; use ecow::EcoString; use itertools::Itertools; use serde::{Deserialize, Serialize}; use std::fmt::{Debug, Display}; use std::sync::Arc; use std::time::SystemTime; use std::{collections::HashMap, ffi::OsString, fs::DirEntry, iter::Peekable, process}; use strum::{Display, EnumIter, EnumString, EnumVariantNames, VariantNames}; use vec1::Vec1; #[derive( Debug, Serialize, Deserialize, EnumString, EnumVariantNames, EnumIter, Clone, Copy, PartialEq, Eq, )] #[strum(serialize_all = "lowercase")] pub enum Target { #[strum(serialize = "erlang", serialize = "erl")] #[serde(rename = "erlang", alias = "erl")] Erlang, #[strum(serialize = "javascript", serialize = "js")] #[serde(rename = "javascript", alias = "js")] JavaScript, } impl Target { pub fn as_presentable_str(&self) -> &str { match self { Target::Erlang => "Erlang", Target::JavaScript => "JavaScript", } } pub fn variant_strings() -> Vec { Self::VARIANTS.iter().map(|s| (*s).into()).collect() } /// Returns `true` if the target is [`JavaScript`]. /// /// [`JavaScript`]: Target::JavaScript #[must_use] pub fn is_javascript(&self) -> bool { matches!(self, Self::JavaScript) } /// Returns `true` if the target is [`Erlang`]. /// /// [`Erlang`]: Target::Erlang #[must_use] pub fn is_erlang(&self) -> bool { matches!(self, Self::Erlang) } } #[derive(Debug, PartialEq, Eq, Clone, Copy)] /// This is used to skip compiling the root package when running the main /// function coming from a dependency. This way a dependency can be run even /// there's compilation errors in the root package. /// pub enum Compile { /// The default compiler behaviour, compile all packages. /// All, /// Only compile the dependency packages, skipping the root package. /// DepsOnly, } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum Codegen { All, DepsOnly, None, } impl Codegen { fn should_codegen(&self, is_root_package: bool) -> bool { match self { Codegen::All => true, Codegen::DepsOnly => !is_root_package, Codegen::None => false, } } } #[derive( Debug, Serialize, Deserialize, EnumString, EnumVariantNames, Clone, Copy, PartialEq, Eq, )] pub enum Runtime { #[strum(serialize = "nodejs", serialize = "node")] #[serde(rename = "nodejs", alias = "node")] NodeJs, #[strum(serialize = "deno")] #[serde(rename = "deno")] Deno, #[strum(serialize = "bun")] #[serde(rename = "bun")] Bun, } impl Runtime { pub fn as_presentable_str(&self) -> &str { match self { Runtime::NodeJs => "NodeJS", Runtime::Deno => "Deno", Runtime::Bun => "Bun", } } } impl Default for Runtime { fn default() -> Self { Self::NodeJs } } #[derive(Debug)] pub enum TargetCodegenConfiguration { JavaScript { emit_typescript_definitions: bool, prelude_location: Utf8PathBuf, }, Erlang { app_file: Option, }, } impl TargetCodegenConfiguration { pub fn target(&self) -> Target { match self { Self::JavaScript { .. } => Target::JavaScript, Self::Erlang { .. } => Target::Erlang, } } } #[derive(Debug)] pub struct ErlangAppCodegenConfiguration { pub include_dev_deps: bool, /// Some packages have a different OTP application name than their package /// name, as rebar3 (and Mix?) support this. The .app file must use the OTP /// name, not the package name. pub package_name_overrides: HashMap, } #[derive( Debug, Serialize, Deserialize, Display, EnumString, EnumVariantNames, EnumIter, Clone, Copy, PartialEq, )] #[strum(serialize_all = "lowercase")] pub enum Mode { Dev, Prod, Lsp, } impl Mode { /// Returns `true` if the mode includes development code. /// pub fn includes_dev_code(&self) -> bool { match self { Self::Dev | Self::Lsp => true, Self::Prod => false, } } pub fn includes_dev_dependencies(&self) -> bool { match self { Mode::Dev | Mode::Lsp => true, Mode::Prod => false, } } } #[test] fn mode_includes_dev_code() { assert!(Mode::Dev.includes_dev_code()); assert!(Mode::Lsp.includes_dev_code()); assert!(!Mode::Prod.includes_dev_code()); } #[derive(Debug)] pub struct Package { pub config: PackageConfig, pub modules: Vec, pub cached_module_names: Vec, } impl Package { pub fn attach_doc_and_module_comments(&mut self) { for mut module in &mut self.modules { module.attach_doc_and_module_comments(); } } pub fn into_modules_hashmap(self) -> HashMap { self.modules .into_iter() .map(|m| (m.name.to_string(), m)) .collect() } } #[derive(Debug)] pub struct Module { pub name: EcoString, pub code: EcoString, pub mtime: SystemTime, pub input_path: Utf8PathBuf, pub origin: Origin, pub ast: TypedModule, pub extra: ModuleExtra, pub dependencies: Vec<(EcoString, SrcSpan)>, } #[derive(Debug)] /// A data structure used to store all definitions in a single homogeneous /// vector and sort them by their position in order to attach to each the right /// documentation. /// enum DocumentableDefinition<'a> { Constant(&'a mut TypedModuleConstant), TypeAlias(&'a mut TypedTypeAlias), CustomType(&'a mut TypedCustomType), Function(&'a mut TypedFunction), Import(&'a mut TypedImport), } impl<'a> DocumentableDefinition<'a> { pub fn location(&self) -> SrcSpan { match self { Self::Constant(module_constant) => module_constant.location, Self::TypeAlias(type_alias) => type_alias.location, Self::CustomType(custom_type) => custom_type.location, Self::Function(function) => function.location, Self::Import(import) => import.location, } } pub fn put_doc(&mut self, new_documentation: (u32, EcoString)) { match self { Self::Import(_import) => (), Self::Function(function) => { let _ = function.documentation.replace(new_documentation); } Self::TypeAlias(type_alias) => { let _ = type_alias.documentation.replace(new_documentation); } Self::CustomType(custom_type) => { let _ = custom_type.documentation.replace(new_documentation); } Self::Constant(constant) => { let _ = constant.documentation.replace(new_documentation); } } } } impl Module { pub fn erlang_name(&self) -> EcoString { module_erlang_name(&self.name) } pub fn compiled_erlang_path(&self) -> Utf8PathBuf { let mut path = Utf8PathBuf::from(&module_erlang_name(&self.name)); assert!(path.set_extension("erl"), "Couldn't set file extension"); path } pub fn find_node(&self, byte_index: u32) -> Option> { self.ast.find_node(byte_index) } pub fn attach_doc_and_module_comments(&mut self) { // Module Comments self.ast.documentation = self .extra .module_comments .iter() .map(|span| Comment::from((span, self.code.as_str())).content.into()) .collect(); self.ast.type_info.documentation = self.ast.documentation.clone(); // Order definitions to avoid misassociating doc comments after the // order has changed during compilation. let TypedDefinitions { imports, constants, custom_types, type_aliases, functions, } = &mut self.ast.definitions; let mut definitions = ((imports.iter_mut()).map(DocumentableDefinition::Import)) .chain((constants.iter_mut()).map(DocumentableDefinition::Constant)) .chain((custom_types.iter_mut()).map(DocumentableDefinition::CustomType)) .chain((type_aliases.iter_mut()).map(DocumentableDefinition::TypeAlias)) .chain((functions.iter_mut()).map(DocumentableDefinition::Function)) .sorted_by_key(|definition| definition.location()) .collect_vec(); // Doc Comments let mut doc_comments = self.extra.doc_comments.iter().peekable(); for definition in &mut definitions { let (docs_start, docs): (u32, Vec<&str>) = doc_comments_before( &mut doc_comments, &self.extra, definition.location().start, &self.code, ); if !docs.is_empty() { let doc = docs.join("\n").into(); definition.put_doc((docs_start, doc)); } if let DocumentableDefinition::CustomType(CustomType { constructors, .. }) = definition { for constructor in constructors { let (docs_start, docs): (u32, Vec<&str>) = doc_comments_before( &mut doc_comments, &self.extra, constructor.location.start, &self.code, ); if !docs.is_empty() { let doc = docs.join("\n").into(); constructor.put_doc((docs_start, doc)); } for argument in constructor.arguments.iter_mut() { let (docs_start, docs): (u32, Vec<&str>) = doc_comments_before( &mut doc_comments, &self.extra, argument.location.start, &self.code, ); if !docs.is_empty() { let doc = docs.join("\n").into(); argument.put_doc((docs_start, doc)); } } } } } } } pub fn module_erlang_name(gleam_name: &EcoString) -> EcoString { gleam_name.replace("/", "@") } #[derive(Debug, Clone, PartialEq)] pub struct UnqualifiedImport<'a> { pub name: &'a EcoString, pub module: &'a EcoString, pub is_type: bool, pub location: &'a SrcSpan, } /// The position of a located expression. Used to determine extra context, /// such as whether to provide label completions if the expression is in /// argument position. #[derive(Debug, Clone, Copy, PartialEq)] pub enum ExpressionPosition<'a> { Expression, ArgumentOrLabel { called_function: &'a TypedExpr, function_arguments: &'a [TypedCallArg], }, } #[derive(Debug, Clone, PartialEq)] pub enum Located<'a> { Pattern(&'a TypedPattern), PatternSpread { spread_location: SrcSpan, pattern: &'a TypedPattern, }, // A prefix alias or a suffix variable defined in a string prefix pattern: // "prefix" as alias <> suffix StringPrefixPatternVariable { location: SrcSpan, name: &'a EcoString, }, Statement(&'a TypedStatement), Expression { expression: &'a TypedExpr, position: ExpressionPosition<'a>, }, VariantConstructorDefinition(&'a TypedRecordConstructor), FunctionBody(&'a TypedFunction), Arg(&'a TypedArg), Annotation { ast: &'a TypeAst, type_: std::sync::Arc, }, UnqualifiedImport(UnqualifiedImport<'a>), Label(SrcSpan, std::sync::Arc), ModuleName { location: SrcSpan, module_name: EcoString, module_alias: EcoString, layer: ast::Layer, }, Constant(&'a TypedConstant), ClauseGuard(&'a TypedClauseGuard), // A module's top level definitions ModuleFunction(&'a TypedFunction), ModuleConstant(&'a TypedModuleConstant), ModuleImport(&'a TypedImport), ModuleCustomType(&'a TypedCustomType), ModuleTypeAlias(&'a TypedTypeAlias), } impl<'a> Located<'a> { // Looks up the type constructor for the given type and then create the location. fn type_location( &self, importable_modules: &'a im::HashMap, type_: std::sync::Arc, ) -> Option { type_constructor_from_modules(importable_modules, type_).map(|t| DefinitionLocation { module: Some(t.module.clone()), span: t.origin, }) } pub fn definition_location( &self, importable_modules: &'a im::HashMap, ) -> Option { match self { Self::PatternSpread { .. } => None, Self::Pattern(pattern) => pattern.definition_location(), Self::StringPrefixPatternVariable { location, .. } => Some(DefinitionLocation { module: None, span: *location, }), Self::Statement(statement) => statement.definition_location(), Self::FunctionBody(statement) => None, Self::Expression { expression, .. } => expression.definition_location(), Self::ModuleImport(import) => Some(DefinitionLocation { module: Some(import.module.clone()), span: SrcSpan { start: 0, end: 0 }, }), Self::ModuleConstant(constant) => Some(DefinitionLocation { module: None, span: constant.location, }), Self::ModuleCustomType(custom_type) => Some(DefinitionLocation { module: None, span: custom_type.location, }), Self::ModuleFunction(function) => Some(DefinitionLocation { module: None, span: function.location, }), Self::ModuleTypeAlias(type_alias) => Some(DefinitionLocation { module: None, span: type_alias.location, }), Self::VariantConstructorDefinition(record) => Some(DefinitionLocation { module: None, span: record.location, }), Self::UnqualifiedImport(UnqualifiedImport { module, name, is_type, .. }) => importable_modules.get(*module).and_then(|m| { if *is_type { m.types.get(*name).map(|t| DefinitionLocation { module: Some((*module).clone()), span: t.origin, }) } else { m.values.get(*name).map(|v| DefinitionLocation { module: Some((*module).clone()), span: v.definition_location().span, }) } }), Self::Arg(_) => None, Self::Annotation { type_, .. } => self.type_location(importable_modules, type_.clone()), Self::Label(_, _) => None, Self::ModuleName { module_name, .. } => Some(DefinitionLocation { module: Some(module_name.clone()), span: SrcSpan::new(0, 0), }), Self::Constant(constant) => constant.definition_location(), Self::ClauseGuard(guard) => guard.definition_location(), } } pub(crate) fn type_(&self) -> Option> { match self { Located::Pattern(pattern) => Some(pattern.type_()), Located::StringPrefixPatternVariable { .. } => Some(type_::string()), Located::Statement(statement) => Some(statement.type_()), Located::Expression { expression, .. } => Some(expression.type_()), Located::Arg(arg) => Some(arg.type_.clone()), Located::Label(_, type_) | Located::Annotation { type_, .. } => Some(type_.clone()), Located::Constant(constant) => Some(constant.type_()), Located::ClauseGuard(guard) => Some(guard.type_()), Located::PatternSpread { .. } | Located::ModuleConstant(_) | Located::ModuleCustomType(_) | Located::ModuleFunction(_) | Located::ModuleImport(_) | Located::ModuleTypeAlias(_) | Located::VariantConstructorDefinition(_) | Located::FunctionBody(_) | Located::UnqualifiedImport(_) | Located::ModuleName { .. } => None, } } pub fn type_definition_locations( &self, importable_modules: &im::HashMap, ) -> Option> { let type_ = self.type_()?; Some(type_to_definition_locations(type_, importable_modules)) } } /// Returns the locations of all the types that one could reach starting from /// the given type (included). This includes all types that are part of a /// tuple/function type or that are used as args in a named type. /// /// For example, given this type `Dict(Int, #(Wibble, Wobble))` all the /// "reachable" include: `Dict`, `Int`, `Wibble` and `Wobble`. /// /// This is what powers the "go to type definition" capability of the language /// server. /// fn type_to_definition_locations<'a>( type_: Arc, importable_modules: &'a im::HashMap, ) -> Vec { match type_.as_ref() { // For named types we start with the location of the named type itself // followed by the locations of all types they reference in their args. // // For example with a `Dict(Wibble, Wobble)` we'd start with the // definition of `Dict`, followed by the definition of `Wibble` and // `Wobble`. // Type::Named { module, name, arguments, .. } => { let Some(module) = importable_modules.get(module) else { return vec![]; }; let Some(type_) = module.get_public_type(&name) else { return vec![]; }; let mut locations = vec![DefinitionLocation { module: Some(module.name.clone()), span: type_.origin, }]; for argument in arguments { locations.extend(type_to_definition_locations( argument.clone(), importable_modules, )); } locations } // For fn types we just get the locations of their arguments and return // type. // Type::Fn { arguments, return_ } => arguments .iter() .flat_map(|argument| type_to_definition_locations(argument.clone(), importable_modules)) .chain(type_to_definition_locations( return_.clone(), importable_modules, )) .collect_vec(), // In case of a var we just follow it and get the locations of the type // it points to. // Type::Var { type_ } => match type_.borrow().clone() { type_::TypeVar::Unbound { .. } | type_::TypeVar::Generic { .. } => vec![], type_::TypeVar::Link { type_ } => { type_to_definition_locations(type_, importable_modules) } }, // In case of tuples we get the locations of the wrapped types. // Type::Tuple { elements } => elements .iter() .flat_map(|element| type_to_definition_locations(element.clone(), importable_modules)) .collect_vec(), } } // Looks up the type constructor for the given type pub fn type_constructor_from_modules( importable_modules: &im::HashMap, type_: std::sync::Arc, ) -> Option<&type_::TypeConstructor> { let type_ = type_::collapse_links(type_); match type_.as_ref() { Type::Named { name, module, .. } => importable_modules .get(module) .and_then(|i| i.types.get(name)), _ => None, } } #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum Origin { Src, Test, Dev, } impl Origin { /// Returns `true` if the origin is [`Src`]. /// /// [`Src`]: Origin::Src #[must_use] pub fn is_src(&self) -> bool { matches!(self, Self::Src) } /// Returns `true` if the origin is [`Test`]. /// /// [`Test`]: Origin::Test #[must_use] pub fn is_test(&self) -> bool { matches!(self, Self::Test) } /// Returns `true` if the origin is [`Dev`]. /// /// [`Dev`]: Origin::Dev #[must_use] pub fn is_dev(&self) -> bool { matches!(self, Self::Dev) } /// Name of the folder containing the origin. #[must_use] pub fn folder_name(&self) -> &str { match self { Origin::Src => "src", Origin::Test => "test", Origin::Dev => "dev", } } } fn doc_comments_before<'a>( doc_comments_spans: &mut Peekable>, extra: &ModuleExtra, byte: u32, src: &'a str, ) -> (u32, Vec<&'a str>) { let mut comments = vec![]; let mut comment_start = u32::MAX; while let Some(SrcSpan { start, end }) = doc_comments_spans.peek() { if start > &byte { break; } if extra.has_comment_between(*end, byte) { // We ignore doc comments that come before a regular comment. _ = doc_comments_spans.next(); continue; } let comment = doc_comments_spans .next() .expect("Comment before accessing next span"); if comment.start < comment_start { comment_start = comment.start; } comments.push(Comment::from((comment, src)).content) } (comment_start, comments) } #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct SourceFingerprint(u64); impl SourceFingerprint { pub fn new(source: &str) -> Self { SourceFingerprint(xxhash_rust::xxh3::xxh3_64(source.as_bytes())) } pub fn to_numerical_string(&self) -> String { self.0.to_string() } } /// Like a `Result`, but the operation can partially succeed or fail. /// #[derive(Debug)] pub enum Outcome { /// The operation was totally succesful. Ok(T), /// The operation was partially successful but there were problems. PartialFailure(T, E), /// The operation was entirely unsuccessful. TotalFailure(E), } impl Outcome where E: Debug, { #[cfg(test)] /// Panic if there's any errors pub fn unwrap(self) -> T { match self { Outcome::Ok(t) => t, Outcome::PartialFailure(_, errors) => panic!("Error: {:?}", errors), Outcome::TotalFailure(error) => panic!("Error: {:?}", error), } } /// Panic if there's any errors pub fn expect(self, e: &'static str) -> T { match self { Outcome::Ok(t) => t, Outcome::PartialFailure(_, errors) => panic!("{e}: {:?}", errors), Outcome::TotalFailure(error) => panic!("{e}: {:?}", error), } } pub fn into_result(self) -> Result { match self { Outcome::Ok(t) => Ok(t), Outcome::PartialFailure(_, e) | Outcome::TotalFailure(e) => Err(e), } } pub fn map(self, f: impl FnOnce(T) -> T2) -> Outcome { match self { Outcome::Ok(t) => Outcome::Ok(f(t)), Outcome::PartialFailure(t, e) => Outcome::PartialFailure(f(t), e), Outcome::TotalFailure(e) => Outcome::TotalFailure(e), } } } ================================================ FILE: compiler-core/src/call_graph/into_dependency_order_tests.rs ================================================ use super::*; use crate::{ ast::{Arg, Function, ModuleConstant, Publicity}, type_::{ Deprecation, expression::{Implementations, Purity}, }, }; use ecow::EcoString; type FuncInput = (&'static str, &'static [&'static str], &'static str); type ConstInput = (&'static str, &'static str); fn parse_and_order( functions: &[FuncInput], constants: &[ConstInput], ) -> Result>, Error> { let functions = functions .iter() .map(|(name, arguments, src)| Function { name: Some((SrcSpan::default(), EcoString::from(*name))), arguments: arguments .iter() .map(|name| Arg { names: crate::ast::ArgNames::Named { name: EcoString::from(*name), location: Default::default(), }, location: Default::default(), annotation: None, type_: (), }) .collect_vec(), body: crate::parse::parse_statement_sequence(src) .expect("syntax error") .to_vec(), location: Default::default(), body_start: None, return_annotation: None, publicity: Publicity::Public, deprecation: Deprecation::NotDeprecated, end_position: src.len() as u32, return_type: (), documentation: None, external_erlang: None, external_javascript: None, implementations: Implementations { gleam: true, uses_erlang_externals: true, uses_javascript_externals: false, can_run_on_erlang: true, can_run_on_javascript: true, }, purity: Purity::Impure, }) .collect_vec(); let constants = constants .iter() .map(|(name, value)| { let const_value = crate::parse::parse_const_value(value).expect("syntax error"); ModuleConstant { documentation: None, location: Default::default(), publicity: Publicity::Public, name: EcoString::from(*name), name_location: SrcSpan::default(), annotation: None, value: Box::from(const_value), implementations: Implementations { gleam: true, uses_erlang_externals: true, uses_javascript_externals: false, can_run_on_erlang: true, can_run_on_javascript: true, }, type_: (), deprecation: Deprecation::NotDeprecated, } }) .collect_vec(); Ok(into_dependency_order(functions, constants)? .into_iter() .map(|level| { level .into_iter() .map(|function| match function { CallGraphNode::Function(f) => f.name.map(|(_, name)| name).unwrap(), CallGraphNode::ModuleConstant(c) => c.name, }) .collect_vec() }) .collect()) } #[test] fn empty() { let functions = []; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), Vec::>::new() ); } #[test] fn no_deps() { let functions = [ ("a", [].as_slice(), "1"), ("b", [].as_slice(), r#""ok""#), ("c", [].as_slice(), r#"1"#), ("d", [].as_slice(), r#"1.0"#), ("e", [].as_slice(), r#"todo"#), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["a"], vec!["b"], vec!["c"], vec!["d"], vec!["e"]] ); } #[test] fn one_dep() { let functions = [ ("a", [].as_slice(), "1"), ("b", [].as_slice(), r#"c"#), ("c", [].as_slice(), r#"0"#), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["a"], vec!["c"], vec!["b"]] ); } #[test] fn unknown_vars() { let functions = [ ("a", [].as_slice(), "1"), ("b", [].as_slice(), r#"Nil"#), ("c", [].as_slice(), r#"Ok"#), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["a"], vec!["b"], vec!["c"]] ); } #[test] fn calling_function() { let functions = [ ("a", [].as_slice(), r#"b()"#), ("b", [].as_slice(), r#"c(1, 2)"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["c"], vec!["b"], vec!["a"]] ); } #[test] fn ref_in_call_argument() { let functions = [ ("a", [].as_slice(), r#"c(1, b())"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["c"], vec!["a"]] ); } #[test] fn sequence() { let functions = [ ("a", [].as_slice(), r#"c({ 1 2 b })"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["c"], vec!["a"]] ); } #[test] fn tuple() { let functions = [ ("a", [].as_slice(), r#"#(b, c, 1)"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["c"], vec!["a"]] ); } #[test] fn pipeline() { let functions = [ ("a", [].as_slice(), r#"1 |> b |> c"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["c"], vec!["a"]] ); } #[test] fn list() { let functions = [ ("a", [].as_slice(), r#"[b, b, c, 1]"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["c"], vec!["a"]] ); } #[test] fn list_spread() { let functions = [ ("a", [].as_slice(), r#"[b, b, ..c]"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["c"], vec!["a"]] ); } #[test] fn record_access() { let functions = [ ("a", [].as_slice(), "1"), ("b", [].as_slice(), r#"b().wibble"#), ("c", [].as_slice(), r#"123"#), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["a"], vec!["b"], vec!["c"]] ); } #[test] fn binop() { let functions = [ ("a", [].as_slice(), r#"1 + a() + 2 / b() * 4"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["a"], vec!["c"]] ); } #[test] fn bit_arrays() { let functions = [ ("a", [].as_slice(), r#"<>"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["c"], vec!["a"]] ); } #[test] fn tuple_index() { let functions = [ ("a", [].as_slice(), r#"b.0"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["a"], vec!["c"]] ); } #[test] fn record_update() { let functions = [ ("a", [].as_slice(), r#"Wibble(..b, wobble: c())"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["c"], vec!["a"]] ); } #[test] fn negate() { let functions = [ ("a", [].as_slice(), r#"!c()"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["c"], vec!["a"]] ); } #[test] fn use_() { let functions = [ ("a", [].as_slice(), r#"use x <- c"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["c"], vec!["a"]] ); } #[test] fn use_shadowing() { let functions = [ ("a", [].as_slice(), r#"123"#), ("b", [].as_slice(), r#"{ use c <- a c }"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["a"], vec!["b"], vec!["c"]] ); } #[test] fn fn_argument_shadowing() { let functions = &[ ("a", [].as_slice(), r#"fn(b) { c b }"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["c"], vec!["a"]] ); } #[test] fn fn_argument_shadowing_then_not() { let functions = [ ("a", [].as_slice(), r#"{ fn(b) { c b } b }"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["c"], vec!["a"]] ); } #[test] fn let_var() { let functions = [ ("a", [].as_slice(), r#"{ let c = b c }"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["a"], vec!["c"]] ); } #[test] fn pattern_int() { let functions = [("a", [].as_slice(), r#"{ let 1 = x }"#)]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["a"]] ); } #[test] fn pattern_float() { let functions = [("a", [].as_slice(), r#"{ let 1.0 = x }"#)]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["a"]] ); } #[test] fn pattern_string() { let functions = [("a", [].as_slice(), r#"{ let "1.0" = x }"#)]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["a"]] ); } #[test] fn pattern_underscore() { let functions = [("a", [].as_slice(), r#"{ let _ = x }"#)]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["a"]] ); } #[test] fn pattern_concat() { let functions = [ ("a", [].as_slice(), r#"{ let "a" <> c = b c }"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["a"], vec!["c"]] ); } #[test] fn pattern_tuple() { let functions = [ ("a", [].as_slice(), r#"{ let #(a, c) = b a c }"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["a"], vec!["c"]] ); } #[test] fn pattern_list() { let functions = [ ("a", [].as_slice(), r#"{ let [a, c] = b a c }"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["a"], vec!["c"]] ); } #[test] fn pattern_list_spread() { let functions = [ ("a", [].as_slice(), r#"{ let [a, ..c] = b a c }"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["a"], vec!["c"]] ); } #[test] fn pattern_bit_array_segment_size_var_usage() { let functions = [ ( "a", [].as_slice(), r#"{ let <> = c y }"#, ), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["c"], vec!["a"]] ); } #[test] fn pattern_assign() { let functions = [ ("a", [].as_slice(), r#"{ let 1 as b = c b }"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["c"], vec!["a"]] ); } #[test] fn pattern_constructor() { let functions = [ ("a", [].as_slice(), r#"{ let Ok(b) = c b }"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["c"], vec!["a"]] ); } #[test] fn scope_reset() { let functions = [ ("a", [].as_slice(), r#"{ let x = { let b = 1 b } b }"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["a"], vec!["c"]] ); } #[test] fn case_subject() { let functions = [ ("a", [].as_slice(), r#"case b { _ -> 1 }"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["a"], vec!["c"]] ); } #[test] fn case_subjects() { let functions = [ ("a", [].as_slice(), r#"case b, c { _, _ -> 1 }"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["c"], vec!["a"]] ); } #[test] fn case_pattern_shadow() { let functions = [ ("a", [].as_slice(), r#"case 1 { b -> b }"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["a"], vec!["b"], vec!["c"]] ); } #[test] fn case_use_in_clause() { let functions = [ ("a", [].as_slice(), r#"case 1 { _ -> b }"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["a"], vec!["c"]] ); } #[test] fn case_clause_doesnt_shadow_later_clauses() { let functions = [ ("a", [].as_slice(), r#"case 1 { b -> 1 _ -> b }"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["a"], vec!["c"]] ); } #[test] fn case_clause_doesnt_shadow_after() { let functions = [ ("a", [].as_slice(), r#"{ case 1 { b -> 1 } b }"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["a"], vec!["c"]] ); } #[test] fn guard() { let functions = [ ("a", [].as_slice(), r#"case 1 { _ if b -> 1 }"#), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["a"], vec!["c"]] ); } #[test] fn big_guard() { let functions = [ ( "a", [].as_slice(), r#"case 1 { _ if 1 == 2 || x != #(Ok(b), 123) -> 1 }"#, ), ("b", [].as_slice(), r#"123"#), ("c", [].as_slice(), "1"), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b"], vec!["a"], vec!["c"]] ); } #[test] fn duplicate_external_function_name() { let functions = [("c", [].as_slice(), "1"), ("c", [].as_slice(), "1")]; _ = parse_and_order(functions.as_slice(), [].as_slice()).unwrap_err(); } #[test] fn duplicate_function_name() { let functions = [ ("b", [].as_slice(), r#"123456"#), ("b", [].as_slice(), r#"123456"#), ]; _ = parse_and_order(functions.as_slice(), [].as_slice()).unwrap_err(); } #[test] fn more_complex_cycle() { let functions = [ ("a1", [].as_slice(), r#"{ a2 }"#), ("a2", [].as_slice(), r#"{ a3 a1 }"#), ("a3", [].as_slice(), r#"{ a1 }"#), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["a2", "a3", "a1"]] ); } #[test] fn function_argument_shadowing() { let functions = [ ("a", ["b"].as_slice(), r#"b"#), ("b", [].as_slice(), r#"Nil"#), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["a"], vec!["b"]] ); } #[test] fn constants_and_functions() { let functions = [ ("a", ["b"].as_slice(), r#"b"#), ("b", [].as_slice(), r#"c"#), ]; let constants = [("d", r#"c"#), ("c", r#"a"#)]; assert_eq!( parse_and_order(functions.as_slice(), constants.as_slice()).unwrap(), vec![vec!["a"], vec!["c"], vec!["b"], vec!["d"]] ); } // https://github.com/gleam-lang/gleam/issues/2275 #[test] fn bug_2275() { let functions = [ ("one", [].as_slice(), r#"two one"#), ("two", [].as_slice(), r#"two"#), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["two"], vec!["one"]] ); } #[test] fn let_assert_message() { let functions = [ ("a", [].as_slice(), r#"{ let assert True = False as b() }"#), ("b", [].as_slice(), r#"a()"#), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b", "a"]] ); } #[test] fn assert_subject() { let functions = [ ("a", [].as_slice(), r#"{ assert b() }"#), ("b", [].as_slice(), r#"a()"#), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b", "a"]] ); } #[test] fn assert_message() { let functions = [ ("a", [].as_slice(), r#"{ assert False as b() }"#), ("b", [].as_slice(), r#"a()"#), ]; assert_eq!( parse_and_order(functions.as_slice(), [].as_slice()).unwrap(), vec![vec!["b", "a"]] ); } ================================================ FILE: compiler-core/src/call_graph.rs ================================================ //! Graphs that represent the relationships between entities in a Gleam module, //! such as module functions or constants. #[cfg(test)] mod into_dependency_order_tests; use crate::{ Result, ast::{ AssignName, AssignmentKind, BitArrayOption, BitArraySize, ClauseGuard, Constant, Pattern, SrcSpan, Statement, UntypedClauseGuard, UntypedExpr, UntypedFunction, UntypedModuleConstant, UntypedPattern, UntypedStatement, }, type_::Error, }; use itertools::Itertools; use petgraph::stable_graph::NodeIndex; use petgraph::{Directed, stable_graph::StableGraph}; #[derive(Debug, Default)] struct CallGraphBuilder<'a> { names: im::HashMap<&'a str, Option<(NodeIndex, SrcSpan)>>, graph: StableGraph<(), (), Directed>, current_function: NodeIndex, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum CallGraphNode { Function(UntypedFunction), ModuleConstant(UntypedModuleConstant), } impl<'a> CallGraphBuilder<'a> { fn into_graph(self) -> StableGraph<(), (), Directed> { self.graph } /// Add each function to the graph, storing the index of the node under the /// name of the function. fn register_module_function_existence( &mut self, function: &'a UntypedFunction, ) -> Result<(), Error> { let (_, name) = function .name .as_ref() .expect("A module's function must be named"); let location = function.location; let index = self.graph.add_node(()); let previous = self.names.insert(name, Some((index, location))); if let Some(Some((_, previous_location))) = previous { return Err(Error::DuplicateName { location_a: location, location_b: previous_location, name: name.clone(), }); } Ok(()) } /// Add each constant to the graph, storing the index of the node under the /// name of the constant. fn register_module_const_existence( &mut self, constant: &'a UntypedModuleConstant, ) -> Result<(), Error> { let name = &constant.name; let location = constant.location; let index = self.graph.add_node(()); let previous = self.names.insert(name, Some((index, location))); if let Some(Some((_, previous_location))) = previous { return Err(Error::DuplicateName { location_a: location, location_b: previous_location, name: name.clone(), }); } Ok(()) } fn register_references_constant(&mut self, constant: &'a UntypedModuleConstant) { self.current_function = self .names .get(constant.name.as_str()) .expect("Constant must already have been registered as existing") .expect("Constant must not be shadowed at module level") .0; self.constant(&constant.value); } fn register_references(&mut self, function: &'a UntypedFunction) { let names = self.names.clone(); self.current_function = self .names .get( function .name .as_ref() .map(|(_, name)| name.as_str()) .expect("A module's function must be named"), ) .expect("Function must already have been registered as existing") .expect("Function must not be shadowed at module level") .0; for name in function .arguments .iter() .flat_map(|a| a.get_variable_name()) { self.define(name); } self.statements(&function.body); self.names = names; } fn referenced(&mut self, name: &str) { // If we don't know what the target is then it's either a programmer // error to be detected later, or it's not a module function and as such // is not a value we are tracking. let Some(target) = self.names.get(name) else { return; }; // If the target is known but registered as None then it's local value // that shadows a module function. let Some((target, _)) = target else { return }; _ = self.graph.add_edge(self.current_function, *target, ()); } fn statements(&mut self, statements: &'a [UntypedStatement]) { let names = self.names.clone(); for statement in statements { self.statement(statement); } self.names = names; } fn statement(&mut self, statement: &'a UntypedStatement) { match statement { Statement::Expression(expression) => { self.expression(expression); } Statement::Assignment(assignment) => { self.expression(&assignment.value); self.pattern(&assignment.pattern); match &assignment.kind { AssignmentKind::Assert { message: Some(message), .. } => self.expression(message), AssignmentKind::Let | AssignmentKind::Generated | AssignmentKind::Assert { message: None, .. } => {} } } Statement::Use(use_) => { self.expression(&use_.call); for assignment in &use_.assignments { self.pattern(&assignment.pattern); } } Statement::Assert(assert) => { self.expression(&assert.value); if let Some(message) = &assert.message { self.expression(message) } } }; } fn expression(&mut self, expression: &'a UntypedExpr) { match expression { UntypedExpr::Int { .. } | UntypedExpr::Float { .. } | UntypedExpr::String { .. } => (), UntypedExpr::Todo { message, .. } => { if let Some(msg_expr) = message { self.expression(msg_expr) } } UntypedExpr::Panic { message, .. } => { if let Some(msg_expr) = message { self.expression(msg_expr) } } UntypedExpr::Echo { expression, location: _, keyword_end: _, message, } => { if let Some(expression) = expression { self.expression(expression); } if let Some(message) = message { self.expression(message); } } // Aha! A variable is being referenced. UntypedExpr::Var { name, .. } => { self.referenced(name); } UntypedExpr::Call { fun, arguments, .. } => { self.expression(fun); for argument in arguments { self.expression(&argument.value); } } UntypedExpr::PipeLine { expressions } => { for expression in expressions { self.expression(expression); } } UntypedExpr::Tuple { elements, .. } => { for expression in elements { self.expression(expression); } } UntypedExpr::Block { statements, .. } => { let names = self.names.clone(); self.statements(statements); self.names = names; } UntypedExpr::BinOp { left, right, .. } => { self.expression(left); self.expression(right); } UntypedExpr::List { elements, tail, .. } => { for element in elements { self.expression(element); } if let Some(tail) = tail { self.expression(tail); } } UntypedExpr::NegateInt { value: expression, .. } | UntypedExpr::NegateBool { value: expression, .. } | UntypedExpr::TupleIndex { tuple: expression, .. } | UntypedExpr::FieldAccess { container: expression, .. } => { self.expression(expression); } UntypedExpr::BitArray { segments, .. } => { for segment in segments { self.expression(&segment.value); for option in &segment.options { if let BitArrayOption::Size { value, .. } = option { self.expression(value); } } } } UntypedExpr::RecordUpdate { record, arguments, .. } => { self.expression(&record.base); for argument in arguments { self.expression(&argument.value); } } UntypedExpr::Fn { arguments, body, .. } => { let names = self.names.clone(); for argument in arguments { if let Some(name) = argument.names.get_variable_name() { self.define(name) } } self.statements(body); self.names = names; } UntypedExpr::Case { subjects, clauses, .. } => { for subject in subjects { self.expression(subject); } for clause in clauses.as_deref().unwrap_or_default() { let names = self.names.clone(); for pattern in &clause.pattern { self.pattern(pattern); } if let Some(guard) = &clause.guard { self.guard(guard); } self.expression(&clause.then); self.names = names; } } } } fn pattern(&mut self, pattern: &'a UntypedPattern) { match pattern { Pattern::Discard { .. } | Pattern::Int { .. } | Pattern::Float { .. } | Pattern::String { .. } | Pattern::StringPrefix { right_side_assignment: AssignName::Discard(_), .. } | Pattern::Invalid { .. } => (), Pattern::StringPrefix { right_side_assignment: AssignName::Variable(name), .. } | Pattern::Variable { name, .. } => { self.define(name); } Pattern::Tuple { elements: patterns, .. } => { for pattern in patterns { self.pattern(pattern); } } Pattern::List { elements, tail, .. } => { for element in elements { self.pattern(element); } if let Some(tail) = tail { self.pattern(&tail.pattern); } } Pattern::BitArraySize(size) => { self.bit_array_size(size); } Pattern::Assign { name, pattern, .. } => { self.define(name); self.pattern(pattern); } Pattern::Constructor { arguments, .. } => { for argument in arguments { self.pattern(&argument.value); } } Pattern::BitArray { segments, .. } => { for segment in segments { for option in &segment.options { self.bit_array_option(option, |s, p| s.pattern(p)); } self.pattern(&segment.value); } } } } fn bit_array_size(&mut self, size: &'a BitArraySize<()>) { match size { BitArraySize::Int { .. } => {} BitArraySize::Variable { name, .. } => self.referenced(name), BitArraySize::BinaryOperator { left, right, .. } => { self.bit_array_size(left); self.bit_array_size(right); } BitArraySize::Block { inner, .. } => self.bit_array_size(inner), } } fn define(&mut self, name: &'a str) { _ = self.names.insert(name, None); } fn bit_array_option( &mut self, option: &'a BitArrayOption, process: impl Fn(&mut Self, &'a T), ) { match option { BitArrayOption::Big { .. } | BitArrayOption::Bytes { .. } | BitArrayOption::Bits { .. } | BitArrayOption::Float { .. } | BitArrayOption::Int { .. } | BitArrayOption::Little { .. } | BitArrayOption::Native { .. } | BitArrayOption::Signed { .. } | BitArrayOption::Unit { .. } | BitArrayOption::Unsigned { .. } | BitArrayOption::Utf16 { .. } | BitArrayOption::Utf16Codepoint { .. } | BitArrayOption::Utf32 { .. } | BitArrayOption::Utf32Codepoint { .. } | BitArrayOption::Utf8 { .. } | BitArrayOption::Utf8Codepoint { .. } => (), BitArrayOption::Size { value: pattern, .. } => { process(self, pattern); } } } fn guard(&mut self, guard: &'a UntypedClauseGuard) { match guard { ClauseGuard::BinaryOperator { left, right, .. } => { self.guard(left); self.guard(right); } ClauseGuard::Block { value, .. } => self.guard(value), ClauseGuard::Not { expression, .. } => self.guard(expression), ClauseGuard::Var { name, .. } => self.referenced(name), ClauseGuard::TupleIndex { tuple, .. } => self.guard(tuple), ClauseGuard::FieldAccess { container, .. } => self.guard(container), ClauseGuard::ModuleSelect { module_name, .. } => self.referenced(module_name), ClauseGuard::Constant(constant) => self.constant(constant), } } fn constant(&mut self, constant: &'a Constant<(), ()>) { match constant { Constant::Int { .. } | Constant::Float { .. } | Constant::String { .. } | Constant::Invalid { .. } | Constant::Var { module: Some(_), .. } => (), Constant::List { elements, .. } | Constant::Tuple { elements, .. } => { for element in elements { self.constant(element); } } Constant::Record { arguments, .. } => { for argument in arguments { self.constant(&argument.value); } } Constant::RecordUpdate { record, arguments, .. } => { self.constant(&record.base); for argument in arguments { self.constant(&argument.value); } } Constant::Var { module: None, name, .. } => self.referenced(name), Constant::BitArray { segments, .. } => { for segment in segments { for option in &segment.options { self.bit_array_option(option, |s, c| s.constant(c)); } self.constant(&segment.value); } } Constant::StringConcatenation { left, right, .. } => { self.constant(left); self.constant(right); } } } } /// Determine the order in which functions and constants should be compiled and if any /// mutually recursive functions need to be compiled together. /// pub fn into_dependency_order( functions: Vec, constants: Vec, ) -> Result>, Error> { let mut grapher = CallGraphBuilder::default(); for function in &functions { grapher.register_module_function_existence(function)?; } for constant in &constants { grapher.register_module_const_existence(constant)?; } // Build the call graph between the module functions. for function in &functions { grapher.register_references(function); } for constant in &constants { grapher.register_references_constant(constant); } // Consume the grapher to get the graph let graph = grapher.into_graph(); // Determine the order in which the functions should be compiled by looking // at which other functions they depend on. let indices = crate::graph::into_dependency_order(graph); // We got node indices back, so we need to map them back to the functions // they represent. // We wrap them each with `Some` so we can use `.take()`. let mut definitions = functions .into_iter() .map(CallGraphNode::Function) .chain(constants.into_iter().map(CallGraphNode::ModuleConstant)) .map(Some) .collect_vec(); let ordered = indices .into_iter() .map(|level| { level .into_iter() .map(|index| { definitions .get_mut(index.index()) .expect("Index out of bounds") .take() .expect("Function already taken") }) .collect_vec() }) .collect_vec(); Ok(ordered) } ================================================ FILE: compiler-core/src/codegen.rs ================================================ use crate::{ Result, build::{ ErlangAppCodegenConfiguration, Module, module_erlang_name, package_compiler::StdlibPackage, }, config::PackageConfig, erlang, io::FileSystemWriter, javascript::{self, ModuleConfig}, line_numbers::LineNumbers, }; use ecow::EcoString; use erlang::escape_atom_string; use itertools::Itertools; use std::fmt::Debug; use camino::Utf8Path; /// A code generator that creates a .erl Erlang module and record header files /// for each Gleam module in the package. #[derive(Debug)] pub struct Erlang<'a> { build_directory: &'a Utf8Path, include_directory: &'a Utf8Path, } impl<'a> Erlang<'a> { pub fn new(build_directory: &'a Utf8Path, include_directory: &'a Utf8Path) -> Self { Self { build_directory, include_directory, } } pub fn render( &self, writer: Writer, modules: &[Module], root: &Utf8Path, ) -> Result<()> { for module in modules { let erl_name = module.erlang_name(); self.erlang_module(&writer, module, &erl_name, root)?; self.erlang_record_headers(&writer, module, &erl_name)?; } Ok(()) } fn erlang_module( &self, writer: &Writer, module: &Module, erl_name: &str, root: &Utf8Path, ) -> Result<()> { let name = format!("{erl_name}.erl"); let path = self.build_directory.join(&name); let line_numbers = LineNumbers::new(&module.code); let output = erlang::module(&module.ast, &line_numbers, root); tracing::debug!(name = ?name, "Generated Erlang module"); writer.write(&path, &output?) } fn erlang_record_headers( &self, writer: &Writer, module: &Module, erl_name: &str, ) -> Result<()> { for (name, text) in erlang::records(&module.ast) { let name = format!("{erl_name}_{name}.hrl"); tracing::debug!(name = ?name, "Generated Erlang header"); writer.write(&self.include_directory.join(name), &text)?; } Ok(()) } } /// A code generator that creates a .app Erlang application file for the package #[derive(Debug)] pub struct ErlangApp<'a> { output_directory: &'a Utf8Path, config: &'a ErlangAppCodegenConfiguration, } impl<'a> ErlangApp<'a> { pub fn new(output_directory: &'a Utf8Path, config: &'a ErlangAppCodegenConfiguration) -> Self { Self { output_directory, config, } } pub fn render( &self, writer: Writer, config: &PackageConfig, modules: &[Module], native_modules: Vec, ) -> Result<()> { fn tuple(key: &str, value: &str) -> String { format!(" {{{key}, {value}}},\n") } let path = self.output_directory.join(format!("{}.app", &config.name)); let start_module = match config.erlang.application_start_module.as_ref() { None => "".into(), Some(module) => { let module = module_erlang_name(module); let argument = match config.erlang.application_start_argument.as_ref() { Some(argument) => argument.as_str(), None => "[]", }; tuple("mod", &format!("{{'{module}', {argument}}}")) } }; let modules = modules .iter() .map(|m| m.erlang_name()) .chain(native_modules) .unique() .sorted() .map(escape_atom_string) .join(",\n "); // TODO: When precompiling for production (i.e. as a precompiled hex // package) we will need to exclude the dev deps. let applications = config .dependencies .keys() .chain( config .dev_dependencies .keys() .take_while(|_| self.config.include_dev_deps), ) // TODO: test this! .map(|name| self.config.package_name_overrides.get(name).unwrap_or(name)) .chain(config.erlang.extra_applications.iter()) .sorted() .join(",\n "); let text = format!( r#"{{application, {package}, [ {start_module} {{vsn, "{version}"}}, {{applications, [{applications}]}}, {{description, "{description}"}}, {{modules, [{modules}]}}, {{registered, []}} ]}}. "#, applications = applications, description = config.description, modules = modules, package = config.name, start_module = start_module, version = config.version, ); writer.write(&path, &text) } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TypeScriptDeclarations { None, Emit, } #[derive(Debug)] pub struct JavaScript<'a> { output_directory: &'a Utf8Path, prelude_location: &'a Utf8Path, project_root: &'a Utf8Path, typescript: TypeScriptDeclarations, } impl<'a> JavaScript<'a> { pub fn new( output_directory: &'a Utf8Path, typescript: TypeScriptDeclarations, prelude_location: &'a Utf8Path, project_root: &'a Utf8Path, ) -> Self { Self { prelude_location, output_directory, project_root, typescript, } } pub fn render( &self, writer: &impl FileSystemWriter, modules: &[Module], stdlib_package: StdlibPackage, ) -> Result<()> { for module in modules { let js_name = module.name.clone(); if self.typescript == TypeScriptDeclarations::Emit { self.ts_declaration(writer, module, &js_name)?; } self.js_module(writer, module, &js_name, stdlib_package)? } self.write_prelude(writer)?; Ok(()) } fn write_prelude(&self, writer: &impl FileSystemWriter) -> Result<()> { let rexport = format!("export * from \"{}\";\n", self.prelude_location); let prelude_path = &self.output_directory.join("gleam.mjs"); // This check skips unnecessary `gleam.mjs` writes which confuse // watchers and HMR build tools if !writer.exists(prelude_path) { writer.write(prelude_path, &rexport)?; } if self.typescript == TypeScriptDeclarations::Emit { let rexport = format!( "export * from \"{}\";\nexport type * from \"{}\";\n", self.prelude_location, self.prelude_location.as_str().replace(".mjs", ".d.mts") ); let prelude_declaration_path = &self.output_directory.join("gleam.d.mts"); // Type declaration may trigger badly configured watchers if !writer.exists(prelude_declaration_path) { writer.write(prelude_declaration_path, &rexport)?; } } Ok(()) } fn ts_declaration( &self, writer: &impl FileSystemWriter, module: &Module, js_name: &str, ) -> Result<()> { let name = format!("{js_name}.d.mts"); let path = self.output_directory.join(name); let output = javascript::ts_declaration(&module.ast); tracing::debug!(name = ?js_name, "Generated TS declaration"); writer.write(&path, &output) } fn js_module( &self, writer: &impl FileSystemWriter, module: &Module, js_name: &str, stdlib_package: StdlibPackage, ) -> Result<()> { let name = format!("{js_name}.mjs"); let path = self.output_directory.join(name); let line_numbers = LineNumbers::new(&module.code); let output = javascript::module(ModuleConfig { module: &module.ast, line_numbers: &line_numbers, path: &module.input_path, project_root: self.project_root, src: &module.code, typescript: self.typescript, stdlib_package, }); tracing::debug!(name = ?js_name, "Generated js module"); writer.write(&path, &output) } } ================================================ FILE: compiler-core/src/config/stale_package_remover.rs ================================================ use crate::manifest::Manifest; use crate::requirement::Requirement; use ecow::EcoString; use hexpm::version::Version; use std::collections::{HashMap, HashSet}; #[derive(Debug)] pub struct StalePackageRemover<'a> { // These are the packages for which the requirement or their parents // requirement has not changed. fresh: HashSet<&'a str>, locked: HashMap>, } impl<'a> StalePackageRemover<'a> { pub fn fresh_and_locked( requirements: &'a HashMap, manifest: &'a Manifest, ) -> HashMap { let locked = manifest .packages .iter() .map(|p| (p.name.clone(), &p.requirements)) .collect(); Self { fresh: HashSet::new(), locked, } .run(requirements, manifest) } fn run( &mut self, requirements: &'a HashMap, manifest: &'a Manifest, ) -> HashMap { // Record all the requirements that have not changed for (name, requirement) in requirements { if manifest.requirements.get(name) != Some(requirement) { continue; // This package has changed, don't record it } // Recursively record the package and its deps as being fresh self.record_tree_fresh(name); } // Return all the previously resolved packages that have not been // recorded as fresh manifest .packages .iter() .filter(|package| { let new = requirements.contains_key(package.name.as_str()) && !manifest.requirements.contains_key(package.name.as_str()); let fresh = self.fresh.contains(package.name.as_str()); let locked = !new && fresh; if !locked { tracing::info!(name = package.name.as_str(), "unlocking_stale_package"); } locked }) .map(|package| (package.name.clone(), package.version.clone())) .collect() } fn record_tree_fresh(&mut self, name: &'a str) { // Record the top level package let _ = self.fresh.insert(name); let Some(deps) = self.locked.get(name) else { // If the package is not in the manifest then it means that the package is an optional // dependency that has not been included. That or someone has been editing the manifest // and broken it, but let's hope that's not the case. return; }; // Record each of its deps recursively for package in *deps { self.record_tree_fresh(package); } } } #[cfg(test)] mod tests { use super::*; use crate::manifest::{Base16Checksum, Manifest, ManifestPackage, ManifestPackageSource}; use crate::requirement::Requirement; use hexpm::version::{Range, Version}; use std::collections::HashMap; // https://github.com/gleam-lang/gleam/issues/4152 #[test] fn optional_package_not_in_manifest() { let requirements = HashMap::from_iter([( "required_package".into(), Requirement::Hex { version: Range::new("1.0.0".into()).unwrap(), }, )]); let manifest = Manifest { requirements: requirements.clone(), packages: vec![ManifestPackage { name: "required_package".into(), version: Version::new(1, 0, 0), build_tools: vec!["gleam".into()], otp_app: None, requirements: vec![ // NOTE: this package isn't in the manifest. This will have been because it is // an optional dep of `required_package`. "optional_package".into(), ], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![]), }, }], }; assert_eq!( StalePackageRemover::fresh_and_locked(&requirements, &manifest), HashMap::from_iter([("required_package".into(), Version::new(1, 0, 0))]) ); } } ================================================ FILE: compiler-core/src/config.rs ================================================ mod stale_package_remover; use crate::error::{FileIoAction, FileKind}; use crate::io::FileSystemReader; use crate::io::ordered_map; use crate::manifest::Manifest; use crate::requirement::Requirement; use crate::version::COMPILER_VERSION; use crate::{Error, Result}; use camino::{Utf8Path, Utf8PathBuf}; use ecow::EcoString; use globset::{Glob, GlobSetBuilder}; use hexpm::version::{self, LowestVersion, Version}; use http::Uri; use serde::ser::SerializeSeq; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fmt::{self}; use std::marker::PhantomData; #[cfg(test)] use crate::manifest::ManifestPackage; use crate::build::{Mode, Runtime, Target}; fn default_version() -> Version { Version::parse("0.1.0").expect("default version") } fn erlang_target() -> Target { Target::Erlang } fn default_javascript_runtime() -> Runtime { Runtime::NodeJs } pub type Dependencies = HashMap; #[derive(Clone, Debug, PartialEq, Eq)] pub struct SpdxLicense { pub licence: String, } impl ToString for SpdxLicense { fn to_string(&self) -> String { String::from(&self.licence) } } impl<'de> Deserialize<'de> for SpdxLicense { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { deserializer.deserialize_str(SpdxLicenseVisitor) } } struct SpdxLicenseVisitor; impl<'de> serde::de::Visitor<'de> for SpdxLicenseVisitor { type Value = SpdxLicense; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("a SPDX License ID") } fn visit_str(self, value: &str) -> Result where E: serde::de::Error, { match spdx::license_id(value) { None => Err(serde::de::Error::custom(format!( "{value} is not a valid SPDX License ID" ))), Some(_) => Ok(SpdxLicense { licence: value.to_string(), }), } } } impl Serialize for SpdxLicense { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.serialize_str(&self.licence) } } impl AsRef for SpdxLicense { fn as_ref(&self) -> &str { self.licence.as_str() } } #[derive(Debug, PartialEq, Clone)] pub struct GleamVersion(version::Range); impl From for GleamVersion { fn from(range: version::Range) -> Self { Self(range) } } impl From for version::Range { fn from(gleam_version: GleamVersion) -> Self { gleam_version.0 } } impl From for pubgrub::Range { fn from(gleam_version: GleamVersion) -> Self { gleam_version.0.into() } } impl GleamVersion { pub fn from_pubgrub(range: pubgrub::Range) -> Self { let range: version::Range = range.into(); range.into() } pub fn as_pubgrub(&self) -> &pubgrub::Range { self.0.to_pubgrub() } pub fn new(spec: String) -> Result { let hex = version::Range::new(spec.to_string()).map_err(|e| Error::InvalidVersionFormat { input: spec, error: e.to_string(), })?; Ok(hex.into()) } pub fn lowest_version(&self) -> Option { self.as_pubgrub().lowest_version() } pub fn hex(&self) -> &version::Range { &self.0 } } #[derive(Deserialize, Serialize, Debug, PartialEq, Clone)] pub struct PackageConfig { #[serde(deserialize_with = "package_name::deserialize")] pub name: EcoString, #[serde(default = "default_version")] pub version: Version, #[serde( default, rename = "gleam", deserialize_with = "deserialise_gleam_version", serialize_with = "serialise_gleam_version" )] pub gleam_version: Option, #[serde(default, alias = "licenses")] pub licences: Vec, #[serde(default)] pub description: EcoString, #[serde(default, alias = "docs")] pub documentation: Docs, #[serde(default, serialize_with = "ordered_map")] pub dependencies: Dependencies, #[serde(default, alias = "dev-dependencies", serialize_with = "ordered_map")] pub dev_dependencies: Dependencies, #[serde(default)] pub repository: Option, #[serde(default)] pub links: Vec, #[serde(default)] pub erlang: ErlangConfig, #[serde(default)] pub javascript: JavaScriptConfig, #[serde(default = "erlang_target")] pub target: Target, #[serde(default)] pub internal_modules: Option>, } pub fn serialise_gleam_version( gleam_gersion: &Option, serializer: S, ) -> Result where S: serde::Serializer, { match gleam_gersion { Some(version) => serializer.serialize_str(&version.hex().to_string()), None => serializer.serialize_none(), } } pub fn deserialise_gleam_version<'de, D>(deserialiser: D) -> Result, D::Error> where D: serde::Deserializer<'de>, { match Deserialize::deserialize(deserialiser)? { Some(range_string) => { let hex = version::Range::new(range_string).map_err(serde::de::Error::custom)?; Ok(Some(hex.into())) } None => Ok(None), } } impl PackageConfig { pub fn dependencies_for(&self, mode: Mode) -> Result { match mode { Mode::Dev | Mode::Lsp => self.all_direct_dependencies(), Mode::Prod => Ok(self.dependencies.clone()), } } // Return all the dependencies listed in the configuration, that is, all the // direct dependencies, both in the `dependencies` and `dev_dependencies`. pub fn all_direct_dependencies(&self) -> Result { let mut deps = HashMap::with_capacity(self.dependencies.len() + self.dev_dependencies.len()); for (name, requirement) in self.dependencies.iter().chain(&self.dev_dependencies) { let already_inserted = deps.insert(name.clone(), requirement.clone()).is_some(); if already_inserted { return Err(Error::DuplicateDependency(name.clone())); } } Ok(deps) } pub fn read>( path: P, fs: &FS, ) -> Result { let toml = fs.read(path.as_ref())?; deserialise_config(path, toml) } /// Get the locked packages for the current config and a given (optional) /// manifest of previously locked packages. /// /// If a package is removed or the specified required version range for it /// changes then it is not considered locked. This also goes for any child /// packages of the package which have no other parents. /// /// This function should be used each time resolution is performed so that /// outdated deps are removed from the manifest and not locked to the /// previously selected versions. /// pub fn locked(&self, manifest: Option<&Manifest>) -> Result> { match manifest { None => Ok(HashMap::new()), Some(manifest) => { let requirements = self.all_direct_dependencies()?; let fresh_and_locked = stale_package_remover::StalePackageRemover::fresh_and_locked( &requirements, manifest, ); Ok(fresh_and_locked) } } } /// Determines whether the given module should be hidden in the docs or not /// /// The developer can specify a list of glob patterns in the gleam.toml file /// to determine modules that should not be shown in the package's documentation pub fn is_internal_module(&self, module: &str) -> bool { let package = &self.name; match &self.internal_modules { Some(globs) => { let mut builder = GlobSetBuilder::new(); for glob in globs { _ = builder.add(glob.clone()); } builder.build() } // If no patterns were specified in the config then we use a default value None => GlobSetBuilder::new() .add(Glob::new(&format!("{package}/internal")).expect("internal module glob")) .add(Glob::new(&format!("{package}/internal/*")).expect("internal module glob")) .build(), } .expect("internal module globs") .is_match(module) } // Checks to see if the gleam version specified in the config is compatible // with the current compiler version pub fn check_gleam_compatibility(&self) -> Result<(), Error> { if let Some(version) = &self.gleam_version { let range = version.as_pubgrub(); let compiler_version = Version::parse(COMPILER_VERSION).expect("Parse compiler semantic version"); // We ignore the pre-release and build metadata when checking compatibility let mut version_without_pre = compiler_version.clone(); version_without_pre.pre = vec![]; version_without_pre.build = None; if !range.contains(&version_without_pre) { return Err(Error::IncompatibleCompilerVersion { package: self.name.to_string(), required_version: range.to_string(), gleam_version: COMPILER_VERSION.to_string(), }); } } Ok(()) } pub fn tag_for_version(&self, version: &Version) -> String { let prefix = match self.repository.as_ref() { Some( Repository::GitHub { tag_prefix, .. } | Repository::GitLab { tag_prefix, .. } | Repository::BitBucket { tag_prefix, .. } | Repository::Codeberg { tag_prefix, .. } | Repository::SourceHut { tag_prefix, .. } | Repository::Gitea { tag_prefix, .. } | Repository::Forgejo { tag_prefix, .. } | Repository::Tangled { tag_prefix, .. }, ) => tag_prefix.as_ref(), Some(Repository::Custom { .. }) | None => None, }; match prefix { Some(prefix) => format!("{prefix}v{version}"), None => format!("v{version}"), } } } fn deserialise_config>( path: P, toml: String, ) -> std::result::Result { let config: PackageConfig = toml::from_str(&toml).map_err(|e| Error::FileIo { action: FileIoAction::Parse, kind: FileKind::File, path: path.as_ref().to_path_buf(), err: Some(e.to_string()), })?; Ok(config) } // https://github.com/gleam-lang/gleam/issues/4867 #[test] fn deny_extra_deps_properties() { let toml = r#" name = "wibble" version = "1.0.0" [dependencies] aide_generator = { git = "git@github.com:crowdhailer/aide.git", ref = "f559c5bc", extra = "idk what this is" } "#; let error = deserialise_config("gleam.toml", toml.into()) .expect_err("should fail to deserialise because of additional path"); insta::assert_snapshot!(insta::internals::AutoName, error.pretty_string()); } #[test] fn locked_no_manifest() { let mut config = PackageConfig::default(); config.dependencies = [ ("prod1".into(), Requirement::hex("~> 1.0").unwrap()), ("prod2".into(), Requirement::hex("~> 2.0").unwrap()), ] .into(); config.dev_dependencies = [ ("dev1".into(), Requirement::hex("~> 1.0").unwrap()), ("dev2".into(), Requirement::hex("~> 2.0").unwrap()), ] .into(); assert_eq!(config.locked(None).unwrap(), [].into()); } #[test] fn locked_no_changes() { let mut config = PackageConfig::default(); config.dependencies = [ ("prod1".into(), Requirement::hex("~> 1.0").unwrap()), ("prod2".into(), Requirement::hex("~> 2.0").unwrap()), ] .into(); config.dev_dependencies = [ ("dev1".into(), Requirement::hex("~> 1.0").unwrap()), ("dev2".into(), Requirement::hex("~> 2.0").unwrap()), ] .into(); let manifest = Manifest { requirements: config.all_direct_dependencies().unwrap(), packages: vec![ manifest_package("prod1", "1.1.0", &[]), manifest_package("prod2", "1.2.0", &[]), manifest_package("dev1", "1.1.0", &[]), manifest_package("dev2", "1.2.0", &[]), ], }; assert_eq!( config.locked(Some(&manifest)).unwrap(), [ locked_version("prod1", "1.1.0"), locked_version("prod2", "1.2.0"), locked_version("dev1", "1.1.0"), locked_version("dev2", "1.2.0"), ] .into() ); } #[test] fn locked_some_removed() { let mut config = PackageConfig::default(); config.dependencies = [("prod1".into(), Requirement::hex("~> 1.0").unwrap())].into(); config.dev_dependencies = [("dev2".into(), Requirement::hex("~> 2.0").unwrap())].into(); let manifest = Manifest { requirements: config.all_direct_dependencies().unwrap(), packages: vec![ manifest_package("prod1", "1.1.0", &[]), manifest_package("prod2", "1.2.0", &[]), // Not in config manifest_package("dev1", "1.1.0", &[]), // Not in config manifest_package("dev2", "1.2.0", &[]), ], }; assert_eq!( config.locked(Some(&manifest)).unwrap(), [ // prod2 removed // dev1 removed locked_version("prod1", "1.1.0"), locked_version("dev2", "1.2.0"), ] .into() ); } #[test] fn locked_some_changed() { let mut config = PackageConfig::default(); config.dependencies = [ ("prod1".into(), Requirement::hex("~> 3.0").unwrap()), // Does not match manifest ("prod2".into(), Requirement::hex("~> 2.0").unwrap()), ] .into(); config.dev_dependencies = [ ("dev1".into(), Requirement::hex("~> 3.0").unwrap()), // Does not match manifest ("dev2".into(), Requirement::hex("~> 2.0").unwrap()), ] .into(); let manifest = Manifest { requirements: [ ("prod1".into(), Requirement::hex("~> 1.0").unwrap()), ("prod2".into(), Requirement::hex("~> 2.0").unwrap()), ("dev1".into(), Requirement::hex("~> 1.0").unwrap()), ("dev2".into(), Requirement::hex("~> 2.0").unwrap()), ] .into(), packages: vec![ manifest_package("prod1", "1.1.0", &[]), manifest_package("prod2", "1.2.0", &[]), manifest_package("dev1", "1.1.0", &[]), manifest_package("dev2", "1.2.0", &[]), ], }; assert_eq!( config.locked(Some(&manifest)).unwrap(), [ // prod1 removed // dev1 removed locked_version("prod2", "1.2.0"), locked_version("dev2", "1.2.0"), ] .into() ); } #[test] fn locked_nested_are_removed_too() { let mut config = PackageConfig::default(); config.dependencies = [ ("1".into(), Requirement::hex("~> 2.0").unwrap()), // Does not match manifest ("2".into(), Requirement::hex("~> 1.0").unwrap()), ] .into(); config.dev_dependencies = [].into(); let manifest = Manifest { requirements: [ ("1".into(), Requirement::hex("~> 1.0").unwrap()), ("2".into(), Requirement::hex("~> 1.0").unwrap()), ] .into(), packages: vec![ manifest_package("1", "1.1.0", &["1.1", "1.2"]), manifest_package("1.1", "1.1.0", &["1.1.1", "1.1.2"]), manifest_package("1.1.1", "1.1.0", &["shared"]), manifest_package("1.1.2", "1.1.0", &[]), manifest_package("1.2", "1.1.0", &["1.2.1", "1.2.2"]), manifest_package("1.2.1", "1.1.0", &[]), manifest_package("1.2.2", "1.1.0", &[]), manifest_package("2", "2.1.0", &["2.1", "2.2"]), manifest_package("2.1", "2.1.0", &["2.1.1", "2.1.2"]), manifest_package("2.1.1", "2.1.0", &[]), manifest_package("2.1.2", "2.1.0", &[]), manifest_package("2.2", "2.1.0", &["2.2.1", "2.2.2", "shared"]), manifest_package("2.2.1", "2.1.0", &[]), manifest_package("2.2.2", "2.1.0", &[]), manifest_package("shared", "2.1.0", &[]), ], }; assert_eq!( config.locked(Some(&manifest)).unwrap(), [ // 1* removed locked_version("2", "2.1.0"), locked_version("2.1", "2.1.0"), locked_version("2.1.1", "2.1.0"), locked_version("2.1.2", "2.1.0"), locked_version("2.2", "2.1.0"), locked_version("2.2.1", "2.1.0"), locked_version("2.2.2", "2.1.0"), locked_version("shared", "2.1.0"), ] .into() ); } // https://github.com/gleam-lang/gleam/issues/1754 #[test] fn locked_unlock_new() { let mut config = PackageConfig::default(); config.dependencies = [ ("1".into(), Requirement::hex("~> 1.0").unwrap()), ("2".into(), Requirement::hex("~> 1.0").unwrap()), ("3".into(), Requirement::hex("~> 3.0").unwrap()), // Does not match manifest ] .into(); config.dev_dependencies = [].into(); let manifest = Manifest { requirements: [ ("1".into(), Requirement::hex("~> 1.0").unwrap()), ("2".into(), Requirement::hex("~> 1.0").unwrap()), ] .into(), packages: vec![ manifest_package("1", "1.1.0", &["3"]), manifest_package("2", "1.1.0", &["3"]), manifest_package("3", "1.1.0", &[]), ], }; assert_eq!( config.locked(Some(&manifest)).unwrap(), [locked_version("1", "1.1.0"), locked_version("2", "1.1.0"),].into() ) } #[test] fn default_internal_modules() { // When no internal modules are specified then we default to // `["$package/internal", "$package/internal/*"]` let mut config = PackageConfig::default(); config.name = "my_package".into(); config.internal_modules = None; assert!(config.is_internal_module("my_package/internal")); assert!(config.is_internal_module("my_package/internal/wibble")); assert!(config.is_internal_module("my_package/internal/wibble/wobble")); assert!(!config.is_internal_module("my_package/internallll")); assert!(!config.is_internal_module("my_package/other")); assert!(!config.is_internal_module("my_package/other/wibble")); assert!(!config.is_internal_module("other/internal")); } #[test] fn no_internal_modules() { // When no internal modules are specified then we default to // `["$package/internal", "$package/internal/*"]` let mut config = PackageConfig::default(); config.name = "my_package".into(); config.internal_modules = Some(vec![]); assert!(!config.is_internal_module("my_package/internal")); assert!(!config.is_internal_module("my_package/internal/wibble")); assert!(!config.is_internal_module("my_package/internal/wibble/wobble")); assert!(!config.is_internal_module("my_package/internallll")); assert!(!config.is_internal_module("my_package/other")); assert!(!config.is_internal_module("my_package/other/wibble")); assert!(!config.is_internal_module("other/internal")); } #[test] fn hidden_a_directory_from_docs() { let mut config = PackageConfig::default(); config.internal_modules = Some(vec![Glob::new("package/internal/*").expect("")]); let mod1 = "package/internal"; let mod2 = "package/internal/module"; assert_eq!(config.is_internal_module(mod1), false); assert_eq!(config.is_internal_module(mod2), true); } #[test] fn hidden_two_directories_from_docs() { let mut config = PackageConfig::default(); config.internal_modules = Some(vec![ Glob::new("package/internal1/*").expect(""), Glob::new("package/internal2/*").expect(""), ]); let mod1 = "package/internal1"; let mod2 = "package/internal1/module"; let mod3 = "package/internal2"; let mod4 = "package/internal2/module"; assert_eq!(config.is_internal_module(mod1), false); assert_eq!(config.is_internal_module(mod2), true); assert_eq!(config.is_internal_module(mod3), false); assert_eq!(config.is_internal_module(mod4), true); } #[test] fn hidden_a_directory_and_a_file_from_docs() { let mut config = PackageConfig::default(); config.internal_modules = Some(vec![ Glob::new("package/internal1/*").expect(""), Glob::new("package/module").expect(""), ]); let mod1 = "package/internal1"; let mod2 = "package/internal1/module"; let mod3 = "package/module"; let mod4 = "package/module/inner"; assert_eq!(config.is_internal_module(mod1), false); assert_eq!(config.is_internal_module(mod2), true); assert_eq!(config.is_internal_module(mod3), true); assert_eq!(config.is_internal_module(mod4), false); } #[test] fn hidden_a_file_in_all_directories_from_docs() { let mut config = PackageConfig::default(); config.internal_modules = Some(vec![Glob::new("package/*/module1").expect("")]); let mod1 = "package/internal1/module1"; let mod2 = "package/internal2/module1"; let mod3 = "package/internal2/module2"; let mod4 = "package/module"; assert_eq!(config.is_internal_module(mod1), true); assert_eq!(config.is_internal_module(mod2), true); assert_eq!(config.is_internal_module(mod3), false); assert_eq!(config.is_internal_module(mod4), false); } #[cfg(test)] fn manifest_package( name: &'static str, version: &'static str, requirements: &'static [&'static str], ) -> ManifestPackage { use crate::manifest::Base16Checksum; ManifestPackage { name: name.into(), version: Version::parse(version).unwrap(), build_tools: vec![], otp_app: None, requirements: requirements .iter() .map(|requirement| (*requirement).into()) .collect(), source: crate::manifest::ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![]), }, } } #[cfg(test)] fn locked_version(name: &'static str, version: &'static str) -> (EcoString, Version) { (name.into(), Version::parse(version).unwrap()) } impl Default for PackageConfig { fn default() -> Self { Self { name: Default::default(), version: default_version(), gleam_version: Default::default(), description: Default::default(), documentation: Default::default(), dependencies: Default::default(), erlang: Default::default(), javascript: Default::default(), repository: Default::default(), dev_dependencies: Default::default(), licences: Default::default(), links: Default::default(), internal_modules: Default::default(), target: Target::Erlang, } } } #[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Default, Clone)] pub struct ErlangConfig { /// An module that can be set in the `.app` file as the entrypoint for a stateful application /// that defines a singleton supervision tree. /// Erlang syntax. #[serde(default)] pub application_start_module: Option, /// The argument for the start module start function. If not set then `[]` is used as the /// default argument. /// Erlang syntax. #[serde(default)] pub application_start_argument: Option, #[serde(default)] pub extra_applications: Vec, } #[derive(Deserialize, Serialize, Debug, PartialEq, Default, Clone)] pub struct JavaScriptConfig { #[serde(default)] pub typescript_declarations: bool, #[serde(default = "default_javascript_runtime")] pub runtime: Runtime, #[serde(default, rename = "deno")] pub deno: DenoConfig, } #[derive(Deserialize, Debug, PartialEq, Eq, Clone)] pub enum DenoFlag { AllowAll, Allow(Vec), } impl Default for DenoFlag { fn default() -> Self { Self::Allow(Vec::new()) } } impl Serialize for DenoFlag { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { match self { DenoFlag::AllowAll => serializer.serialize_bool(true), DenoFlag::Allow(items) => { let mut seq = serializer.serialize_seq(Some(items.len()))?; for e in items { seq.serialize_element(e)?; } seq.end() } } } } fn bool_or_seq_string_to_deno_flag<'de, D>(deserializer: D) -> Result where D: serde::Deserializer<'de>, { struct StringOrVec(PhantomData>); impl<'de> serde::de::Visitor<'de> for StringOrVec { type Value = DenoFlag; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("bool or list of strings") } fn visit_bool(self, value: bool) -> Result where E: serde::de::Error, { if value { Ok(DenoFlag::AllowAll) } else { Ok(DenoFlag::default()) } } fn visit_seq(self, visitor: S) -> Result where S: serde::de::SeqAccess<'de>, { let allow: Vec = Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(visitor)) .unwrap_or_default(); Ok(DenoFlag::Allow(allow)) } } deserializer.deserialize_any(StringOrVec(PhantomData)) } #[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Default, Clone)] pub struct DenoConfig { #[serde(default, deserialize_with = "bool_or_seq_string_to_deno_flag")] pub allow_env: DenoFlag, #[serde(default)] pub allow_sys: bool, #[serde(default)] pub allow_hrtime: bool, #[serde(default, deserialize_with = "bool_or_seq_string_to_deno_flag")] pub allow_net: DenoFlag, #[serde(default)] pub allow_ffi: bool, #[serde(default, deserialize_with = "bool_or_seq_string_to_deno_flag")] pub allow_read: DenoFlag, #[serde(default, deserialize_with = "bool_or_seq_string_to_deno_flag")] pub allow_run: DenoFlag, #[serde(default, deserialize_with = "bool_or_seq_string_to_deno_flag")] pub allow_write: DenoFlag, #[serde(default)] pub allow_all: bool, #[serde(default)] pub unstable: bool, #[serde( default, serialize_with = "uri_serde::serialize_option", deserialize_with = "uri_serde::deserialize_option" )] pub location: Option, } #[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)] #[serde(tag = "type")] pub enum Repository { #[serde(rename = "github")] GitHub { user: String, repo: String, path: Option, #[serde(alias = "tag-prefix")] tag_prefix: Option, }, #[serde(rename = "gitlab")] GitLab { user: String, repo: String, path: Option, #[serde(alias = "tag-prefix")] tag_prefix: Option, }, #[serde(rename = "bitbucket")] BitBucket { user: String, repo: String, path: Option, #[serde(alias = "tag-prefix")] tag_prefix: Option, }, #[serde(rename = "codeberg")] Codeberg { user: String, repo: String, path: Option, #[serde(alias = "tag-prefix")] tag_prefix: Option, }, #[serde(rename = "gitea")] Gitea { user: String, repo: String, path: Option, #[serde(alias = "tag-prefix")] tag_prefix: Option, #[serde( serialize_with = "uri_serde::serialize", deserialize_with = "uri_serde_default_https::deserialize" )] host: Uri, }, #[serde(rename = "forgejo")] Forgejo { user: String, repo: String, path: Option, #[serde(alias = "tag-prefix")] tag_prefix: Option, #[serde( serialize_with = "uri_serde::serialize", deserialize_with = "uri_serde_default_https::deserialize" )] host: Uri, }, #[serde(rename = "sourcehut")] SourceHut { user: String, repo: String, path: Option, #[serde(alias = "tag-prefix")] tag_prefix: Option, }, #[serde(rename = "tangled")] Tangled { user: String, repo: String, path: Option, #[serde(alias = "tag-prefix")] tag_prefix: Option, }, #[serde(rename = "custom")] Custom { url: String, #[serde(alias = "tag-prefix")] tag_prefix: Option, }, } impl Repository { pub fn url(&self) -> String { match self { Repository::GitHub { repo, user, .. } => { format!("https://github.com/{user}/{repo}") } Repository::GitLab { repo, user, .. } => { format!("https://gitlab.com/{user}/{repo}") } Repository::BitBucket { repo, user, .. } => { format!("https://bitbucket.com/{user}/{repo}") } Repository::Codeberg { repo, user, .. } => { format!("https://codeberg.org/{user}/{repo}") } Repository::SourceHut { repo, user, .. } => { format!("https://git.sr.ht/~{user}/{repo}") } Repository::Tangled { repo, user, .. } => { format!("https://tangled.sh/{user}/{repo}") } Repository::Gitea { repo, user, host, .. } | Repository::Forgejo { repo, user, host, .. } => { let string_host = host.to_string(); let cleaned_host = string_host.trim_end_matches('/'); format!("{cleaned_host}/{user}/{repo}") } Repository::Custom { url, .. } => url.clone(), } } pub fn path(&self) -> Option<&String> { match self { Repository::GitHub { path, .. } | Repository::GitLab { path, .. } | Repository::BitBucket { path, .. } | Repository::Codeberg { path, .. } | Repository::SourceHut { path, .. } | Repository::Tangled { path, .. } | Repository::Gitea { path, .. } | Repository::Forgejo { path, .. } => path.as_ref(), Repository::Custom { .. } => None, } } } #[derive(Deserialize, Serialize, Default, Debug, PartialEq, Eq, Clone)] pub struct Docs { #[serde(default)] pub pages: Vec, } #[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)] pub struct DocsPage { pub title: String, pub path: String, pub source: Utf8PathBuf, } #[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)] pub struct Link { pub title: String, #[serde(with = "uri_serde")] pub href: Uri, } // Note we don't use http-serde since we also want to validate the scheme and host is set. mod uri_serde { use http::uri::InvalidUri; use serde::{Deserialize, Deserializer, de::Error as _}; pub fn deserialize<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { let string = String::deserialize(deserializer)?; let uri: http::Uri = string .parse() .map_err(|err: InvalidUri| D::Error::custom(err.to_string()))?; if uri.scheme().is_none() || uri.host().is_none() { return Err(D::Error::custom("uri without scheme")); } Ok(uri) } pub fn deserialize_option<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { let string: Option = Option::deserialize(deserializer)?; match string { Some(s) => { let deserializer = serde::de::value::StringDeserializer::new(s); deserialize(deserializer).map(Some) } None => Ok(None), } } pub fn serialize_option(uri: &Option, serializer: S) -> Result where S: serde::Serializer, { match uri { Some(uri) => serialize(uri, serializer), None => serializer.serialize_unit(), } } pub fn serialize(uri: &http::Uri, serializer: S) -> Result where S: serde::Serializer, { serializer.serialize_str(&uri.to_string()) } } // This prefixes https as a default in the event no scheme was provided mod uri_serde_default_https { use http::uri::InvalidUri; use serde::{Deserialize, Deserializer, de::Error as _}; pub fn deserialize<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { let string = String::deserialize(deserializer)?; let uri: http::Uri = string .parse() .map_err(|err: InvalidUri| D::Error::custom(err.to_string()))?; if uri.host().is_none() { return Err(D::Error::custom("uri without host")); } match uri.scheme().is_none() { true => format!("https://{string}") .parse() .map_err(|err: InvalidUri| D::Error::custom(err.to_string())), false => Ok(uri), } } } mod package_name { use ecow::EcoString; use regex::Regex; use serde::Deserializer; use std::{fmt, sync::OnceLock}; static PACKAGE_NAME_PATTERN: OnceLock = OnceLock::new(); pub fn deserialize<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { deserializer.deserialize_str(NameVisitor) } struct NameVisitor; impl<'de> serde::de::Visitor<'de> for NameVisitor { type Value = EcoString; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("a package name") } fn visit_str(self, value: &str) -> Result where E: serde::de::Error, { if PACKAGE_NAME_PATTERN .get_or_init(|| Regex::new("^[a-z][a-z0-9_]*$").expect("Package name regex")) .is_match(value) { Ok(value.into()) } else { let error = "Package names may only contain lowercase letters, numbers, and underscores"; Err(serde::de::Error::custom(error)) } } } } #[test] fn name_with_dash() { let input = r#" name = "one-two" "#; insta::assert_snapshot!( insta::internals::AutoName, toml::from_str::(input) .unwrap_err() .to_string() ); } #[test] fn name_with_number_start() { let input = r#" name = "1" "#; insta::assert_snapshot!( insta::internals::AutoName, toml::from_str::(input) .unwrap_err() .to_string(), ) } #[test] fn package_config_to_json() { let input = r#" name = "my_project" version = "1.0.0" licences = ["Apache-2.0", "MIT"] description = "Pretty complex config" target = "erlang" repository = { type = "github", user = "example", repo = "my_dep" } links = [{ title = "Home page", href = "https://example.com" }] internal_modules = ["my_app/internal"] gleam = ">= 0.30.0" [dependencies] gleam_stdlib = ">= 0.18.0 and < 2.0.0" my_other_project = { path = "../my_other_project" } [dev_dependencies] gleeunit = ">= 1.0.0 and < 2.0.0" [documentation] pages = [{ title = "My Page", path = "my-page.html", source = "./path/to/my-page.md" }] [erlang] application_start_module = "my_app/application" extra_applications = ["inets", "ssl"] [javascript] typescript_declarations = true runtime = "node" [javascript.deno] allow_all = false allow_ffi = true allow_env = ["DATABASE_URL"] allow_net = ["example.com:443"] allow_read = ["./database.sqlite"] "#; let config = toml::from_str::(input).unwrap(); let json = serde_json::to_string_pretty(&config).unwrap(); let output = format!("--- GLEAM.TOML\n{input}\n\n--- EXPORTED JSON\n\n{json}"); insta::assert_snapshot!(output); let roundtrip = serde_json::from_str::(&json).unwrap(); assert_eq!(config, roundtrip); } #[test] fn barebones_package_config_to_json() { let input = r#" name = "my_project" version = "1.0.0" "#; let config = toml::from_str::(input).unwrap(); let json = serde_json::to_string_pretty(&config).unwrap(); let output = format!("--- GLEAM.TOML\n{input}\n\n--- EXPORTED JSON\n\n{json}"); insta::assert_snapshot!(output); } #[test] fn dev_deps_field_name() { let toml = r#" name = "wibble" version = "1.0.0" [dev_dependencies] wibble = ">= 1.0.0 and < 2.0.0" "#; let hyphen_alternative = deserialise_config("gleam.toml", toml.into()).expect("valid config"); let toml = r#" name = "wibble" version = "1.0.0" [dev_dependencies] wibble = ">= 1.0.0 and < 2.0.0" "#; let canonical = deserialise_config("gleam.toml", toml.into()).expect("valid config"); assert_eq!(canonical, hyphen_alternative) } ================================================ FILE: compiler-core/src/dep_tree.rs ================================================ use ecow::EcoString; use petgraph::{Direction, algo::Cycle, graph::NodeIndex}; use std::collections::{HashMap, HashSet}; #[cfg(test)] use pretty_assertions::assert_eq; /// Take a sequence of values and their deps, and return the values in /// order so that deps come before the dependants. /// /// Any deps that are not nodes are ignored and presumed to be nodes /// that do not need processing. /// /// Errors if there are duplicate values, unknown deps, or cycles. /// pub fn toposort_deps(inputs: Vec<(EcoString, Vec)>) -> Result, Error> { let mut graph = petgraph::Graph::<(), ()>::with_capacity(inputs.len(), inputs.len() * 5); let mut values = HashMap::with_capacity(inputs.len()); let mut indexes = HashMap::with_capacity(inputs.len()); for (value, _deps) in &inputs { let index = graph.add_node(()); let _ = indexes.insert(value.clone(), index); let _ = values.insert(index, value.clone()); } for (value, deps) in inputs { let &from_index = indexes.get(&value).expect("Finding index for value"); for &to_index in deps.into_iter().filter_map(|dep| indexes.get(&dep)) { let _ = graph.add_edge(from_index, to_index, ()); } } match petgraph::algo::toposort(&graph, None) { Err(e) => Err(Error::Cycle(import_cycle(e, &graph, values))), Ok(seq) => Ok(seq .into_iter() .map(|i| values.remove(&i).expect("Finding value for index")) .rev() .collect()), } } fn import_cycle( cycle: Cycle, graph: &petgraph::Graph<(), ()>, mut values: HashMap, ) -> Vec { let origin = cycle.node_id(); let mut path = vec![]; let _ = find_cycle(origin, origin, graph, &mut path, &mut HashSet::new()); path.iter() .map(|index| { values .remove(index) .expect("dep_tree::import_cycle(): cannot find values for index") }) .collect() } fn find_cycle( origin: NodeIndex, parent: NodeIndex, graph: &petgraph::Graph<(), ()>, path: &mut Vec, seen: &mut HashSet, ) -> bool { let _ = seen.insert(parent); for node in graph.neighbors_directed(parent, Direction::Outgoing) { if node == origin { path.push(node); return true; } if seen.contains(&node) { continue; } if find_cycle(origin, node, graph, path, seen) { path.push(node); return true; } } false } #[derive(Debug, PartialEq)] pub enum Error { Cycle(Vec), } #[cfg(test)] mod tests { use super::{assert_eq, *}; #[test] fn toposort_deps_test() { // All deps are nodes assert_eq!( toposort_deps(vec![ ("a".into(), vec!["b".into()]), ("c".into(), vec![]), ("b".into(), vec!["c".into()]) ]), Ok(vec!["c".into(), "b".into(), "a".into()]) ); // No deps assert_eq!( toposort_deps(vec![ ("no-deps-1".into(), vec![]), ("no-deps-2".into(), vec![]) ]), Ok(vec!["no-deps-1".into(), "no-deps-2".into(),]) ); // Some deps are not nodes (and thus are ignored) assert_eq!( toposort_deps(vec![ ("a".into(), vec!["b".into(), "z".into()]), ("b".into(), vec!["x".into()]) ]), Ok(vec!["b".into(), "a".into()]) ); } #[test] fn cycle_detection() { // a ---+ // ^ | // | v // +----+ assert_eq!( toposort_deps(vec![("a".into(), vec!["a".into()])]), Err(Error::Cycle(vec!["a".into()])) ); // a -> b -> c // ^ v // | | // +---------+ assert_eq!( toposort_deps(vec![ ("a".into(), vec!["b".into()]), ("b".into(), vec!["c".into()]), ("c".into(), vec!["a".into()]), ]), Err(Error::Cycle(vec!["c".into(), "b".into(), "a".into()])) ); // a -> b <- e // | | ^ // v v | // f c -> d assert_eq!( toposort_deps(vec![ ("a".into(), vec!["b".into()]), ("b".into(), vec!["c".into()]), ("c".into(), vec!["d".into()]), ("d".into(), vec!["e".into()]), ("e".into(), vec!["b".into()]), ("a".into(), vec!["f".into()]), ]), Err(Error::Cycle(vec![ "e".into(), "d".into(), "c".into(), "b".into(), ])) ); } } ================================================ FILE: compiler-core/src/dependency.rs ================================================ use std::{cell::RefCell, cmp::Reverse, collections::HashMap, rc::Rc}; use crate::{Error, Result, manifest}; use ecow::EcoString; use hexpm::{ Dependency, Release, version::{Range, Version}, }; use pubgrub::{Dependencies, Map}; use thiserror::Error; pub type PackageVersions = HashMap; type PubgrubRange = pubgrub::Range; pub fn resolve_versions( package_fetcher: &impl PackageFetcher, provided_packages: HashMap, root_name: EcoString, dependencies: Requirements, locked: &HashMap, ) -> Result where Requirements: Iterator, { tracing::info!("resolving_versions"); let root_version = Version::new(0, 0, 0); let requirements = root_dependencies(dependencies, locked)?; // Creating a map of all the required packages that have exact versions specified let exact_deps = &requirements .iter() .filter_map(|(name, dep)| parse_exact_version(dep.requirement.as_str()).map(|v| (name, v))) .map(|(name, version)| (name.clone(), version)) .collect(); let root = hexpm::Package { name: root_name.as_str().into(), repository: "local".into(), releases: vec![Release { version: root_version.clone(), outer_checksum: vec![], retirement_status: None, requirements, meta: (), }], }; let packages = pubgrub::resolve( &DependencyProvider::new(package_fetcher, provided_packages, root, locked, exact_deps), root_name.as_str().into(), root_version, ) .map_err(|error| Error::dependency_resolution_failed(error, root_name.clone()))? .into_iter() .filter(|(name, _)| name.as_str() != root_name.as_str()) .collect(); Ok(packages) } /** * Used to compare 2 versions of a package. */ pub type PackageVersionDiffs = HashMap; fn resolve_versions_diffs( package_fetcher: &impl PackageFetcher, versions: PackageVersions, check_major_versions: bool, ) -> PackageVersionDiffs { versions .iter() .filter_map(|(package, version)| { let Ok(hex_package) = package_fetcher.get_dependencies(package) else { return None; }; let latest = hex_package .releases .iter() .map(|release| &release.version) .filter(|version| !version.is_pre()) .max()?; // If we're checking for major version updates, only include the // package if a new major version is available. Otherwise, include // the package if there is any new version available. match check_major_versions { true => { if latest.major <= version.major { return None; } } false => { if latest <= version { return None; } } } Some((package.to_string(), (version.clone(), latest.clone()))) }) .collect() } /// Check for major version updates for direct dependencies that are being blocked by some version /// constraints. pub fn check_for_major_version_updates( manifest: &manifest::Manifest, package_fetcher: &impl PackageFetcher, ) -> PackageVersionDiffs { let versions: PackageVersions = manifest .packages .iter() .filter(|manifest_package| { manifest .requirements .iter() .any(|(required_pkg, _)| manifest_package.name == *required_pkg) }) .map(|manifest_pkg| (manifest_pkg.name.to_string(), manifest_pkg.version.clone())) .collect(); resolve_versions_diffs(package_fetcher, versions, true) } /// Check for version updates for direct and transitive dependencies that are being blocked by some version /// constraints. pub fn check_for_version_updates( manifest: &manifest::Manifest, package_fetcher: &impl PackageFetcher, ) -> PackageVersionDiffs { let versions = manifest .packages .iter() .filter(|manifest_package| { matches!( manifest_package.source, manifest::ManifestPackageSource::Hex { .. } ) }) .map(|manifest_pkg| (manifest_pkg.name.to_string(), manifest_pkg.version.clone())) .collect(); resolve_versions_diffs(package_fetcher, versions, false) } // If the string would parse to an exact version then return the version fn parse_exact_version(ver: &str) -> Option { let version = ver.trim(); let first_byte = version.as_bytes().first(); // Version is exact if it starts with an explicit == or a number if version.starts_with("==") || first_byte.is_some_and(|v| v.is_ascii_digit()) { let version = version.replace("==", ""); let version = version.as_str().trim(); Version::parse(version).ok() } else { None } } fn root_dependencies( base_requirements: Requirements, locked: &HashMap, ) -> Result, Error> where Requirements: Iterator, { // Record all of the already locked versions as hard requirements let mut requirements: HashMap<_, _> = locked .iter() .map(|(name, version)| { ( name.to_string(), Dependency { app: None, optional: false, repository: None, requirement: version.clone().into(), }, ) }) .collect(); for (name, range) in base_requirements { match locked.get(&name) { // If the package was not already locked then we can use the // specified version requirement without modification. None => { let _ = requirements.insert( name.into(), Dependency { app: None, optional: false, repository: None, requirement: range, }, ); } // If the version was locked we verify that the requirement is // compatible with the locked version. Some(locked_version) => { let compatible = range.to_pubgrub().contains(locked_version); if !compatible { return Err(Error::IncompatibleLockedVersion { error: format!( "{name} is specified with the requirement `{range}`, \ but it is locked to {locked_version}, which is incompatible.", ), }); } } }; } Ok(requirements) } pub trait PackageFetcher { fn get_dependencies(&self, package: &str) -> Result, PackageFetchError>; } #[derive(Debug, Error)] pub enum PackageFetchError { #[error("The package {0} was not found in the package repository")] NotFoundError(String), #[error("{0}")] ApiError(hexpm::ApiError), #[error("{0}")] FetchError(String), } impl PackageFetchError { pub fn fetch_error(err: T) -> Self { Self::FetchError(err.to_string()) } pub fn from_api_error(api_error: hexpm::ApiError, package: &str) -> Self { match &api_error { hexpm::ApiError::NotFound => Self::NotFoundError(package.to_string()), hexpm::ApiError::Json(_) | hexpm::ApiError::IncorrectOneTimePassword | hexpm::ApiError::OAuthRefreshTokenRejected | hexpm::ApiError::OAuthAccessDenied | hexpm::ApiError::OAuthTimeout | hexpm::ApiError::ExpiredToken | hexpm::ApiError::Io(_) | hexpm::ApiError::InvalidProtobuf(_) | hexpm::ApiError::UnexpectedResponse(_, _) | hexpm::ApiError::RateLimited | hexpm::ApiError::InvalidCredentials | hexpm::ApiError::InvalidPackageNameFormat(_) | hexpm::ApiError::IncorrectPayloadSignature | hexpm::ApiError::InvalidVersionFormat(_) | hexpm::ApiError::InvalidVersionRequirementFormat(_) | hexpm::ApiError::IncorrectChecksum | hexpm::ApiError::Forbidden | hexpm::ApiError::NotReplacing | hexpm::ApiError::LateModification => Self::ApiError(api_error), } } } #[derive(Debug)] pub struct DependencyProvider<'a, T: PackageFetcher> { packages: RefCell>, remote: &'a T, locked: &'a HashMap, // Map of packages where an exact version was requested // We need this because by default pubgrub checks exact version by checking if a version is between the exact // and the version 1 bump ahead. That default breaks on prerelease builds since a bump includes the whole patch exact_only: &'a HashMap, optional_dependencies: RefCell>>, } impl<'a, T> DependencyProvider<'a, T> where T: PackageFetcher, { fn new( remote: &'a T, mut packages: HashMap, root: hexpm::Package, locked: &'a HashMap, exact_only: &'a HashMap, ) -> Self { let _ = packages.insert(root.name.as_str().into(), root); Self { packages: RefCell::new(packages), locked, remote, exact_only, optional_dependencies: RefCell::new(Default::default()), } } /// Download information about the package from the registry into the local /// store. Does nothing if the packages are already known. /// /// Package versions are sorted from newest to oldest, with all pre-releases /// at the end to ensure that a non-prerelease version will be picked first /// if there is one. // fn ensure_package_fetched( // We would like to use `&mut self` but the pubgrub library enforces // `&self` with interop mutability. &self, name: &str, ) -> Result<(), PackageFetchError> { let mut packages = self.packages.borrow_mut(); if packages.get(name).is_none() { let package = self.remote.get_dependencies(name)?; // mut (therefore clone) is required here in order to sort the releases let mut package = (*package).clone(); // Sort the packages from newest to oldest, pres after all others package.releases.sort_by(|a, b| a.version.cmp(&b.version)); package.releases.reverse(); let (pre, mut norm): (_, Vec<_>) = package .releases .into_iter() .partition(|r| r.version.is_pre()); norm.extend(pre); package.releases = norm; let _ = packages.insert(name.into(), package); } Ok(()) } } type PackageName = String; pub type ResolutionError<'a, T> = pubgrub::PubGrubError>; impl pubgrub::DependencyProvider for DependencyProvider<'_, T> where T: PackageFetcher, { fn get_dependencies( &self, package: &Self::P, version: &Self::V, ) -> Result, Self::Err> { self.ensure_package_fetched(package)?; let packages = self.packages.borrow(); let release = match packages .get(package.as_str()) .into_iter() .flat_map(|p| p.releases.iter()) .find(|r| &r.version == version) { Some(release) => release, None => { return Ok(Dependencies::Unavailable(format!( "{package}@{version} is not available" ))); } }; // Only use retired versions if they have been locked if release.is_retired() && self.locked.get(package.as_str()) != Some(version) { return Ok(Dependencies::Unavailable(format!( "{package}@{version} is retired" ))); } let mut deps: Map = Default::default(); for (name, d) in &release.requirements { let mut range = d.requirement.to_pubgrub().clone(); let mut opt_deps = self.optional_dependencies.borrow_mut(); // if it's optional and it was not provided yet, store and skip if d.optional && !packages.contains_key(name.as_str()) { let _ = opt_deps .entry(name.into()) .and_modify(|stored_range| { *stored_range = range.intersection(stored_range); }) .or_insert(range); continue; } // if a now required dep was optional before, add back the constraints if let Some(other_range) = opt_deps.remove(name.as_str()) { range = range.intersection(&other_range); } let _ = deps.insert(name.clone(), range); } Ok(Dependencies::Available(deps)) } fn prioritize( &self, package: &Self::P, range: &Self::VS, _package_conflicts_counts: &pubgrub::PackageResolutionStatistics, ) -> Self::Priority { Reverse( self.packages .borrow() .get(package.as_str()) .cloned() .into_iter() .flat_map(|p| { p.releases .into_iter() .filter(|r| range.contains(&r.version)) }) .count(), ) } fn choose_version( &self, package: &Self::P, range: &Self::VS, ) -> std::result::Result, Self::Err> { self.ensure_package_fetched(package)?; let exact_package = self.exact_only.get(package); let potential_versions = self .packages .borrow() .get(package.as_str()) .cloned() .into_iter() .flat_map(move |p| { p.releases .into_iter() // if an exact version of a package is specified then we only want to allow that version as available .filter_map(move |release| match exact_package { Some(ver) => (ver == &release.version).then_some(release.version), _ => Some(release.version), }) }) .filter(|v| range.contains(v)); match potential_versions.clone().filter(|v| !v.is_pre()).max() { // Don't resolve to a pre-releaase package unless we *have* to Some(v) => Ok(Some(v)), None => Ok(potential_versions.max()), } } type P = PackageName; type V = Version; type VS = PubgrubRange; type Priority = Reverse; type M = String; type Err = PackageFetchError; } #[cfg(test)] mod tests { use hexpm::RetirementStatus; use crate::{ derivation_tree::DerivationTreePrinter, manifest::{Base16Checksum, ManifestPackage, ManifestPackageSource}, requirement, }; use super::*; struct Remote { deps: HashMap>, } impl PackageFetcher for Remote { fn get_dependencies(&self, package: &str) -> Result, PackageFetchError> { self.deps .get(package) .map(Rc::clone) .ok_or(PackageFetchError::NotFoundError(package.to_string())) } } fn make_remote() -> Remote { remote(vec![ ( "gleam_stdlib", vec![ release("0.1.0", vec![]), release("0.2.0", vec![]), release("0.2.2", vec![]), release("0.3.0", vec![]), ], ), ( "gleam_otp", vec![ release("0.1.0", vec![("gleam_stdlib", ">= 0.1.0")]), release("0.2.0", vec![("gleam_stdlib", ">= 0.1.0")]), release("0.3.0-rc1", vec![("gleam_stdlib", ">= 0.1.0")]), release("0.3.0-rc2", vec![("gleam_stdlib", ">= 0.1.0")]), ], ), ( "package_with_retired", vec![ release("0.1.0", vec![]), retired_release( "0.2.0", vec![], hexpm::RetirementReason::Security, "it's bad", ), ], ), ( "package_with_optional", vec![release_with_optional( "0.1.0", vec![], vec![("gleam_stdlib", ">= 0.1.0 and < 0.3.0")], )], ), ( "direct_pkg_with_major_version", vec![ release("0.1.0", vec![("gleam_stdlib", ">= 0.1.0 and < 0.3.0")]), release("1.0.0", vec![("gleam_stdlib", ">= 0.1.0 and < 0.3.0")]), release("1.1.0", vec![("gleam_stdlib", ">= 0.1.0 and < 0.3.0")]), ], ), ( "depends_on_old_version_of_direct_pkg", vec![release( "0.1.0", vec![("direct_pkg_with_major_version", ">= 0.1.0 and < 0.3.0")], )], ), ( "this_pkg_depends_on_indirect_pkg", vec![release( "0.1.0", vec![("indirect_pkg_with_major_version", ">= 0.1.0 and < 1.0.0")], )], ), ( "indirect_pkg_with_major_version", vec![ release("0.1.0", vec![("gleam_stdlib", ">= 0.1.0 and < 0.3.0")]), release("1.0.0", vec![("gleam_stdlib", ">= 0.1.0 and < 0.3.0")]), release("1.1.0", vec![("gleam_stdlib", ">= 0.1.0 and < 0.3.0")]), ], ), ]) } #[test] fn resolution_with_locked() { let locked_stdlib = ("gleam_stdlib".into(), Version::parse("0.1.0").unwrap()); let result = resolve_versions( &make_remote(), HashMap::new(), "app".into(), vec![("gleam_stdlib".into(), Range::new("~> 0.1".into()).unwrap())].into_iter(), &vec![locked_stdlib].into_iter().collect(), ) .unwrap(); assert_eq!( result, vec![("gleam_stdlib".into(), Version::parse("0.1.0").unwrap())] .into_iter() .collect() ); } #[test] fn resolution_without_deps() { let result = resolve_versions( &make_remote(), HashMap::new(), "app".into(), vec![].into_iter(), &vec![].into_iter().collect(), ) .unwrap(); assert_eq!(result, vec![].into_iter().collect()) } #[test] fn resolution_1_dep() { let result = resolve_versions( &make_remote(), HashMap::new(), "app".into(), vec![("gleam_stdlib".into(), Range::new("~> 0.1".into()).unwrap())].into_iter(), &vec![].into_iter().collect(), ) .unwrap(); assert_eq!( result, vec![("gleam_stdlib".into(), Version::try_from("0.3.0").unwrap())] .into_iter() .collect() ); } #[test] fn resolution_with_nested_deps() { let result = resolve_versions( &make_remote(), HashMap::new(), "app".into(), vec![("gleam_otp".into(), Range::new("~> 0.1".into()).unwrap())].into_iter(), &vec![].into_iter().collect(), ) .unwrap(); assert_eq!( result, vec![ ("gleam_otp".into(), Version::try_from("0.2.0").unwrap()), ("gleam_stdlib".into(), Version::try_from("0.3.0").unwrap()) ] .into_iter() .collect() ); } #[test] fn resolution_with_optional_deps() { let result = resolve_versions( &make_remote(), HashMap::new(), "app".into(), vec![( "package_with_optional".into(), Range::new("~> 0.1".into()).unwrap(), )] .into_iter(), &vec![].into_iter().collect(), ) .unwrap(); assert_eq!( result, vec![( "package_with_optional".into(), Version::try_from("0.1.0").unwrap() )] .into_iter() .collect() ); } #[test] fn resolution_with_optional_deps_explicitly_provided() { let result = resolve_versions( &make_remote(), HashMap::new(), "app".into(), vec![ ( "package_with_optional".into(), Range::new("~> 0.1".into()).unwrap(), ), ("gleam_stdlib".into(), Range::new("~> 0.1".into()).unwrap()), ] .into_iter(), &vec![].into_iter().collect(), ) .unwrap(); assert_eq!( result, vec![ ("gleam_stdlib".into(), Version::try_from("0.2.2").unwrap()), ( "package_with_optional".into(), Version::try_from("0.1.0").unwrap() ), ] .into_iter() .collect() ); } #[test] fn resolution_with_optional_deps_incompatible() { let result = resolve_versions( &make_remote(), HashMap::new(), "app".into(), vec![ ( "package_with_optional".into(), Range::new("~> 0.1".into()).unwrap(), ), ("gleam_stdlib".into(), Range::new("~> 0.3".into()).unwrap()), ] .into_iter(), &vec![].into_iter().collect(), ); assert!(result.is_err()); } #[test] fn resolution_with_optional_deps_required_by_nested_deps() { let result = resolve_versions( &make_remote(), HashMap::new(), "app".into(), vec![ ( "package_with_optional".into(), Range::new("~> 0.1".into()).unwrap(), ), ("gleam_otp".into(), Range::new("~> 0.1".into()).unwrap()), ] .into_iter(), &vec![].into_iter().collect(), ) .unwrap(); assert_eq!( result, vec![ ("gleam_stdlib".into(), Version::try_from("0.2.2").unwrap()), ("gleam_otp".into(), Version::try_from("0.2.0").unwrap()), ( "package_with_optional".into(), Version::try_from("0.1.0").unwrap() ), ] .into_iter() .collect() ); } #[test] fn resolution_with_optional_deps_keep_constraints() {} #[test] fn resolution_locked_to_older_version() { let result = resolve_versions( &make_remote(), HashMap::new(), "app".into(), vec![("gleam_otp".into(), Range::new("~> 0.1.0".into()).unwrap())].into_iter(), &vec![].into_iter().collect(), ) .unwrap(); assert_eq!( result, vec![ ("gleam_otp".into(), Version::try_from("0.1.0").unwrap()), ("gleam_stdlib".into(), Version::try_from("0.3.0").unwrap()) ] .into_iter() .collect() ); } #[test] fn resolution_retired_versions_not_used_by_default() { let result = resolve_versions( &make_remote(), HashMap::new(), "app".into(), vec![( "package_with_retired".into(), Range::new("> 0.0.0".into()).unwrap(), )] .into_iter(), &vec![].into_iter().collect(), ) .unwrap(); assert_eq!( result, vec![( "package_with_retired".into(), // Uses the older version that hasn't been retired Version::try_from("0.1.0").unwrap() ),] .into_iter() .collect() ); } #[test] fn resolution_retired_versions_can_be_used_if_locked() { let result = resolve_versions( &make_remote(), HashMap::new(), "app".into(), vec![( "package_with_retired".into(), Range::new("> 0.0.0".into()).unwrap(), )] .into_iter(), &vec![("package_with_retired".into(), Version::new(0, 2, 0))] .into_iter() .collect(), ) .unwrap(); assert_eq!( result, vec![( "package_with_retired".into(), // Uses the locked version even though it's retired Version::new(0, 2, 0) ),] .into_iter() .collect() ); } #[test] fn resolution_prerelease_can_be_selected() { let result = resolve_versions( &make_remote(), HashMap::new(), "app".into(), vec![( "gleam_otp".into(), Range::new("~> 0.3.0-rc1".into()).unwrap(), )] .into_iter(), &vec![].into_iter().collect(), ) .unwrap(); assert_eq!( result, vec![ ("gleam_stdlib".into(), Version::try_from("0.3.0").unwrap()), ("gleam_otp".into(), Version::try_from("0.3.0-rc2").unwrap()), ] .into_iter() .collect(), ); } #[test] fn resolution_exact_prerelease_can_be_selected() { let result = resolve_versions( &make_remote(), HashMap::new(), "app".into(), vec![("gleam_otp".into(), Range::new("0.3.0-rc1".into()).unwrap())].into_iter(), &vec![].into_iter().collect(), ) .unwrap(); assert_eq!( result, vec![ ("gleam_stdlib".into(), Version::try_from("0.3.0").unwrap()), ("gleam_otp".into(), Version::try_from("0.3.0-rc1").unwrap()), ] .into_iter() .collect(), ); } #[test] fn resolution_not_found_dep() { let err = resolve_versions( &make_remote(), HashMap::new(), "app".into(), vec![("unknown".into(), Range::new("~> 0.1".into()).unwrap())].into_iter(), &vec![].into_iter().collect(), ) .unwrap_err(); match err { Error::DependencyResolutionError(error) => assert_eq!( error, "An error occurred while choosing the version of unknown: The package unknown was not found in the package repository" ), _ => panic!("wrong error: {err}"), } } #[test] fn resolution_no_matching_version() { let _ = resolve_versions( &make_remote(), HashMap::new(), "app".into(), vec![("gleam_stdlib".into(), Range::new("~> 99.0".into()).unwrap())].into_iter(), &vec![].into_iter().collect(), ) .unwrap_err(); } #[test] fn resolution_locked_version_doesnt_satisfy_requirements() { let err = resolve_versions( &make_remote(), HashMap::new(), "app".into(), vec![( "gleam_stdlib".into(), Range::new("~> 0.1.0".into()).unwrap(), )] .into_iter(), &vec![("gleam_stdlib".into(), Version::new(0, 2, 0))] .into_iter() .collect(), ) .unwrap_err(); match err { Error::IncompatibleLockedVersion { error } => assert_eq!( error, "gleam_stdlib is specified with the requirement `~> 0.1.0`, but it is locked to 0.2.0, which is incompatible." ), _ => panic!("wrong error: {err}"), } } #[test] fn resolution_with_exact_dep() { let result = resolve_versions( &make_remote(), HashMap::new(), "app".into(), vec![("gleam_stdlib".into(), Range::new("0.1.0".into()).unwrap())].into_iter(), &vec![].into_iter().collect(), ) .unwrap(); assert_eq!( result, vec![("gleam_stdlib".into(), Version::try_from("0.1.0").unwrap())] .into_iter() .collect() ); } #[test] fn parse_exact_version_test() { assert_eq!( parse_exact_version("1.0.0"), Some(Version::parse("1.0.0").unwrap()) ); assert_eq!( parse_exact_version("==1.0.0"), Some(Version::parse("1.0.0").unwrap()) ); assert_eq!( parse_exact_version("== 1.0.0"), Some(Version::parse("1.0.0").unwrap()) ); assert_eq!(parse_exact_version("~> 1.0.0"), None); assert_eq!(parse_exact_version(">= 1.0.0"), None); } #[test] fn resolve_major_version_upgrades() { let manifest = manifest::Manifest { requirements: vec![ ( EcoString::from("package_depends_on_indirect_pkg"), requirement::Requirement::Hex { version: Range::new("> 0.1.0 and <= 1.0.0".into()).unwrap(), }, ), ( EcoString::from("direct_pkg_with_major_version"), requirement::Requirement::Hex { version: Range::new("> 0.1.0 and <= 2.0.0".into()).unwrap(), }, ), ( EcoString::from("depends_on_old_version_of_direct_pkg"), requirement::Requirement::Hex { version: Range::new("> 0.1.0 and <= 1.0.0".into()).unwrap(), }, ), ] .into_iter() .collect(), packages: vec![ ManifestPackage { name: "direct_pkg_with_major_version".into(), version: Version::parse("0.1.0").unwrap(), build_tools: ["gleam".into()].into(), otp_app: None, requirements: vec![], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![1, 2, 3]), }, }, ManifestPackage { name: "depends_on_old_version_of_direct_pkg".into(), version: Version::parse("0.1.0").unwrap(), build_tools: ["gleam".into()].into(), otp_app: None, requirements: vec!["direct_pkg_with_major_version".into()], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![1, 2, 3]), }, }, ManifestPackage { name: "pkg_depends_on_indirect_pkg".into(), version: Version::parse("0.1.0").unwrap(), build_tools: ["gleam".into()].into(), otp_app: None, requirements: vec!["indirect_pkg_with_major_version".into()], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![1, 2, 3]), }, }, ManifestPackage { name: "indirect_pkg_with_major_version".into(), version: Version::parse("0.1.0").unwrap(), build_tools: ["gleam".into()].into(), otp_app: None, requirements: vec![], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![1, 2, 3]), }, }, ], }; let result = check_for_major_version_updates(&manifest, &make_remote()); // indirect package with major version will not be in the result even though a major // version of it is available assert_eq!( result, vec![( "direct_pkg_with_major_version".into(), ( Version::try_from("0.1.0").unwrap(), Version::try_from("1.1.0").unwrap() ) ),] .into_iter() .collect() ); } fn retired_release( version: &str, requirements: Vec<(&str, &str)>, reason: hexpm::RetirementReason, message: &str, ) -> Release<()> { Release { retirement_status: Some(RetirementStatus { reason, message: message.into(), }), ..release(version, requirements) } } fn release(version: &str, requirements: Vec<(&str, &str)>) -> Release<()> { release_with_optional(version, requirements, vec![]) } fn release_with_optional( version: &str, requirements: Vec<(&str, &str)>, optional_requirements: Vec<(&str, &str)>, ) -> Release<()> { let mut all_requirements = HashMap::new(); for (name, range) in requirements { let requirement = Range::new(range.to_string()).unwrap(); let dependency = Dependency { requirement, optional: false, app: None, repository: None, }; let _ = all_requirements.insert(name.to_string(), dependency); } for (name, range) in optional_requirements { let requirement = Range::new(range.to_string()).unwrap(); let dependency = Dependency { requirement, optional: true, app: None, repository: None, }; let _ = all_requirements.insert(name.to_string(), dependency); } Release { version: Version::try_from(version).unwrap(), requirements: all_requirements, retirement_status: None, outer_checksum: vec![1, 2, 3], meta: (), } } fn remote(dependencies: Vec<(&str, Vec>)>) -> Remote { let mut deps = HashMap::new(); for (package, releases) in dependencies { let _ = deps.insert( package.into(), Rc::new(hexpm::Package { name: package.into(), repository: "hexpm".into(), releases, }), ); } Remote { deps } } #[test] fn resolution_error_message() { let remote = remote(vec![ ( "wibble", vec![ release("1.2.0", vec![("wobble", ">= 1.0.0 and < 2.0.0")]), release("1.3.0", vec![("wobble", ">= 2.0.0 and < 3.0.0")]), ], ), ( "wobble", vec![ release("1.1.0", vec![("woo", ">= 1.0.0 and < 2.0.0")]), release("2.0.0", vec![("waa", ">= 1.0.0 and < 2.0.0")]), ], ), ( "woo", vec![release("1.0.0", vec![]), release("2.0.0", vec![])], ), ( "waa", vec![release("1.0.0", vec![]), release("2.0.0", vec![])], ), ]); let result = resolve_versions( &remote, HashMap::new(), "app".into(), vec![ ( "wibble".into(), Range::new(">= 1.0.0 and < 2.0.0".into()).unwrap(), ), ( "woo".into(), Range::new(">= 2.0.0 and < 3.0.0".into()).unwrap(), ), ( "waa".into(), Range::new(">= 2.0.0 and < 3.0.0".into()).unwrap(), ), ] .into_iter(), &vec![].into_iter().collect(), ); if let Err(Error::DependencyResolutionNoSolution { root_package_name, derivation_tree, }) = result { let message = crate::error::wrap( &DerivationTreePrinter::new(root_package_name, derivation_tree.0).print(), ); insta::assert_snapshot!(message) } else { panic!("expected a resolution error message") } } } ================================================ FILE: compiler-core/src/derivation_tree.rs ================================================ use crate::error::wrap; use ecow::EcoString; use hexpm::version::Version; use im::HashSet; use itertools::Itertools; use petgraph::Direction; use petgraph::algo::all_simple_paths; use petgraph::graph::NodeIndex; use petgraph::prelude::StableGraph; use pubgrub::External; use pubgrub::{DerivationTree, Derived, Ranges}; use std::collections::HashMap; use std::hash::RandomState; use std::ops::Bound::{Excluded, Included, Unbounded}; use std::sync::Arc; macro_rules! wrap_format { ($($tts:tt)*) => { wrap(&format!($($tts)*)) } } /// Makes a best effort at turning a derivation tree into a nice readable error /// message. /// pub struct DerivationTreePrinter { derivation_tree: DerivationTree, String>, /// The name of the root package for which we're trying to add new /// dependencies. This is the starting point we use to find and report /// dependency conflicts! root_package_name: EcoString, /// The graph of dependencies built from the derivation tree. The nodes are /// packages and the arcs connecting them represent a dependency: /// /// ```txt /// wibble ---- (range1, range2) ---> wobble /// ``` /// /// Means "package wibble with version `range1` requires package wobble /// with version `range2`". /// dependencies: StableGraph, Ranges)>, /// A map going from package name to its index in the dependencies graph. /// nodes: HashMap, } impl DerivationTreePrinter { pub fn new( root_package_name: EcoString, mut derivation_tree: DerivationTree, String>, ) -> Self { // We start by trying to simplify the derivation tree as much as // possible. derivation_tree.collapse_no_versions(); simplify_derivation_tree(&mut derivation_tree); let mut dependencies = StableGraph::new(); let mut nodes = HashMap::new(); build_dependencies_graph(&derivation_tree, &mut dependencies, &mut nodes); DerivationTreePrinter { root_package_name, derivation_tree, dependencies, nodes, } } pub fn print(&self) -> String { self.pretty_explanation() .unwrap_or_else(|| self.fallback_explanation()) } /// Tries and print a pretty explanation for the given resolution tree. /// If for some reason our heuristic to produce a nice error message fails /// we return `None` so we can still produce a good enough error message! /// fn pretty_explanation(&self) -> Option { let root_package_index = self.nodes.get(self.root_package_name.as_str())?; let unresolvable_nodes = self.find_unresolvable_nodes(); if unresolvable_nodes.is_empty() { return None; } let mut unresolvable = vec![]; for unresolvable_node in unresolvable_nodes { let paths = all_simple_paths::, _, RandomState>( &self.dependencies, *root_package_index, unresolvable_node, 0, None, ); let package = self .dependencies .node_weight(unresolvable_node) .expect("package is in the graph"); let heading = format!("There's no compatible version of `{package}`:"); let explanation = paths.sorted().map(|path| self.pretty_path(path)).join("\n"); unresolvable.push(format!("{heading}\n{explanation}")); } Some(unresolvable.join("\n\n")) } fn pretty_path(&self, path: Vec) -> String { let (you, dependee, rest) = match path.as_slice() { [you, dependee, rest @ ..] => (you, dependee, rest), _ => panic!("path with less than two nodes"), }; let dependee_name = self .dependencies .node_weight(*dependee) .expect("path node is in the graph"); let (_, dependee_range) = self .ranges_between(you, dependee) .expect("path edge is in the graph"); let mut message = format!( " - You require {dependee_name} {}", pretty_range(dependee_range) ); let mut previous = dependee; for next in rest { let previous_name = self .dependencies .node_weight(*previous) .expect("path node is in the graph"); let next_name = self .dependencies .node_weight(*next) .expect("path node is in the graph"); let (_, next_range) = self .ranges_between(previous, next) .expect("path edge is in the graph"); message.push_str(&format!( "\n - {previous_name} requires {next_name} {}", pretty_range(next_range) )); previous = next; } message } fn find_unresolvable_nodes(&self) -> Vec { self.dependencies .node_indices() .filter(|node_index| { self.dependencies .neighbors_directed(*node_index, Direction::Incoming) .count() > 1 }) .sorted() .collect_vec() } fn ranges_between( &self, one: &NodeIndex, other: &NodeIndex, ) -> Option<(&Ranges, &Ranges)> { let edge = self.dependencies.find_edge(*one, *other)?; self.dependencies .edge_weight(edge) .map(|(one, other)| (one, other)) } /// A good enough explanation in case we're not able to produce anything /// nicer. fn fallback_explanation(&self) -> String { let mut conflicting_packages = HashSet::new(); collect_conflicting_packages(&self.derivation_tree, &mut conflicting_packages); wrap_format!( "Unable to find compatible versions for \ the version constraints in your gleam.toml. \ The conflicting packages are: {} ", conflicting_packages .into_iter() .map(|s| format!("- {s}")) .join("\n") ) } } fn build_dependencies_graph( derivation_tree: &DerivationTree, String>, graph: &mut StableGraph, Ranges)>, nodes: &mut HashMap>, ) { match derivation_tree { DerivationTree::External(External::FromDependencyOf( one, range_one, other, range_other, )) => { let one_index = match nodes.get(one) { Some(index) => *index, None => { let index = graph.add_node(one.clone()); let _ = nodes.insert(one.clone(), index); index } }; let other_index = match nodes.get(other) { Some(index) => *index, None => { let index = graph.add_node(other.clone()); let _ = nodes.insert(other.clone(), index); index } }; let edges = graph.edges_connecting(one_index, other_index); let edge_weight = match edges.peekable().peek() { Some(edge) => { let (old_range_one, old_range_other) = edge.weight(); ( range_one.union(old_range_one), range_other.union(old_range_other), ) } None => (range_one.clone(), range_other.clone()), }; let _ = graph.update_edge(one_index, other_index, edge_weight); } DerivationTree::External(_) => (), DerivationTree::Derived(Derived { cause1, cause2, .. }) => { build_dependencies_graph(cause1, graph, nodes); build_dependencies_graph(cause2, graph, nodes); } } } /// This function collapses adjacent levels of a derivation tree that are all /// relative to the same dependency. /// /// By default a derivation tree might have many nodes for a specific package, /// each node referring to a specific version range. For example: /// /// - package_wibble `>= 1.0.0 and < 1.1.0` requires package_wobble `>= 1.1.0` /// - package_wibble `>= 1.1.0 and < 1.2.0` requires package_wobble `>= 1.2.0` /// - package_wibble `1.1.0` requires package_wobble `>= 1.1.0` /// /// This level of fine-grained detail would be quite overwhelming in the vast /// majority of cases so we're fine with collapsing all these details into a /// single node taking the union of all the ranges that are there: /// /// - package_wibble `>= 1.0.0 and < 1.2.0` requires package_wobble `>= 1.1.0` /// /// This way we can print an error message that is way more concise and still /// informative about what went wrong, at the cost of /// fn simplify_derivation_tree(derivation_tree: &mut DerivationTree, String>) { match derivation_tree { DerivationTree::External(_) => {} DerivationTree::Derived(derived) => { simplify_derivation_tree(Arc::make_mut(&mut derived.cause1)); simplify_derivation_tree(Arc::make_mut(&mut derived.cause2)); simplify_derivation_tree_outer(derivation_tree); } } } fn simplify_derivation_tree_outer( derivation_tree: &mut DerivationTree, String>, ) { match derivation_tree { DerivationTree::External(_) => {} DerivationTree::Derived(derived) => { match ( Arc::make_mut(&mut derived.cause1), Arc::make_mut(&mut derived.cause2), ) { ( DerivationTree::External(External::FromDependencyOf( package, package_range, required_package, required_package_range, )), DerivationTree::External(External::FromDependencyOf( maybe_package, other_package_range, maybe_required_package, other_required_package_range, )), ) if package == maybe_package && required_package == maybe_required_package => { *derivation_tree = DerivationTree::External(External::FromDependencyOf( package.clone(), package_range.union(other_package_range), required_package.clone(), required_package_range.union(other_required_package_range), )) } _ => {} } } } } fn collect_conflicting_packages<'dt>( derivation_tree: &'dt DerivationTree, String>, conflicting_packages: &mut HashSet<&'dt String>, ) { match derivation_tree { DerivationTree::External(external) => match external { External::NotRoot(package, _) | External::NoVersions(package, _) | External::Custom(package, _, _) => { let _ = conflicting_packages.insert(package); } External::FromDependencyOf(package, _, dep_package, _) => { let _ = conflicting_packages.insert(package); let _ = conflicting_packages.insert(dep_package); } }, DerivationTree::Derived(derived) => { collect_conflicting_packages(&derived.cause1, conflicting_packages); collect_conflicting_packages(&derived.cause2, conflicting_packages); } } } fn pretty_range(range: &Ranges) -> String { range .iter() .map(|(lower, upper)| match (lower, upper) { (Included(lower), Included(upper)) if lower == upper => format!("{lower}"), (Included(lower), Included(upper)) => format!(">= {lower} and <= {upper}"), (Included(lower), Excluded(upper)) => format!(">= {lower} and < {upper}"), (Excluded(lower), Included(upper)) => format!("> {lower} and <= {upper}"), (Excluded(lower), Excluded(upper)) => format!("> {lower} and < {upper}"), (Included(version), Unbounded) => format!(">= {version}"), (Excluded(version), Unbounded) => format!("> {version}"), (Unbounded, Included(version)) => format!("<= {version}"), (Unbounded, Excluded(version)) => format!("< {version}"), (Unbounded, Unbounded) => "".into(), }) .join(" or ") } ================================================ FILE: compiler-core/src/diagnostic.rs ================================================ use std::collections::HashMap; use camino::Utf8PathBuf; pub use codespan_reporting::diagnostic::{LabelStyle, Severity}; use codespan_reporting::{diagnostic::Label as CodespanLabel, files::SimpleFiles}; use ecow::EcoString; use termcolor::Buffer; use crate::ast::SrcSpan; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Level { Error, Warning, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct Label { pub text: Option, pub span: SrcSpan, } impl Label { fn to_codespan_label(&self, fileid: usize, style: LabelStyle) -> CodespanLabel { let label = CodespanLabel::new( style, fileid, (self.span.start as usize)..(self.span.end as usize), ); match &self.text { None => label, Some(text) => label.with_message(text.clone()), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct ExtraLabel { pub src_info: Option<(EcoString, Utf8PathBuf)>, pub label: Label, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct Location { pub src: EcoString, pub path: Utf8PathBuf, pub label: Label, pub extra_labels: Vec, } // TODO: split this into locationed diagnostics and locationless diagnostics #[derive(Debug, Clone, PartialEq, Eq)] pub struct Diagnostic { pub title: String, pub text: String, pub level: Level, pub location: Option, pub hint: Option, } impl Diagnostic { pub fn write(&self, buffer: &mut Buffer) { use std::io::Write; match &self.location { Some(location) => self.write_span(location, buffer), None => self.write_title(buffer), }; if !self.text.is_empty() { writeln!(buffer, "{}", self.text).expect("write text"); } if let Some(hint) = &self.hint { writeln!(buffer, "Hint: {hint}").expect("write hint"); } } fn write_span(&self, location: &Location, buffer: &mut Buffer) { let mut file_map = HashMap::new(); let mut files = SimpleFiles::new(); let main_location_path = location.path.as_str(); let main_location_src = location.src.as_str(); let main_file_id = files.add(main_location_path, main_location_src); let _ = file_map.insert(main_location_path, main_file_id); let mut labels = vec![ location .label .to_codespan_label(main_file_id, LabelStyle::Primary), ]; location .extra_labels .iter() .map(|label| { let (location_src, location_path) = match &label.src_info { Some(info) => (info.0.as_str(), info.1.as_str()), _ => (main_location_src, main_location_path), }; match file_map.get(location_path) { None => { let file_id = files.add(location_path, location_src); let _ = file_map.insert(location_path, file_id); label .label .to_codespan_label(file_id, LabelStyle::Secondary) } Some(i) => label.label.to_codespan_label(*i, LabelStyle::Secondary), } }) .for_each(|label| labels.push(label)); let severity = match self.level { Level::Error => Severity::Error, Level::Warning => Severity::Warning, }; let diagnostic = codespan_reporting::diagnostic::Diagnostic::new(severity) .with_message(&self.title) .with_labels(labels); let config = codespan_reporting::term::Config::default(); codespan_reporting::term::emit(buffer, &config, &files, &diagnostic) .expect("write_diagnostic"); } fn write_title(&self, buffer: &mut Buffer) { use std::io::Write; use termcolor::{Color, ColorSpec, WriteColor}; let (kind, colour) = match self.level { Level::Error => ("error", Color::Red), Level::Warning => ("warning", Color::Yellow), }; buffer .set_color(ColorSpec::new().set_bold(true).set_fg(Some(colour))) .expect("write_title_color1"); write!(buffer, "{kind}").expect("write_title_kind"); buffer .set_color(ColorSpec::new().set_bold(true)) .expect("write_title_color2"); write!(buffer, ": {}\n\n", self.title).expect("write_title_title"); buffer .set_color(&ColorSpec::new()) .expect("write_title_reset"); } } ================================================ FILE: compiler-core/src/docs/printer.rs ================================================ use std::{ collections::{HashMap, HashSet}, ops::Deref, }; use ecow::{EcoString, eco_format}; use itertools::Itertools; use crate::{ ast::{ ArgNames, CustomType, Function, Publicity, RecordConstructorArg, SrcSpan, TypeAlias, TypedArg, TypedDefinitions, TypedModuleConstant, TypedRecordConstructor, }, docvec, pretty::{Document, Documentable, break_, join, line, nil, zero_width_string}, type_::{ Deprecation, PRELUDE_MODULE_NAME, PRELUDE_PACKAGE_NAME, Type, TypeVar, printer::{Names, PrintMode}, }, }; use super::{ Dependency, DependencyKind, DocsValues, TypeConstructor, TypeConstructorArg, TypeDefinition, markdown_documentation, source_links::SourceLinker, text_documentation, }; #[derive(Clone, Copy)] pub struct PrintOptions { pub print_highlighting: bool, pub print_html: bool, } impl PrintOptions { pub fn all() -> Self { Self { print_highlighting: true, print_html: true, } } } pub struct Printer<'a> { options: PrintOptions, names: &'a Names, package: EcoString, module: EcoString, /// Type variables which don't have annotated names and we have generated /// names for. printed_type_variables: HashMap, /// Names of type variables that we have generated or printed in a given /// definition. This ensures that we have no duplicate generated names. printed_type_variable_names: HashSet, /// An incrementing number used to generate the next type variable name. /// `0` becomes `a`, `1` becomes `b`, etc. next_type_variable_id: u64, dependencies: &'a HashMap, } impl Printer<'_> { pub fn new<'a>( package: EcoString, module: EcoString, names: &'a Names, dependencies: &'a HashMap, ) -> Printer<'a> { Printer { options: PrintOptions::all(), names, package, module, printed_type_variables: HashMap::new(), printed_type_variable_names: HashSet::new(), next_type_variable_id: 0, dependencies, } } // This is currently only used in the tests, though it might be useful in // application code in future. If it is needed, simply remove this attribute. #[cfg(test)] pub fn set_options(&mut self, options: PrintOptions) { self.options = options; } pub fn type_definitions<'a>( &mut self, source_links: &SourceLinker, definitions: &'a TypedDefinitions, ) -> Vec> { let mut type_definitions = vec![]; for CustomType { location, name, publicity, constructors, documentation, deprecation, opaque, parameters, .. } in &definitions.custom_types { if !publicity.is_public() { continue; } type_definitions.push(TypeDefinition { name, definition: print(self.custom_type(name, parameters, constructors, *opaque)), raw_definition: self .raw(|this| this.custom_type(name, parameters, constructors, *opaque)), documentation: markdown_documentation(documentation), text_documentation: text_documentation(documentation), deprecation_message: match deprecation { Deprecation::NotDeprecated => "".to_string(), Deprecation::Deprecated { message } => message.to_string(), }, constructors: if *opaque { Vec::new() } else { constructors .iter() .map(|constructor| TypeConstructor { definition: print(self.record_constructor(constructor)), raw_definition: self.raw(|this| this.record_constructor(constructor)), documentation: markdown_documentation(&constructor.documentation), text_documentation: text_documentation(&constructor.documentation), arguments: constructor .arguments .iter() .filter_map(|arg| arg.label.as_ref().map(|(_, label)| (arg, label))) .map(|(argument, label)| TypeConstructorArg { name: label.trim_end().to_string(), doc: markdown_documentation(&argument.doc), text_documentation: text_documentation(&argument.doc), }) .filter(|arg| !arg.doc.is_empty()) .collect(), }) .collect() }, source_url: source_links.url(*location), opaque: *opaque, }) } for TypeAlias { location, alias: name, parameters, type_, publicity, documentation, deprecation, .. } in &definitions.type_aliases { if !publicity.is_public() { continue; } type_definitions.push(TypeDefinition { name, definition: print(self.type_alias(name, type_, parameters).group()), raw_definition: self.raw(|this| this.type_alias(name, type_, parameters).group()), documentation: markdown_documentation(documentation), text_documentation: text_documentation(documentation), constructors: vec![], source_url: source_links.url(*location), deprecation_message: match deprecation { Deprecation::NotDeprecated => "".to_string(), Deprecation::Deprecated { message } => message.to_string(), }, opaque: false, }) } type_definitions.sort(); type_definitions } /// Print a definition without HTML highlighting, such as for search data fn raw<'a, F>(&mut self, definition: F) -> String where F: FnOnce(&mut Self) -> Document<'a>, { let options = self.options; // Turn off highlighting for this definition self.options = PrintOptions { print_highlighting: false, print_html: false, }; let result = print(definition(self)); // Restore previous options self.options = options; format!("```\n{result}\n```") } pub fn value_definitions<'a>( &mut self, source_links: &SourceLinker, definitions: &'a TypedDefinitions, ) -> Vec> { let mut value_definitions = vec![]; for Function { location, name, arguments, publicity, deprecation, return_type, documentation, .. } in &definitions.functions { let Some((_, name)) = name else { continue }; if !publicity.is_public() { continue; } // Ensure that any type variables we printed in previous definitions don't // affect our printing of this definition. Two type variables in different // definitions can have the same name without clashing. self.printed_type_variable_names.clear(); self.next_type_variable_id = 0; value_definitions.push(DocsValues { name, definition: print(self.function_signature(name, arguments, return_type)), raw_definition: self .raw(|this| this.function_signature(name, arguments, return_type)), documentation: markdown_documentation(documentation), text_documentation: text_documentation(documentation), source_url: source_links.url(*location), deprecation_message: match deprecation { Deprecation::NotDeprecated => "".to_string(), Deprecation::Deprecated { message } => message.to_string(), }, }) } for TypedModuleConstant { documentation, location, publicity, name, type_, deprecation, .. } in &definitions.constants { if !publicity.is_public() { continue; } value_definitions.push(DocsValues { name, definition: print(self.constant(name, type_)), raw_definition: self.raw(|this| this.constant(name, type_)), documentation: markdown_documentation(documentation), text_documentation: text_documentation(documentation), source_url: source_links.url(*location), deprecation_message: match deprecation { Deprecation::NotDeprecated => "".to_string(), Deprecation::Deprecated { message } => message.to_string(), }, }) } value_definitions.sort(); value_definitions } fn custom_type<'a>( &mut self, name: &'a str, parameters: &'a [(SrcSpan, EcoString)], constructors: &'a [TypedRecordConstructor], opaque: bool, ) -> Document<'a> { let arguments = if parameters.is_empty() { nil() } else { Self::wrap_arguments( parameters .iter() .map(|(_, parameter)| self.variable(parameter)), ) }; let keywords = if opaque { "pub opaque type " } else { "pub type " }; let type_head = docvec![self.keyword(keywords), self.title(name), arguments]; if constructors.is_empty() || opaque { return type_head; } let constructors = constructors .iter() .map(|constructor| { line() .append(self.record_constructor(constructor)) .nest(INDENT) }) .collect_vec(); docvec![type_head, " {", constructors, line(), "}"] } pub fn record_constructor<'a>( &mut self, constructor: &'a TypedRecordConstructor, ) -> Document<'a> { if constructor.arguments.is_empty() { return self.title(&constructor.name); } let arguments = constructor.arguments.iter().map( |RecordConstructorArg { label, type_, .. }| match label { Some((_, label)) => self .variable(label) .append(": ") .append(self.type_(type_, PrintMode::Normal)), None => self.type_(type_, PrintMode::Normal), }, ); let arguments = Self::wrap_arguments(arguments); docvec![self.title(&constructor.name), arguments].group() } fn type_alias<'a>( &mut self, name: &'a str, type_: &Type, parameters: &[(SrcSpan, EcoString)], ) -> Document<'a> { let parameters = if parameters.is_empty() { nil() } else { let arguments = parameters .iter() .map(|(_, parameter)| self.variable(parameter)); Self::wrap_arguments(arguments) }; docvec![ self.keyword("pub type "), self.title(name), parameters, " =", line() .append(self.type_(type_, PrintMode::ExpandAliases)) .nest(INDENT) ] } fn constant<'a>(&mut self, name: &'a str, type_: &Type) -> Document<'a> { self.register_local_type_variable_names(type_); docvec![ self.keyword("pub const "), self.title(name), ": ", self.type_(type_, PrintMode::Normal) ] } fn function_signature<'a>( &mut self, name: &'a str, arguments: &'a [TypedArg], return_type: &Type, ) -> Document<'a> { for argument in arguments { self.register_local_type_variable_names(&argument.type_); } self.register_local_type_variable_names(return_type); let arguments = if arguments.is_empty() { "()".to_doc() } else { Self::wrap_arguments(arguments.iter().map(|argument| { let name = self.variable(self.argument_name(argument)); docvec![name, ": ", self.type_(&argument.type_, PrintMode::Normal)].group() })) }; docvec![ self.keyword("pub fn "), self.title(name), arguments, " -> ", self.type_(return_type, PrintMode::Normal) ] .group() } fn argument_name<'a>(&self, arg: &'a TypedArg) -> Document<'a> { match &arg.names { ArgNames::Named { name, .. } => name.to_doc(), ArgNames::NamedLabelled { label, name, .. } => docvec![label, " ", name], // We remove the underscore from discarded function arguments since we don't want to // expose this kind of detail: https://github.com/gleam-lang/gleam/issues/2561 ArgNames::Discard { name, .. } => match name.strip_prefix('_').unwrap_or(name) { "" => "arg".to_doc(), name => name.to_doc(), }, ArgNames::LabelledDiscard { label, name, .. } => { docvec![label, " ", name.strip_prefix('_').unwrap_or(name).to_doc()] } } } fn wrap_arguments<'a>(arguments: impl IntoIterator>) -> Document<'a> { break_("(", "(") .append(join(arguments, break_(",", ", "))) .nest_if_broken(INDENT) .append(break_(",", "")) .append(")") } fn type_arguments<'a>(arguments: impl IntoIterator>) -> Document<'a> { break_("", "") .append(join(arguments, break_(",", ", "))) .nest_if_broken(INDENT) .append(break_(",", "")) .group() .surround("(", ")") } fn type_(&mut self, type_: &Type, print_mode: PrintMode) -> Document<'static> { match type_ { Type::Named { package, module, name, arguments, publicity, .. } => { let name = match print_mode { // If we are printing a type for a type alias, and the alias // is reexporting an internal type, we want to show that it // is aliasing that internal type, rather than showing it as // aliasing itself. PrintMode::ExpandAliases if *package == self.package => { self.named_type_name(publicity, package, module, name) } // If we are printing a type alias which aliases an internal // type from a different package, we still want to print the // public name for that type. If we are not printing a type // alias at all, we also want to use the public name. PrintMode::ExpandAliases | PrintMode::Normal => { // If we are using a reexported internal type, we want to // print it public name, whether it is from this package // or otherwise. if let Some((module, alias)) = self.names.reexport_alias(module.clone(), name.clone()) { self.named_type_name(&Publicity::Public, package, module, alias) } else { self.named_type_name(publicity, package, module, name) } } }; if arguments.is_empty() { name } else { name.append(Self::type_arguments( arguments .iter() .map(|argument| self.type_(argument, PrintMode::Normal)), )) } } Type::Fn { arguments, return_ } => docvec![ self.keyword("fn"), Self::type_arguments( arguments .iter() .map(|argument| self.type_(argument, PrintMode::Normal)) ), " -> ", self.type_(return_, PrintMode::Normal) ], Type::Tuple { elements } => docvec![ "#", Self::type_arguments( elements .iter() .map(|element| self.type_(element, PrintMode::Normal)) ), ], Type::Var { type_ } => match type_.as_ref().borrow().deref() { TypeVar::Link { type_ } => self.type_(type_, PrintMode::Normal), TypeVar::Unbound { id } | TypeVar::Generic { id } => { let name = self.type_variable(*id); self.variable(name) } }, } } fn type_variable(&mut self, id: u64) -> EcoString { if let Some(name) = self.names.get_type_variable(id) { return name.clone(); } if let Some(name) = self.printed_type_variables.get(&id) { return name.clone(); } loop { let name = self.next_letter(); if !self.printed_type_variable_names.contains(&name) { _ = self.printed_type_variable_names.insert(name.clone()); _ = self.printed_type_variables.insert(id, name.clone()); return name; } } } // Copied from the `next_letter` method of the `type_::printer`. fn next_letter(&mut self) -> EcoString { let alphabet_length = 26; let char_offset = b'a'; let mut chars = vec![]; let mut n; let mut rest = self.next_type_variable_id; loop { n = rest % alphabet_length; rest = rest / alphabet_length; chars.push((n as u8 + char_offset) as char); if rest == 0 { break; } rest -= 1 } self.next_type_variable_id += 1; chars.into_iter().rev().collect() } fn named_type_name( &self, publicity: &Publicity, package: &str, module: &str, name: &EcoString, ) -> Document<'static> { // There's no documentation page for the prelude if package == PRELUDE_PACKAGE_NAME && module == PRELUDE_MODULE_NAME { return self.title(name); } // Internal types don't get linked if !publicity.is_public() { return docvec![self.comment("@internal ".to_doc()), self.title(name)]; } // Linking to a type within the same page if package == self.package && module == self.module { return self.link(eco_format!("#{name}"), self.title(name), None); } // Linking to a module within the package if package == self.package { // If we are linking to the current package, we might be viewing the // documentation locally and so we need to generate a relative link. let mut module_path = module.split('/').peekable(); let mut current_module = self.module.split('/'); // The documentation page for the final segment of the module is just // an html file by itself, so it doesn't form part of the path and doesn't // need to be backtracked using `..`. let module_name = module_path.next_back().unwrap_or(module); _ = current_module.next_back(); // The two modules might have some sharer part of the path, which we // don't need to traverse back through. However, if the two modules are // something like `gleam/a/wibble/wobble` and `gleam/b/wibble/wobble`, // the `wibble` folders are two different folders despite being at the // same position with the same name. let mut encountered_different_path = false; let mut path = Vec::new(); // Calculate how far backwards in the directory tree we need to walk for segment in current_module { // If this is still part of the shared path, we can just skip it: // no need to go back and forth through the same directory in the // path! if !encountered_different_path && module_path.peek() == Some(&segment) { _ = module_path.next(); } else { encountered_different_path = true; path.push(".."); } } // Once we have walked backwards, we walk forwards again to the correct // page. path.extend(module_path); path.push(module_name); let qualified_name = docvec![ self.variable(EcoString::from(module_name)), ".", self.title(name) ]; let title = eco_format!("{module}.{{type {name}}}"); return self.link( eco_format!("{path}.html#{name}", path = path.join("/")), qualified_name, Some(title), ); } let module_name = module.split('/').next_back().unwrap_or(module); let qualified_name = docvec![ self.variable(EcoString::from(module_name)), ".", self.title(name) ]; let title = eco_format!("{module}.{{type {name}}}"); // We can't reliably link to documentation if the type is from a path // or git dependency match self.dependencies.get(package) { Some(Dependency { kind: DependencyKind::Hex, version, }) => self.link( eco_format!("https://hexdocs.pm/{package}/{version}/{module}.html#{name}"), qualified_name, Some(title), ), Some(_) | None => self.span_with_title(qualified_name, title), } } /// Walk a type and register all the type variable names which occur within /// it. This is to ensure that when generating type variable names for /// unannotated arguments, we don't print any that clash with existing names. /// /// We preregister all names before actually printing anything, because we /// could run into code like this: /// /// ```gleam /// pub fn wibble(_, _: a) -> b {} /// ``` /// /// If we did not preregister the type variables in this case, we would end /// up printing `fn wibble(_: a, _: a) -> b` which is not correct. /// fn register_local_type_variable_names(&mut self, type_: &Type) { match type_ { Type::Named { arguments, .. } => { for argument in arguments { self.register_local_type_variable_names(argument); } } Type::Fn { arguments, return_ } => { for argument in arguments { self.register_local_type_variable_names(argument); } self.register_local_type_variable_names(return_); } Type::Var { type_ } => match type_.borrow().deref() { TypeVar::Link { type_ } => self.register_local_type_variable_names(type_), TypeVar::Unbound { id } | TypeVar::Generic { id } => { if let Some(name) = self.names.get_type_variable(*id) { _ = self.printed_type_variable_names.insert(name.clone()); } } }, Type::Tuple { elements } => { for element in elements { self.register_local_type_variable_names(element); } } } } fn keyword<'a>(&self, keyword: impl Documentable<'a>) -> Document<'a> { self.colour_span(keyword, "keyword") } fn comment<'a>(&self, name: impl Documentable<'a>) -> Document<'a> { self.colour_span(name, "comment") } fn title<'a>(&self, name: impl Documentable<'a>) -> Document<'a> { self.colour_span(name, "title") } fn variable<'a>(&self, name: impl Documentable<'a>) -> Document<'a> { self.colour_span(name, "variable") } fn colour_span<'a>( &self, name: impl Documentable<'a>, colour_class: &'static str, ) -> Document<'a> { if !self.options.print_highlighting { return name.to_doc(); } name.to_doc().surround( zero_width_string(eco_format!(r#""#)), zero_width_string("".into()), ) } fn link<'a>( &self, href: EcoString, name: impl Documentable<'a>, title: Option, ) -> Document<'a> { if !self.options.print_html { return name.to_doc(); } let opening_tag = if let Some(title) = title { eco_format!(r#""#) } else { eco_format!(r#""#) }; name.to_doc().surround( zero_width_string(opening_tag), zero_width_string("".into()), ) } fn span_with_title<'a>(&self, name: impl Documentable<'a>, title: EcoString) -> Document<'a> { if !self.options.print_html { return name.to_doc(); } name.to_doc().surround( zero_width_string(eco_format!(r#""#)), zero_width_string("".into()), ) } } const MAX_COLUMNS: isize = 65; const INDENT: isize = 2; fn print(doc: Document<'_>) -> String { doc.to_pretty_string(MAX_COLUMNS) } ================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__canonical_link.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: "compile_with_markdown_pages(config, modules, pages,\nCompileWithMarkdownPagesOpts::default())" --- //// LICENSE.html LICENSE · test_project_name · v0.1.0

LICENSE

Lucy
says
trans
rights
now
Search Document //// app.html app · test_project_name · v0.1.0

app

Values

pub fn one() -> Int

Here is some documentation

Lucy
says
trans
rights
now
Search Document //// gleam/otp/actor.html gleam/otp/actor · test_project_name · v0.1.0

gleam/otp/actor

Values

pub fn one() -> Int

Here is some documentation

Lucy
says
trans
rights
now
Search Document ================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__constructor_with_long_types_and_many_fields.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ---- SOURCE CODE -- option.gleam pub type Option(a) -- main.gleam import option pub type Uri { Uri( scheme: option.Option(String), userinfo: option.Option(String), host: option.Option(String), port: option.Option(Int), path: String, query: option.Option(String), fragment: option.Option(String) ) } ---- TYPES --- Uri
pub type Uri {
  Uri(
    scheme: option.Option(String),
    userinfo: option.Option(String),
    host: option.Option(String),
    port: option.Option(Int),
    path: String,
    query: option.Option(String),
    fragment: option.Option(String),
  )
}
-- CONSTRUCTORS
Uri(
  scheme: option.Option(String),
  userinfo: option.Option(String),
  host: option.Option(String),
  port: option.Option(Int),
  path: String,
  query: option.Option(String),
  fragment: option.Option(String),
)
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__constructor_with_long_types_and_many_fields_that_need_splitting.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ---- SOURCE CODE -- option.gleam pub type Option(a) -- main.gleam import option pub type TypeWithAVeryLoooooooooooooooooooongName pub type Wibble { Wibble( wibble: #(TypeWithAVeryLoooooooooooooooooooongName, TypeWithAVeryLoooooooooooooooooooongName), wobble: option.Option(String), ) } ---- TYPES --- TypeWithAVeryLoooooooooooooooooooongName
pub type TypeWithAVeryLoooooooooooooooooooongName
--- Wibble
pub type Wibble {
  Wibble(
    wibble: #(
      TypeWithAVeryLoooooooooooooooooooongName,
      TypeWithAVeryLoooooooooooooooooooongName,
    ),
    wobble: option.Option(String),
  )
}
-- CONSTRUCTORS
Wibble(
  wibble: #(
    TypeWithAVeryLoooooooooooooooooooongName,
    TypeWithAVeryLoooooooooooooooooooongName,
  ),
  wobble: option.Option(String),
)
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__discarded_arguments_are_not_shown.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: "compile(config, modules)" --- //// app.html app · test_project_name · v0.1.0

app

Values

pub fn discard(discarded: a) -> Int
Lucy
says
trans
rights
now
Search Document ================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__docs_of_a_type_constructor_are_not_used_by_the_following_function.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: "compile(config, modules)" --- //// app.html app · test_project_name · v0.1.0

app

Types

pub type Wibble {
  Wobble(wabble: Int)
}

Constructors

  • Wobble(wabble: Int)

    Arguments

    wabble

    Documentation!!

Values

pub fn main() -> a
Lucy
says
trans
rights
now
Search Document ================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__function_uses_reexport_of_internal_type.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ---- SOURCE CODE -- thepackage/internal.gleam pub type Internal -- main.gleam import thepackage/internal pub type External = internal.Internal pub fn do_thing(value: internal.Internal) -> External { value } ---- TYPES --- External
pub type External =
  @internal Internal
---- VALUES --- do_thing
pub fn do_thing(value: External) -> External
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__function_uses_reexport_of_internal_type_in_other_module.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ---- SOURCE CODE -- thepackage/internal.gleam pub type Internal -- thepackage/something.gleam import thepackage/internal pub type External = internal.Internal -- main.gleam import thepackage/something pub fn do_thing(value: something.External) { value } ---- VALUES --- do_thing
pub fn do_thing(value: something.External) -> something.External
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__generated_type_variables.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ---- SOURCE CODE -- main.gleam pub fn wibble(_a, _b, _c, _d) { todo } ---- VALUES --- wibble
pub fn wibble(a: a, b: b, c: c, d: d) -> e
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__generated_type_variables_do_not_take_into_account_other_definitions.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ---- SOURCE CODE -- main.gleam pub fn wibble(_a: a, _b: b, _c: c) -> d { todo } pub fn identity(x) { x } ---- VALUES --- identity
pub fn identity(x: a) -> a
--- wibble
pub fn wibble(a: a, b: b, c: c) -> d
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__generated_type_variables_mixed_with_existing_variables.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ---- SOURCE CODE -- main.gleam pub fn wibble(_a: b, _b: a, _c, _d) { todo } ---- VALUES --- wibble
pub fn wibble(a: b, b: a, c: c, d: d) -> e
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__generated_type_variables_with_existing_variables_coming_afterwards.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ---- SOURCE CODE -- main.gleam pub fn wibble(_a, _b, _c: b, _d: a) { todo } ---- VALUES --- wibble
pub fn wibble(a: c, b: d, c: b, d: a) -> e
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__hello_docs.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: "compile(config, modules)" --- //// app.html app · test_project_name · v0.1.0

app

Values

pub fn one() -> Int

Here is some documentation

Lucy
says
trans
rights
now
Search Document ================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__highlight_constant_definition.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ---- SOURCE CODE -- main.gleam pub const x = 22 ---- VALUES --- x
pub const x: Int
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__highlight_custom_type.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ---- SOURCE CODE -- main.gleam pub type Wibble(a, b) { Wibble(a, i: Int) Wobble(b: b, c: String) } ---- TYPES --- Wibble
pub type Wibble(a, b) {
  Wibble(a, i: Int)
  Wobble(b: b, c: String)
}
-- CONSTRUCTORS
Wibble(a, i: Int)
Wobble(b: b, c: String)
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__highlight_function_definition.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ---- SOURCE CODE -- main.gleam pub fn wibble(list: List(Int), generic: a, function: fn(a) -> b) -> #(a, b) { todo } ---- VALUES --- wibble
pub fn wibble(
  list: List(Int),
  generic: a,
  function: fn(a) -> b,
) -> #(a, b)
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__highlight_opaque_custom_type.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ---- SOURCE CODE -- main.gleam pub opaque type Wibble(a, b) { Wibble(a, i: Int) Wobble(b: b, c: String) } ---- TYPES --- Wibble
pub opaque type Wibble(a, b)
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__highlight_type_alias.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ---- SOURCE CODE -- main.gleam pub type Option(a) = Result(a, Nil) ---- TYPES --- Option
pub type Option(a) =
  Result(a, Nil)
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__ignored_argument_is_called_arg.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: "compile(config, modules)" --- //// app.html app · test_project_name · v0.1.0

app

Values

pub fn one(arg: a) -> Int
Lucy
says
trans
rights
now
Search Document ================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__internal_definitions_are_not_included.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: "compile(config, modules)" --- //// app.html app · test_project_name · v0.1.0

app

Lucy
says
trans
rights
now
Search Document ================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__internal_type_reexport_in_different_module.snap ================================================ --- source: compiler-core/src/docs/tests.rs assertion_line: 1097 expression: output snapshot_kind: text --- ---- SOURCE CODE -- other.gleam @internal pub type Internal -- main.gleam import other pub type External = other.Internal ---- TYPES --- External
pub type External =
  @internal Internal
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__internal_type_reexport_in_same_module.snap ================================================ --- source: compiler-core/src/docs/tests.rs assertion_line: 1083 expression: output snapshot_kind: text --- ---- SOURCE CODE -- main.gleam @internal pub type Internal pub type External = Internal ---- TYPES --- External
pub type External =
  @internal Internal
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__internal_type_reexport_in_same_module_as_parameter.snap ================================================ --- source: compiler-core/src/docs/tests.rs assertion_line: 1056 expression: output snapshot_kind: text --- ---- SOURCE CODE -- main.gleam @internal pub type Internal pub type External = List(Internal) ---- TYPES --- External
pub type External =
  List(@internal Internal)
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__internal_type_reexport_in_same_module_as_parameter_colours.snap ================================================ --- source: compiler-core/src/docs/tests.rs assertion_line: 1070 expression: output snapshot_kind: text --- ---- SOURCE CODE -- main.gleam @internal pub type Internal pub type External = List(Internal) ---- TYPES --- External
pub type External =
  List(@internal Internal)
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__link_to_type_in_different_module.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ---- SOURCE CODE -- gleam/dict.gleam pub type Dict(a, b) -- main.gleam import gleam/dict pub fn make_dict() -> dict.Dict(a, b) { todo } ---- VALUES --- make_dict
pub fn make_dict() -> dict.Dict(a, b)
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__link_to_type_in_different_module_from_nested_module.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ---- SOURCE CODE -- gleam/dict.gleam pub type Dict(a, b) -- gleam/dynamic/decode.gleam import gleam/dict pub fn decode_dict() -> dict.Dict(a, b) { todo } ---- VALUES --- decode_dict
pub fn decode_dict() -> dict.Dict(a, b)
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__link_to_type_in_different_module_from_nested_module_with_shared_path.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ---- SOURCE CODE -- gleam/dynamic.gleam pub type Dynamic -- gleam/dynamic/decode.gleam import gleam/dynamic pub type Dynamic = dynamic.Dynamic ---- TYPES --- Dynamic
pub type Dynamic =
  dynamic.Dynamic
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__link_to_type_in_different_package.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ---- SOURCE CODE -- gleam/dict.gleam pub type Dict(a, b) -- main.gleam import gleam/dict pub fn make_dict() -> dict.Dict(a, b) { todo } ---- VALUES --- make_dict
pub fn make_dict() -> dict.Dict(a, b)
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__link_to_type_in_same_module.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ---- SOURCE CODE -- main.gleam pub type Dict(a, b) pub fn new() -> Dict(a, b) { todo } ---- TYPES --- Dict
pub type Dict(a, b)
---- VALUES --- new
pub fn new() -> Dict(a, b)
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__long_function_with_no_arguments_parentheses_are_not_split.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ---- SOURCE CODE -- main.gleam pub fn aaaaaaaaaaaaaaaaaaaaaaaaaaaa() -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { todo } ---- VALUES --- aaaaaaaaaaaaaaaaaaaaaaaaaaaa
pub fn aaaaaaaaaaaaaaaaaaaaaaaaaaaa() -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__long_function_wrapping.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: "compile(config, modules)" --- //// app.html app · test_project_name · v0.1.0

app

Types

pub type Option(t) {
  Some(t)
  None
}

Constructors

  • Some(t)
  • None

Values

pub fn lazy_or(
  first: Option(a),
  second: fn() -> Option(a),
) -> Option(a)

Returns the first value if it is Some, otherwise evaluates the given function for a fallback value.

Lucy
says
trans
rights
now
Search Document ================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__markdown_code_from_function_comment_is_trimmed.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: "compile(config, modules)" --- //// app.html app · test_project_name · v0.1.0

app

Values

pub fn indentation_test() -> a

Here’s an example code snippet:

wibble
  |> wobble
Lucy
says
trans
rights
now
Search Document ================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__markdown_code_from_module_comment_is_trimmed.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: "compile(config, modules)" --- //// app.html app · test_project_name · v0.1.0

app

Here’s an example code snippet:

wibble
  |> wobble
Lucy
says
trans
rights
now
Search Document ================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__markdown_code_from_standalone_pages_is_not_trimmed.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: "compile_with_markdown_pages(config, vec![], pages,\nCompileWithMarkdownPagesOpts::default())" --- //// one.html one · test_project_name · v0.1.0

This is an example code snippet that should be indented

pub fn indentation_test() {
  todo as "This line should be indented by two spaces"
}
Lucy
says
trans
rights
now
Search Document ================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__no_hex_publish.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: "compile_with_markdown_pages(config, modules, pages,\nCompileWithMarkdownPagesOpts { hex_publish: Some(DocContext::Build) })" --- //// LICENSE.html LICENSE · test_project_name · v0.1.0

LICENSE

Lucy
says
trans
rights
now
Search Document //// app.html app · test_project_name · v0.1.0

app

Values

pub fn one() -> Int

Here is some documentation

Lucy
says
trans
rights
now
Search Document //// gleam/otp/actor.html gleam/otp/actor · test_project_name · v0.1.0

gleam/otp/actor

Values

pub fn one() -> Int

Here is some documentation

Lucy
says
trans
rights
now
Search Document ================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__no_link_to_type_in_git_dependency.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ---- SOURCE CODE -- gleam/dict.gleam pub type Dict(a, b) -- main.gleam import gleam/dict pub fn make_dict() -> dict.Dict(a, b) { todo } ---- VALUES --- make_dict
pub fn make_dict() -> dict.Dict(a, b)
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__no_link_to_type_in_path_dependency.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ---- SOURCE CODE -- gleam/dict.gleam pub type Dict(a, b) -- main.gleam import gleam/dict pub fn make_dict() -> dict.Dict(a, b) { todo } ---- VALUES --- make_dict
pub fn make_dict() -> dict.Dict(a, b)
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__no_links_to_prelude_types.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ---- SOURCE CODE -- main.gleam pub fn int_to_string(i: Int) -> String { todo } ---- VALUES --- int_to_string
pub fn int_to_string(i: Int) -> String
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__output_of_search_data_json.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: json --- {"items":[{"type":"module","parentTitle":"gleam/option","title":"gleam/option","doc":"","ref":"gleam/option.html"},{"type":"type","parentTitle":"gleam/option","title":"Option","doc":"`Option` represents a value that may be present or not. `Some` means the value is present, `None` means the value is not.","ref":"gleam/option.html#Option"},{"type":"value","parentTitle":"gleam/option","title":"unwrap","doc":"Extracts the value from an `Option`, returning a default value if there is none.","ref":"gleam/option.html#unwrap"},{"type":"value","parentTitle":"gleam/dynamic/decode","title":"bool","doc":"A decoder that decodes `Bool` values.\n\n # Examples\n\n \n let result = decode.run(dynamic.from(True), decode.bool)\n assert result == Ok(True)\n \n","ref":"gleam/dynamic/decode.html#bool"}],"proglang":"gleam"} ================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__print_qualified_names_from_other_modules.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ---- SOURCE CODE -- gleam/option.gleam pub type Option(t) { Some(t) None } -- main.gleam import gleam/option.{type Option, Some, None} pub fn from_option(o: Option(t), e: e) -> Result(t, e) { case o { Some(t) -> Ok(t) None -> Error(e) } } ---- VALUES --- from_option
pub fn from_option(o: option.Option(t), e: e) -> Result(t, e)
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__print_type_variables_in_function_signatures.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ---- SOURCE CODE -- main.gleam pub type Dict(key, value) pub fn insert(dict: Dict(key, value), key: key, value: value) -> Dict(key, value) { dict } ---- TYPES --- Dict
pub type Dict(key, value)
---- VALUES --- insert
pub fn insert(
  dict: Dict(key, value),
  key: key,
  value: value,
) -> Dict(key, value)
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__public_type_reexport_in_different_internal_module.snap ================================================ --- source: compiler-core/src/docs/tests.rs assertion_line: 1111 expression: output snapshot_kind: text --- ---- SOURCE CODE -- thepackage/internal/other.gleam pub type Internal -- main.gleam import thepackage/internal/other pub type External = other.Internal ---- TYPES --- External
pub type External =
  @internal Internal
================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__search_item_for_constant.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ------ SOURCE CODE /// Reverses a `List`, and returns the list in reverse. /// ``` /// reverse([1, 2, 3]) /// // [3, 2, 1] /// ``` pub fn reverse(list: List(a), out: List(a)) -> List(a) { case list { [] -> out [first, ..rest] -> reverse(rest, [first, ..out]) } } ------------------------------------ TITLE: main PARENT TITLE: main TYPE: Module REFERENCE: main.html CONTENT: ------------------------------------ TITLE: reverse PARENT TITLE: module TYPE: Value REFERENCE: module.html#reverse CONTENT: ``` pub fn reverse(list: List(a), out: List(a)) -> List(a) ``` Reverses a `List`, and returns the list in reverse. reverse([1, 2, 3]) // [3, 2, 1] Synonyms: module.reverse module reverse ------------------------------------ ================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__search_item_for_custom_type.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ------ SOURCE CODE /// # The `Option` type /// Represents an optional value, either `Some` or `None`. /// If it is None, the value is absent /// [Read more](https://example.com) pub type Option(inner) { /// Here is some /// documentation for the `Some` constructor Some( /// And even documentation on a **field**! value: inner ) None } ------------------------------------ TITLE: main PARENT TITLE: main TYPE: Module REFERENCE: main.html CONTENT: ------------------------------------ TITLE: Option PARENT TITLE: module TYPE: Type REFERENCE: module.html#Option CONTENT: ``` pub type Option(inner) { Some(value: inner) None } ``` # The `Option` type Represents an optional value, either `Some` or `None`. If it is None, the value is absent [Read more](https://example.com) ``` Some(value: inner) ``` Here is some documentation for the `Some` constructor value And even documentation on a **field**! ``` None ``` Synonyms: module.Option module Option ------------------------------------ ================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__search_item_for_function.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ------ SOURCE CODE /// Pi is the ration between a circle's **radius** and its /// *circumference*. Pretty cool! pub const pi = 3.14 ------------------------------------ TITLE: main PARENT TITLE: main TYPE: Module REFERENCE: main.html CONTENT: ------------------------------------ TITLE: pi PARENT TITLE: module TYPE: Value REFERENCE: module.html#pi CONTENT: ``` pub const pi: Float ``` Pi is the ration between a circle's **radius** and its *circumference*. Pretty cool! Synonyms: module.pi module pi ------------------------------------ ================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__search_item_for_type_alias.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ------ SOURCE CODE /// This is a type alias to a list /// of integer values. /// ## Examples /// ``` /// // Examples /// ``` pub type IntList = List(Int) ------------------------------------ TITLE: main PARENT TITLE: main TYPE: Module REFERENCE: main.html CONTENT: ------------------------------------ TITLE: IntList PARENT TITLE: module TYPE: Type REFERENCE: module.html#IntList CONTENT: ``` pub type IntList = List(Int) ``` This is a type alias to a list of integer values. ## Examples // Examples Synonyms: module.IntList module IntList ------------------------------------ ================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__tables.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: "compile(config, modules)" --- //// app.html app · test_project_name · v0.1.0

app

Values

pub fn one() -> Int
heading 1heading 2
row 1 cell 1row 1 cell 2
row 2 cell 1row 2 cell 2
Lucy
says
trans
rights
now
Search Document ================================================ FILE: compiler-core/src/docs/snapshots/gleam_core__docs__tests__use_reexport_from_other_package.snap ================================================ --- source: compiler-core/src/docs/tests.rs expression: output --- ---- SOURCE CODE -- some_package/internal.gleam pub type Internal -- some_package/api.gleam import some_package/internal pub type External = internal.Internal -- main.gleam import some_package/api pub fn do_thing(value: api.External) { value } ---- VALUES --- do_thing
pub fn do_thing(value: api.External) -> api.External
================================================ FILE: compiler-core/src/docs/source_links.rs ================================================ use crate::{ ast::SrcSpan, build, config::{PackageConfig, Repository}, line_numbers::LineNumbers, paths::ProjectPaths, }; use camino::{Utf8Component, Utf8Path, Utf8PathBuf}; pub struct SourceLinker { line_numbers: LineNumbers, url_pattern: Option<(String, String)>, } impl SourceLinker { pub fn new( paths: &ProjectPaths, project_config: &PackageConfig, module: &build::Module, ) -> Self { let path = paths .src_directory() .join(module.name.as_str()) .strip_prefix(paths.root()) .expect("path is not in root") .with_extension("gleam"); let path_in_repo = match project_config .repository .as_ref() .map(|r| r.path()) .unwrap_or_default() { Some(repo_path) => to_url_path(&Utf8PathBuf::from(repo_path).join(path)), _ => to_url_path(&path), } .unwrap_or_default(); let tag = project_config.tag_for_version(&project_config.version); let url_pattern = project_config .repository .as_ref() .map(|r| match r { Repository::GitHub { user, repo, .. } => Some(( format!("https://github.com/{user}/{repo}/blob/{tag}/{path_in_repo}#L"), "-L".into(), )), Repository::GitLab { user, repo, .. } => Some(( format!("https://gitlab.com/{user}/{repo}/-/blob/{tag}/{path_in_repo}#L"), "-".into(), )), Repository::BitBucket { user, repo, .. } => Some(( format!("https://bitbucket.com/{user}/{repo}/src/{tag}/{path_in_repo}#lines-"), ":".into(), )), Repository::Codeberg { user, repo, .. } => Some(( format!("https://codeberg.org/{user}/{repo}/src/tag/{tag}/{path_in_repo}#L"), "-".into(), )), Repository::SourceHut { user, repo, .. } => Some(( format!("https://git.sr.ht/~{user}/{repo}/tree/{tag}/item/{path_in_repo}#L"), "-".into(), )), Repository::Tangled { user, repo, .. } => Some(( format!("https://tangled.sh/{user}/{repo}/tree/{tag}/{path_in_repo}#L"), "-".into(), )), Repository::Gitea { user, repo, host, .. } | Repository::Forgejo { user, repo, host, .. } => { let string_host = host.to_string(); let cleaned_host = string_host.trim_end_matches('/'); Some(( format!("{cleaned_host}/{user}/{repo}/src/tag/{tag}/{path_in_repo}#L",), "-L".into(), )) } Repository::Custom { .. } => None, }) .unwrap_or_default(); SourceLinker { line_numbers: LineNumbers::new(&module.code), url_pattern, } } pub fn url(&self, span: SrcSpan) -> String { match &self.url_pattern { Some((base, line_sep)) => { let start_line = self.line_numbers.line_number(span.start); let end_line = self.line_numbers.line_number(span.end); if start_line == end_line { format!("{base}{start_line}") } else { format!("{base}{start_line}{line_sep}{end_line}") } } None => "".into(), } } } fn to_url_path(path: &Utf8Path) -> Option { let mut buf = String::new(); for c in path.components() { if let Utf8Component::Normal(s) = c { buf.push_str(s); } buf.push('/'); } let _ = buf.pop(); Some(buf) } ================================================ FILE: compiler-core/src/docs/tests.rs ================================================ use std::{ collections::{HashMap, HashSet}, time::SystemTime, }; use super::{ Dependency, DependencyKind, DocumentationConfig, SearchData, SearchItem, SearchItemType, SearchProgrammingLanguage, printer::{PrintOptions, Printer}, source_links::SourceLinker, }; use crate::{ build::{ self, Mode, NullTelemetry, Origin, PackageCompiler, StaleTracker, TargetCodegenConfiguration, }, config::{DocsPage, PackageConfig, Repository}, docs::{DocContext, search_item_for_module, search_item_for_type, search_item_for_value}, io::{FileSystemWriter, memory::InMemoryFileSystem}, paths::ProjectPaths, type_, uid::UniqueIdGenerator, version::COMPILER_VERSION, warning::WarningEmitter, }; use camino::Utf8PathBuf; use ecow::{EcoString, eco_format}; use hexpm::version::Version; use http::Uri; use itertools::Itertools; use serde_json::to_string as serde_to_string; #[derive(Default)] struct CompileWithMarkdownPagesOpts { hex_publish: Option, } fn compile_with_markdown_pages( config: PackageConfig, modules: Vec<(&str, &str)>, markdown_pages: Vec<(&str, &str)>, opts: CompileWithMarkdownPagesOpts, ) -> EcoString { let fs = InMemoryFileSystem::new(); for (name, src) in modules { fs.write(&Utf8PathBuf::from(format!("/src/{name}")), src) .unwrap(); } // We're saving the pages under a different `InMemoryFileSystem` for these // tests so we don't have to juggle with borrows and lifetimes. // The package compiler is going to take ownership of `fs` but later // `generate_html` also needs a `FileSystemReader` to go and read the // markdown pages' content. let pages_fs = InMemoryFileSystem::new(); for (title, src) in markdown_pages.iter() { pages_fs .write(&Utf8PathBuf::from(format!("{title}.md")), src) .unwrap(); } let ids = UniqueIdGenerator::new(); let mut type_manifests = im::HashMap::new(); let mut defined_modules = im::HashMap::new(); let warnings = WarningEmitter::null(); let target = TargetCodegenConfiguration::Erlang { app_file: None }; let root = Utf8PathBuf::from("/"); let build = root.join("build"); let lib = root.join("lib"); let paths = ProjectPaths::new(root.clone()); let mut compiler = PackageCompiler::new(&config, Mode::Dev, &root, &build, &lib, &target, ids, fs); compiler.write_entrypoint = false; compiler.write_metadata = false; compiler.compile_beam_bytecode = true; let mut modules = compiler .compile( &warnings, &mut type_manifests, &mut defined_modules, &mut StaleTracker::default(), &mut HashSet::new(), &NullTelemetry, ) .unwrap() .modules; for module in &mut modules { module.attach_doc_and_module_comments(); } let docs_pages = markdown_pages .into_iter() .map(|(title, _)| DocsPage { title: (*title).into(), path: format!("{title}.html"), source: format!("{title}.md").into(), }) .collect_vec(); super::generate_html( &paths, DocumentationConfig { package_config: &config, dependencies: HashMap::new(), analysed: &modules, docs_pages: &docs_pages, rendering_timestamp: SystemTime::UNIX_EPOCH, context: if let Some(doc_context) = opts.hex_publish { doc_context } else { DocContext::HexPublish }, }, pages_fs, ) .into_iter() .filter(|file| file.path.extension() == Some("html")) .sorted_by(|a, b| a.path.cmp(&b.path)) .flat_map(|file| { Some(format!( "//// {}\n\n{}\n\n", file.path.as_str(), file.content .text()? .replace(COMPILER_VERSION, "GLEAM_VERSION_HERE") )) }) .collect::() .chars() .collect() } pub fn compile(config: PackageConfig, modules: Vec<(&str, &str)>) -> EcoString { compile_with_markdown_pages( config, modules, vec![], CompileWithMarkdownPagesOpts::default(), ) } fn compile_documentation( module_name: &str, module_src: &str, modules: Vec<(&str, &str, &str)>, dependency_kind: DependencyKind, options: PrintOptions, ) -> EcoString { let module = type_::tests::compile_module(module_name, module_src, None, modules.clone()) .expect("Module should compile successfully"); let mut config = PackageConfig::default(); config.name = "thepackage".into(); let paths = ProjectPaths::new("/".into()); let build_module = build::Module { name: "main".into(), code: module_src.into(), mtime: SystemTime::now(), input_path: "/".into(), origin: Origin::Src, ast: module, extra: Default::default(), dependencies: Default::default(), }; let source_links = SourceLinker::new(&paths, &config, &build_module); let module = build_module.ast; let dependencies = modules .iter() .map(|(package, _, _)| { ( EcoString::from(*package), Dependency { version: Version::new(1, 0, 0), kind: dependency_kind, }, ) }) .collect(); let mut printer = Printer::new( module.type_info.package.clone(), module.name.clone(), &module.names, &dependencies, ); printer.set_options(options); let types = printer.type_definitions(&source_links, &module.definitions); let values = printer.value_definitions(&source_links, &module.definitions); let mut output = EcoString::new(); output.push_str("---- SOURCE CODE\n"); for (_package, name, src) in modules { output.push_str(&format!("-- {name}.gleam\n{src}\n\n")); } output.push_str("-- "); output.push_str(module_name); output.push_str(".gleam\n"); output.push_str(module_src); if !types.is_empty() { output.push_str("\n\n---- TYPES"); } for type_ in types { output.push_str("\n\n--- "); output.push_str(type_.name); if !type_.documentation.is_empty() { output.push('\n'); output.push_str(&type_.documentation); } output.push_str("\n
");
        output.push_str(&type_.definition);
        output.push_str("
"); if !type_.constructors.is_empty() { output.push_str("\n\n-- CONSTRUCTORS"); } for constructor in type_.constructors { output.push_str("\n\n"); if !constructor.documentation.is_empty() { output.push_str(&constructor.documentation); output.push('\n'); } output.push_str("
");
            output.push_str(&constructor.definition);
            output.push_str("
"); } } if !values.is_empty() { output.push_str("\n\n---- VALUES"); } for value in values { output.push_str("\n\n--- "); output.push_str(value.name); if !value.documentation.is_empty() { output.push('\n'); output.push_str(&value.documentation); } output.push_str("\n
");
        output.push_str(&value.definition);
        output.push_str("
"); } output } macro_rules! assert_documentation { ($src:literal $(,)?) => { assert_documentation!($src, PrintOptions::all()); }; ($src:literal, $options:expr $(,)?) => { let output = compile_documentation("main", $src, Vec::new(), DependencyKind::Hex, $options); insta::assert_snapshot!(output); }; ($(($name:expr, $module_src:literal)),+, $src:literal $(,)?) => { let output = compile_documentation( "main", $src, vec![$(("thepackage", $name, $module_src)),*], DependencyKind::Hex, PrintOptions::all(), ); insta::assert_snapshot!(output); }; ($(($name:expr, $module_src:literal)),+, $src:literal, $options:expr $(,)?) => { let output = compile_documentation( "main", $src, vec![$(("thepackage", $name, $module_src)),*], DependencyKind::Hex, $options, ); insta::assert_snapshot!(output); }; ($(($name:expr, $module_src:literal)),+, $main_module:literal, $src:literal, $options:expr $(,)?) => { let output = compile_documentation( $main_module, $src, vec![$(("thepackage", $name, $module_src)),*], DependencyKind::Hex, $options, ); insta::assert_snapshot!(output); }; ($(($package:expr, $name:expr, $module_src:literal)),+, $src:literal $(,)?) => { let output = compile_documentation( "main", $src, vec![$(($package, $name, $module_src)),*], DependencyKind::Hex, PrintOptions::all(), ); insta::assert_snapshot!(output); }; ($(($package:expr, $name:expr, $module_src:literal)),+, $src:literal, $options:expr $(,)?) => { let output = compile_documentation( "main", $src, vec![$(($package, $name, $module_src)),*], DependencyKind::Hex, $options, ); insta::assert_snapshot!(output); }; (git: $(($package:expr, $name:expr, $module_src:literal)),+, $src:literal, $options:expr $(,)?) => { let output = compile_documentation( "main", $src, vec![$(($package, $name, $module_src)),*], DependencyKind::Git, $options, ); insta::assert_snapshot!(output); }; (path: $(($package:expr, $name:expr, $module_src:literal)),+, $src:literal, $options:expr $(,)?) => { let output = compile_documentation( "main", $src, vec![$(($package, $name, $module_src)),*], DependencyKind::Path, $options, ); insta::assert_snapshot!(output); }; } #[test] fn hello_docs() { let mut config = PackageConfig::default(); config.name = EcoString::from("test_project_name"); let modules = vec![( "app.gleam", r#" /// Here is some documentation pub fn one() { 1 } "#, )]; insta::assert_snapshot!(compile(config, modules)); } #[test] fn ignored_argument_is_called_arg() { let mut config = PackageConfig::default(); config.name = EcoString::from("test_project_name"); let modules = vec![("app.gleam", "pub fn one(_) { 1 }")]; insta::assert_snapshot!(compile(config, modules)); } // https://github.com/gleam-lang/gleam/issues/2347 #[test] fn tables() { let mut config = PackageConfig::default(); config.name = EcoString::from("test_project_name"); let modules = vec![( "app.gleam", r#" /// | heading 1 | heading 2 | /// |--------------|--------------| /// | row 1 cell 1 | row 1 cell 2 | /// | row 2 cell 1 | row 2 cell 2 | /// pub fn one() { 1 } "#, )]; insta::assert_snapshot!(compile(config, modules)); } // https://github.com/gleam-lang/gleam/issues/2202 #[test] fn long_function_wrapping() { let mut config = PackageConfig::default(); config.name = EcoString::from("test_project_name"); let modules = vec![( "app.gleam", r#" pub type Option(t) { Some(t) None } /// Returns the first value if it is `Some`, otherwise evaluates the given /// function for a fallback value. /// pub fn lazy_or(first: Option(a), second: fn() -> Option(a)) -> Option(a) { case first { Some(_) -> first None -> second() } } "#, )]; insta::assert_snapshot!(compile(config, modules)); } #[test] fn internal_definitions_are_not_included() { let mut config = PackageConfig::default(); config.name = EcoString::from("test_project_name"); let modules = vec![( "app.gleam", r#" @internal pub const wibble = 1 @internal pub type Wibble = Int @internal pub type Wobble { Wobble } @internal pub fn one() { 1 } "#, )]; insta::assert_snapshot!(compile(config, modules)); } // https://github.com/gleam-lang/gleam/issues/2561 #[test] fn discarded_arguments_are_not_shown() { let mut config = PackageConfig::default(); config.name = EcoString::from("test_project_name"); let modules = vec![("app.gleam", "pub fn discard(_discarded: a) -> Int { 1 }")]; insta::assert_snapshot!(compile(config, modules)); } // https://github.com/gleam-lang/gleam/issues/2631 #[test] fn docs_of_a_type_constructor_are_not_used_by_the_following_function() { let mut config = PackageConfig::default(); config.name = EcoString::from("test_project_name"); let modules = vec![( "app.gleam", r#" pub type Wibble { Wobble( /// Documentation!! wabble: Int, ) } pub fn main() { todo } "#, )]; insta::assert_snapshot!(compile(config, modules)); } #[test] fn markdown_code_from_standalone_pages_is_not_trimmed() { let mut config = PackageConfig::default(); config.name = EcoString::from("test_project_name"); let pages = vec![( "one", " This is an example code snippet that should be indented ```gleam pub fn indentation_test() { todo as \"This line should be indented by two spaces\" } ```", )]; insta::assert_snapshot!(compile_with_markdown_pages( config, vec![], pages, CompileWithMarkdownPagesOpts::default() )); } #[test] fn markdown_code_from_function_comment_is_trimmed() { let mut config = PackageConfig::default(); config.name = EcoString::from("test_project_name"); let modules = vec![( "app.gleam", " /// Here's an example code snippet: /// ``` /// wibble /// |> wobble /// ``` /// pub fn indentation_test() { todo } ", )]; insta::assert_snapshot!(compile(config, modules)); } #[test] fn markdown_code_from_module_comment_is_trimmed() { let mut config = PackageConfig::default(); config.name = EcoString::from("test_project_name"); let modules = vec![( "app.gleam", " //// Here's an example code snippet: //// ``` //// wibble //// |> wobble //// ``` //// ", )]; insta::assert_snapshot!(compile(config, modules)); } #[test] fn doc_for_commented_definitions_is_not_included_in_next_constant() { let mut config = PackageConfig::default(); config.name = EcoString::from("test_project_name"); let modules = vec![( "app.gleam", " /// Not included! // pub fn wibble() {} /// Included! pub const wobble = 1 ", )]; assert!(!compile(config, modules).contains("Not included!")); } #[test] fn doc_for_commented_definitions_is_not_included_in_next_type() { let mut config = PackageConfig::default(); config.name = EcoString::from("test_project_name"); let modules = vec![( "app.gleam", " /// Not included! // pub fn wibble() {} /// Included! pub type Wibble { /// Wobble! Wobble } ", )]; assert!(!compile(config, modules).contains("Not included!")); } #[test] fn doc_for_commented_definitions_is_not_included_in_next_function() { let mut config = PackageConfig::default(); config.name = EcoString::from("test_project_name"); let modules = vec![( "app.gleam", " /// Not included! // pub fn wibble() {} /// Included! pub fn wobble(arg) {} ", )]; assert!(!compile(config, modules).contains("Not included!")); } #[test] fn doc_for_commented_definitions_is_not_included_in_next_type_alias() { let mut config = PackageConfig::default(); config.name = EcoString::from("test_project_name"); let modules = vec![( "app.gleam", " /// Not included! // pub fn wibble() {} /// Included! pub type Wibble = Int ", )]; assert!(!compile(config, modules).contains("Not included!")); } #[test] fn source_link_for_github_repository() { let mut config = PackageConfig::default(); config.name = EcoString::from("test_project_name"); config.repository = Some(Repository::GitHub { user: "wibble".to_string(), repo: "wobble".to_string(), path: None, tag_prefix: None, }); let modules = vec![("app.gleam", "pub type Wibble = Int")]; assert!( compile(config, modules) .contains("https://github.com/wibble/wobble/blob/v0.1.0/src/app.gleam#L1") ); } #[test] fn source_link_for_github_repository_with_path_and_tag_prefix() { let mut config = PackageConfig::default(); config.name = EcoString::from("test_project_name"); config.repository = Some(Repository::GitHub { user: "wibble".to_string(), repo: "wobble".to_string(), path: Some("path/to/package".to_string()), tag_prefix: Some("subdir-".into()), }); let modules = vec![("app.gleam", "pub type Wibble = Int")]; assert!(compile(config, modules).contains( "https://github.com/wibble/wobble/blob/subdir-v0.1.0/path/to/package/src/app.gleam#L1" )); } #[test] fn canonical_link() { let mut config = PackageConfig::default(); config.name = EcoString::from("test_project_name"); let modules = vec![ ( "app.gleam", r#" /// Here is some documentation pub fn one() { 1 } "#, ), ( "gleam/otp/actor.gleam", r#" /// Here is some documentation pub fn one() { 1 } "#, ), ]; let pages = vec![( "LICENSE", r#" # LICENSE "#, )]; insta::assert_snapshot!(compile_with_markdown_pages( config, modules, pages, CompileWithMarkdownPagesOpts::default() )); } #[test] fn no_hex_publish() { let mut config = PackageConfig::default(); config.name = EcoString::from("test_project_name"); let modules = vec![ ( "app.gleam", r#" /// Here is some documentation pub fn one() { 1 } "#, ), ( "gleam/otp/actor.gleam", r#" /// Here is some documentation pub fn one() { 1 } "#, ), ]; let pages = vec![( "LICENSE", r#" # LICENSE "#, )]; insta::assert_snapshot!(compile_with_markdown_pages( config, modules, pages, CompileWithMarkdownPagesOpts { hex_publish: Some(DocContext::Build) } )); } fn create_sample_search_data() -> SearchData { SearchData { items: vec![ SearchItem { type_: SearchItemType::Module, parent_title: "gleam/option".to_string(), title: "gleam/option".to_string(), content: "".to_string(), reference: "gleam/option.html".to_string(), }, SearchItem { type_: SearchItemType::Type, parent_title: "gleam/option".to_string(), title: "Option".to_string(), content: "`Option` represents a value that may be present or not. `Some` means the value is present, `None` means the value is not.".to_string(), reference: "gleam/option.html#Option".to_string(), }, SearchItem { type_: SearchItemType::Value, parent_title: "gleam/option".to_string(), title: "unwrap".to_string(), content: "Extracts the value from an `Option`, returning a default value if there is none.".to_string(), reference: "gleam/option.html#unwrap".to_string(), }, SearchItem { type_: SearchItemType::Value, parent_title: "gleam/dynamic/decode".to_string(), title: "bool".to_string(), content: "A decoder that decodes `Bool` values.\n\n # Examples\n\n \n let result = decode.run(dynamic.from(True), decode.bool)\n assert result == Ok(True)\n \n".to_string(), reference: "gleam/dynamic/decode.html#bool".to_string(), }, ], programming_language: SearchProgrammingLanguage::Gleam, } } #[test] fn ensure_search_data_matches_exdocs_search_data_model_specification() { let data = create_sample_search_data(); let json = serde_to_string(&data).unwrap(); let parsed: serde_json::Value = serde_json::from_str(&json).unwrap(); // Ensure output of SearchData matches specification assert!(parsed.is_object()); let obj = parsed.as_object().unwrap(); assert!(obj.contains_key("items")); assert!(obj.contains_key("proglang")); // Ensure output of SearchItem matches specification let items = obj.get("items").unwrap().as_array().unwrap(); for item in items { let item = item.as_object().unwrap(); assert!(item.contains_key("type")); assert!(item.contains_key("parentTitle")); assert!(item.contains_key("title")); assert!(item.contains_key("doc")); assert!(item.contains_key("ref")); } } #[test] fn output_of_search_data_json() { let data = create_sample_search_data(); let json = serde_to_string(&data).unwrap(); insta::assert_snapshot!(json); } const ONLY_LINKS: PrintOptions = PrintOptions { print_highlighting: false, print_html: true, }; const NONE: PrintOptions = PrintOptions { print_highlighting: false, print_html: false, }; #[test] fn highlight_function_definition() { assert_documentation!( " pub fn wibble(list: List(Int), generic: a, function: fn(a) -> b) -> #(a, b) { todo } " ); } #[test] fn highlight_constant_definition() { assert_documentation!( " pub const x = 22 " ); } #[test] fn highlight_type_alias() { assert_documentation!( " pub type Option(a) = Result(a, Nil) " ); } #[test] fn highlight_custom_type() { assert_documentation!( " pub type Wibble(a, b) { Wibble(a, i: Int) Wobble(b: b, c: String) } " ); } #[test] fn highlight_opaque_custom_type() { assert_documentation!( " pub opaque type Wibble(a, b) { Wibble(a, i: Int) Wobble(b: b, c: String) } " ); } // https://github.com/gleam-lang/gleam/issues/2629 #[test] fn print_type_variables_in_function_signatures() { assert_documentation!( " pub type Dict(key, value) pub fn insert(dict: Dict(key, value), key: key, value: value) -> Dict(key, value) { dict } ", NONE ); } // https://github.com/gleam-lang/gleam/issues/828 #[test] fn print_qualified_names_from_other_modules() { assert_documentation!( ( "gleam/option", " pub type Option(t) { Some(t) None } " ), " import gleam/option.{type Option, Some, None} pub fn from_option(o: Option(t), e: e) -> Result(t, e) { case o { Some(t) -> Ok(t) None -> Error(e) } } ", NONE ); } // https://github.com/gleam-lang/gleam/issues/3461 #[test] fn link_to_type_in_same_module() { assert_documentation!( " pub type Dict(a, b) pub fn new() -> Dict(a, b) { todo } ", ONLY_LINKS ); } // https://github.com/gleam-lang/gleam/issues/3461 #[test] fn link_to_type_in_different_module() { assert_documentation!( ("gleam/dict", "pub type Dict(a, b)"), " import gleam/dict pub fn make_dict() -> dict.Dict(a, b) { todo } ", ONLY_LINKS ); } #[test] fn link_to_type_in_different_module_from_nested_module() { assert_documentation!( ("gleam/dict", "pub type Dict(a, b)"), "gleam/dynamic/decode", " import gleam/dict pub fn decode_dict() -> dict.Dict(a, b) { todo } ", ONLY_LINKS ); } #[test] fn link_to_type_in_different_module_from_nested_module_with_shared_path() { assert_documentation!( ("gleam/dynamic", "pub type Dynamic"), "gleam/dynamic/decode", " import gleam/dynamic pub type Dynamic = dynamic.Dynamic ", ONLY_LINKS ); } // https://github.com/gleam-lang/gleam/issues/3461 #[test] fn link_to_type_in_different_package() { assert_documentation!( ("gleam_stdlib", "gleam/dict", "pub type Dict(a, b)"), " import gleam/dict pub fn make_dict() -> dict.Dict(a, b) { todo } ", ONLY_LINKS ); } #[test] fn no_link_to_type_in_git_dependency() { assert_documentation!( git: ("gleam_stdlib", "gleam/dict", "pub type Dict(a, b)"), " import gleam/dict pub fn make_dict() -> dict.Dict(a, b) { todo } ", ONLY_LINKS ); } #[test] fn no_link_to_type_in_path_dependency() { assert_documentation!( path: ("gleam_stdlib", "gleam/dict", "pub type Dict(a, b)"), " import gleam/dict pub fn make_dict() -> dict.Dict(a, b) { todo } ", ONLY_LINKS ); } #[test] fn no_links_to_prelude_types() { assert_documentation!( " pub fn int_to_string(i: Int) -> String { todo } ", ONLY_LINKS ); } #[test] fn generated_type_variables() { assert_documentation!( " pub fn wibble(_a, _b, _c, _d) { todo } ", NONE ); } #[test] fn generated_type_variables_mixed_with_existing_variables() { assert_documentation!( " pub fn wibble(_a: b, _b: a, _c, _d) { todo } ", NONE ); } #[test] fn generated_type_variables_with_existing_variables_coming_afterwards() { assert_documentation!( " pub fn wibble(_a, _b, _c: b, _d: a) { todo } ", NONE ); } #[test] fn generated_type_variables_do_not_take_into_account_other_definitions() { assert_documentation!( " pub fn wibble(_a: a, _b: b, _c: c) -> d { todo } pub fn identity(x) { x } ", NONE ); } #[test] fn internal_type_reexport_in_same_module_as_parameter() { assert_documentation!( " @internal pub type Internal pub type External = List(Internal) ", ONLY_LINKS ); } #[test] fn internal_type_reexport_in_same_module_as_parameter_colours() { assert_documentation!( " @internal pub type Internal pub type External = List(Internal) ", ); } #[test] fn internal_type_reexport_in_same_module() { assert_documentation!( " @internal pub type Internal pub type External = Internal ", ONLY_LINKS ); } #[test] fn internal_type_reexport_in_different_module() { assert_documentation!( ("other", "@internal pub type Internal"), " import other pub type External = other.Internal ", ONLY_LINKS ); } #[test] fn public_type_reexport_in_different_internal_module() { assert_documentation!( ("thepackage/internal/other", "pub type Internal"), " import thepackage/internal/other pub type External = other.Internal ", ONLY_LINKS ); } #[test] fn use_reexport_from_other_package() { assert_documentation!( ("some_package", "some_package/internal", "pub type Internal"), ( "some_package", "some_package/api", " import some_package/internal pub type External = internal.Internal " ), " import some_package/api pub fn do_thing(value: api.External) { value } ", ONLY_LINKS ); } #[test] fn function_uses_reexport_of_internal_type() { assert_documentation!( ("thepackage/internal", "pub type Internal"), " import thepackage/internal pub type External = internal.Internal pub fn do_thing(value: internal.Internal) -> External { value } ", ONLY_LINKS ); } #[test] fn function_uses_reexport_of_internal_type_in_other_module() { assert_documentation!( ("thepackage/internal", "pub type Internal"), ( "thepackage/something", " import thepackage/internal pub type External = internal.Internal " ), " import thepackage/something pub fn do_thing(value: something.External) { value } ", ONLY_LINKS ); } #[test] fn constructor_with_long_types_and_many_fields() { assert_documentation!( ("option", "pub type Option(a)"), " import option pub type Uri { Uri( scheme: option.Option(String), userinfo: option.Option(String), host: option.Option(String), port: option.Option(Int), path: String, query: option.Option(String), fragment: option.Option(String) ) } ", NONE ); } #[test] fn constructor_with_long_types_and_many_fields_that_need_splitting() { assert_documentation!( ("option", "pub type Option(a)"), " import option pub type TypeWithAVeryLoooooooooooooooooooongName pub type Wibble { Wibble( wibble: #(TypeWithAVeryLoooooooooooooooooooongName, TypeWithAVeryLoooooooooooooooooooongName), wobble: option.Option(String), ) } ", NONE ); } #[test] fn gitea_repository_url_has_no_double_slash() { let repo = Repository::Forgejo { host: "https://code.example.org/".parse::().unwrap(), user: "person".into(), repo: "forgejo_bug".into(), path: None, tag_prefix: None, }; assert_eq!(repo.url(), "https://code.example.org/person/forgejo_bug"); } #[test] fn long_function_with_no_arguments_parentheses_are_not_split() { assert_documentation!( " pub fn aaaaaaaaaaaaaaaaaaaaaaaaaaaa() -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { todo } ", NONE ); } #[test] fn forgejo_single_line_definition() { let mut config = PackageConfig::default(); let repo = Repository::Forgejo { host: "https://code.example.org/".parse::().unwrap(), user: "wibble".into(), repo: "wobble".into(), path: None, tag_prefix: None, }; config.name = EcoString::from("test_project_name"); config.repository = Some(repo); let modules = vec![("app.gleam", "pub type Wibble = Int")]; let html = compile(config, modules); assert!( html.contains("https://code.example.org/wibble/wobble/src/tag/v0.1.0/src/app.gleam#L1") ); } #[test] fn forgejo_multiple_line_definition() { let mut config = PackageConfig::default(); let repo = Repository::Forgejo { host: "https://code.example.org/".parse::().unwrap(), user: "wibble".into(), repo: "wobble".into(), path: None, tag_prefix: None, }; config.name = EcoString::from("test_project_name"); config.repository = Some(repo); let modules = vec![("app.gleam", "pub type Wibble \n\n= Int")]; let html = compile(config, modules); assert!( html.contains("https://code.example.org/wibble/wobble/src/tag/v0.1.0/src/app.gleam#L1-L3") ); } fn generate_search_data(module_name: &str, module_src: &str) -> EcoString { let module = type_::tests::compile_module(module_name, module_src, None, Vec::new()) .expect("Module should compile successfully"); let mut config = PackageConfig::default(); config.name = "thepackage".into(); let paths = ProjectPaths::new("/".into()); let build_module = build::Module { name: "main".into(), code: module_src.into(), mtime: SystemTime::now(), input_path: "/".into(), origin: Origin::Src, ast: module, extra: Default::default(), dependencies: Default::default(), }; let source_links = SourceLinker::new(&paths, &config, &build_module); let module = &build_module.ast; let dependencies = HashMap::new(); let mut printer = Printer::new( module.type_info.package.clone(), module.name.clone(), &module.names, &dependencies, ); let mut search_items = Vec::new(); let types = printer.type_definitions(&source_links, &module.definitions); let values = printer.value_definitions(&source_links, &module.definitions); search_items.push(search_item_for_module(&build_module)); for type_ in types { search_items.push(search_item_for_type(module_name, &type_)); } for value in values { search_items.push(search_item_for_value(module_name, &value)); } let mut output = EcoString::new(); output.push_str("------ SOURCE CODE\n"); output.push_str(module_src); output.push_str("\n------------------------------------\n\n"); for item in search_items { let SearchItem { type_, parent_title, title, content, reference, } = item; let type_ = match type_ { SearchItemType::Value => "Value", SearchItemType::Module => "Module", SearchItemType::Page => "Page", SearchItemType::Type => "Type", }; output.push_str(&eco_format!( "TITLE: {title} PARENT TITLE: {parent_title} TYPE: {type_} REFERENCE: {reference} CONTENT: {content} ------------------------------------ " )); } output } #[test] fn search_item_for_custom_type() { let output = generate_search_data( "module", " /// # The `Option` type /// Represents an optional value, either `Some` or `None`. /// If it is None, the value is absent /// [Read more](https://example.com) pub type Option(inner) { /// Here is some /// documentation for the `Some` constructor Some( /// And even documentation on a **field**! value: inner ) None } ", ); insta::assert_snapshot!(output); } #[test] fn search_item_for_type_alias() { let output = generate_search_data( "module", " /// This is a type alias to a list /// of integer values. /// ## Examples /// ``` /// // Examples /// ``` pub type IntList = List(Int) ", ); insta::assert_snapshot!(output); } #[test] fn search_item_for_function() { let output = generate_search_data( "module", " /// Pi is the ration between a circle's **radius** and its /// *circumference*. Pretty cool! pub const pi = 3.14 ", ); insta::assert_snapshot!(output); } #[test] fn search_item_for_constant() { let output = generate_search_data( "module", " /// Reverses a `List`, and returns the list in reverse. /// ``` /// reverse([1, 2, 3]) /// // [3, 2, 1] /// ``` pub fn reverse(list: List(a), out: List(a)) -> List(a) { case list { [] -> out [first, ..rest] -> reverse(rest, [first, ..out]) } } ", ); insta::assert_snapshot!(output); } ================================================ FILE: compiler-core/src/docs.rs ================================================ mod printer; mod source_links; #[cfg(test)] mod tests; use std::{collections::HashMap, time::SystemTime}; use camino::Utf8PathBuf; use hexpm::version::Version; use printer::Printer; use crate::{ build::{Module, Package}, config::{DocsPage, PackageConfig}, docs::source_links::SourceLinker, io::{Content, FileSystemReader, OutputFile}, package_interface::PackageInterface, paths::ProjectPaths, type_::{self}, version::COMPILER_VERSION, }; use askama::Template; use ecow::EcoString; use itertools::Itertools; use serde::{Deserialize, Serialize}; use serde_json::to_string as serde_to_string; #[derive(PartialEq, Eq, Copy, Clone, Debug)] pub enum DocContext { HexPublish, Build, } #[derive(PartialEq, Debug, Serialize, Deserialize)] pub struct PackageInformation { #[serde(rename = "gleam.toml")] package_config: PackageConfig, } /// Like `ManifestPackage`, but lighter and cheaper to clone as it is all that /// we need for printing documentation. #[derive(Debug, Clone)] pub struct Dependency { pub version: Version, pub kind: DependencyKind, } #[derive(Debug, Clone, Copy)] pub enum DependencyKind { Hex, Path, Git, } #[derive(Debug)] pub struct DocumentationConfig<'a> { pub package_config: &'a PackageConfig, pub dependencies: HashMap, pub analysed: &'a [Module], pub docs_pages: &'a [DocsPage], pub rendering_timestamp: SystemTime, pub context: DocContext, } pub fn generate_html( paths: &ProjectPaths, config: DocumentationConfig<'_>, fs: IO, ) -> Vec { let DocumentationConfig { package_config: config, dependencies, analysed, docs_pages, rendering_timestamp, context: is_hex_publish, } = config; let modules = analysed .iter() .filter(|module| module.origin.is_src()) .filter(|module| !config.is_internal_module(&module.name)); let rendering_timestamp = rendering_timestamp .duration_since(SystemTime::UNIX_EPOCH) .expect("get current timestamp") .as_secs() .to_string(); // Define user-supplied (or README) pages let pages: Vec<_> = docs_pages .iter() .map(|page| Link { name: page.title.to_string(), path: page.path.to_string(), }) .collect(); let doc_links = config.links.iter().map(|doc_link| Link { name: doc_link.title.to_string(), path: doc_link.href.to_string(), }); let repo_link = config .repository .as_ref() .map(|r| r.url()) .map(|path| Link { name: "Repository".into(), path, }); let host = if is_hex_publish == DocContext::HexPublish { "https://hexdocs.pm" } else { "" }; // https://github.com/gleam-lang/gleam/issues/3020 let links: Vec<_> = match is_hex_publish { DocContext::HexPublish => doc_links .chain(repo_link) .chain([Link { name: "Hex".into(), path: format!("https://hex.pm/packages/{0}", config.name).to_string(), }]) .collect(), DocContext::Build => doc_links.chain(repo_link).collect(), }; let mut files = vec![]; let mut search_items = vec![]; let modules_links: Vec<_> = modules .clone() .map(|m| { let path = [&m.name, ".html"].concat(); Link { path, name: m.name.split('/').join("/"), } }) .sorted() .collect(); // Generate user-supplied (or README) pages for page in docs_pages { let content = fs.read(&page.source).unwrap_or_default(); let rendered_content = render_markdown(&content, MarkdownSource::Standalone); let unnest = page_unnest(&page.path); let page_path_without_ext = page.path.split('.').next().unwrap_or(""); let page_title = match page_path_without_ext { // The index page, such as README, should not push it's page title "index" => format!("{} · v{}", config.name, config.version), // Other page title's should say so _other => format!("{} · {} · v{}", page.title, config.name, config.version), }; let page_meta_description = match page_path_without_ext { "index" => config.description.to_string().clone(), _other => "".to_owned(), }; let path = Utf8PathBuf::from(&page.path); let temp = PageTemplate { gleam_version: COMPILER_VERSION, links: &links, pages: &pages, modules: &modules_links, project_name: &config.name, page_title: &page_title, page_meta_description: &page_meta_description, file_path: &path.clone(), project_version: &config.version.to_string(), content: rendered_content, rendering_timestamp: &rendering_timestamp, host, unnest: &unnest, }; files.push(OutputFile { path, content: Content::Text(temp.render().expect("Page template rendering")), }); search_items.push(search_item_for_page(&config.name, &page.path, content)) } // Generate module documentation pages for module in modules { let name = module.name.clone(); let unnest = page_unnest(&module.name); // Read module src & create line number lookup structure let source_links = SourceLinker::new(paths, config, module); let documentation_content = module.ast.documentation.iter().join("\n"); let rendered_documentation = render_markdown(&documentation_content, MarkdownSource::Comment); let mut printer = Printer::new( module.ast.type_info.package.clone(), module.name.clone(), &module.ast.names, &dependencies, ); let types = printer.type_definitions(&source_links, &module.ast.definitions); let values = printer.value_definitions(&source_links, &module.ast.definitions); types .iter() .for_each(|type_| search_items.push(search_item_for_type(&module.name, type_))); values .iter() .for_each(|value| search_items.push(search_item_for_value(&module.name, value))); search_items.push(search_item_for_module(module)); let page_title = format!("{} · {} · v{}", name, config.name, config.version); let page_meta_description = ""; let path = Utf8PathBuf::from(format!("{}.html", module.name)); let template = ModuleTemplate { gleam_version: COMPILER_VERSION, host, unnest, links: &links, pages: &pages, documentation: rendered_documentation, modules: &modules_links, project_name: &config.name, page_title: &page_title, page_meta_description, module_name: EcoString::from(&name), file_path: &path.clone(), project_version: &config.version.to_string(), types, values, rendering_timestamp: &rendering_timestamp, }; files.push(OutputFile { path, content: Content::Text( template .render() .expect("Module documentation template rendering"), ), }); } // Render static assets files.push(OutputFile { path: Utf8PathBuf::from("css/atom-one-light.min.css"), content: Content::Text( std::include_str!("../templates/docs-css/atom-one-light.min.css").to_string(), ), }); files.push(OutputFile { path: Utf8PathBuf::from("css/atom-one-dark.min.css"), content: Content::Text( std::include_str!("../templates/docs-css/atom-one-dark.min.css").to_string(), ), }); files.push(OutputFile { path: Utf8PathBuf::from("css/index.css"), content: Content::Text(std::include_str!("../templates/docs-css/index.css").to_string()), }); // highlightjs: files.push(OutputFile { path: Utf8PathBuf::from("js/highlight.min.js"), content: Content::Text( std::include_str!("../templates/docs-js/highlight.min.js").to_string(), ), }); files.push(OutputFile { path: Utf8PathBuf::from("js/highlightjs-gleam.js"), content: Content::Text( std::include_str!("../templates/docs-js/highlightjs-gleam.js").to_string(), ), }); files.push(OutputFile { path: Utf8PathBuf::from("js/highlightjs-erlang.min.js"), content: Content::Text( std::include_str!("../templates/docs-js/highlightjs-erlang.min.js").to_string(), ), }); files.push(OutputFile { path: Utf8PathBuf::from("js/highlightjs-elixir.min.js"), content: Content::Text( std::include_str!("../templates/docs-js/highlightjs-elixir.min.js").to_string(), ), }); files.push(OutputFile { path: Utf8PathBuf::from("js/highlightjs-javascript.min.js"), content: Content::Text( std::include_str!("../templates/docs-js/highlightjs-javascript.min.js").to_string(), ), }); files.push(OutputFile { path: Utf8PathBuf::from("js/highlightjs-typescript.min.js"), content: Content::Text( std::include_str!("../templates/docs-js/highlightjs-typescript.min.js").to_string(), ), }); // lunr.min.js, search-data.json and index.js files.push(OutputFile { path: Utf8PathBuf::from("js/lunr.min.js"), content: Content::Text(std::include_str!("../templates/docs-js/lunr.min.js").to_string()), }); let search_data_json = serde_to_string(&SearchData { items: search_items, programming_language: SearchProgrammingLanguage::Gleam, }) .expect("search index serialization"); files.push(OutputFile { path: Utf8PathBuf::from("search-data.json"), content: Content::Text(search_data_json.to_string()), }); files.push(OutputFile { path: Utf8PathBuf::from("js/index.js"), content: Content::Text(std::include_str!("../templates/docs-js/index.js").to_string()), }); // web fonts: files.push(OutputFile { path: Utf8PathBuf::from("fonts/karla-v23-regular-latin-ext.woff2"), content: Content::Binary( include_bytes!("../templates/docs-fonts/karla-v23-regular-latin-ext.woff2").to_vec(), ), }); files.push(OutputFile { path: Utf8PathBuf::from("fonts/karla-v23-regular-latin.woff2"), content: Content::Binary( include_bytes!("../templates/docs-fonts/karla-v23-regular-latin.woff2").to_vec(), ), }); files.push(OutputFile { path: Utf8PathBuf::from("fonts/karla-v23-bold-latin-ext.woff2"), content: Content::Binary( include_bytes!("../templates/docs-fonts/karla-v23-bold-latin-ext.woff2").to_vec(), ), }); files.push(OutputFile { path: Utf8PathBuf::from("fonts/karla-v23-bold-latin.woff2"), content: Content::Binary( include_bytes!("../templates/docs-fonts/karla-v23-bold-latin.woff2").to_vec(), ), }); files.push(OutputFile { path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-cyrillic-ext.woff2"), content: Content::Binary( include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-cyrillic-ext.woff2") .to_vec(), ), }); files.push(OutputFile { path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-cyrillic.woff2"), content: Content::Binary( include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-cyrillic.woff2") .to_vec(), ), }); files.push(OutputFile { path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-greek-ext.woff2"), content: Content::Binary( include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-greek-ext.woff2") .to_vec(), ), }); files.push(OutputFile { path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-greek.woff2"), content: Content::Binary( include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-greek.woff2").to_vec(), ), }); files.push(OutputFile { path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-latin-ext.woff2"), content: Content::Binary( include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-latin-ext.woff2") .to_vec(), ), }); files.push(OutputFile { path: Utf8PathBuf::from("fonts/ubuntu-mono-v15-regular-latin.woff2"), content: Content::Binary( include_bytes!("../templates/docs-fonts/ubuntu-mono-v15-regular-latin.woff2").to_vec(), ), }); files } fn search_item_for_page(package: &str, path: &str, content: String) -> SearchItem { SearchItem { type_: SearchItemType::Page, parent_title: package.to_string(), title: package.to_string(), content, reference: path.to_string(), } } fn search_item_for_type(module: &str, type_: &TypeDefinition<'_>) -> SearchItem { let constructors = type_ .constructors .iter() .map(|constructor| { let arguments = constructor .arguments .iter() .map(|argument| format!("{}\n{}", argument.name, argument.text_documentation)) .join("\n"); format!( "{}\n{}\n{}", constructor.raw_definition, constructor.text_documentation, arguments ) }) .join("\n"); SearchItem { type_: SearchItemType::Type, parent_title: module.to_string(), title: type_.name.to_string(), content: format!( "{}\n{}\n{}\n{}", type_.raw_definition, type_.text_documentation, constructors, import_synonyms(module, type_.name) ), reference: format!("{}.html#{}", module, type_.name), } } fn search_item_for_value(module: &str, value: &DocsValues<'_>) -> SearchItem { SearchItem { type_: SearchItemType::Value, parent_title: module.to_string(), title: value.name.to_string(), content: format!( "{}\n{}\n{}", value.raw_definition, value.text_documentation, import_synonyms(module, value.name) ), reference: format!("{}.html#{}", module, value.name), } } fn search_item_for_module(module: &Module) -> SearchItem { SearchItem { type_: SearchItemType::Module, parent_title: module.name.to_string(), title: module.name.to_string(), content: module.ast.documentation.iter().join("\n"), reference: format!("{}.html", module.name), } } pub fn generate_json_package_interface( path: Utf8PathBuf, package: &Package, cached_modules: &im::HashMap, ) -> OutputFile { OutputFile { path, content: Content::Text( serde_json::to_string(&PackageInterface::from_package(package, cached_modules)) .expect("JSON module interface serialisation"), ), } } pub fn generate_json_package_information(path: Utf8PathBuf, config: PackageConfig) -> OutputFile { OutputFile { path, content: Content::Text(package_information_as_json(config)), } } fn package_information_as_json(config: PackageConfig) -> String { let info = PackageInformation { package_config: config, }; serde_json::to_string_pretty(&info).expect("JSON module information serialisation") } fn page_unnest(path: &str) -> String { let unnest = path .strip_prefix('/') .unwrap_or(path) .split('/') .skip(1) .map(|_| "..") .join("/"); if unnest.is_empty() { ".".into() } else { unnest } } #[test] fn page_unnest_test() { // Pages assert_eq!(page_unnest("wibble.html"), "."); assert_eq!(page_unnest("/wibble.html"), "."); assert_eq!(page_unnest("/wibble/woo.html"), ".."); assert_eq!(page_unnest("/wibble/wobble/woo.html"), "../.."); // Modules assert_eq!(page_unnest("string"), "."); assert_eq!(page_unnest("gleam/string"), ".."); assert_eq!(page_unnest("gleam/string/inspect"), "../.."); } fn import_synonyms(parent: &str, child: &str) -> String { format!("Synonyms:\n{parent}.{child}\n{parent} {child}") } fn text_documentation(doc: &Option<(u32, EcoString)>) -> String { let raw_text = doc .as_ref() .map(|(_, it)| it.to_string()) .unwrap_or_else(|| "".into()); // TODO: parse markdown properly and extract the text nodes raw_text.replace("```gleam", "").replace("```", "") } fn markdown_documentation(doc: &Option<(u32, EcoString)>) -> String { doc.as_ref() .map(|(_, doc)| render_markdown(doc, MarkdownSource::Comment)) .unwrap_or_default() } /// An enum to represent the source of a Markdown string to render. enum MarkdownSource { /// A Markdown string that comes from the documentation of a /// definition/module. This means that each line is going to be preceded by /// a whitespace. Comment, /// A Markdown string coming from a standalone file like a README.md. Standalone, } fn render_markdown(text: &str, source: MarkdownSource) -> String { let text = match source { MarkdownSource::Standalone => text.into(), // Doc comments start with "///\s", which can confuse the markdown parser // and prevent tables from rendering correctly, so remove that first space. MarkdownSource::Comment => text .split('\n') .map(|s| s.strip_prefix(' ').unwrap_or(s)) .join("\n"), }; let mut s = String::with_capacity(text.len() * 3 / 2); let p = pulldown_cmark::Parser::new_ext(&text, pulldown_cmark::Options::all()); pulldown_cmark::html::push_html(&mut s, p); s } #[derive(PartialEq, Eq, PartialOrd, Ord, Clone)] struct Link { name: String, path: String, } #[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] struct TypeConstructor { definition: String, raw_definition: String, documentation: String, text_documentation: String, arguments: Vec, } #[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] struct TypeConstructorArg { name: String, doc: String, text_documentation: String, } #[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] struct TypeDefinition<'a> { name: &'a str, definition: String, raw_definition: String, documentation: String, constructors: Vec, text_documentation: String, source_url: String, deprecation_message: String, opaque: bool, } #[derive(PartialEq, Eq, PartialOrd, Ord)] struct DocsValues<'a> { name: &'a str, definition: String, raw_definition: String, documentation: String, text_documentation: String, source_url: String, deprecation_message: String, } #[derive(Template)] #[template(path = "documentation_page.html")] struct PageTemplate<'a> { gleam_version: &'a str, unnest: &'a str, host: &'a str, page_title: &'a str, page_meta_description: &'a str, file_path: &'a Utf8PathBuf, project_name: &'a str, project_version: &'a str, pages: &'a [Link], links: &'a [Link], modules: &'a [Link], content: String, rendering_timestamp: &'a str, } #[derive(Template)] #[template(path = "documentation_module.html")] struct ModuleTemplate<'a> { gleam_version: &'a str, unnest: String, host: &'a str, page_title: &'a str, page_meta_description: &'a str, file_path: &'a Utf8PathBuf, module_name: EcoString, project_name: &'a str, project_version: &'a str, pages: &'a [Link], links: &'a [Link], modules: &'a [Link], types: Vec>, values: Vec>, documentation: String, rendering_timestamp: &'a str, } /// Search data for use by Hexdocs search, as well as the search built-in to /// generated documentation #[derive(Serialize, PartialEq, Eq, PartialOrd, Ord, Clone)] struct SearchData { items: Vec, #[serde(rename = "proglang")] programming_language: SearchProgrammingLanguage, } /// A single item that can appear as a search result #[derive(Serialize, PartialEq, Eq, PartialOrd, Ord, Clone)] struct SearchItem { /// The type of item this is: Value, Type, Module, or other Page #[serde(rename = "type")] type_: SearchItemType, /// The title of the module or package containing this search item #[serde(rename = "parentTitle")] parent_title: String, /// The title of this item title: String, /// Markdown text which describes this item, containing documentation from /// doc comments, as well as rendered definitions of types and values. #[serde(rename = "doc")] content: String, /// The relative URL to the documentation for this search item, for example /// `gleam/option.html#Option` #[serde(rename = "ref")] reference: String, } #[derive(Serialize, PartialEq, Eq, PartialOrd, Ord, Clone)] #[serde(rename_all = "lowercase")] enum SearchItemType { Value, Module, Page, Type, } #[derive(Serialize, PartialEq, Eq, PartialOrd, Ord, Clone)] #[serde(rename_all = "lowercase")] enum SearchProgrammingLanguage { // Elixir, // Erlang, Gleam, } #[test] fn package_config_to_json() { let input = r#" name = "my_project" version = "1.0.0" licences = ["Apache-2.0", "MIT"] description = "Pretty complex config" target = "erlang" repository = { type = "github", user = "example", repo = "my_dep" } links = [{ title = "Home page", href = "https://example.com" }] internal_modules = ["my_app/internal"] gleam = ">= 0.30.0" [dependencies] gleam_stdlib = ">= 0.18.0 and < 2.0.0" my_other_project = { path = "../my_other_project" } [dev_dependencies] gleeunit = ">= 1.0.0 and < 2.0.0" [documentation] pages = [{ title = "My Page", path = "my-page.html", source = "./path/to/my-page.md" }] [erlang] application_start_module = "my_app/application" extra_applications = ["inets", "ssl"] [javascript] typescript_declarations = true runtime = "node" [javascript.deno] allow_all = false allow_ffi = true allow_env = ["DATABASE_URL"] allow_net = ["example.com:443"] allow_read = ["./database.sqlite"] "#; let config = toml::from_str::(input).unwrap(); let info = PackageInformation { package_config: config.clone(), }; let json = package_information_as_json(config); let output = format!("--- GLEAM.TOML\n{input}\n\n--- EXPORTED JSON\n\n{json}"); insta::assert_snapshot!(output); let roundtrip: PackageInformation = serde_json::from_str(&json).unwrap(); assert_eq!(info, roundtrip); } #[test] fn barebones_package_config_to_json() { let input = r#" name = "my_project" version = "1.0.0" "#; let config = toml::from_str::(input).unwrap(); let json = package_information_as_json(config); let output = format!("--- GLEAM.TOML\n{input}\n\n--- EXPORTED JSON\n\n{json}"); insta::assert_snapshot!(output); } ================================================ FILE: compiler-core/src/encryption.rs ================================================ use thiserror::Error; pub fn encrypt_with_passphrase( message: &[u8], passphrase: &str, ) -> Result { let passphrase = age::secrecy::SecretString::from(passphrase); let recipient = age::scrypt::Recipient::new(passphrase.clone()); let encrypted = age::encrypt_and_armor(&recipient, message)?; Ok(encrypted) } // the function `decrypt_with_passphrase` has two possible failure cases: // - when decryption fails // - when the data was decrypted succesfully but the result is not UTF-8 valid #[derive(Error, Debug)] pub enum DecryptError { #[error("unable to decrypt message: {0}")] Decrypt(#[from] age::DecryptError), #[error("decrypted message is not UTF-8 valid: {0}")] Io(#[from] std::string::FromUtf8Error), } pub fn decrypt_with_passphrase( encrypted_message: &[u8], passphrase: &str, ) -> Result { let passphrase = age::secrecy::SecretString::from(passphrase); let identity = age::scrypt::Identity::new(passphrase); let decrypted = age::decrypt(&identity, encrypted_message)?; let decrypted = String::from_utf8(decrypted)?; Ok(decrypted) } ================================================ FILE: compiler-core/src/erlang/pattern.rs ================================================ use ecow::eco_format; use crate::analyse::Inferred; use super::*; pub(super) struct PatternPrinter<'a, 'env> { pub environment: &'env mut Env<'a>, pub variables: Vec<&'a str>, pub guards: Vec>, /// In case we're dealing with string patterns, we might have something like /// this: `"a" as letter <> rest`. In this case we want to compile it to /// `<<"a"/utf8, rest/binary>>` and then bind a variable to `"a"`. /// This way it's easier for the erlang compiler to optimise the pattern /// matching. /// /// Here we store a list of gleam variable name to its name used in the /// Erlang code and its literal value. pub assignments: Vec>, } /// This is used to hold data about string patterns with an alias like: /// `"a" as letter <> _` pub struct StringPatternAssignment<'a> { /// The name assigned to the pattern in the Gleam code: /// /// ```gleam /// "a" as letter <> _ /// // ^^^^^^ This one /// ``` /// pub gleam_name: EcoString, /// The name we're using for that same variable in the generated Erlang /// code, could have numbers added to it to make sure it's unique, like /// `Letter@1`. /// pub erlang_name: Document<'a>, /// The document representing the literal value of that variable. For /// example, if we had this pattern `"a" <> letter` it's literal value in /// Erlang is going to be a document with the following string /// `<<"a"/utf8>>`. /// pub literal_value: Document<'a>, } impl<'a> StringPatternAssignment<'a> { pub fn to_assignment_doc(&self) -> Document<'a> { docvec![self.erlang_name.clone(), " = ", self.literal_value.clone()] } } impl<'a, 'env> PatternPrinter<'a, 'env> { pub(super) fn new(environment: &'env mut Env<'a>) -> Self { Self { environment, variables: vec![], guards: vec![], assignments: vec![], } } pub(super) fn reset_variables(&mut self) { self.variables = vec![]; } pub(super) fn print(&mut self, pattern: &'a TypedPattern) -> Document<'a> { match pattern { Pattern::Assign { name, pattern, .. } => { self.variables.push(name); self.print(pattern) .append(" = ") .append(self.environment.next_local_var_name(name)) } Pattern::List { elements, tail, .. } => self.pattern_list(elements, tail.as_deref()), Pattern::Discard { .. } => "_".to_doc(), Pattern::BitArraySize(size) => match size { BitArraySize::Int { .. } | BitArraySize::Variable { .. } | BitArraySize::Block { .. } => self.bit_array_size(size), BitArraySize::BinaryOperator { .. } => self.bit_array_size(size).surround("(", ")"), }, Pattern::Variable { name, .. } => { self.variables.push(name); self.environment.next_local_var_name(name) } Pattern::Int { value, .. } => int(value), Pattern::Float { value, .. } => float(value), Pattern::String { value, .. } => string(value), Pattern::Constructor { arguments, constructor: Inferred::Known(PatternConstructor { name, .. }), .. } => self.tag_tuple_pattern(name, arguments), Pattern::Constructor { constructor: Inferred::Unknown, .. } => { panic!("Erlang generation performed with uninferred pattern constructor") } Pattern::Tuple { elements, .. } => { tuple(elements.iter().map(|pattern| self.print(pattern))) } Pattern::BitArray { segments, .. } => bit_array( segments .iter() .map(|s| self.pattern_segment(&s.value, &s.options)), ), Pattern::StringPrefix { left_side_string, right_side_assignment, left_side_assignment, .. } => { let right = match right_side_assignment { AssignName::Variable(right) => { self.variables.push(right); self.environment.next_local_var_name(right) } AssignName::Discard(_) => "_".to_doc(), }; if let Some((left_name, _)) = left_side_assignment { // "wibble" as prefix <> rest // ^^^^^^^^^ In case the left prefix of the pattern matching is given an alias // we bind it to a local variable so that it can be correctly // referenced inside the case branch. // // So we will end up with something that looks like this: // // <<"wibble"/binary, Rest/binary>> -> // Prefix = "wibble", // ... // self.variables.push(left_name); self.assignments.push(StringPatternAssignment { gleam_name: left_name.clone(), erlang_name: self.environment.next_local_var_name(left_name), literal_value: string(left_side_string), }); } docvec![ "<<\"", string_inner(left_side_string), "\"/utf8", ", ", right, "/binary>>" ] } Pattern::Invalid { .. } => panic!("invalid patterns should not reach code generation"), } } fn bit_array_size(&mut self, size: &'a TypedBitArraySize) -> Document<'a> { match size { BitArraySize::Int { value, .. } => int(value), BitArraySize::Block { inner, .. } => self.bit_array_size(inner).surround("(", ")"), BitArraySize::Variable { name, constructor, .. } => { let variant = &constructor .as_ref() .expect("Constructor not found for variable usage") .variant; match variant { ValueConstructorVariant::ModuleConstant { literal, .. } => { const_inline(literal, self.environment) } ValueConstructorVariant::LocalVariable { .. } | ValueConstructorVariant::ModuleFn { .. } | ValueConstructorVariant::Record { .. } => { self.environment.local_var_name(name) } } } BitArraySize::BinaryOperator { operator, left, right, .. } => { let operator = match operator { IntOperator::Add => " + ", IntOperator::Subtract => " - ", IntOperator::Multiply => " * ", IntOperator::Divide => { return self.bit_array_size_divide(left, right, "div"); } IntOperator::Remainder => { return self.bit_array_size_divide(left, right, "rem"); } }; docvec![ self.bit_array_size(left), operator, self.bit_array_size(right) ] } } } fn bit_array_size_divide( &mut self, left: &'a TypedBitArraySize, right: &'a TypedBitArraySize, operator: &'static str, ) -> Document<'a> { if right.non_zero_compile_time_number() { return self.bit_array_size_operator(left, operator, right); } let left = self.bit_array_size(left); let right = self.bit_array_size(right); let denominator = self.environment.next_local_var_name("gleam@denominator"); let clauses = docvec![ line(), "0 -> 0;", line(), denominator.clone(), " -> ", binop_documents(left, operator, denominator) ]; docvec!["case ", right, " of", clauses.nest(INDENT), line(), "end"] } fn bit_array_size_operator( &mut self, left: &'a TypedBitArraySize, operator: &'static str, right: &'a TypedBitArraySize, ) -> Document<'a> { let left = if let BitArraySize::BinaryOperator { .. } = left { self.bit_array_size(left).surround("(", ")") } else { self.bit_array_size(left) }; let right = if let BitArraySize::BinaryOperator { .. } = right { self.bit_array_size(right).surround("(", ")") } else { self.bit_array_size(right) }; binop_documents(left, operator, right) } fn tag_tuple_pattern( &mut self, name: &'a str, arguments: &'a [CallArg], ) -> Document<'a> { if arguments.is_empty() { atom_string(to_snake_case(name)) } else { tuple( [atom_string(to_snake_case(name))] .into_iter() .chain(arguments.iter().map(|argument| self.print(&argument.value))), ) } } fn pattern_list( &mut self, elements: &'a [TypedPattern], tail: Option<&'a TypedTailPattern>, ) -> Document<'a> { let elements = join( elements.iter().map(|element| self.print(element)), break_(",", ", "), ); let tail = tail.map(|tail| self.print(&tail.pattern)); list(elements, tail) } fn pattern_segment( &mut self, value: &'a TypedPattern, options: &'a [BitArrayOption], ) -> Document<'a> { let pattern_is_a_string_literal = matches!(value, Pattern::String { .. }); let pattern_is_a_discard = matches!(value, Pattern::Discard { .. }); let create_document = |this: &mut PatternPrinter<'a, 'env>| match value { Pattern::String { value, .. } => string_inner(value).surround("\"", "\""), Pattern::Discard { .. } | Pattern::Variable { .. } | Pattern::Int { .. } | Pattern::Float { .. } => this.print(value), Pattern::Assign { name, pattern, .. } => { this.variables.push(name); let variable_name = this.environment.next_local_var_name(name); match pattern.as_ref() { // In Erlang, assignment patterns inside bit arrays are not allowed. So instead of // generating `<<1 = A>>`, we use guards, and generate `<> when A =:= 1`. Pattern::Int { value, .. } => { this.guards .push(docvec![variable_name.clone(), " =:= ", int(value)]); variable_name } Pattern::Float { value, .. } => { this.guards .push(docvec![variable_name.clone(), " =:= ", float(value)]); variable_name } // Here we do the same as for floats and ints, but we must calculate the size of // the string first, so we can correctly match the bit array segment then compare // it afterwards. Pattern::String { value, .. } => { this.guards .push(docvec![variable_name.clone(), " =:= ", string(value)]); docvec![variable_name, ":", string_length_utf8_bytes(value)] } // Doing a pattern such as `<<_ as a>>` is the same as just `<>`, so we treat it // as such. Pattern::Discard { .. } => variable_name, // Any other pattern is invalid as a bit array segment. We already handle the case // of `<>` in the type-checker, and assignment patterns cannot be nested. Pattern::Variable { .. } | Pattern::BitArraySize(_) | Pattern::Assign { .. } | Pattern::List { .. } | Pattern::Constructor { .. } | Pattern::Tuple { .. } | Pattern::BitArray { .. } | Pattern::StringPrefix { .. } | Pattern::Invalid { .. } => panic!("Pattern segment match not recognised"), } } Pattern::BitArraySize(_) | Pattern::List { .. } | Pattern::Constructor { .. } | Pattern::Tuple { .. } | Pattern::BitArray { .. } | Pattern::StringPrefix { .. } | Pattern::Invalid { .. } => panic!("Pattern segment match not recognised"), }; let size = |value: &'a TypedPattern, this: &mut PatternPrinter<'a, 'env>| { Some(":".to_doc().append(this.print(value))) }; let unit = |value: &'a u8| Some(eco_format!("unit:{value}").to_doc()); bit_array_segment( create_document, options, size, unit, pattern_is_a_string_literal, pattern_is_a_discard, self, ) } } ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__allowed_string_escapes.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub fn a() { \"\\n\" \"\\r\" \"\\t\" \"\\\\\" \"\\\"\" \"\\\\^\" }" --- ----- SOURCE CODE pub fn a() { "\n" "\r" "\t" "\\" "\"" "\\^" } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([a/0]). -file("project/test/my/mod.gleam", 1). -spec a() -> binary(). a() -> <<"\n"/utf8>>, <<"\r"/utf8>>, <<"\t"/utf8>>, <<"\\"/utf8>>, <<"\""/utf8>>, <<"\\^"/utf8>>. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__binop_parens.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\npub fn main() {\n let a = 2 * {3 + 1} / 2\n let b = 5 + 3 / 3 * 2 - 6 * 4\n b\n}\n" --- ----- SOURCE CODE pub fn main() { let a = 2 * {3 + 1} / 2 let b = 5 + 3 / 3 * 2 - 6 * 4 b } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> A = (2 * (3 + 1)) div 2, B = (5 + ((3 div 3) * 2)) - (6 * 4), B. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__bit_pattern_shadowing.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\npub fn main() {\n let code = <<\"hello world\":utf8>>\n let pre = 1\n case code {\n <> -> pre\n _ -> panic\n }\n} " --- ----- SOURCE CODE pub fn main() { let code = <<"hello world":utf8>> let pre = 1 case code { <> -> pre _ -> panic } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> bitstring(). main() -> Code = <<"hello world"/utf8>>, Pre = 1, case Code of <> -> Pre@1; _ -> erlang:error(#{gleam_error => panic, message => <<"`panic` expression evaluated."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 7}) end. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__block_assignment.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\npub fn main() {\n let x = {\n 1\n 2\n }\n x\n}\n" --- ----- SOURCE CODE pub fn main() { let x = { 1 2 } x } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> X = begin 1, 2 end, X. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__constant_named_module_info.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\npub const module_info = 1\n\npub fn main() {\n module_info\n}\n" --- ----- SOURCE CODE pub const module_info = 1 pub fn main() { module_info } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 4). -spec main() -> integer(). main() -> 1. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__constant_named_module_info_imported.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\nimport some_module\n\npub fn main() {\n some_module.module_info\n}\n" --- ----- SOURCE CODE import some_module pub fn main() { some_module.module_info } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 4). -spec main() -> integer(). main() -> 1. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__constant_named_module_info_imported_qualified.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\nimport some_module.{module_info}\n\npub fn main() {\n module_info\n}\n" --- ----- SOURCE CODE import some_module.{module_info} pub fn main() { module_info } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 4). -spec main() -> integer(). main() -> 1. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__constant_named_module_info_with_function_inside.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\npub fn function() {\n 1\n}\n\npub const module_info = function\n\npub fn main() {\n module_info()\n}\n" --- ----- SOURCE CODE pub fn function() { 1 } pub const module_info = function pub fn main() { module_info() } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([function/0, main/0]). -file("project/test/my/mod.gleam", 2). -spec function() -> integer(). function() -> 1. -file("project/test/my/mod.gleam", 8). -spec main() -> integer(). main() -> function(). ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__constant_named_module_info_with_function_inside_imported.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\nimport some_module\n\npub fn main() {\n some_module.module_info()\n}\n" --- ----- SOURCE CODE import some_module pub fn main() { some_module.module_info() } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 4). -spec main() -> integer(). main() -> fun some_module:function/0(). ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__constant_named_module_info_with_function_inside_imported_qualified.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\nimport some_module.{module_info}\n\npub fn main() {\n module_info()\n}\n" --- ----- SOURCE CODE import some_module.{module_info} pub fn main() { module_info() } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 4). -spec main() -> integer(). main() -> some_module:function(). ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__discard_in_assert.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub fn x(y) {\n let assert Ok(_) = y\n 1\n}" --- ----- SOURCE CODE pub fn x(y) { let assert Ok(_) = y 1 } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([x/1]). -file("project/test/my/mod.gleam", 1). -spec x({ok, any()} | {error, any()}) -> integer(). x(Y) -> case Y of {ok, _} -> nil; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"x"/utf8>>, line => 2, value => _assert_fail, start => 16, 'end' => 36, pattern_start => 27, pattern_end => 32}) end, 1. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__dynamic.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: pub type Dynamic --- ----- SOURCE CODE pub type Dynamic ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export_type([dynamic_/0]). -type dynamic_() :: any(). ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__field_access_function_call.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\npub type FnBox {\n FnBox(f: fn(Int) -> Int)\n}\n\npub fn main() {\n let b = FnBox(f: fn(x) { x })\n b.f(5)\n}\n" --- ----- SOURCE CODE pub type FnBox { FnBox(f: fn(Int) -> Int) } pub fn main() { let b = FnBox(f: fn(x) { x }) b.f(5) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -export_type([fn_box/0]). -type fn_box() :: {fn_box, fun((integer()) -> integer())}. -file("project/test/my/mod.gleam", 6). -spec main() -> integer(). main() -> B = {fn_box, fun(X) -> X end}, (erlang:element(2, B))(5). ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__field_access_function_call1.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\npub fn main() {\n let t = #(fn(x) { x })\n\n t.0(5)\n}\n" --- ----- SOURCE CODE pub fn main() { let t = #(fn(x) { x }) t.0(5) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> T = {fun(X) -> X end}, (erlang:element(1, T))(5). ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__float_division_by_literal_non_zero.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\npub fn main() {\n 1.0 /. 2.0\n} " --- ----- SOURCE CODE pub fn main() { 1.0 /. 2.0 } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> float(). main() -> 1.0 / 2.0. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__float_division_by_literal_zero.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\npub fn main() {\n 1.0 /. 0.0\n} " --- ----- SOURCE CODE pub fn main() { 1.0 /. 0.0 } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> float(). main() -> +0.0. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__function_argument_shadowing.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub fn main(a) {\n Box\n}\n\npub type Box {\n Box(Int)\n}\n" --- ----- SOURCE CODE pub fn main(a) { Box } pub type Box { Box(Int) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -export_type([box/0]). -type box() :: {box, integer()}. -file("project/test/my/mod.gleam", 1). -spec main(any()) -> fun((integer()) -> box()). main(A) -> fun(Field@0) -> {box, Field@0} end. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__function_named_module_info.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\npub fn module_info() {\n 1\n}\n\npub fn main() {\n module_info()\n}\n" --- ----- SOURCE CODE pub fn module_info() { 1 } pub fn main() { module_info() } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export(['moduleInfo'/0, main/0]). -file("project/test/my/mod.gleam", 2). -spec 'moduleInfo'() -> integer(). 'moduleInfo'() -> 1. -file("project/test/my/mod.gleam", 6). -spec main() -> integer(). main() -> 'moduleInfo'(). ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__function_named_module_info_imported.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\nimport some_module\n\npub fn main() {\n some_module.module_info()\n}\n" --- ----- SOURCE CODE import some_module pub fn main() { some_module.module_info() } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 4). -spec main() -> integer(). main() -> some_module:'moduleInfo'(). ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__function_named_module_info_imported_qualified.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\nimport some_module.{module_info}\n\npub fn main() {\n module_info()\n}\n" --- ----- SOURCE CODE import some_module.{module_info} pub fn main() { module_info() } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 4). -spec main() -> integer(). main() -> some_module:'moduleInfo'(). ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__function_named_module_info_in_constant.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\npub fn module_info() {\n 1\n}\n\npub const constant = module_info\n\npub fn main() {\n constant()\n}\n" --- ----- SOURCE CODE pub fn module_info() { 1 } pub const constant = module_info pub fn main() { constant() } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export(['moduleInfo'/0, main/0]). -file("project/test/my/mod.gleam", 2). -spec 'moduleInfo'() -> integer(). 'moduleInfo'() -> 1. -file("project/test/my/mod.gleam", 8). -spec main() -> integer(). main() -> 'moduleInfo'(). ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__function_named_module_info_in_constant_imported.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\nimport some_module\n\npub fn main() {\n some_module.constant()\n}\n" --- ----- SOURCE CODE import some_module pub fn main() { some_module.constant() } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 4). -spec main() -> integer(). main() -> fun some_module:'moduleInfo'/0(). ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__function_named_module_info_in_constant_imported_qualified.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\nimport some_module.{constant}\n\npub fn main() {\n constant()\n}\n" --- ----- SOURCE CODE import some_module.{constant} pub fn main() { constant() } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 4). -spec main() -> integer(). main() -> some_module:'moduleInfo'(). ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__guard_variable_rewriting.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub fn main() {\n case 1.0 {\n a if a <. 0.0 -> {\n let a = a\n a\n }\n _ -> 0.0\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case 1.0 { a if a <. 0.0 -> { let a = a a } _ -> 0.0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 1). -spec main() -> float(). main() -> case 1.0 of A when A < +0.0 -> A@1 = A, A@1; _ -> +0.0 end. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__inline_const_pattern_option.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub fn main() {\n let fifteen = 15\n let x = <<5:size(sixteen)>>\n case x {\n <<5:size(sixteen)>> -> <<5:size(sixteen)>>\n <<6:size(fifteen)>> -> <<5:size(fifteen)>>\n _ -> <<>>\n }\n }\n\n pub const sixteen = 16" --- ----- SOURCE CODE pub fn main() { let fifteen = 15 let x = <<5:size(sixteen)>> case x { <<5:size(sixteen)>> -> <<5:size(sixteen)>> <<6:size(fifteen)>> -> <<5:size(fifteen)>> _ -> <<>> } } pub const sixteen = 16 ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 1). -spec main() -> bitstring(). main() -> Fifteen = 15, X = <<5:(lists:max([(16), 0]))>>, case X of <<5:16>> -> <<5:(lists:max([(16), 0]))>>; <<6:Fifteen>> -> <<5:(lists:max([(Fifteen), 0]))>>; _ -> <<>> end. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub fn go() {\nlet x = #(100000000000000000, #(2000000000, 3000000000000, 40000000000), 50000, 6000000000)\n x\n}" --- ----- SOURCE CODE pub fn go() { let x = #(100000000000000000, #(2000000000, 3000000000000, 40000000000), 50000, 6000000000) x } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 1). -spec go() -> {integer(), {integer(), integer(), integer()}, integer(), integer()}. go() -> X = {100000000000000000, {2000000000, 3000000000000, 40000000000}, 50000, 6000000000}, X. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test0_1.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub fn go() {\n let y = 1\n let y = 2\n y\n}" --- ----- SOURCE CODE pub fn go() { let y = 1 let y = 2 y } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 1). -spec go() -> integer(). go() -> Y = 1, Y@1 = 2, Y@1. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test0_2.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub fn go() {\n let fifteen = 0xF\n let nine = 0o11\n let ten = 0b1010\n fifteen\n}" --- ----- SOURCE CODE pub fn go() { let fifteen = 0xF let nine = 0o11 let ten = 0b1010 fifteen } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 1). -spec go() -> integer(). go() -> Fifteen = 16#F, Nine = 8#11, Ten = 2#1010, Fifteen. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test0_3.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub fn go() {\n let y = 1\n let y = 2\n y\n}" --- ----- SOURCE CODE pub fn go() { let y = 1 let y = 2 y } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 1). -spec go() -> integer(). go() -> Y = 1, Y@1 = 2, Y@1. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test1.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub fn t() { True }" --- ----- SOURCE CODE pub fn t() { True } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([t/0]). -file("project/test/my/mod.gleam", 1). -spec t() -> boolean(). t() -> true. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test10.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub type Null { Null }\npub fn x() { Null }" --- ----- SOURCE CODE pub type Null { Null } pub fn x() { Null } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([x/0]). -export_type([null/0]). -type null() :: null. -file("project/test/my/mod.gleam", 2). -spec x() -> null(). x() -> null. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test11.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub type Point { Point(x: Int, y: Int) }\npub fn x() { Point(x: 4, y: 6) Point(y: 1, x: 9) }" --- ----- SOURCE CODE pub type Point { Point(x: Int, y: Int) } pub fn x() { Point(x: 4, y: 6) Point(y: 1, x: 9) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([x/0]). -export_type([point/0]). -type point() :: {point, integer(), integer()}. -file("project/test/my/mod.gleam", 2). -spec x() -> point(). x() -> {point, 4, 6}, {point, 9, 1}. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test12.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub type Point { Point(x: Int, y: Int) }\npub fn x(y) { let Point(a, b) = y a }" --- ----- SOURCE CODE pub type Point { Point(x: Int, y: Int) } pub fn x(y) { let Point(a, b) = y a } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([x/1]). -export_type([point/0]). -type point() :: {point, integer(), integer()}. -file("project/test/my/mod.gleam", 2). -spec x(point()) -> integer(). x(Y) -> {point, A, B} = Y, A. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test13.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub type State{ Start(Int) End(Int) }\n pub fn build(constructor : fn(Int) -> a) -> a { constructor(1) }\n pub fn main() { build(End) }" --- ----- SOURCE CODE pub type State{ Start(Int) End(Int) } pub fn build(constructor : fn(Int) -> a) -> a { constructor(1) } pub fn main() { build(End) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([build/1, main/0]). -export_type([state/0]). -type state() :: {start, integer()} | {'end', integer()}. -file("project/test/my/mod.gleam", 2). -spec build(fun((integer()) -> I)) -> I. build(Constructor) -> Constructor(1). -file("project/test/my/mod.gleam", 3). -spec main() -> state(). main() -> build(fun(Field@0) -> {'end', Field@0} end). ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test16.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "fn go(x xx, y yy) { xx }\npub fn x() { go(x: 1, y: 2) go(y: 3, x: 4) }" --- ----- SOURCE CODE fn go(x xx, y yy) { xx } pub fn x() { go(x: 1, y: 2) go(y: 3, x: 4) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([x/0]). -file("project/test/my/mod.gleam", 1). -spec go(I, any()) -> I. go(Xx, Yy) -> Xx. -file("project/test/my/mod.gleam", 2). -spec x() -> integer(). x() -> go(1, 2), go(4, 3). ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test17.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\npub type User { User(id: Int, name: String, age: Int) }\npub fn create_user(user_id) { User(age: 22, id: user_id, name: \"\") }\n" --- ----- SOURCE CODE pub type User { User(id: Int, name: String, age: Int) } pub fn create_user(user_id) { User(age: 22, id: user_id, name: "") } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([create_user/1]). -export_type([user/0]). -type user() :: {user, integer(), binary(), integer()}. -file("project/test/my/mod.gleam", 3). -spec create_user(integer()) -> user(). create_user(User_id) -> {user, User_id, <<""/utf8>>, 22}. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test18.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub fn run() { case 1, 2 { a, b -> a } }" --- ----- SOURCE CODE pub fn run() { case 1, 2 { a, b -> a } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([run/0]). -file("project/test/my/mod.gleam", 1). -spec run() -> integer(). run() -> case {1, 2} of {A, B} -> A end. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test19.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub type X { X(x: Int, y: Float) }\npub fn x() { X(x: 1, y: 2.) X(y: 3., x: 4) }" --- ----- SOURCE CODE pub type X { X(x: Int, y: Float) } pub fn x() { X(x: 1, y: 2.) X(y: 3., x: 4) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([x/0]). -export_type([x/0]). -type x() :: {x, integer(), float()}. -file("project/test/my/mod.gleam", 2). -spec x() -> x(). x() -> {x, 1, 2.0}, {x, 4, 3.0}. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test1_1.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub type Money { Pound(Int) }\npub fn pound(x) { Pound(x) }" --- ----- SOURCE CODE pub type Money { Pound(Int) } pub fn pound(x) { Pound(x) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([pound/1]). -export_type([money/0]). -type money() :: {pound, integer()}. -file("project/test/my/mod.gleam", 2). -spec pound(integer()) -> money(). pound(X) -> {pound, X}. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test1_2.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub fn loop() { loop() }" --- ----- SOURCE CODE pub fn loop() { loop() } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([loop/0]). -file("project/test/my/mod.gleam", 1). -spec loop() -> any(). loop() -> loop(). ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test1_4.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "fn inc(x) { x + 1 }\n pub fn go() { 1 |> inc |> inc |> inc }" --- ----- SOURCE CODE fn inc(x) { x + 1 } pub fn go() { 1 |> inc |> inc |> inc } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 1). -spec inc(integer()) -> integer(). inc(X) -> X + 1. -file("project/test/my/mod.gleam", 2). -spec go() -> integer(). go() -> _pipe = 1, _pipe@1 = inc(_pipe), _pipe@2 = inc(_pipe@1), inc(_pipe@2). ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test1_5.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "fn add(x, y) { x + y }\n pub fn go() { 1 |> add(_, 1) |> add(2, _) |> add(_, 3) }" --- ----- SOURCE CODE fn add(x, y) { x + y } pub fn go() { 1 |> add(_, 1) |> add(2, _) |> add(_, 3) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 1). -spec add(integer(), integer()) -> integer(). add(X, Y) -> X + Y. -file("project/test/my/mod.gleam", 2). -spec go() -> integer(). go() -> _pipe = 1, _pipe@1 = add(_pipe, 1), _pipe@2 = add(2, _pipe@1), add(_pipe@2, 3). ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test1_6.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub fn and(x, y) { x && y }\npub fn or(x, y) { x || y }\npub fn remainder(x, y) { x % y }\npub fn fdiv(x, y) { x /. y }\n " --- ----- SOURCE CODE pub fn and(x, y) { x && y } pub fn or(x, y) { x || y } pub fn remainder(x, y) { x % y } pub fn fdiv(x, y) { x /. y } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export(['and'/2, 'or'/2, remainder/2, fdiv/2]). -file("project/test/my/mod.gleam", 1). -spec 'and'(boolean(), boolean()) -> boolean(). 'and'(X, Y) -> X andalso Y. -file("project/test/my/mod.gleam", 2). -spec 'or'(boolean(), boolean()) -> boolean(). 'or'(X, Y) -> X orelse Y. -file("project/test/my/mod.gleam", 3). -spec remainder(integer(), integer()) -> integer(). remainder(X, Y) -> case Y of 0 -> 0; Gleam@denominator -> X rem Gleam@denominator end. -file("project/test/my/mod.gleam", 4). -spec fdiv(float(), float()) -> float(). fdiv(X, Y) -> case Y of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator -> X / Gleam@denominator end. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test2.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub fn second(list) { case list { [x, y] -> y z -> 1 } }\npub fn tail(list) { case list { [x, ..xs] -> xs z -> list } }\n " --- ----- SOURCE CODE pub fn second(list) { case list { [x, y] -> y z -> 1 } } pub fn tail(list) { case list { [x, ..xs] -> xs z -> list } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([second/1, tail/1]). -file("project/test/my/mod.gleam", 1). -spec second(list(integer())) -> integer(). second(List) -> case List of [X, Y] -> Y; Z -> 1 end. -file("project/test/my/mod.gleam", 2). -spec tail(list(P)) -> list(P). tail(List) -> case List of [X | Xs] -> Xs; Z -> List end. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test20.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\npub fn go(a) {\n let a = a + 1\n a\n}\n\n " --- ----- SOURCE CODE pub fn go(a) { let a = a + 1 a } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/1]). -file("project/test/my/mod.gleam", 2). -spec go(integer()) -> integer(). go(A) -> A@1 = A + 1, A@1. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test21.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\npub fn go(a) {\n let a = 1\n a\n}\n\n " --- ----- SOURCE CODE pub fn go(a) { let a = 1 a } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/1]). -file("project/test/my/mod.gleam", 2). -spec go(any()) -> integer(). go(A) -> A@1 = 1, A@1. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test22.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\npub fn factory(f, i) {\n f(i)\n}\n\npub type Box {\n Box(i: Int)\n}\n\npub fn main() {\n factory(Box, 0)\n}\n" --- ----- SOURCE CODE pub fn factory(f, i) { f(i) } pub type Box { Box(i: Int) } pub fn main() { factory(Box, 0) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([factory/2, main/0]). -export_type([box/0]). -type box() :: {box, integer()}. -file("project/test/my/mod.gleam", 2). -spec factory(fun((J) -> N), J) -> N. factory(F, I) -> F(I). -file("project/test/my/mod.gleam", 10). -spec main() -> box(). main() -> factory(fun(Field@0) -> {box, Field@0} end, 0). ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test23.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\npub fn main(args) {\n case args {\n _ -> {\n let a = 1\n a\n }\n }\n let a = 2\n a\n}\n" --- ----- SOURCE CODE pub fn main(args) { case args { _ -> { let a = 1 a } } let a = 2 a } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -file("project/test/my/mod.gleam", 2). -spec main(any()) -> integer(). main(Args) -> case Args of _ -> A = 1, A end, A@1 = 2, A@1. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test3.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub type Point { Point(x: Int, y: Int) }\npub fn y() { fn() { Point }()(4, 6) }" --- ----- SOURCE CODE pub type Point { Point(x: Int, y: Int) } pub fn y() { fn() { Point }()(4, 6) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([y/0]). -export_type([point/0]). -type point() :: {point, integer(), integer()}. -file("project/test/my/mod.gleam", 2). -spec y() -> point(). y() -> {point, 4, 6}. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test5.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub fn tail(list) {\n case list {\n [x, ..] -> x\n _ -> 0\n }\n}" --- ----- SOURCE CODE pub fn tail(list) { case list { [x, ..] -> x _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([tail/1]). -file("project/test/my/mod.gleam", 1). -spec tail(list(integer())) -> integer(). tail(List) -> case List of [X | _] -> X; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test6.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub fn x() { let x = 1 let x = x + 1 x }" --- ----- SOURCE CODE pub fn x() { let x = 1 let x = x + 1 x } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([x/0]). -file("project/test/my/mod.gleam", 1). -spec x() -> integer(). x() -> X = 1, X@1 = X + 1, X@1. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test8.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub fn x() { 1. <. 2.3 }" --- ----- SOURCE CODE pub fn x() { 1. <. 2.3 } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([x/0]). -file("project/test/my/mod.gleam", 1). -spec x() -> boolean(). x() -> 1.0 < 2.3. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__integration_test9.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub type Pair(x, y) { Pair(x: x, y: y) } pub fn x() { Pair(1, 2) Pair(3., 4.) }" --- ----- SOURCE CODE pub type Pair(x, y) { Pair(x: x, y: y) } pub fn x() { Pair(1, 2) Pair(3., 4.) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([x/0]). -export_type([pair/2]). -type pair(I, J) :: {pair, I, J}. -file("project/test/my/mod.gleam", 1). -spec x() -> pair(float(), float()). x() -> {pair, 1, 2}, {pair, 3.0, 4.0}. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__keyword_constructors.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub type X { Div }" --- ----- SOURCE CODE pub type X { Div } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export_type([x/0]). -type x() :: 'div'. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__keyword_constructors1.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub type X { Fun(Int) }" --- ----- SOURCE CODE pub type X { Fun(Int) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export_type([x/0]). -type x() :: {'fun', integer()}. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__negation.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub fn negate(x) {\n !x\n}" --- ----- SOURCE CODE pub fn negate(x) { !x } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([negate/1]). -file("project/test/my/mod.gleam", 1). -spec negate(boolean()) -> boolean(). negate(X) -> not X. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__negation_block.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub fn negate(x) {\n !{\n 123\n x\n }\n}" --- ----- SOURCE CODE pub fn negate(x) { !{ 123 x } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([negate/1]). -file("project/test/my/mod.gleam", 1). -spec negate(boolean()) -> boolean(). negate(X) -> not begin 123, X end. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__operator_pipe_right_hand_side.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "fn id(x) {\n x\n}\n\npub fn bool_expr(x, y) {\n y || x |> id\n}" --- ----- SOURCE CODE fn id(x) { x } pub fn bool_expr(x, y) { y || x |> id } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([bool_expr/2]). -file("project/test/my/mod.gleam", 1). -spec id(I) -> I. id(X) -> X. -file("project/test/my/mod.gleam", 5). -spec bool_expr(boolean(), boolean()) -> boolean(). bool_expr(X, Y) -> Y orelse begin _pipe = X, id(_pipe) end. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__positive_zero.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\npub fn main() {\n 0.0\n}\n" --- ----- SOURCE CODE pub fn main() { 0.0 } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> float(). main() -> +0.0. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__recursive_type.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\nfn id(x) {\n x\n}\n\npub fn main() {\n id(id)\n}\n" --- ----- SOURCE CODE fn id(x) { x } pub fn main() { id(id) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec id(I) -> I. id(X) -> X. -file("project/test/my/mod.gleam", 6). -spec main() -> fun((M) -> M). main() -> id(fun id/1). ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__scientific_notation.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\npub fn main() {\n 1.0e6\n 1.e6\n}\n" --- ----- SOURCE CODE pub fn main() { 1.0e6 1.e6 } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> float(). main() -> 1.0e6, 1.0e6. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__tail_maybe_expr_block.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub fn a() {\n let fake_tap = fn(x) { x }\n let b = [99]\n [\n 1,\n 2,\n ..b\n |> fake_tap\n ]\n}\n" --- ----- SOURCE CODE pub fn a() { let fake_tap = fn(x) { x } let b = [99] [ 1, 2, ..b |> fake_tap ] } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([a/0]). -file("project/test/my/mod.gleam", 1). -spec a() -> list(integer()). a() -> Fake_tap = fun(X) -> X end, B = [99], [1, 2 | begin _pipe = B, Fake_tap(_pipe) end]. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__tuple_access_in_guard.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\npub fn main() {\n let key = 10\n let x = [#(10, 2), #(1, 2)]\n case x {\n [first, ..rest] if first.0 == key -> \"ok\"\n _ -> \"ko\"\n }\n}\n" --- ----- SOURCE CODE pub fn main() { let key = 10 let x = [#(10, 2), #(1, 2)] case x { [first, ..rest] if first.0 == key -> "ok" _ -> "ko" } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> binary(). main() -> Key = 10, X = [{10, 2}, {1, 2}], case X of [First | Rest] when erlang:element(1, First) =:= Key -> <<"ok"/utf8>>; _ -> <<"ko"/utf8>> end. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__type_named_else.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\npub type Else {\n Else\n}\n\npub fn main() {\n Else\n}\n" --- ----- SOURCE CODE pub type Else { Else } pub fn main() { Else } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -export_type(['else'/0]). -type 'else'() :: 'else'. -file("project/test/my/mod.gleam", 6). -spec main() -> 'else'(). main() -> 'else'. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__type_named_module_info.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "\npub type ModuleInfo {\n ModuleInfo\n}\n\npub fn main() {\n ModuleInfo\n}\n" --- ----- SOURCE CODE pub type ModuleInfo { ModuleInfo } pub fn main() { ModuleInfo } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -export_type([module_info/0]). -type module_info() :: module_info. -file("project/test/my/mod.gleam", 6). -spec main() -> module_info(). main() -> module_info. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__variable_name_underscores_preserved.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub fn a(name_: String) -> String {\n let name__ = name_\n let name = name__\n let one_1 = 1\n let one1 = one_1\n name\n}" --- ----- SOURCE CODE pub fn a(name_: String) -> String { let name__ = name_ let name = name__ let one_1 = 1 let one1 = one_1 name } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([a/1]). -file("project/test/my/mod.gleam", 1). -spec a(binary()) -> binary(). a(Name_) -> Name__ = Name_, Name = Name__, One_1 = 1, One1 = One_1, Name. ================================================ FILE: compiler-core/src/erlang/snapshots/gleam_core__erlang__tests__windows_file_escaping_bug.snap ================================================ --- source: compiler-core/src/erlang/tests.rs expression: "pub fn main() { Nil }" --- -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "C:\\root\\project\\test\\my\\mod.gleam"). -export([main/0]). -file("C:\\root\\project\\test\\my\\mod.gleam", 1). -spec main() -> nil. main() -> nil. ================================================ FILE: compiler-core/src/erlang/tests/assert.rs ================================================ use crate::assert_erl; #[test] fn assert_variable() { assert_erl!( " pub fn main() { let x = True assert x } " ); } #[test] fn assert_literal() { assert_erl!( " pub fn main() { assert False } " ); } #[test] fn assert_binary_operation() { assert_erl!( " pub fn main() { let x = True assert x || False } " ); } #[test] fn assert_binary_operation2() { assert_erl!( " pub fn eq(a, b) { assert a == b } " ); } #[test] fn assert_binary_operation3() { assert_erl!( " pub fn assert_answer(x) { assert x == 42 } " ); } #[test] fn assert_function_call() { assert_erl!( " fn bool() { True } pub fn main() { assert bool() } " ); } #[test] fn assert_function_call2() { assert_erl!( " fn and(a, b) { a && b } pub fn go(x) { assert and(True, x) } " ); } #[test] fn assert_nested_function_call() { assert_erl!( " fn and(x, y) { x && y } pub fn main() { assert and(and(True, False), True) } " ); } #[test] fn assert_binary_operator_with_side_effects() { assert_erl!( " fn wibble(a, b) { let result = a + b result == 10 } pub fn go(x) { assert True && wibble(1, 4) } " ); } #[test] fn assert_binary_operator_with_side_effects2() { assert_erl!( " fn wibble(a, b) { let result = a + b result == 10 } pub fn go(x) { assert wibble(5, 5) && wibble(4, 6) } " ); } #[test] fn assert_with_message() { assert_erl!( r#" pub fn main() { assert True as "This shouldn't fail" } "# ); } #[test] fn assert_with_block_message() { assert_erl!( r#" fn identity(a) { a } pub fn main() { assert identity(True) as { let message = identity("This shouldn't fail") message } } "# ); } ================================================ FILE: compiler-core/src/erlang/tests/bit_arrays.rs ================================================ use crate::assert_erl; #[test] fn bit_array() { assert_erl!( r#"pub fn main() { let a = 1 let simple = <<1, a>> let complex = <<4:int-big, 5.0:little-float, 6:native-int>> let assert <<7:2, 8:size(3), b:bytes-size(4)>> = <<1>> let assert <> = <<1>> simple } "# ); } #[test] fn bit_array_float() { assert_erl!( r#"pub fn main() { let b = 16 let floats = <<1.0:16-float, 5.0:float-32, 6.0:float-64-little, 1.0:float-size(b)>> let assert <<1.0:16-float, 5.0:float-32, 6.0:float-64-little, 1.0:float-size(b)>> = floats }"# ); } #[test] fn bit_array1() { assert_erl!( r#"pub fn x() { 2 } pub fn main() { let a = -1 let b = <> b } "# ); } #[test] fn bit_array2() { assert_erl!( r#"pub fn main() { let a = 1 let assert <> = <<1, a>> b } "# ); } #[test] fn bit_array3() { assert_erl!( r#"pub fn main() { let a = <<"test":utf8>> let assert <> = a b } "# ); } #[test] fn bit_array4() { assert_erl!( r#"fn x() { 1 } pub fn main() { let a = <> a } "# ); } #[test] fn bit_array5() { assert_erl!( r#"const bit_size = 8 pub fn main() { let a = <<10:size(bit_size)>> a } "# ); } #[test] fn bit_array_discard() { // https://github.com/gleam-lang/gleam/issues/704 assert_erl!( r#" pub fn bit_array_discard(x) -> Bool { case x { <<_:utf8, rest:bytes>> -> True _ -> False } } "# ); } #[test] fn bit_array_discard1() { assert_erl!( r#" pub fn bit_array_discard(x) -> Bool { case x { <<_discardme:utf8, rest:bytes>> -> True _ -> False } } "# ); } #[test] fn bit_array_declare_and_use_var() { assert_erl!( r#"pub fn go(x) { let assert <> = x name }"# ); } // https://github.com/gleam-lang/gleam/issues/3050 #[test] fn unicode_bit_array_1() { assert_erl!( r#" pub fn main() { let emoji = "\u{1F600}" let arr = <> }"# ); } #[test] fn unicode_bit_array_2() { assert_erl!( r#" pub fn main() { let arr = <<"\u{1F600}":utf8>> }"# ); } #[test] fn bit_array_literal_string_constant_is_treated_as_utf8() { assert_erl!( r#" const a = <<"hello", " ", "world">> pub fn main() { a } "# ); } #[test] fn bit_array_literal_string_is_treated_as_utf8() { assert_erl!( r#" pub fn main() { <<"hello", " ", "world">> }"# ); } #[test] fn bit_array_literal_string_pattern_is_treated_as_utf8() { assert_erl!( r#" pub fn main() { case <<>> { <<"a", "b", _:bits>> -> 1 _ -> 2 } }"# ); } #[test] fn discard_utf8_pattern() { assert_erl!( r#" pub fn main() { let assert <<_:utf8, rest:bits>> = <<>> }"# ); } // https://github.com/gleam-lang/gleam/issues/3944 #[test] fn pipe_size_segment() { assert_erl!( " pub fn main() { <<0xAE:size(5 |> identity)>> } fn identity(x) { x } " ); } #[test] fn utf16_codepoint_little_endian() { assert_erl!( " pub fn go(codepoint) { <> } " ); } #[test] fn utf32_codepoint_little_endian() { assert_erl!( " pub fn go(codepoint) { <> } " ); } #[test] fn pattern_match_utf16_codepoint_little_endian() { assert_erl!( " pub fn go(x) { let assert <> = x codepoint } " ); } #[test] fn pattern_match_utf32_codepoint_little_endian() { assert_erl!( " pub fn go(x) { let assert <> = x codepoint } " ); } #[test] fn operator_in_pattern_size() { assert_erl!( " pub fn main() { let assert <> = <<>> } " ); } #[test] fn operator_in_pattern_size2() { assert_erl!( " pub fn main() { let assert <> = <<>> } " ); } #[test] fn operator_in_pattern_size3() { assert_erl!( " pub fn main() { let additional = 10 let assert <> = <<>> } " ); } #[test] fn block_in_pattern_size() { assert_erl!( " pub fn main() { let assert <> = <<>> } " ); } #[test] fn non_byte_aligned_size_calculation() { assert_erl!( " pub fn main() { case <<>> { <> -> c + b _ -> 1 } } " ); } #[test] fn unicode_character_encoding_in_bit_array_pattern_segment() { assert_erl!( r#" pub fn main() -> Nil { let wibble = <<"\u{00A9}wibble":utf8>> let _bits = case wibble { <<"\u{00A9}":utf8, rest: bits>> -> rest _ -> wibble } Nil } "# ); } ================================================ FILE: compiler-core/src/erlang/tests/case.rs ================================================ use crate::assert_erl; // https://github.com/gleam-lang/gleam/issues/1675 #[test] fn alternative_pattern_variable_rewriting() { assert_erl!( " pub fn myfun(mt) { case mt { 1 | _ -> 1 |> Ok } 1 |> Ok } " ) } // https://github.com/gleam-lang/gleam/issues/2349 #[test] fn positive_zero_pattern() { assert_erl!( " pub fn main(x) { case x { 0.0 -> 1 _ -> 2 } } " ) } // https://github.com/gleam-lang/gleam/issues/2349 #[test] fn negative_zero_pattern() { assert_erl!( " pub fn main(x) { case x { -0.0 -> 1 _ -> 2 } } " ) } #[test] fn not() { assert_erl!( r#"pub fn main(x, y) { case x { _ if !y -> 0 _ -> 1 } } "#, ); } #[test] fn not_two() { assert_erl!( r#"pub fn main(x, y) { case x { _ if !y && !x -> 0 _ -> 1 } } "#, ); } // https://github.com/gleam-lang/gleam/issues/2657 #[test] fn spread_empty_list() { assert_erl!( r#" pub fn main() { case [] { [..] -> 1 } } "#, ); } // https://github.com/gleam-lang/gleam/issues/2657 #[test] fn spread_empty_list_assigning() { assert_erl!( r#" pub fn main() { case [] { [..rest] -> rest } } "#, ); } // https://github.com/gleam-lang/gleam/issues/5055 #[test] fn alternative_patter_with_string_alias() { assert_erl!( r#" pub fn main(x) { case x { "a" as letter <> _ | "b" as letter <> _ -> letter _ -> "wibble" } } "#, ); } // https://github.com/gleam-lang/gleam/issues/5115 #[test] fn aliased_string_prefix_pattern_referenced_in_guard() { assert_erl!( r#" pub fn main(x) { case x { "a" as letter <> _ if letter == x -> letter _ -> "wibble" } } "#, ); } ================================================ FILE: compiler-core/src/erlang/tests/conditional_compilation.rs ================================================ use crate::assert_erl; #[test] fn excluded_attribute_syntax() { assert_erl!( "@target(javascript) pub fn main() { 1 } " ); } #[test] fn included_attribute_syntax() { assert_erl!( "@target(erlang) pub fn main() { 1 } " ); } ================================================ FILE: compiler-core/src/erlang/tests/consts.rs ================================================ use crate::assert_erl; // https://github.com/gleam-lang/gleam/issues/2163 #[test] fn record_constructor() { assert_erl!( r#" pub type X { X(Int) } pub const z = X pub fn main() { z }"# ); } // https://github.com/gleam-lang/gleam/issues/2163 #[test] fn record_constructor_in_tuple() { assert_erl!( r#" pub type X { X(Int) } pub const z = #(X) pub fn main() { z }"# ); } // https://github.com/gleam-lang/gleam/issues/2179 #[test] fn const_type_variable() { assert_erl!( r#" fn identity(a: a) -> a { a } const id: fn(a) -> a = identity "# ); } #[test] fn const_generalise() { assert_erl!( r#" fn identity(a: a) -> a { a } const id = identity pub fn main(){ let num = id(1) let word = id("Word") }"# ); } #[test] fn pub_const_equal_to_private_function() { assert_erl!( r#" fn identity(a) { a } pub const id = identity "# ); } #[test] fn pub_const_equal_to_record_with_private_function_field() { assert_erl!( r#" fn identity(a) { a } pub type Mapper(b) { Mapper(fn(b) -> b) } pub const id_mapper = Mapper(identity) "# ); } #[test] fn pub_const_equal_to_record_with_nested_private_function_field() { assert_erl!( r#" fn identity(a) { a } pub type Mapper(b) { Mapper(fn(b) -> b) } pub type Funcs(b) { Funcs(mapper: Mapper(b)) } pub const id_mapper = Funcs(Mapper(identity)) "# ); } #[test] fn use_unqualified_pub_const_equal_to_private_function() { assert_erl!( r#" fn identity(a) { a } pub const id = identity "# ); } #[test] fn use_unqualified_pub_const_equal_to_record_with_private_function_field() { assert_erl!( r#" fn identity(a) { a } pub type Mapper(b) { Mapper(fn(b) -> b) } pub const id_mapper = Mapper(identity) "# ); } #[test] fn use_qualified_pub_const_equal_to_record_with_private_function_field() { assert_erl!( r#" fn identity(a) { a } pub type Mapper(b) { Mapper(fn(b) -> b) } pub const id_mapper = Mapper(identity) "# ); } #[test] fn use_private_in_internal() { assert_erl!( r#" fn identity(a) { a } pub type Mapper(b) { Mapper(fn(b) -> b) } @internal pub const id_mapper = Mapper(identity) "# ); } #[test] fn use_private_in_list() { assert_erl!( r#" fn identity(a) { a } pub const funcs = [identity] "# ) } #[test] fn use_private_in_tuple() { assert_erl!( r#" fn identity(a) { a } pub const funcs = #(identity) "# ) } #[test] fn list_prepend() { assert_erl!( " const wibble = [2, 3, 4] pub const wobble = [0, 1, ..wibble] pub fn main() { wobble } " ); } #[test] fn list_prepend_from_other_module() { assert_erl!( ("thepackage", "mod", "pub const wibble = [2, 3, 4]"), " import mod pub const wobble = [0, 1, ..mod.wibble] pub fn main() { wobble } " ); } #[test] fn list_prepend_literal() { assert_erl!( " pub const wibble = [0, 1, ..[2, 3, 4]] pub fn main() { wibble } " ); } ================================================ FILE: compiler-core/src/erlang/tests/custom_types.rs ================================================ use crate::assert_erl; #[test] fn phantom() { assert_erl!("pub type Map(k, v)"); } #[test] fn annotated_external_type() { assert_erl!( r#" @external(erlang, "gleam_stdlib", "dict") pub type Dict(key, value) "# ); } #[test] fn annotated_external_type_used_in_function() { assert_erl!( r#" @external(erlang, "gleam_stdlib", "dict") pub type Dict(key, value) @external(erlang, "maps", "get") pub fn get(dict: Dict(key, value), key: key) -> Result(value, Nil) "# ); } // https://github.com/gleam-lang/gleam/issues/5127 #[test] fn unused_opaque_constructor_is_generated_correctly() { assert_erl!( " type Wibble { Wibble } pub opaque type Wobble { Wobble(Wibble) } " ); } ================================================ FILE: compiler-core/src/erlang/tests/documentation.rs ================================================ use crate::assert_erl; #[test] fn function_with_documentation() { assert_erl!( r#" /// Function doc! pub fn documented() { 1 }"# ); } #[test] fn function_with_multiline_documentation() { assert_erl!( r#" /// Function doc! /// Hello!! /// pub fn documented() { 1 }"# ); } #[test] fn quotes_in_documentation_are_escaped() { assert_erl!( r#" /// "hello" pub fn documented() { 1 }"# ); } #[test] fn backslashes_in_documentation_are_escaped() { assert_erl!( r#" /// \hello\ pub fn documented() { 1 }"# ); } #[test] fn single_line_module_comment() { assert_erl!( r#" //// Hello! This is a single line module comment. pub fn main() { 1 }"# ); } #[test] fn multi_line_module_comment() { assert_erl!( r#" //// Hello! This is a multi- //// line module comment. //// pub fn main() { 1 }"# ); } #[test] fn double_quotes_are_escaped_in_module_comment() { assert_erl!( r#" //// "quotes!" pub fn main() { 1 }"# ); } #[test] fn backslashes_are_escaped_in_module_comment() { assert_erl!( r#" //// \backslashes!\ pub fn main() { 1 }"# ); } #[test] fn internal_function_has_no_documentation() { assert_erl!( r#" /// hidden! @internal pub fn main() { 1 }"# ); } ================================================ FILE: compiler-core/src/erlang/tests/echo.rs ================================================ use crate::assert_erl; #[test] pub fn echo_with_a_simple_expression() { assert_erl!( r#" pub fn main() { echo 1 } "# ); } #[test] pub fn echo_with_a_simple_expression_and_a_message() { assert_erl!( r#" pub fn main() { echo 1 as "hello!" } "# ); } #[test] pub fn echo_with_complex_expression_as_a_message() { assert_erl!( r#" pub fn main() { echo 1 as case name() { "Giacomo" -> "hello Jak!" _ -> "hello!" } } fn name() { "Giacomo" } "# ); } #[test] pub fn multiple_echos_inside_expression() { assert_erl!( r#" pub fn main() { echo 1 echo 2 } "# ); } #[test] pub fn echo_with_a_case_expression() { assert_erl!( r#" pub fn main() { echo case 1 { _ -> 2 } } "# ); } #[test] pub fn echo_with_a_panic() { assert_erl!( r#" pub fn main() { echo panic } "# ); } #[test] pub fn echo_with_a_function_call() { assert_erl!( r#" pub fn main() { echo wibble(1, 2) } fn wibble(n: Int, m: Int) { n + m } "# ); } #[test] pub fn echo_with_a_function_call_and_a_message() { assert_erl!( r#" pub fn main() { echo wibble(1, 2) as message() } fn wibble(n: Int, m: Int) { n + m } fn message() { "Hello!" } "# ); } #[test] pub fn echo_with_a_block() { assert_erl!( r#" pub fn main() { echo { Nil 1 } } "# ); } #[test] pub fn echo_in_a_pipeline() { assert_erl!( r#" pub fn main() { [1, 2, 3] |> echo |> wibble } pub fn wibble(n) { n } "# ) } #[test] pub fn echo_in_a_pipeline_with_message() { assert_erl!( r#" pub fn main() { [1, 2, 3] |> echo as "message!!" |> wibble } pub fn wibble(n) { n } "# ) } #[test] pub fn multiple_echos_in_a_pipeline() { assert_erl!( r#" pub fn main() { [1, 2, 3] |> echo |> wibble |> echo |> wibble |> echo } pub fn wibble(n) { n } "# ) } #[test] pub fn pipeline_printed_by_echo_is_wrapped_in_begin_end_block() { assert_erl!( r#" pub fn main() { echo 123 |> wibble |> wibble } pub fn wibble(n) { n } "# ) } #[test] pub fn record_update_printed_by_echo_is_wrapped_in_begin_end_block() { assert_erl!( r#" pub type Wobble { Wobble(id: Int, name: String) } pub fn main() { let wobble = Wobble(1, "wobble") echo Wobble(..wobble, id: 1) } "# ) } ================================================ FILE: compiler-core/src/erlang/tests/external_fn.rs ================================================ use crate::{assert_erl, assert_js_module_error, assert_module_error}; #[test] fn integration_test1_3() { assert_erl!( r#" @external(erlang, "Elixir.MyApp", "run") pub fn run() -> Int "# ); } #[test] fn integration_test7() { assert_erl!( r#" @external(erlang, "try", "and") pub fn receive() -> Int pub fn catch(x) { receive() } "# ); } #[test] fn private_external_function_calls() { // Private external function calls are inlined assert_erl!( r#" @external(erlang, "m", "f") fn go(x x: Int, y y: Int) -> Int pub fn x() { go(x: 1, y: 2) go(y: 3, x: 4) }"# ); } #[test] fn public_local_function_calls() { // Public external function calls are inlined but the wrapper function is // also printed in the erlang output and exported assert_erl!( r#" @external(erlang, "m", "f") pub fn go(x x: Int, y y: Int) -> Int pub fn x() { go(x: 1, y: 2) go(y: 3, x: 4) } "# ); } #[test] fn private_local_function_references() { // Private external function references are inlined assert_erl!( r#" @external(erlang, "m", "f") fn go(x: Int, y: Int) -> Int pub fn x() { go } "# ); } #[test] fn inlining_external_functions_from_another_module() { assert_erl!( ( "lib", "atom", r#" pub type Atom @external(erlang, "erlang", "binary_to_atom") pub fn make(x: String) -> Atom "# ), r#"import atom pub fn main() { atom.make("ok") } "# ); } #[test] fn unqualified_inlining_external_functions_from_another_module() { assert_erl!( ( "lib", "atom", r#" pub type Atom @external(erlang, "erlang", "binary_to_atom") pub fn make(x: String) -> Atom "# ), "import atom.{make} pub fn main() { make(\"ok\") } " ); } #[test] fn reference_to_imported_elixir_external_fn() { assert_erl!( ( "lib", "my_app", r#" @external(erlang, "Elixir.MyApp", "run") pub fn run() -> Int "# ), r#"import my_app pub fn main() { let x = my_app.run id(my_app.run) } fn id(x) { x } "# ); } #[test] fn unqualified_reference_to_imported_elixir_external_fn() { assert_erl!( ( "lib", "my_app", r#" @external(erlang, "Elixir.MyApp", "run") pub fn run() -> Int "# ), r#"import my_app.{run} pub fn main() { let x = run id(run) } fn id(x) { x } "# ); } #[test] fn attribute_erlang() { assert_erl!( r#" @external(erlang, "one", "one") pub fn one(x: Int) -> Int { todo } "# ); } #[test] fn attribute_javascript() { assert_erl!( r#" @external(javascript, "./one.mjs", "one") pub fn one(x: Int) -> Int { todo } "# ); } #[test] fn erlang_and_javascript() { assert_erl!( r#" @external(erlang, "one", "one") @external(javascript, "./one.mjs", "one") pub fn one(x: Int) -> Int { todo } "# ); } #[test] fn no_type_annotation_for_parameter() { assert_module_error!( r#" @external(erlang, "one", "one") pub fn one(x: Int, y) -> Int { todo } "# ); } #[test] fn no_type_annotation_for_return() { assert_module_error!( r#" @external(erlang, "one", "one") pub fn one(x: Int) { todo } "# ); } #[test] fn hole_parameter_erlang() { assert_module_error!( r#" @external(erlang, "one", "one") pub fn one(x: List(_)) -> Int { todo } "# ); } #[test] fn hole_return_erlang() { assert_module_error!( r#" @external(erlang, "one", "one") pub fn one(x: List(Int)) -> List(_) { todo } "# ); } #[test] fn hole_parameter_javascript() { assert_module_error!( r#" @external(javascript, "one", "one") pub fn one(x: List(_)) -> Int { todo } "# ); } #[test] fn hole_return_javascript() { assert_module_error!( r#" @external(javascript, "one", "one") pub fn one(x: List(Int)) -> List(_) { todo } "# ); } #[test] fn no_body() { assert_erl!( r#" @external(erlang, "one", "one") pub fn one(x: Int) -> Int "# ); } #[test] fn no_body_or_implementation() { assert_module_error!( r#" pub fn one(x: Int) -> Float "# ); } #[test] fn private() { assert_erl!( r#" pub fn main() { do() } @external(erlang, "library", "main") fn do() -> Int "# ); } #[test] fn elixir() { assert_erl!( r#" pub fn main() { #(do, do()) } @external(erlang, "Elixir.String", "main") fn do() -> Int "# ); } #[test] fn public_elixir() { assert_erl!( r#" @external(erlang, "Elixir.String", "main") pub fn do() -> Int "# ); } #[test] fn javascript_only() { assert_erl!( r#" pub fn should_be_generated(x: Int) -> Int { x } @external(javascript, "one", "one") pub fn should_not_be_generated(x: Int) -> Int "# ); } #[test] fn javascript_only_indirect() { assert_erl!( r#" pub fn should_be_generated(x: Int) -> Int { x } @external(javascript, "one", "one") pub fn should_not_be_generated(x: Int) -> Int pub fn also_should_not_be_generated() { should_not_be_generated(1) |> should_be_generated } "# ); } #[test] fn both_externals_no_valid_impl() { assert_erl!( r#" @external(javascript, "one", "one") pub fn js() -> Nil @external(erlang, "one", "one") pub fn erl() -> Nil pub fn should_not_be_generated() { js() erl() } "# ); } #[test] fn no_gleam_impl_no_annotations_function_fault_tolerance() { // A function not having annotations when required does not stop analysis. assert_module_error!( r#" @external(erlang, "one", "two") pub fn no_impl() pub type X = UnknownType "# ); } #[test] fn no_target_supported_function_fault_tolerance() { // A function not supporting the current target does not stop analysis. assert_js_module_error!( r#" // This will error for having no support on this platform @external(erlang, "one", "two") pub fn no_impl() -> Int pub fn main() { // This will due to no_impl not having an appropriate implementation for the // target, NOT because it doesn't exist. The analyser should still know about // it, even though it is invalid. no_impl() } "# ); } #[test] fn discarded_arg_in_external_are_passed_correctly() { assert_erl!( r#" @external(erlang, "wibble", "wobble") pub fn woo(_a: a) -> Nil "# ); } #[test] fn multiple_discarded_args_in_external_are_passed_correctly() { assert_erl!( r#" @external(erlang, "wibble", "wobble") pub fn woo(_: a, _: b) -> Nil "# ); } #[test] fn multiple_discarded_args_in_external_are_passed_correctly_2() { assert_erl!( r#" @external(erlang, "wibble", "wobble") pub fn woo(__: a, _two: b) -> Nil "# ); } ================================================ FILE: compiler-core/src/erlang/tests/functions.rs ================================================ use crate::assert_erl; #[test] fn function_as_value() { assert_erl!( r#" fn other() { Nil } pub fn main() { other } "# ); } #[test] fn nested_imported_function_as_value() { assert_erl!( ("package", "some/other", "pub fn wibble() { Nil }"), r#" import some/other pub fn main() { other.wibble } "# ); } #[test] fn nested_unqualified_imported_function_as_value() { assert_erl!( ("package", "some/other", "pub fn wibble() { Nil }"), r#" import some/other.{wibble} pub fn main() { wibble } "# ); } #[test] fn nested_aliased_imported_function_as_value() { assert_erl!( ("package", "some/other", "pub fn wibble() { Nil }"), r#" import some/other.{wibble as wobble} pub fn main() { wobble } "# ); } #[test] fn function_called() { assert_erl!( r#" pub fn main() { main() } "# ); } #[test] fn nested_imported_function_called() { assert_erl!( ("package", "some/other", "pub fn wibble() { Nil }"), r#" import some/other pub fn main() { other.wibble() } "# ); } #[test] fn nested_unqualified_imported_function_called() { assert_erl!( ("package", "some/other", "pub fn wibble() { Nil }"), r#" import some/other.{wibble} pub fn main() { wibble() } "# ); } #[test] fn nested_aliased_imported_function_called() { assert_erl!( ("package", "some/other", "pub fn wibble() { Nil }"), r#" import some/other.{wibble as wobble} pub fn main() { wobble() } "# ); } #[test] fn labelled_argument_ordering() { // https://github.com/gleam-lang/gleam/issues/3671 assert_erl!( " type A { A } type B { B } type C { C } type D { D } fn wibble(a a: A, b b: B, c c: C, d d: D) { Nil } pub fn main() { wibble(A, C, D, b: B) wibble(A, C, D, b: B) wibble(B, C, D, a: A) wibble(B, C, a: A, d: D) wibble(B, C, d: D, a: A) wibble(B, D, a: A, c: C) wibble(B, D, c: C, a: A) wibble(C, D, b: B, a: A) } " ); } #[test] fn unused_private_functions() { assert_erl!( r#" pub fn main() -> Int { used() } fn used() -> Int { 123 } fn unused1() -> Int { unused2() } fn unused2() -> Int { used() } fn unused3() -> Int { used() } "# ); } ================================================ FILE: compiler-core/src/erlang/tests/guards.rs ================================================ use crate::assert_erl; #[test] fn clause_guards() { // Clause guards assert_erl!( r#" pub fn main(args) { case args { x if x == args -> 1 _ -> 0 } } "# ); } #[test] fn clause_guards_1() { assert_erl!( r#" pub fn main(args) { case args { x if {x != x} == {args == args} -> 1 _ -> 0 } } "# ); } #[test] fn clause_guards_2() { assert_erl!( r#" pub fn main(args) { case args { x if x && x || x == x && x -> 1 _ -> 0 } } "# ); } #[test] fn clause_guards_3() { assert_erl!( r#" pub fn main() { case 1, 0 { x, y if x > y -> 1 _, _ -> 0 } } "# ); } #[test] fn clause_guards_4() { assert_erl!( r#" pub fn main() { case 1, 0 { x, y if x >= y -> 1 _, _ -> 0 } } "# ); } #[test] fn clause_guards_5() { assert_erl!( r#" pub fn main() { case 1, 0 { x, y if x < y -> 1 _, _ -> 0 } } "# ); } #[test] fn clause_guards_6() { assert_erl!( r#" pub fn main() { case 1, 0 { x, y if x <= y -> 1 _, _ -> 0 } } "# ); } #[test] fn clause_guards_7() { assert_erl!( r#" pub fn main() { case 1.0, 0.1 { x, y if x >. y -> 1 _, _ -> 0 } } "# ); } #[test] fn clause_guards_8() { assert_erl!( r#" pub fn main() { case 1.0, 0.1 { x, y if x >=. y -> 1 _, _ -> 0 } } "# ); } #[test] fn clause_guards_9() { assert_erl!( r#" pub fn main() { let x = 0.123 case x { 99.9854 -> 1 _ -> 0 } } "# ); } #[test] fn clause_guards_10() { assert_erl!( r#" pub fn main() { let x = 0.123 case x { _ if x == 3.14 -> 1 _ -> 0 } } "# ); } #[test] fn clause_guards20() { assert_erl!( r#" pub fn main() { let x = 0.123 case x { _ if 0.123 <. x -> 1 _ -> 0 } } "# ); } #[test] fn clause_guards21() { assert_erl!( r#" pub fn main(x) { case x { _ if x == [1, 2, 3] -> 1 _ -> 0 } } "# ); } #[test] fn clause_guards22() { assert_erl!( r#" pub fn main() { let x = 0 case x { 0 -> 1 _ -> 0 } } "# ); } #[test] fn clause_guards23() { // Tuple literals in guards assert_erl!( r#" pub fn main() { let x = #(1, 2, 3) case x { _ if x == #(1, 2, 3) -> 1 _ -> 0 } } "# ); } #[test] fn clause_guards24() { assert_erl!( r#" pub fn main() { let x = #(1, 2, 3) case x { _ if x == #(1, 2, 3) -> 1 _ if x == #(2, 3, 4) -> 2 _ -> 0 } } "# ); } #[test] fn clause_guards25() { // Int literals in guards assert_erl!( r#" pub fn main() { let x = 0 case x { _ if x == 0 -> 1 _ -> 0 } } "# ); } #[test] fn clause_guards26() { assert_erl!( r#" pub fn main() { let x = 0 case x { _ if 0 < x -> 1 _ -> 0 } } "# ); } #[test] fn clause_guards27() { // String literals in guards assert_erl!( r#" pub fn main() { case "test" { x if x == "test" -> 1 _ -> 0 } } "# ); } #[test] fn clause_guards28() { // Record literals in guards assert_erl!( r#" type Test { Test(x: Int, y: Float) } pub fn main() { let x = Test(1, 3.0) case x { _ if x == Test(1, 1.0) -> 1 _ if x == Test(y: 2.0, x: 2) -> 2 _ if x != Test(2, 3.0) -> 2 _ -> 0 } } "# ); } #[test] fn clause_guards29() { // Float vars in guards assert_erl!( r#" pub fn main() { case 0.1, 1.0 { x, y if x <. y -> 1 _, _ -> 0 } } "# ); } #[test] fn clause_guards30() { assert_erl!( r#" pub fn main() { case 0.1, 1.0 { x, y if x <=. y -> 1 _, _ -> 0 } } "# ); } #[test] fn clause_guards31() { assert_erl!( r#" pub fn main(args) { case args { [x] | [x, _] if x -> 1 _ -> 0 } } "# ); } #[test] fn clause_guards32() { assert_erl!( r#" pub fn main() { let wibble = "wobble" case wibble { x if x == "wob" <> "ble" -> 1 _ -> 0 } } "# ); } #[test] fn constants_in_guards() { assert_erl!( r#" pub const string_value = "constant value" pub const float_value = 3.14 pub const int_value = 42 pub const tuple_value = #(1, 2.0, "3") pub const list_value = [1, 2, 3] pub fn main(arg) { let _ = list_value case arg { #(w, x, y, z) if w == tuple_value && x == string_value && y >. float_value && z == int_value -> 1 _ -> 0 } } "# ); } #[test] fn constants_in_guards1() { assert_erl!( r#" pub const list = [1, 2, 3] pub fn main(arg) { case arg { _ if arg == list -> 1 _ -> 0 } } "# ); } #[test] fn only_guards() { assert_erl!( r#" pub const string_value = "constant value" pub fn main(arg) { case arg { _ if arg == string_value -> 1 _ -> 0 } } "# ); } #[test] fn only_guards1() { assert_erl!( r#" pub const bits = <<1, "ok":utf8, 3, 4:50>> pub fn main(arg) { case arg { _ if arg == bits -> 1 _ -> 0 } } "# ); } #[test] fn only_guards2() { assert_erl!( r#" pub const constant = #(1, 2.0) pub fn main(arg) { case arg { _ if arg == constant -> 1 _ -> 0 } } "# ); } #[test] fn only_guards3() { assert_erl!( r#" pub const float_value = 3.14 pub fn main(arg) { case arg { _ if arg >. float_value -> 1 _ -> 0 } } "# ); } #[test] fn field_access() { assert_erl!( r#" pub type Person { Person(username: String, name: String, age: Int) } pub fn main() { let given_name = "jack" let raiden = Person("raiden", "jack", 31) case given_name { name if name == raiden.name -> "It's jack" _ -> "It's not jack" } } "# ) } #[test] fn nested_record_access() { assert_erl!( r#" pub type A { A(b: B) } pub type B { B(c: C) } pub type C { C(d: Bool) } pub fn a(a: A) { case a { _ if a.b.c.d -> 1 _ -> 0 } } "# ); } #[test] fn module_string_access() { assert_erl!( ( "package", "hero", r#" pub const ironman = "Tony Stark" "# ), r#" import hero pub fn main() { let name = "Tony Stark" case name { n if n == hero.ironman -> True _ -> False } } "# ); } #[test] fn module_list_access() { assert_erl!( ( "package", "hero", r#" pub const heroes = ["Tony Stark", "Bruce Wayne"] "# ), r#" import hero pub fn main() { let names = ["Tony Stark", "Bruce Wayne"] case names { n if n == hero.heroes -> True _ -> False } } "# ); } #[test] fn module_tuple_access() { assert_erl!( ( "package", "hero", r#" pub const hero = #("ironman", "Tony Stark") "# ), r#" import hero pub fn main() { let name = "Tony Stark" case name { n if n == hero.hero.1 -> True _ -> False } } "# ); } #[test] fn module_access() { assert_erl!( ( "package", "hero", r#" pub type Hero { Hero(name: String) } pub const ironman = Hero("Tony Stark") "# ), r#" import hero pub fn main() { let name = "Tony Stark" case name { n if n == hero.ironman.name -> True _ -> False } } "# ); } #[test] fn module_nested_access() { assert_erl!( ( "package", "hero", r#" pub type Person { Person(name: String) } pub type Hero { Hero(secret_identity: Person) } const bruce = Person("Bruce Wayne") pub const batman = Hero(bruce) "# ), r#" import hero pub fn main() { let name = "Bruce Wayne" case name { n if n == hero.batman.secret_identity.name -> True _ -> False } } "# ); } ================================================ FILE: compiler-core/src/erlang/tests/inlining.rs ================================================ use crate::assert_erl; const BOOL_MODULE: &str = " pub fn guard( when condition: Bool, return value: a, otherwise callback: fn() -> a, ) -> a { case condition { True -> value False -> callback() } } pub fn lazy_guard( when condition: Bool, return consequence: fn() -> a, otherwise alternative: fn() -> a, ) -> a { case condition { True -> consequence() False -> alternative() } } "; const RESULT_MODULE: &str = " pub fn try(result: Result(a, e), apply f: fn(a) -> Result(b, e)) -> Result(b, e) { case result { Ok(value) -> f(value) Error(error) -> Error(error) } } pub fn map(over result: Result(a, e), with f: fn(a) -> b) -> Result(b, e) { case result { Ok(value) -> Ok(f(value)) Error(error) -> Error(error) } } "; #[test] fn inline_higher_order_function() { assert_erl!( ("gleam_stdlib", "gleam/result", RESULT_MODULE), " import gleam/result pub fn main() { result.map(over: Ok(10), with: double) } fn double(x) { x + x } " ); } #[test] fn inline_higher_order_function_with_capture() { assert_erl!( ("gleam_stdlib", "gleam/result", RESULT_MODULE), " import gleam/result pub fn main() { result.try(Ok(10), divide(_, 2)) } fn divide(a: Int, b: Int) -> Result(Int, Nil) { case a % b { 0 -> Ok(a / b) _ -> Error(Nil) } } " ); } #[test] fn inline_higher_order_function_anonymous() { assert_erl!( ("gleam_stdlib", "gleam/result", RESULT_MODULE), " import gleam/result pub fn main() { result.try(Ok(10), fn(value) { Ok({ value + 2 } * 4) }) } " ); } #[test] fn inline_function_which_calls_other_function() { // This function calls `result.try`, meaning this must be inlined twice to // achieve the desired result. assert_erl!( ("gleam_stdlib", "gleam/result", RESULT_MODULE), ( "gleam_stdlib", "testing", " import gleam/result.{try} pub fn always_inline(result, f) -> Result(b, e) { try(result, f) } " ), " import testing pub fn main() { testing.always_inline(Ok(10), Error) } " ); } #[test] fn inline_function_with_use() { assert_erl!( ("gleam_stdlib", "gleam/bool", BOOL_MODULE), " import gleam/bool pub fn divide(a, b) { use <- bool.guard(when: b == 0, return: 0) a / b } " ); } #[test] fn inline_function_with_use_and_anonymous() { assert_erl!( ("gleam_stdlib", "gleam/bool", BOOL_MODULE), r#" import gleam/bool pub fn divide(a, b) { use <- bool.lazy_guard(b == 0, fn() { panic as "Cannot divide by 0" }) a / b } "# ); } #[test] fn inline_function_with_use_becomes_tail_recursive() { assert_erl!( ("gleam_stdlib", "gleam/bool", BOOL_MODULE), " import gleam/bool pub fn count(from: Int, to: Int) -> Int { use <- bool.guard(when: from >= to, return: from) echo from count(from + 1, to) } " ); } #[test] fn do_not_inline_parameters_used_more_than_once() { // Since the `something` parameter is used more than once in the body of the // function, it should not be inlined, and should be assigned once at the // beginning of the function. assert_erl!( ( "gleam_stdlib", "testing", " pub fn always_inline(something) { case something { True -> something False -> False } } " ), " import testing pub fn main() { testing.always_inline(True) } " ); } #[test] fn do_not_inline_parameters_that_have_side_effects() { assert_erl!( ("gleam_stdlib", "gleam/result", RESULT_MODULE), r#" import gleam/result pub fn main() { result.map(Ok(10), do_side_effects()) } fn do_side_effects() { let function = fn(x) { x + 1 } panic as "Side effects" function } "# ); } #[test] fn inline_anonymous_function_call() { assert_erl!( " pub fn main() { fn(a, b) { #(a, b) }(42, False) } " ); } #[test] fn inline_anonymous_function_in_pipe() { assert_erl!( " pub fn main() { 1 |> fn(x) { x + 1 } |> fn(y) { y * y } } " ); } #[test] fn inline_function_capture_in_pipe() { // The function capture is desugared to an anonymous function, so it should // be turned into a direct call to `add` assert_erl!( " pub fn main() { 1 |> add(4, _) } fn add(a, b) { a + b } " ); } #[test] fn inlining_works_through_blocks() { assert_erl!( " pub fn main() { { fn(x) { Ok(x + 1) } }(41) } " ); } #[test] fn blocks_get_preserved_when_needed() { assert_erl!( " pub fn main() { { 4 |> make_adder }(6) } fn make_adder(a) { fn(b) { a + b } } " ); } #[test] fn blocks_get_preserved_when_needed2() { assert_erl!( " pub fn main() { fn(x) { 1 + x }(2) * 3 } " ); } #[test] fn parameters_from_nested_functions_are_correctly_inlined() { assert_erl!( ("gleam_stdlib", "gleam/result", RESULT_MODULE), " import gleam/result pub fn halve_all(a, b, c) { use x <- result.try(divide(a, 2)) use y <- result.try(divide(b, 2)) use z <- result.map(divide(c, 2)) #(x, y, z) } fn divide(a, b) { case a % b { 0 -> Ok(a / b) _ -> Error(Nil) } } " ); } // https://github.com/gleam-lang/gleam/issues/4852 #[test] fn inlining_works_properly_with_record_updates() { assert_erl!( ("gleam_stdlib", "gleam/result", RESULT_MODULE), " import gleam/result pub type Wibble { Wibble(a: Int, b: Int) } pub fn main() { let w = Wibble(1, 2) use b <- result.map(Ok(3)) Wibble(..w, b:) } " ); } // https://github.com/gleam-lang/gleam/issues/4877 #[test] fn inline_shadowed_variable() { assert_erl!( " pub fn main() { let a = 10 let b = 20 fn(x) { let a = 7 x + a }(a + b) a } " ); } #[test] fn inline_variable_shadowing_parameter() { assert_erl!( " pub fn sum(a, b) { fn(x) { let a = 7 x + a }(a + b) a } " ); } #[test] fn inline_shadowed_variable_nested() { assert_erl!( " pub fn sum(a, b) { fn(x) { let a = 7 fn(y) { let a = 10 y - a }(x + a) a }(a + b) a } " ); } #[test] fn inline_variable_shadowed_in_case_pattern() { assert_erl!( " pub fn sum() { let a = 10 let b = 20 fn(x) { case 7, 8 { a, b -> a + b + x } }(a + b) a + b } " ); } #[test] fn inline_variable_shadowing_case_pattern() { assert_erl!( " pub fn sum() { case 1, 2 { a, b -> fn(x) { let a = 7 x + a }(a + b) } } " ); } ================================================ FILE: compiler-core/src/erlang/tests/let_assert.rs ================================================ use crate::assert_erl; #[test] fn one_var() { // One var assert_erl!( r#"pub fn go() { let assert Ok(y) = Ok(1) y }"# ); } #[test] fn more_than_one_var() { // More vars assert_erl!( r#"pub fn go(x) { let assert [1, a, b, c] = x [a, b, c] }"# ); } #[test] fn pattern_let() { // Pattern::Let assert_erl!( r#"pub fn go(x) { let assert [1 as a, b, c] = x [a, b, c] }"# ); } #[test] fn variable_rewrites() { // Following asserts use appropriate variable rewrites assert_erl!( r#"pub fn go() { let assert Ok(y) = Ok(1) let assert Ok(y) = Ok(1) y }"# ); } #[test] fn message() { assert_erl!( r#" pub fn unwrap_or_panic(value) { let assert Ok(inner) = value as "Oops, there was an error" inner } "# ); } #[test] fn variable_message() { assert_erl!( r#" pub fn expect(value, message) { let assert Ok(inner) = value as message inner } "# ); } #[test] fn just_variable() { assert_erl!( r#"pub fn go() { let assert x = Ok(1) x }"# ); } #[test] fn tuple_pattern() { assert_erl!( r#"pub fn go() { let assert #(a, b, c) = #(1, 2, 3) a + b + c }"# ); } #[test] fn int_pattern() { assert_erl!( r#"pub fn go() { let assert 1 = 2 }"# ); } #[test] fn float_pattern() { assert_erl!( r#"pub fn go() { let assert 1.5 = 5.1 }"# ); } #[test] fn string_pattern() { assert_erl!( r#"pub fn go() { let assert "Hello!" = "Hel" <> "lo!" }"# ); } #[test] fn assignment_pattern() { assert_erl!( r#"pub fn go() { let assert 123 as x = 123 x }"# ); } #[test] fn discard_pattern() { assert_erl!( r#"pub fn go() { let assert _ = 123 }"# ); } #[test] fn list_pattern() { assert_erl!( r#"pub fn go() { let assert [1, x, 3] = [1, 2, 3] x }"# ); } #[test] fn list_pattern_with_multiple_variables() { assert_erl!( r#"pub fn go() { let assert [a, b, c] = [1, 2, 3] a + b + c }"# ); } #[test] fn constructor_pattern() { assert_erl!( r#"pub fn go() { let assert Ok(x) = Error(Nil) x }"# ); } #[test] fn constructor_pattern_with_multiple_variables() { assert_erl!( r#" pub type Wibble { Wibble(Int, Float) } pub fn go() { let assert Wibble(x, 2.0 as y) = Wibble(1, 2.0) x }"# ); } #[test] fn bit_array_pattern() { assert_erl!( r#"pub fn go() { let assert <> = <<123>> a + b + c }"# ); } #[test] fn string_prefix_pattern() { assert_erl!( r#"pub fn go() { let assert "Hello " <> name = "Hello John" name }"# ); } #[test] fn string_prefix_pattern_with_prefix_binding() { assert_erl!( r#"pub fn go() { let assert "Hello " as greeting <> name = "Hello John" #(greeting, name) }"# ); } #[test] fn let_assert_at_end_of_block() { assert_erl!( r#" pub fn go() { let result = Ok(10) let x = { let assert Ok(_) = result } x }"# ); } // https://github.com/gleam-lang/gleam/issues/4145 #[test] fn reference_earlier_segment() { assert_erl!( " pub fn main() { let assert <> = <<3, 1, 2, 3>> bytes } " ); } // https://github.com/gleam-lang/gleam/issues/3375 #[test] fn bit_array_assignment_int() { assert_erl!( " pub fn main() { let assert <<1 as a>> = <<1>> a } " ); } // https://github.com/gleam-lang/gleam/issues/3375 #[test] fn bit_array_assignment_float() { assert_erl!( " pub fn main() { let assert <<3.14 as pi:float>> = <<3.14>> pi } " ); } // https://github.com/gleam-lang/gleam/issues/3375 #[test] fn bit_array_assignment_string() { assert_erl!( r#" pub fn main() { let assert <<"Hello, world!" as message:utf8>> = <<"Hello, world!">> message } "# ); } // https://github.com/gleam-lang/gleam/issues/3375 #[test] fn bit_array_assignment_discard() { assert_erl!( r#" pub fn main() { let assert <<_ as number>> = <<10>> number } "# ); } // https://github.com/gleam-lang/gleam/issues/4924 #[test] fn let_assert_should_not_use_redefined_variable() { assert_erl!( r#" fn split_once(x: String, y: String) -> Result(#(String, String), String) { Ok(#(x, y)) } pub fn main() { let string = "Hello, world!" let assert Ok(#(prefix, string)) = split_once(string, "\n") as { "Failed to split: " <> string } } "# ); } ================================================ FILE: compiler-core/src/erlang/tests/numbers.rs ================================================ use crate::assert_erl; #[test] fn numbers_with_underscores() { assert_erl!( r#" pub fn main() { 100_000 100_000.00101 } "# ); } #[test] fn numbers_with_underscores1() { assert_erl!( r#" const i = 100_000 const f = 100_000.00101 pub fn main() { i f } "# ); } #[test] fn numbers_with_underscores2() { assert_erl!( r#" pub fn main() { let assert 100_000 = 1 let assert 100_000.00101 = 1. 1 } "# ); } #[test] fn numbers_with_scientific_notation() { assert_erl!( r#" const i = 100.001e223 const j = -100.001e-223 pub fn main() { i j } "# ); } #[test] fn int_negation() { assert_erl!( r#" pub fn main() { let a = 3 let b = -a } "# ); } #[test] fn repeated_int_negation() { assert_erl!( r#" pub fn main() { let a = 3 let b = --a } "# ); } // https://github.com/gleam-lang/gleam/issues/2356 #[test] fn zero_b_in_hex() { assert_erl!( r#" pub fn main() { 0xffe0bb } "# ); } ================================================ FILE: compiler-core/src/erlang/tests/panic.rs ================================================ use crate::assert_erl; #[test] fn panic_as() { assert_erl!( r#" pub fn main() { panic as "wibble" } "# ); } #[test] fn plain() { assert_erl!( r#" pub fn main() { panic } "# ); } #[test] fn panic_as_function() { assert_erl!( r#" pub fn retstring() { "wibble" } pub fn main() { panic as { retstring() <> "wobble" } } "# ); } // https://github.com/gleam-lang/gleam/issues/2176 #[test] fn piped() { assert_erl!( r#" pub fn main() { "lets" |> panic } "# ); } #[test] fn piped_chain() { assert_erl!( r#" pub fn main() { "lets" |> panic as "pipe" |> panic as "other panic" } "# ); } ================================================ FILE: compiler-core/src/erlang/tests/patterns.rs ================================================ use crate::assert_erl; #[test] fn alternative_patterns() { // reassigning name in alternative patterns assert_erl!( r#" pub fn main() { let duplicate_name = 1 case 1 { 1 | 2 -> { let duplicate_name = duplicate_name + 1 duplicate_name } _ -> 0 } }"# ); } #[test] fn alternative_patterns1() { // Alternative patterns with a clause containing vars assert_erl!( r#" pub fn main() { case Ok(1) { Ok(duplicate_name) | Error(duplicate_name) -> duplicate_name } }"# ); } #[test] fn alternative_patterns2() { // Alternative patterns with a guard clause containing vars assert_erl!( r#" pub fn main() { let duplicate_name = 1 case 1 { 1 | 2 if duplicate_name == 1 -> duplicate_name _ -> 0 } }"# ); } #[test] fn alternative_patterns3() { assert_erl!( r#" pub const constant = Ok(1) pub fn main(arg) { let _ = constant case arg { _ if arg == constant -> 1 _ -> 0 } } "# ); } #[test] fn pattern_as() { assert_erl!( "pub fn a(x) { case x { Ok(1 as y) -> 1 _ -> 0 } }" ); } #[test] fn string_prefix_as_pattern_with_multiple_subjects() { assert_erl!( "pub fn a(x) { case x, x { _, \"a\" as a <> _ -> a _, _ -> \"a\" } }" ); } #[test] fn string_prefix_as_pattern_with_multiple_subjects_and_guard() { assert_erl!( "pub fn a(x) { case x, x { _, \"a\" as a <> rest if rest == \"a\" -> a _, _ -> \"a\" } }" ); } #[test] fn string_prefix_as_pattern_with_list() { assert_erl!( "pub fn a(x) { case x { [\"a\" as a <> _, \"b\" as b <> _] -> a <> b _ -> \"\" } }" ); } #[test] fn string_prefix_as_pattern_with_assertion() { assert_erl!( "pub fn a(x) { let assert \"a\" as a <> rest = \"wibble\" a }" ); } ================================================ FILE: compiler-core/src/erlang/tests/pipes.rs ================================================ use crate::assert_erl; #[test] fn clever_pipe_rewriting() { // a |> b assert_erl!( r#" pub fn apply(f: fn(a) -> b, a: a) { a |> f } "# ); } #[test] fn clever_pipe_rewriting1() { // a |> b(c) assert_erl!( r#" pub fn apply(f: fn(a, Int) -> b, a: a) { a |> f(1) } "# ); } // https://github.com/gleam-lang/gleam/issues/952 #[test] fn block_expr_into_pipe() { assert_erl!( r#"fn id(a) { a } pub fn main() { { let x = 1 x } |> id }"# ); } #[test] fn pipe_in_list() { assert_erl!( "pub fn x(f) { [ 1 |> f ] }" ); } #[test] fn pipe_in_tuple() { assert_erl!( "pub fn x(f) { #( 1 |> f ) }" ); } #[test] fn pipe_in_case_subject() { assert_erl!( "pub fn x(f) { case 1 |> f { x -> x } }" ); } // https://github.com/gleam-lang/gleam/issues/1379 #[test] fn pipe_in_record_update() { assert_erl!( "pub type X { X(a: Int, b: Int) } fn id(x) { x } pub fn main(x) { X(..x, a: 1 |> id) }" ); } // https://github.com/gleam-lang/gleam/issues/1385 #[test] fn pipe_in_eq() { assert_erl!( "fn id(x) { x } pub fn main() { 1 == 1 |> id }" ); } // https://github.com/gleam-lang/gleam/issues/1863 #[test] fn call_pipeline_result() { assert_erl!( r#" pub fn main() { { 1 |> add }(1) } pub fn add(x) { fn(y) { x + y } } "# ); } // https://github.com/gleam-lang/gleam/issues/1931 #[test] fn pipe_in_call() { assert_erl!( r#" pub fn main() { 123 |> two( 1 |> two(2), _, ) } pub fn two(a, b) { a } "# ); } #[test] fn multiple_pipes() { assert_erl!( " pub fn main() { 1 |> x |> x 2 |> x |> x } fn x(x) { x } " ); } ================================================ FILE: compiler-core/src/erlang/tests/prelude.rs ================================================ use crate::assert_erl; #[test] fn qualified_prelude() { assert_erl!( "import gleam pub type X { X(gleam.Int) } " ); assert_erl!( "import gleam pub fn x() { gleam.Ok(1) } " ); } ================================================ FILE: compiler-core/src/erlang/tests/records.rs ================================================ use crate::assert_erl; use crate::erlang::*; use crate::type_; #[test] fn basic() { insta::assert_snapshot!(record_definition( "PetCat", &[ ("name", type_::tuple(vec![])), ("is_cute", type_::tuple(vec![])) ] )); } #[test] fn reserve_words() { // Reserved words are escaped in record names and fields insta::assert_snapshot!(record_definition( "div", &[ ("receive", type_::int()), ("catch", type_::tuple(vec![])), ("unreserved", type_::tuple(vec![])) ] )); } #[test] fn type_vars() { // Type vars are printed as `any()` because records don't support generics insta::assert_snapshot!(record_definition( "PetCat", &[ ("name", type_::generic_var(1)), ("is_cute", type_::unbound_var(1)), ("linked", type_::link(type_::int())) ] )); } #[test] fn module_types() { // Types are printed with module qualifiers let module_name = "name".into(); insta::assert_snapshot!(record_definition( "PetCat", &[( "name", Arc::new(Type::Named { publicity: Publicity::Public, package: "package".into(), module: module_name, name: "my_type".into(), arguments: vec![], inferred_variant: None, }) )] )); } #[test] fn long_definition_formatting() { // Long definition formatting insta::assert_snapshot!(record_definition( "PetCat", &[ ("name", type_::generic_var(1)), ("is_cute", type_::unbound_var(1)), ("linked", type_::link(type_::int())), ( "whatever", type_::list(type_::tuple(vec![ type_::nil(), type_::list(type_::tuple(vec![type_::nil(), type_::nil(), type_::nil()])), type_::nil(), type_::list(type_::tuple(vec![type_::nil(), type_::nil(), type_::nil()])), type_::nil(), type_::list(type_::tuple(vec![type_::nil(), type_::nil(), type_::nil()])), ])) ), ] )); } #[test] fn record_accessors() { // We can use record accessors for types with only one constructor assert_erl!( r#" pub type Person { Person(name: String, age: Int) } pub fn get_age(person: Person) { person.age } pub fn get_name(person: Person) { person.name } "# ); } #[test] fn record_accessor_multiple_variants() { // We can access fields on custom types with multiple variants assert_erl!( " pub type Person { Teacher(name: String, title: String) Student(name: String, age: Int) } pub fn get_name(person: Person) { person.name }" ); } #[test] fn record_accessor_multiple_variants_positions_other_than_first() { // We can access fields on custom types with multiple variants // In positions other than the 1st field assert_erl!( " pub type Person { Teacher(name: String, age: Int, title: String) Student(name: String, age: Int) } pub fn get_name(person: Person) { person.name } pub fn get_age(person: Person) { person.age }" ); } #[test] fn record_accessor_multiple_variants_parameterised_types() { // We can access fields on custom types with multiple variants // In positions other than the 1st field assert_erl!( " pub type Person { Teacher(name: String, age: List(Int), title: String) Student(name: String, age: List(Int)) } pub fn get_name(person: Person) { person.name } pub fn get_age(person: Person) { person.age }" ); } #[test] fn record_accessor_multiple_with_first_position_different_types() { // We can access fields on custom types with multiple variants // In positions other than the 1st field assert_erl!( " pub type Person { Teacher(name: Nil, age: Int) Student(name: String, age: Int) } pub fn get_age(person: Person) { person.age }" ); } #[test] fn record_spread() { // Test binding to a record field with the spread operator assert_erl!( r#" pub type Triple { Triple(a: Int, b: Int, c: Int) } pub fn main() { let triple = Triple(1,2,3) let Triple(the_a, ..) = triple the_a } "# ); } #[test] fn record_spread1() { // Test binding to a record field with the spread operator // Test binding to a record field with the spread operator and a labelled argument assert_erl!( r#" pub type Triple { Triple(a: Int, b: Int, c: Int) } pub fn main() { let triple = Triple(1,2,3) let Triple(b: the_b, ..) = triple the_b } "# ); } #[test] fn record_spread2() { // Test binding to a record field with the spread operator with both a labelled argument and a positional argument assert_erl!( r#" pub type Triple { Triple(a: Int, b: Int, c: Int) } pub fn main() { let triple = Triple(1,2,3) let Triple(the_a, c: the_c, ..) = triple the_c } "# ); } #[test] fn record_spread3() { // Test binding to a record field with the spread operator in a match assert_erl!( r#" pub type Triple { Triple(a: Int, b: Int, c: Int) } pub fn main() { let triple = Triple(1,2,3) case triple { Triple(b: the_b, ..) -> the_b } } "# ); } #[test] fn record_updates() { // Record updates assert_erl!( r#" pub type Person { Person(name: String, age: Int) } pub fn main() { let p = Person("Quinn", 27) let new_p = Person(..p, age: 28) new_p } "# ); } #[test] fn record_updates1() { // Record updates with field accesses assert_erl!( r#" pub type Person { Person(name: String, age: Int) } pub fn main() { let p = Person("Quinn", 27) let new_p = Person(..p, age: p.age + 1) new_p } "# ); } #[test] fn record_updates2() { // Record updates with multiple fields assert_erl!( r#" pub type Person { Person(name: String, age: Int) } pub fn main() { let p = Person("Quinn", 27) let new_p = Person(..p, age: 28, name: "Riley") new_p } "# ); } #[test] fn record_updates3() { // Record updates when record is returned from function assert_erl!( r#" pub type Person { Person(name: String, age: Int) } pub fn main() { let new_p = Person(..return_person(), age: 28) new_p } fn return_person() { Person("Quinn", 27) } "# ); } #[test] fn record_updates4() { // Record updates when record is field on another record assert_erl!( r#" pub type Car { Car(make: String, model: String, driver: Person) } pub type Person { Person(name: String, age: Int) } pub fn main() { let car = Car(make: "Amphicar", model: "Model 770", driver: Person(name: "John Doe", age: 27)) let new_p = Person(..car.driver, age: 28) new_p } "# ); } #[test] fn record_constants() { assert_erl!( "pub type Test { A } const some_test = A pub fn a() { A }" ); } // https://github.com/gleam-lang/gleam/issues/1698 #[test] fn pipe_update_subject() { assert_erl!( "pub type Thing { Thing(a: Int, b: Int) } pub fn identity(x) { x } pub fn main() { let thing = Thing(1, 2) Thing(..thing |> identity, b: 1000) }" ); } // https://github.com/gleam-lang/gleam/issues/1698 #[test] fn record_access_block() { assert_erl!( "pub type Thing { Thing(a: Int, b: Int) } pub fn main() { { let thing = Thing(1, 2) thing }.a }" ); } // https://github.com/gleam-lang/gleam/issues/1981 #[test] fn imported_qualified_constructor_as_fn_name_escape() { assert_erl!( ("other_package", "other_module", "pub type Let { Let(Int) }"), "import other_module pub fn main() { other_module.Let }" ); } #[test] fn nested_record_update() { assert_erl!( "pub type Wibble { Wibble(a: Int, b: Wobble, c: Int) } pub type Wobble { Wobble(a: Int, b: Int) } pub fn main() { let base = Wibble(1, Wobble(2, 3), 4) Wibble(..base, b: Wobble(..base.b, b: 5)) }" ); } #[test] fn nested_record_update_with_blocks() { assert_erl!( "pub type A { A(b: B) } pub type B { B(c: C) } pub type C { C(val: Int) } pub fn main(a: A) { A(..a, b: { B(..a.b, c: { C(..a.b.c, val: 0) }) }) }" ) } #[test] fn private_unused_records() { assert_erl!( "type A { A(inner: Int) } type B { B(String) } type C { C(Int) } pub fn main(x: Int) -> Int { let a = A(x) a.inner } " ) } #[test] fn const_record_update_generic_respecialization() { assert_erl!( " pub type Box(a) { Box(name: String, value: a) } pub const base = Box(\"score\", 50) pub const updated = Box(..base, value: \"Hello\") pub fn main() { #(base, updated) } ", ); } #[test] fn record_update_with_unlabelled_fields() { assert_erl!( r#" pub type Wibble { Wibble(Int, Float, b: Bool, s: String) } pub fn main() { let record = Wibble(1, 3.14, True, "Hello") Wibble(..record, b: False) } "# ); } #[test] fn constant_record_update_with_unlabelled_fields() { assert_erl!( r#" pub type Wibble { Wibble(Int, Float, b: Bool, s: String) } pub const record = Wibble(1, 3.14, True, "Hello") pub const updated = Wibble(..record, b: False) pub fn main() { updated } "# ); } ================================================ FILE: compiler-core/src/erlang/tests/reserved.rs ================================================ use crate::assert_erl; #[test] fn build_in_erlang_type_escaping() { assert_erl!("pub type Map"); } #[test] fn escape_erlang_reserved_keywords_in_type_names() { // list of all reserved words in erlang // http://erlang.org/documentation/doc-5.8/doc/reference_manual/introduction.html assert_erl!( r#"pub type After { TestAfter } pub type And { TestAnd } pub type Andalso { TestAndAlso } pub type Band { TestBAnd } pub type Begin { TestBegin } pub type Bnot { TestBNot } pub type Bor { TestBOr } pub type Bsl { TestBsl } pub type Bsr { TestBsr } pub type Bxor { TestBXor } pub type Case { TestCase } pub type Catch { TestCatch } pub type Cond { TestCond } pub type Div { TestDiv } pub type End { TestEnd } pub type Fun { TestFun } pub type If { TestIf } pub type Let { TestLet } pub type Maybe { TestMaybe } pub type Not { TestNot } pub type Of { TestOf } pub type Or { TestOr } pub type Orelse { TestOrElse } pub type Query { TestQuery } pub type Receive { TestReceive } pub type Rem { TestRem } pub type Try { TestTry } pub type When { TestWhen } pub type Xor { TestXor }"# ); } ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__assert__assert_binary_operation.snap ================================================ --- source: compiler-core/src/erlang/tests/assert.rs expression: "\npub fn main() {\n let x = True\n assert x || False\n}\n" --- ----- SOURCE CODE pub fn main() { let x = True assert x || False } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> nil. main() -> X = true, case X orelse false of true -> nil; false -> erlang:error(#{gleam_error => assert, message => <<"Assertion failed."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 4, kind => binary_operator, operator => '||', left => #{kind => expression, value => false, start => 41, 'end' => 42 }, right => #{kind => literal, value => false, start => 46, 'end' => 51 }, start => 34, 'end' => 51, expression_start => 41}) end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__assert__assert_binary_operation2.snap ================================================ --- source: compiler-core/src/erlang/tests/assert.rs expression: "\npub fn eq(a, b) {\n assert a == b\n}\n" --- ----- SOURCE CODE pub fn eq(a, b) { assert a == b } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([eq/2]). -file("project/test/my/mod.gleam", 2). -spec eq(J, J) -> nil. eq(A, B) -> case A =:= B of true -> nil; false -> erlang:error(#{gleam_error => assert, message => <<"Assertion failed."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"eq"/utf8>>, line => 3, kind => binary_operator, operator => '==', left => #{kind => expression, value => A, start => 28, 'end' => 29 }, right => #{kind => expression, value => B, start => 33, 'end' => 34 }, start => 21, 'end' => 34, expression_start => 28}) end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__assert__assert_binary_operation3.snap ================================================ --- source: compiler-core/src/erlang/tests/assert.rs expression: "\npub fn assert_answer(x) {\n assert x == 42\n}\n" --- ----- SOURCE CODE pub fn assert_answer(x) { assert x == 42 } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([assert_answer/1]). -file("project/test/my/mod.gleam", 2). -spec assert_answer(integer()) -> nil. assert_answer(X) -> _assert_subject = 42, case X =:= _assert_subject of true -> nil; false -> erlang:error(#{gleam_error => assert, message => <<"Assertion failed."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"assert_answer"/utf8>>, line => 3, kind => binary_operator, operator => '==', left => #{kind => expression, value => X, start => 36, 'end' => 37 }, right => #{kind => literal, value => _assert_subject, start => 41, 'end' => 43 }, start => 29, 'end' => 43, expression_start => 36}) end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__assert__assert_binary_operator_with_side_effects.snap ================================================ --- source: compiler-core/src/erlang/tests/assert.rs expression: "\nfn wibble(a, b) {\n let result = a + b\n result == 10\n}\n\npub fn go(x) {\n assert True && wibble(1, 4)\n}\n" --- ----- SOURCE CODE fn wibble(a, b) { let result = a + b result == 10 } pub fn go(x) { assert True && wibble(1, 4) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/1]). -file("project/test/my/mod.gleam", 2). -spec wibble(integer(), integer()) -> boolean(). wibble(A, B) -> Result = A + B, Result =:= 10. -file("project/test/my/mod.gleam", 7). -spec go(any()) -> nil. go(X) -> case true of true -> case wibble(1, 4) of true -> nil; false -> erlang:error(#{gleam_error => assert, message => <<"Assertion failed."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"go"/utf8>>, line => 8, kind => binary_operator, operator => '&&', left => #{kind => literal, value => true, start => 82, 'end' => 86 }, right => #{kind => expression, value => false, start => 90, 'end' => 102 }, start => 75, 'end' => 102, expression_start => 82}) end; false -> erlang:error(#{gleam_error => assert, message => <<"Assertion failed."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"go"/utf8>>, line => 8, kind => binary_operator, operator => '&&', left => #{kind => literal, value => false, start => 82, 'end' => 86 }, right => #{kind => unevaluated, start => 90, 'end' => 102 }, start => 75, 'end' => 102, expression_start => 82}) end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__assert__assert_binary_operator_with_side_effects2.snap ================================================ --- source: compiler-core/src/erlang/tests/assert.rs expression: "\nfn wibble(a, b) {\n let result = a + b\n result == 10\n}\n\npub fn go(x) {\n assert wibble(5, 5) && wibble(4, 6)\n}\n" --- ----- SOURCE CODE fn wibble(a, b) { let result = a + b result == 10 } pub fn go(x) { assert wibble(5, 5) && wibble(4, 6) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/1]). -file("project/test/my/mod.gleam", 2). -spec wibble(integer(), integer()) -> boolean(). wibble(A, B) -> Result = A + B, Result =:= 10. -file("project/test/my/mod.gleam", 7). -spec go(any()) -> nil. go(X) -> case wibble(5, 5) of true -> case wibble(4, 6) of true -> nil; false -> erlang:error(#{gleam_error => assert, message => <<"Assertion failed."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"go"/utf8>>, line => 8, kind => binary_operator, operator => '&&', left => #{kind => expression, value => true, start => 82, 'end' => 94 }, right => #{kind => expression, value => false, start => 98, 'end' => 110 }, start => 75, 'end' => 110, expression_start => 82}) end; false -> erlang:error(#{gleam_error => assert, message => <<"Assertion failed."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"go"/utf8>>, line => 8, kind => binary_operator, operator => '&&', left => #{kind => expression, value => false, start => 82, 'end' => 94 }, right => #{kind => unevaluated, start => 98, 'end' => 110 }, start => 75, 'end' => 110, expression_start => 82}) end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__assert__assert_function_call.snap ================================================ --- source: compiler-core/src/erlang/tests/assert.rs expression: "\nfn bool() {\n True\n}\n\npub fn main() {\n assert bool()\n}\n" --- ----- SOURCE CODE fn bool() { True } pub fn main() { assert bool() } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec bool() -> boolean(). bool() -> true. -file("project/test/my/mod.gleam", 6). -spec main() -> nil. main() -> case bool() of true -> nil; false -> erlang:error(#{gleam_error => assert, message => <<"Assertion failed."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 7, kind => function_call, arguments => [], start => 41, 'end' => 54, expression_start => 48}) end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__assert__assert_function_call2.snap ================================================ --- source: compiler-core/src/erlang/tests/assert.rs expression: "\nfn and(a, b) {\n a && b\n}\n\npub fn go(x) {\n assert and(True, x)\n}\n" --- ----- SOURCE CODE fn and(a, b) { a && b } pub fn go(x) { assert and(True, x) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/1]). -file("project/test/my/mod.gleam", 2). -spec 'and'(boolean(), boolean()) -> boolean(). 'and'(A, B) -> A andalso B. -file("project/test/my/mod.gleam", 6). -spec go(boolean()) -> nil. go(X) -> case 'and'(true, X) of true -> nil; false -> erlang:error(#{gleam_error => assert, message => <<"Assertion failed."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"go"/utf8>>, line => 7, kind => function_call, arguments => [#{kind => literal, value => true, start => 56, 'end' => 60 }, #{kind => expression, value => X, start => 62, 'end' => 63 }], start => 45, 'end' => 64, expression_start => 52}) end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__assert__assert_literal.snap ================================================ --- source: compiler-core/src/erlang/tests/assert.rs expression: "\npub fn main() {\n assert False\n}\n" --- ----- SOURCE CODE pub fn main() { assert False } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> nil. main() -> case false of true -> nil; false -> erlang:error(#{gleam_error => assert, message => <<"Assertion failed."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 3, kind => expression, expression => #{kind => literal, value => false, start => 26, 'end' => 31 }, start => 19, 'end' => 31, expression_start => 26}) end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__assert__assert_nested_function_call.snap ================================================ --- source: compiler-core/src/erlang/tests/assert.rs expression: "\nfn and(x, y) {\n x && y\n}\n\npub fn main() {\n assert and(and(True, False), True)\n}\n" --- ----- SOURCE CODE fn and(x, y) { x && y } pub fn main() { assert and(and(True, False), True) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec 'and'(boolean(), boolean()) -> boolean(). 'and'(X, Y) -> X andalso Y. -file("project/test/my/mod.gleam", 6). -spec main() -> nil. main() -> _assert_subject = 'and'(true, false), case 'and'(_assert_subject, true) of true -> nil; false -> erlang:error(#{gleam_error => assert, message => <<"Assertion failed."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 7, kind => function_call, arguments => [#{kind => expression, value => _assert_subject, start => 57, 'end' => 73 }, #{kind => literal, value => true, start => 75, 'end' => 79 }], start => 46, 'end' => 80, expression_start => 53}) end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__assert__assert_variable.snap ================================================ --- source: compiler-core/src/erlang/tests/assert.rs expression: "\npub fn main() {\n let x = True\n assert x\n}\n" --- ----- SOURCE CODE pub fn main() { let x = True assert x } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> nil. main() -> X = true, case X of true -> nil; false -> erlang:error(#{gleam_error => assert, message => <<"Assertion failed."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 4, kind => expression, expression => #{kind => expression, value => false, start => 41, 'end' => 42 }, start => 34, 'end' => 42, expression_start => 41}) end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__assert__assert_with_block_message.snap ================================================ --- source: compiler-core/src/erlang/tests/assert.rs expression: "\nfn identity(a) {\n a\n}\n\npub fn main() {\n assert identity(True) as {\n let message = identity(\"This shouldn't fail\")\n message\n }\n}\n" --- ----- SOURCE CODE fn identity(a) { a } pub fn main() { assert identity(True) as { let message = identity("This shouldn't fail") message } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec identity(I) -> I. identity(A) -> A. -file("project/test/my/mod.gleam", 6). -spec main() -> nil. main() -> case identity(true) of true -> nil; false -> erlang:error(#{gleam_error => assert, message => begin Message = identity(<<"This shouldn't fail"/utf8>>), Message end, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 7, kind => function_call, arguments => [#{kind => literal, value => true, start => 59, 'end' => 63 }], start => 43, 'end' => 64, expression_start => 50}) end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__assert__assert_with_message.snap ================================================ --- source: compiler-core/src/erlang/tests/assert.rs expression: "\npub fn main() {\n assert True as \"This shouldn't fail\"\n}\n" --- ----- SOURCE CODE pub fn main() { assert True as "This shouldn't fail" } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> nil. main() -> case true of true -> nil; false -> erlang:error(#{gleam_error => assert, message => <<"This shouldn't fail"/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 3, kind => expression, expression => #{kind => literal, value => false, start => 26, 'end' => 30 }, start => 19, 'end' => 30, expression_start => 26}) end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__bit_array.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "pub fn main() {\n let a = 1\n let simple = <<1, a>>\n let complex = <<4:int-big, 5.0:little-float, 6:native-int>>\n let assert <<7:2, 8:size(3), b:bytes-size(4)>> = <<1>>\n let assert <> = <<1>>\n\n simple\n}\n" --- ----- SOURCE CODE pub fn main() { let a = 1 let simple = <<1, a>> let complex = <<4:int-big, 5.0:little-float, 6:native-int>> let assert <<7:2, 8:size(3), b:bytes-size(4)>> = <<1>> let assert <> = <<1>> simple } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 1). -spec main() -> bitstring(). main() -> A = 1, Simple = <<1, A>>, Complex = <<4/integer-big, 5.0/little-float, 6/native-integer>>, B@1 = case <<1>> of <<7:2, 8:3, B:4/binary>> -> B; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 5, value => _assert_fail, start => 116, 'end' => 170, pattern_start => 127, pattern_end => 162}) end, {C@1, D@1} = case <<1>> of <> -> {C, D}; _assert_fail@1 -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 6, value => _assert_fail@1, start => 173, 'end' => 232, pattern_start => 184, pattern_end => 224}) end, Simple. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__bit_array1.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "pub fn x() { 2 }\npub fn main() {\n let a = -1\n let b = <>\n\n b\n}\n" --- ----- SOURCE CODE pub fn x() { 2 } pub fn main() { let a = -1 let b = <> b } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([x/0, main/0]). -file("project/test/my/mod.gleam", 1). -spec x() -> integer(). x() -> 2. -file("project/test/my/mod.gleam", 2). -spec main() -> bitstring(). main() -> A = -1, B = <>, B. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__bit_array2.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "pub fn main() {\n let a = 1\n let assert <> = <<1, a>>\n b\n}\n" --- ----- SOURCE CODE pub fn main() { let a = 1 let assert <> = <<1, a>> b } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 1). -spec main() -> integer(). main() -> A = 1, B@1 = case <<1, A>> of <> -> B; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 3, value => _assert_fail, start => 30, 'end' => 60, pattern_start => 41, pattern_end => 49}) end, B@1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__bit_array3.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "pub fn main() {\n let a = <<\"test\":utf8>>\n let assert <> = a\n b\n}\n" --- ----- SOURCE CODE pub fn main() { let a = <<"test":utf8>> let assert <> = a b } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 1). -spec main() -> integer(). main() -> A = <<"test"/utf8>>, B@1 = case A of <> -> B; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 3, value => _assert_fail, start => 44, 'end' => 90, pattern_start => 55, pattern_end => 86}) end, B@1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__bit_array4.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "fn x() { 1 }\npub fn main() {\n let a = <>\n a\n}\n" --- ----- SOURCE CODE fn x() { 1 } pub fn main() { let a = <> a } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 1). -spec x() -> integer(). x() -> 1. -file("project/test/my/mod.gleam", 2). -spec main() -> bitstring(). main() -> A = <<(x())/integer>>, A. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__bit_array5.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "const bit_size = 8\npub fn main() {\n let a = <<10:size(bit_size)>>\n a\n}\n" --- ----- SOURCE CODE const bit_size = 8 pub fn main() { let a = <<10:size(bit_size)>> a } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> bitstring(). main() -> A = <<10:(lists:max([(8), 0]))>>, A. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__bit_array_declare_and_use_var.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "pub fn go(x) {\n let assert <> = x\n name\n}" --- ----- SOURCE CODE pub fn go(x) { let assert <> = x name } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/1]). -file("project/test/my/mod.gleam", 1). -spec go(bitstring()) -> bitstring(). go(X) -> {Name_size@1, Name@1} = case X of <> -> {Name_size, Name}; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"go"/utf8>>, line => 2, value => _assert_fail, start => 17, 'end' => 75, pattern_start => 28, pattern_end => 71}) end, Name@1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__bit_array_discard.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "\npub fn bit_array_discard(x) -> Bool {\n case x {\n <<_:utf8, rest:bytes>> -> True\n _ -> False\n }\n}\n " --- ----- SOURCE CODE pub fn bit_array_discard(x) -> Bool { case x { <<_:utf8, rest:bytes>> -> True _ -> False } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([bit_array_discard/1]). -file("project/test/my/mod.gleam", 2). -spec bit_array_discard(bitstring()) -> boolean(). bit_array_discard(X) -> case X of <<_/utf8, Rest/binary>> -> true; _ -> false end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__bit_array_discard1.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "\npub fn bit_array_discard(x) -> Bool {\n case x {\n <<_discardme:utf8, rest:bytes>> -> True\n _ -> False\n }\n}\n" --- ----- SOURCE CODE pub fn bit_array_discard(x) -> Bool { case x { <<_discardme:utf8, rest:bytes>> -> True _ -> False } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([bit_array_discard/1]). -file("project/test/my/mod.gleam", 2). -spec bit_array_discard(bitstring()) -> boolean(). bit_array_discard(X) -> case X of <<_/utf8, Rest/binary>> -> true; _ -> false end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__bit_array_float.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "pub fn main() {\n let b = 16\n let floats = <<1.0:16-float, 5.0:float-32, 6.0:float-64-little, 1.0:float-size(b)>>\n let assert <<1.0:16-float, 5.0:float-32, 6.0:float-64-little, 1.0:float-size(b)>> = floats\n}" --- ----- SOURCE CODE pub fn main() { let b = 16 let floats = <<1.0:16-float, 5.0:float-32, 6.0:float-64-little, 1.0:float-size(b)>> let assert <<1.0:16-float, 5.0:float-32, 6.0:float-64-little, 1.0:float-size(b)>> = floats } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 1). -spec main() -> bitstring(). main() -> B = 16, Floats = <<1.0:16/float, 5.0:32/float, 6.0:64/float-little, 1.0:(lists:max([(B), 0]))/float>>, case Floats of <<1.0:16/float, 5.0:32/float, 6.0:64/float-little, 1.0:B/float>> -> Floats; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 4, value => _assert_fail, start => 117, 'end' => 207, pattern_start => 128, pattern_end => 198}) end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__bit_array_literal_string_constant_is_treated_as_utf8.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "\nconst a = <<\"hello\", \" \", \"world\">>\npub fn main() { a }\n" --- ----- SOURCE CODE const a = <<"hello", " ", "world">> pub fn main() { a } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 3). -spec main() -> bitstring(). main() -> <<"hello"/utf8, " "/utf8, "world"/utf8>>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__bit_array_literal_string_is_treated_as_utf8.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "\npub fn main() {\n <<\"hello\", \" \", \"world\">>\n}" --- ----- SOURCE CODE pub fn main() { <<"hello", " ", "world">> } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> bitstring(). main() -> <<"hello"/utf8, " "/utf8, "world"/utf8>>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__bit_array_literal_string_pattern_is_treated_as_utf8.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "\npub fn main() {\n case <<>> {\n <<\"a\", \"b\", _:bits>> -> 1\n _ -> 2\n }\n}" --- ----- SOURCE CODE pub fn main() { case <<>> { <<"a", "b", _:bits>> -> 1 _ -> 2 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> case <<>> of <<"a"/utf8, "b"/utf8, _/bitstring>> -> 1; _ -> 2 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__block_in_pattern_size.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "\npub fn main() {\n let assert <> = <<>>\n}\n" --- ----- SOURCE CODE pub fn main() { let assert <> = <<>> } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> bitstring(). main() -> _assert_subject = <<>>, case _assert_subject of <> -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 3, value => _assert_fail, start => 19, 'end' => 75, pattern_start => 30, pattern_end => 68}) end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__discard_utf8_pattern.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "\npub fn main() {\n let assert <<_:utf8, rest:bits>> = <<>>\n}" --- ----- SOURCE CODE pub fn main() { let assert <<_:utf8, rest:bits>> = <<>> } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> bitstring(). main() -> _assert_subject = <<>>, case _assert_subject of <<_/utf8, Rest/bitstring>> -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 3, value => _assert_fail, start => 21, 'end' => 60, pattern_start => 32, pattern_end => 53}) end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__non_byte_aligned_size_calculation.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "\npub fn main() {\n case <<>> {\n <> -> c + b\n _ -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case <<>> { <> -> c + b _ -> 1 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> case <<>> of <> -> C + B; _ -> 1 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__operator_in_pattern_size.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "\npub fn main() {\n let assert <> = <<>>\n}\n" --- ----- SOURCE CODE pub fn main() { let assert <> = <<>> } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> bitstring(). main() -> _assert_subject = <<>>, case _assert_subject of <> -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 3, value => _assert_fail, start => 19, 'end' => 71, pattern_start => 30, pattern_end => 64}) end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__operator_in_pattern_size2.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "\npub fn main() {\n let assert <> = <<>>\n}\n" --- ----- SOURCE CODE pub fn main() { let assert <> = <<>> } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> bitstring(). main() -> _assert_subject = <<>>, case _assert_subject of <> -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 3, value => _assert_fail, start => 19, 'end' => 71, pattern_start => 30, pattern_end => 64}) end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__operator_in_pattern_size3.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "\npub fn main() {\n let additional = 10\n let assert <> = <<>>\n}\n" --- ----- SOURCE CODE pub fn main() { let additional = 10 let assert <> = <<>> } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> bitstring(). main() -> Additional = 10, _assert_subject = <<>>, case _assert_subject of <> -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 4, value => _assert_fail, start => 41, 'end' => 102, pattern_start => 52, pattern_end => 95}) end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__pattern_match_utf16_codepoint_little_endian.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <> = x\n codepoint\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <> = x codepoint } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/1]). -file("project/test/my/mod.gleam", 2). -spec go(bitstring()) -> integer(). go(X) -> Codepoint@1 = case X of <> -> Codepoint; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"go"/utf8>>, line => 3, value => _assert_fail, start => 18, 'end' => 69, pattern_start => 29, pattern_end => 65}) end, Codepoint@1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__pattern_match_utf32_codepoint_little_endian.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <> = x\n codepoint\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <> = x codepoint } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/1]). -file("project/test/my/mod.gleam", 2). -spec go(bitstring()) -> integer(). go(X) -> Codepoint@1 = case X of <> -> Codepoint; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"go"/utf8>>, line => 3, value => _assert_fail, start => 18, 'end' => 69, pattern_start => 29, pattern_end => 65}) end, Codepoint@1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__pipe_size_segment.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "\npub fn main() {\n <<0xAE:size(5 |> identity)>>\n}\n\nfn identity(x) {\n x\n}\n" --- ----- SOURCE CODE pub fn main() { <<0xAE:size(5 |> identity)>> } fn identity(x) { x } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 6). -spec identity(J) -> J. identity(X) -> X. -file("project/test/my/mod.gleam", 2). -spec main() -> bitstring(). main() -> <<16#AE:(lists:max([(begin _pipe = 5, identity(_pipe) end), 0]))>>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__unicode_bit_array_1.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "\n pub fn main() {\n let emoji = \"\\u{1F600}\"\n let arr = <>\n}" --- ----- SOURCE CODE pub fn main() { let emoji = "\u{1F600}" let arr = <> } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> bitstring(). main() -> Emoji = <<"\x{1F600}"/utf8>>, Arr = <>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__unicode_bit_array_2.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "\n pub fn main() {\n let arr = <<\"\\u{1F600}\":utf8>>\n}" --- ----- SOURCE CODE pub fn main() { let arr = <<"\u{1F600}":utf8>> } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> bitstring(). main() -> Arr = <<"\x{1F600}"/utf8>>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__unicode_character_encoding_in_bit_array_pattern_segment.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "\npub fn main() -> Nil {\n let wibble = <<\"\\u{00A9}wibble\":utf8>>\n let _bits = case wibble {\n <<\"\\u{00A9}\":utf8, rest: bits>> -> rest\n _ -> wibble\n }\n Nil\n}\n" --- ----- SOURCE CODE pub fn main() -> Nil { let wibble = <<"\u{00A9}wibble":utf8>> let _bits = case wibble { <<"\u{00A9}":utf8, rest: bits>> -> rest _ -> wibble } Nil } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> nil. main() -> Wibble = <<"\x{00A9}wibble"/utf8>>, _ = case Wibble of <<"\x{00A9}"/utf8, Rest/bitstring>> -> Rest; _ -> Wibble end, nil. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__utf16_codepoint_little_endian.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "\npub fn go(codepoint) {\n <>\n}\n" --- ----- SOURCE CODE pub fn go(codepoint) { <> } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/1]). -file("project/test/my/mod.gleam", 2). -spec go(integer()) -> bitstring(). go(Codepoint) -> <>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__bit_arrays__utf32_codepoint_little_endian.snap ================================================ --- source: compiler-core/src/erlang/tests/bit_arrays.rs expression: "\npub fn go(codepoint) {\n <>\n}\n" --- ----- SOURCE CODE pub fn go(codepoint) { <> } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/1]). -file("project/test/my/mod.gleam", 2). -spec go(integer()) -> bitstring(). go(Codepoint) -> <>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__case__aliased_string_prefix_pattern_referenced_in_guard.snap ================================================ --- source: compiler-core/src/erlang/tests/case.rs expression: "\npub fn main(x) {\n case x {\n \"a\" as letter <> _ if letter == x -> letter\n _ -> \"wibble\"\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { "a" as letter <> _ if letter == x -> letter _ -> "wibble" } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -file("project/test/my/mod.gleam", 2). -spec main(binary()) -> binary(). main(X) -> case X of <<"a"/utf8, _/binary>> when <<"a"/utf8>> =:= X -> Letter = <<"a"/utf8>>, Letter; _ -> <<"wibble"/utf8>> end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__case__alternative_patter_with_string_alias.snap ================================================ --- source: compiler-core/src/erlang/tests/case.rs expression: "\npub fn main(x) {\n case x {\n \"a\" as letter <> _ | \"b\" as letter <> _ -> letter\n _ -> \"wibble\"\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { "a" as letter <> _ | "b" as letter <> _ -> letter _ -> "wibble" } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -file("project/test/my/mod.gleam", 2). -spec main(binary()) -> binary(). main(X) -> case X of <<"a"/utf8, _/binary>> -> Letter = <<"a"/utf8>>, Letter; <<"b"/utf8, _/binary>> -> Letter = <<"b"/utf8>>, Letter; _ -> <<"wibble"/utf8>> end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__case__alternative_pattern_variable_rewriting.snap ================================================ --- source: compiler-core/src/erlang/tests/case.rs expression: "\npub fn myfun(mt) {\n case mt {\n 1 | _ ->\n 1\n |> Ok\n }\n 1\n |> Ok\n}\n" --- ----- SOURCE CODE pub fn myfun(mt) { case mt { 1 | _ -> 1 |> Ok } 1 |> Ok } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([myfun/1]). -file("project/test/my/mod.gleam", 2). -spec myfun(integer()) -> {ok, integer()} | {error, any()}. myfun(Mt) -> case Mt of 1 -> _pipe = 1, {ok, _pipe}; _ -> _pipe = 1, {ok, _pipe} end, _pipe@1 = 1, {ok, _pipe@1}. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__case__negative_zero_pattern.snap ================================================ --- source: compiler-core/src/erlang/tests/case.rs expression: "\npub fn main(x) {\n case x {\n -0.0 -> 1\n _ -> 2\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { -0.0 -> 1 _ -> 2 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -file("project/test/my/mod.gleam", 2). -spec main(float()) -> integer(). main(X) -> case X of -0.0 -> 1; _ -> 2 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__case__not.snap ================================================ --- source: compiler-core/src/erlang/tests/case.rs expression: "pub fn main(x, y) {\n case x {\n _ if !y -> 0\n _ -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x, y) { case x { _ if !y -> 0 _ -> 1 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/2]). -file("project/test/my/mod.gleam", 1). -spec main(any(), boolean()) -> integer(). main(X, Y) -> case X of _ when not Y -> 0; _ -> 1 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__case__not_two.snap ================================================ --- source: compiler-core/src/erlang/tests/case.rs expression: "pub fn main(x, y) {\n case x {\n _ if !y && !x -> 0\n _ -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x, y) { case x { _ if !y && !x -> 0 _ -> 1 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/2]). -file("project/test/my/mod.gleam", 1). -spec main(boolean(), boolean()) -> integer(). main(X, Y) -> case X of _ when not Y andalso not X -> 0; _ -> 1 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__case__positive_zero_pattern.snap ================================================ --- source: compiler-core/src/erlang/tests/case.rs expression: "\npub fn main(x) {\n case x {\n 0.0 -> 1\n _ -> 2\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { 0.0 -> 1 _ -> 2 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -file("project/test/my/mod.gleam", 2). -spec main(float()) -> integer(). main(X) -> case X of +0.0 -> 1; _ -> 2 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__case__spread_empty_list.snap ================================================ --- source: compiler-core/src/erlang/tests/case.rs expression: "\npub fn main() {\n case [] {\n [..] -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case [] { [..] -> 1 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> case [] of _ -> 1 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__case__spread_empty_list_assigning.snap ================================================ --- source: compiler-core/src/erlang/tests/case.rs expression: "\npub fn main() {\n case [] {\n [..rest] -> rest\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case [] { [..rest] -> rest } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> list(any()). main() -> case [] of Rest -> Rest end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__conditional_compilation__excluded_attribute_syntax.snap ================================================ --- source: compiler-core/src/erlang/tests/conditional_compilation.rs expression: "@target(javascript)\n pub fn main() { 1 }\n" --- ----- SOURCE CODE @target(javascript) pub fn main() { 1 } ----- COMPILED ERLANG -module(my@mod). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__conditional_compilation__included_attribute_syntax.snap ================================================ --- source: compiler-core/src/erlang/tests/conditional_compilation.rs expression: "@target(erlang)\n pub fn main() { 1 }\n" --- ----- SOURCE CODE @target(erlang) pub fn main() { 1 } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> 1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__consts__const_generalise.snap ================================================ --- source: compiler-core/src/erlang/tests/consts.rs expression: "\nfn identity(a: a) -> a {\na\n}\n\nconst id = identity\n\npub fn main(){\n let num = id(1)\n let word = id(\"Word\")\n}" --- ----- SOURCE CODE fn identity(a: a) -> a { a } const id = identity pub fn main(){ let num = id(1) let word = id("Word") } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec identity(I) -> I. identity(A) -> A. -file("project/test/my/mod.gleam", 8). -spec main() -> binary(). main() -> Num = identity(1), Word = identity(<<"Word"/utf8>>). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__consts__const_type_variable.snap ================================================ --- source: compiler-core/src/erlang/tests/consts.rs expression: "\nfn identity(a: a) -> a {\n a\n}\n\nconst id: fn(a) -> a = identity\n" --- ----- SOURCE CODE fn identity(a: a) -> a { a } const id: fn(a) -> a = identity ----- COMPILED ERLANG -module(my@mod). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__consts__list_prepend.snap ================================================ --- source: compiler-core/src/erlang/tests/consts.rs expression: "\nconst wibble = [2, 3, 4]\npub const wobble = [0, 1, ..wibble]\n\npub fn main() {\n wobble\n}\n" --- ----- SOURCE CODE const wibble = [2, 3, 4] pub const wobble = [0, 1, ..wibble] pub fn main() { wobble } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 5). -spec main() -> list(integer()). main() -> [0, 1, 2, 3, 4]. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__consts__list_prepend_from_other_module.snap ================================================ --- source: compiler-core/src/erlang/tests/consts.rs expression: "\nimport mod\n\npub const wobble = [0, 1, ..mod.wibble]\n\npub fn main() {\n wobble\n}\n" --- ----- SOURCE CODE import mod pub const wobble = [0, 1, ..mod.wibble] pub fn main() { wobble } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 6). -spec main() -> list(integer()). main() -> [0, 1, 2, 3, 4]. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__consts__list_prepend_literal.snap ================================================ --- source: compiler-core/src/erlang/tests/consts.rs expression: "\npub const wibble = [0, 1, ..[2, 3, 4]]\n\npub fn main() {\n wibble\n}\n" --- ----- SOURCE CODE pub const wibble = [0, 1, ..[2, 3, 4]] pub fn main() { wibble } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 4). -spec main() -> list(integer()). main() -> [0, 1, 2, 3, 4]. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__consts__pub_const_equal_to_private_function.snap ================================================ --- source: compiler-core/src/erlang/tests/consts.rs expression: "\n fn identity(a) {\n a\n }\n\n pub const id = identity\n " --- ----- SOURCE CODE fn identity(a) { a } pub const id = identity ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([identity/1]). -file("project/test/my/mod.gleam", 2). -spec identity(I) -> I. identity(A) -> A. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__consts__pub_const_equal_to_record_with_nested_private_function_field.snap ================================================ --- source: compiler-core/src/erlang/tests/consts.rs expression: "\n fn identity(a) {\n a\n }\n\n pub type Mapper(b) {\n Mapper(fn(b) -> b)\n }\n\n pub type Funcs(b) {\n Funcs(mapper: Mapper(b))\n }\n\n pub const id_mapper = Funcs(Mapper(identity))\n " --- ----- SOURCE CODE fn identity(a) { a } pub type Mapper(b) { Mapper(fn(b) -> b) } pub type Funcs(b) { Funcs(mapper: Mapper(b)) } pub const id_mapper = Funcs(Mapper(identity)) ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([identity/1]). -export_type([mapper/1, funcs/1]). -type mapper(I) :: {mapper, fun((I) -> I)}. -type funcs(J) :: {funcs, mapper(J)}. -file("project/test/my/mod.gleam", 2). -spec identity(K) -> K. identity(A) -> A. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__consts__pub_const_equal_to_record_with_private_function_field.snap ================================================ --- source: compiler-core/src/erlang/tests/consts.rs expression: "\n fn identity(a) {\n a\n }\n\n pub type Mapper(b) {\n Mapper(fn(b) -> b)\n }\n\n pub const id_mapper = Mapper(identity)\n " --- ----- SOURCE CODE fn identity(a) { a } pub type Mapper(b) { Mapper(fn(b) -> b) } pub const id_mapper = Mapper(identity) ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([identity/1]). -export_type([mapper/1]). -type mapper(I) :: {mapper, fun((I) -> I)}. -file("project/test/my/mod.gleam", 2). -spec identity(J) -> J. identity(A) -> A. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__consts__record_constructor.snap ================================================ --- source: compiler-core/src/erlang/tests/consts.rs expression: "\npub type X {\n X(Int)\n}\n\npub const z = X\n\npub fn main() {\n z\n}" --- ----- SOURCE CODE pub type X { X(Int) } pub const z = X pub fn main() { z } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -export_type([x/0]). -type x() :: {x, integer()}. -file("project/test/my/mod.gleam", 8). -spec main() -> fun((integer()) -> x()). main() -> fun(Field@0) -> {x, Field@0} end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__consts__record_constructor_in_tuple.snap ================================================ --- source: compiler-core/src/erlang/tests/consts.rs expression: "\npub type X {\n X(Int)\n}\n\npub const z = #(X)\n\npub fn main() {\n z\n}" --- ----- SOURCE CODE pub type X { X(Int) } pub const z = #(X) pub fn main() { z } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -export_type([x/0]). -type x() :: {x, integer()}. -file("project/test/my/mod.gleam", 8). -spec main() -> {fun((integer()) -> x())}. main() -> {fun(Field@0) -> {x, Field@0} end}. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__consts__use_private_in_internal.snap ================================================ --- source: compiler-core/src/erlang/tests/consts.rs expression: "\n fn identity(a) {\n a\n }\n\n pub type Mapper(b) {\n Mapper(fn(b) -> b)\n }\n\n @internal\n pub const id_mapper = Mapper(identity)\n " --- ----- SOURCE CODE fn identity(a) { a } pub type Mapper(b) { Mapper(fn(b) -> b) } @internal pub const id_mapper = Mapper(identity) ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([identity/1]). -export_type([mapper/1]). -type mapper(I) :: {mapper, fun((I) -> I)}. -file("project/test/my/mod.gleam", 2). -spec identity(J) -> J. identity(A) -> A. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__consts__use_private_in_list.snap ================================================ --- source: compiler-core/src/erlang/tests/consts.rs expression: "\n fn identity(a) {\n a\n }\n\n pub const funcs = [identity]\n " --- ----- SOURCE CODE fn identity(a) { a } pub const funcs = [identity] ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([identity/1]). -file("project/test/my/mod.gleam", 2). -spec identity(I) -> I. identity(A) -> A. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__consts__use_private_in_tuple.snap ================================================ --- source: compiler-core/src/erlang/tests/consts.rs expression: "\n fn identity(a) {\n a\n }\n\n pub const funcs = #(identity)\n " --- ----- SOURCE CODE fn identity(a) { a } pub const funcs = #(identity) ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([identity/1]). -file("project/test/my/mod.gleam", 2). -spec identity(I) -> I. identity(A) -> A. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__consts__use_qualified_pub_const_equal_to_record_with_private_function_field.snap ================================================ --- source: compiler-core/src/erlang/tests/consts.rs expression: "\n fn identity(a) {\n a\n }\n\n pub type Mapper(b) {\n Mapper(fn(b) -> b)\n }\n\n pub const id_mapper = Mapper(identity)\n " --- ----- SOURCE CODE fn identity(a) { a } pub type Mapper(b) { Mapper(fn(b) -> b) } pub const id_mapper = Mapper(identity) ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([identity/1]). -export_type([mapper/1]). -type mapper(I) :: {mapper, fun((I) -> I)}. -file("project/test/my/mod.gleam", 2). -spec identity(J) -> J. identity(A) -> A. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__consts__use_unqualified_pub_const_equal_to_private_function.snap ================================================ --- source: compiler-core/src/erlang/tests/consts.rs expression: "\n fn identity(a) {\n a\n }\n\n pub const id = identity\n " --- ----- SOURCE CODE fn identity(a) { a } pub const id = identity ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([identity/1]). -file("project/test/my/mod.gleam", 2). -spec identity(I) -> I. identity(A) -> A. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__consts__use_unqualified_pub_const_equal_to_record_with_private_function_field.snap ================================================ --- source: compiler-core/src/erlang/tests/consts.rs expression: "\n fn identity(a) {\n a\n }\n\n pub type Mapper(b) {\n Mapper(fn(b) -> b)\n }\n\n pub const id_mapper = Mapper(identity)\n " --- ----- SOURCE CODE fn identity(a) { a } pub type Mapper(b) { Mapper(fn(b) -> b) } pub const id_mapper = Mapper(identity) ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([identity/1]). -export_type([mapper/1]). -type mapper(I) :: {mapper, fun((I) -> I)}. -file("project/test/my/mod.gleam", 2). -spec identity(J) -> J. identity(A) -> A. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__custom_types__annotated_external_type.snap ================================================ --- source: compiler-core/src/erlang/tests/custom_types.rs expression: "\n@external(erlang, \"gleam_stdlib\", \"dict\")\npub type Dict(key, value)\n" --- ----- SOURCE CODE @external(erlang, "gleam_stdlib", "dict") pub type Dict(key, value) ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export_type([dict/2]). -type dict(I, J) :: gleam_stdlib:dict(I, J). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__custom_types__annotated_external_type_used_in_function.snap ================================================ --- source: compiler-core/src/erlang/tests/custom_types.rs expression: "\n@external(erlang, \"gleam_stdlib\", \"dict\")\npub type Dict(key, value)\n\n@external(erlang, \"maps\", \"get\")\npub fn get(dict: Dict(key, value), key: key) -> Result(value, Nil)\n" --- ----- SOURCE CODE @external(erlang, "gleam_stdlib", "dict") pub type Dict(key, value) @external(erlang, "maps", "get") pub fn get(dict: Dict(key, value), key: key) -> Result(value, Nil) ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([get/2]). -export_type([dict/2]). -type dict(I, J) :: gleam_stdlib:dict(I, J). -file("project/test/my/mod.gleam", 6). -spec get(dict(K, L), K) -> {ok, L} | {error, nil}. get(Dict, Key) -> maps:get(Dict, Key). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__custom_types__phantom.snap ================================================ --- source: compiler-core/src/erlang/tests/custom_types.rs expression: "pub type Map(k, v)" --- ----- SOURCE CODE pub type Map(k, v) ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export_type([map_/2]). -type map_(I, J) :: any() | {gleam_phantom, I, J}. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__custom_types__unused_opaque_constructor_is_generated_correctly.snap ================================================ --- source: compiler-core/src/erlang/tests/custom_types.rs expression: "\ntype Wibble {\n Wibble\n}\n\npub opaque type Wobble {\n Wobble(Wibble)\n}\n" --- ----- SOURCE CODE type Wibble { Wibble } pub opaque type Wobble { Wobble(Wibble) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export_type([wibble/0, wobble/0]). -type wibble() :: wibble. -opaque wobble() :: {wobble, wibble()}. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__documentation__backslashes_are_escaped_in_module_comment.snap ================================================ --- source: compiler-core/src/erlang/tests/documentation.rs expression: "\n//// \\backslashes!\\\n\npub fn main() { 1 }" --- ----- SOURCE CODE //// \backslashes!\ pub fn main() { 1 } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC(" \\backslashes!\\\n"). -file("project/test/my/mod.gleam", 4). -spec main() -> integer(). main() -> 1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__documentation__backslashes_in_documentation_are_escaped.snap ================================================ --- source: compiler-core/src/erlang/tests/documentation.rs expression: "\n/// \\hello\\\npub fn documented() { 1 }" --- ----- SOURCE CODE /// \hello\ pub fn documented() { 1 } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([documented/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -file("project/test/my/mod.gleam", 3). ?DOC(" \\hello\\\n"). -spec documented() -> integer(). documented() -> 1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__documentation__double_quotes_are_escaped_in_module_comment.snap ================================================ --- source: compiler-core/src/erlang/tests/documentation.rs expression: "\n//// \"quotes!\"\n\npub fn main() { 1 }" --- ----- SOURCE CODE //// "quotes!" pub fn main() { 1 } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC(" \"quotes!\"\n"). -file("project/test/my/mod.gleam", 4). -spec main() -> integer(). main() -> 1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__documentation__function_with_documentation.snap ================================================ --- source: compiler-core/src/erlang/tests/documentation.rs expression: "\n/// Function doc!\npub fn documented() { 1 }" --- ----- SOURCE CODE /// Function doc! pub fn documented() { 1 } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([documented/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -file("project/test/my/mod.gleam", 3). ?DOC(" Function doc!\n"). -spec documented() -> integer(). documented() -> 1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__documentation__function_with_multiline_documentation.snap ================================================ --- source: compiler-core/src/erlang/tests/documentation.rs expression: "\n/// Function doc!\n/// Hello!!\n///\npub fn documented() { 1 }" --- ----- SOURCE CODE /// Function doc! /// Hello!! /// pub fn documented() { 1 } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([documented/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -file("project/test/my/mod.gleam", 5). ?DOC( " Function doc!\n" " Hello!!\n" ). -spec documented() -> integer(). documented() -> 1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__documentation__internal_function_has_no_documentation.snap ================================================ --- source: compiler-core/src/erlang/tests/documentation.rs expression: "\n/// hidden!\n@internal\npub fn main() { 1 }" --- ----- SOURCE CODE /// hidden! @internal pub fn main() { 1 } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -file("project/test/my/mod.gleam", 4). ?DOC(false). -spec main() -> integer(). main() -> 1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__documentation__multi_line_module_comment.snap ================================================ --- source: compiler-core/src/erlang/tests/documentation.rs expression: "\n//// Hello! This is a multi-\n//// line module comment.\n////\n\npub fn main() { 1 }" --- ----- SOURCE CODE //// Hello! This is a multi- //// line module comment. //// pub fn main() { 1 } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " Hello! This is a multi-\n" " line module comment.\n" "\n" ). -file("project/test/my/mod.gleam", 6). -spec main() -> integer(). main() -> 1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__documentation__quotes_in_documentation_are_escaped.snap ================================================ --- source: compiler-core/src/erlang/tests/documentation.rs expression: "\n/// \"hello\"\npub fn documented() { 1 }" --- ----- SOURCE CODE /// "hello" pub fn documented() { 1 } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([documented/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -file("project/test/my/mod.gleam", 3). ?DOC(" \"hello\"\n"). -spec documented() -> integer(). documented() -> 1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__documentation__single_line_module_comment.snap ================================================ --- source: compiler-core/src/erlang/tests/documentation.rs expression: "\n//// Hello! This is a single line module comment.\n\npub fn main() { 1 }" --- ----- SOURCE CODE //// Hello! This is a single line module comment. pub fn main() { 1 } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC(" Hello! This is a single line module comment.\n"). -file("project/test/my/mod.gleam", 4). -spec main() -> integer(). main() -> 1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__echo__echo_in_a_pipeline.snap ================================================ --- source: compiler-core/src/erlang/tests/echo.rs expression: "\npub fn main() {\n [1, 2, 3]\n |> echo\n |> wibble\n}\n\npub fn wibble(n) { n }\n" --- ----- SOURCE CODE pub fn main() { [1, 2, 3] |> echo |> wibble } pub fn wibble(n) { n } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([wibble/1, main/0]). -file("project/test/my/mod.gleam", 8). -spec wibble(J) -> J. wibble(N) -> N. -file("project/test/my/mod.gleam", 2). -spec main() -> list(integer()). main() -> _pipe = [1, 2, 3], echo(_pipe, nil, 4), wibble(_pipe). % ...omitted code from `templates/echo.erl`... ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__echo__echo_in_a_pipeline_with_message.snap ================================================ --- source: compiler-core/src/erlang/tests/echo.rs expression: "\npub fn main() {\n [1, 2, 3]\n |> echo as \"message!!\"\n |> wibble\n}\n\npub fn wibble(n) { n }\n" --- ----- SOURCE CODE pub fn main() { [1, 2, 3] |> echo as "message!!" |> wibble } pub fn wibble(n) { n } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([wibble/1, main/0]). -file("project/test/my/mod.gleam", 8). -spec wibble(J) -> J. wibble(N) -> N. -file("project/test/my/mod.gleam", 2). -spec main() -> list(integer()). main() -> _pipe = [1, 2, 3], echo(_pipe, <<"message!!"/utf8>>, 4), wibble(_pipe). % ...omitted code from `templates/echo.erl`... ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__echo__echo_with_a_block.snap ================================================ --- source: compiler-core/src/erlang/tests/echo.rs expression: "\npub fn main() {\n echo {\n Nil\n 1\n }\n}\n" --- ----- SOURCE CODE pub fn main() { echo { Nil 1 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> echo( begin nil, 1 end, nil, 3 ). % ...omitted code from `templates/echo.erl`... ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__echo__echo_with_a_case_expression.snap ================================================ --- source: compiler-core/src/erlang/tests/echo.rs expression: "\npub fn main() {\n echo case 1 {\n _ -> 2\n }\n}\n" --- ----- SOURCE CODE pub fn main() { echo case 1 { _ -> 2 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> echo(case 1 of _ -> 2 end, nil, 3). % ...omitted code from `templates/echo.erl`... ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__echo__echo_with_a_function_call.snap ================================================ --- source: compiler-core/src/erlang/tests/echo.rs expression: "\npub fn main() {\n echo wibble(1, 2)\n}\n\nfn wibble(n: Int, m: Int) { n + m }\n" --- ----- SOURCE CODE pub fn main() { echo wibble(1, 2) } fn wibble(n: Int, m: Int) { n + m } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 6). -spec wibble(integer(), integer()) -> integer(). wibble(N, M) -> N + M. -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> echo(wibble(1, 2), nil, 3). % ...omitted code from `templates/echo.erl`... ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__echo__echo_with_a_function_call_and_a_message.snap ================================================ --- source: compiler-core/src/erlang/tests/echo.rs expression: "\npub fn main() {\n echo wibble(1, 2) as message()\n}\n\nfn wibble(n: Int, m: Int) { n + m }\nfn message() { \"Hello!\" }\n" --- ----- SOURCE CODE pub fn main() { echo wibble(1, 2) as message() } fn wibble(n: Int, m: Int) { n + m } fn message() { "Hello!" } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 6). -spec wibble(integer(), integer()) -> integer(). wibble(N, M) -> N + M. -file("project/test/my/mod.gleam", 7). -spec message() -> binary(). message() -> <<"Hello!"/utf8>>. -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> echo(wibble(1, 2), message(), 3). % ...omitted code from `templates/echo.erl`... ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__echo__echo_with_a_panic.snap ================================================ --- source: compiler-core/src/erlang/tests/echo.rs expression: "\npub fn main() {\n echo panic\n}\n" --- ----- SOURCE CODE pub fn main() { echo panic } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> any(). main() -> echo(erlang:error(#{gleam_error => panic, message => <<"`panic` expression evaluated."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 3}), nil, 3). % ...omitted code from `templates/echo.erl`... ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__echo__echo_with_a_simple_expression.snap ================================================ --- source: compiler-core/src/erlang/tests/echo.rs expression: "\npub fn main() {\n echo 1\n}\n" --- ----- SOURCE CODE pub fn main() { echo 1 } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> echo(1, nil, 3). % ...omitted code from `templates/echo.erl`... ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__echo__echo_with_a_simple_expression_and_a_message.snap ================================================ --- source: compiler-core/src/erlang/tests/echo.rs expression: "\npub fn main() {\n echo 1 as \"hello!\"\n}\n" --- ----- SOURCE CODE pub fn main() { echo 1 as "hello!" } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> echo(1, <<"hello!"/utf8>>, 3). % ...omitted code from `templates/echo.erl`... ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__echo__echo_with_complex_expression_as_a_message.snap ================================================ --- source: compiler-core/src/erlang/tests/echo.rs expression: "\npub fn main() {\n echo 1 as case name() {\n \"Giacomo\" -> \"hello Jak!\"\n _ -> \"hello!\"\n }\n}\n\nfn name() { \"Giacomo\" }\n" --- ----- SOURCE CODE pub fn main() { echo 1 as case name() { "Giacomo" -> "hello Jak!" _ -> "hello!" } } fn name() { "Giacomo" } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 9). -spec name() -> binary(). name() -> <<"Giacomo"/utf8>>. -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> echo(1, case name() of <<"Giacomo"/utf8>> -> <<"hello Jak!"/utf8>>; _ -> <<"hello!"/utf8>> end, 3). % ...omitted code from `templates/echo.erl`... ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__echo__multiple_echos_in_a_pipeline.snap ================================================ --- source: compiler-core/src/erlang/tests/echo.rs expression: "\npub fn main() {\n [1, 2, 3]\n |> echo\n |> wibble\n |> echo\n |> wibble\n |> echo\n}\n\npub fn wibble(n) { n }\n" --- ----- SOURCE CODE pub fn main() { [1, 2, 3] |> echo |> wibble |> echo |> wibble |> echo } pub fn wibble(n) { n } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([wibble/1, main/0]). -file("project/test/my/mod.gleam", 11). -spec wibble(J) -> J. wibble(N) -> N. -file("project/test/my/mod.gleam", 2). -spec main() -> list(integer()). main() -> _pipe = [1, 2, 3], echo(_pipe, nil, 4), _pipe@1 = wibble(_pipe), echo(_pipe@1, nil, 6), _pipe@2 = wibble(_pipe@1), echo(_pipe@2, nil, 8). % ...omitted code from `templates/echo.erl`... ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__echo__multiple_echos_inside_expression.snap ================================================ --- source: compiler-core/src/erlang/tests/echo.rs expression: "\npub fn main() {\n echo 1\n echo 2\n}\n" --- ----- SOURCE CODE pub fn main() { echo 1 echo 2 } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> echo(1, nil, 3), echo(2, nil, 4). % ...omitted code from `templates/echo.erl`... ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__echo__pipeline_printed_by_echo_is_wrapped_in_begin_end_block.snap ================================================ --- source: compiler-core/src/erlang/tests/echo.rs expression: "\npub fn main() {\n echo\n 123\n |> wibble\n |> wibble\n}\n\npub fn wibble(n) { n }\n" --- ----- SOURCE CODE pub fn main() { echo 123 |> wibble |> wibble } pub fn wibble(n) { n } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([wibble/1, main/0]). -file("project/test/my/mod.gleam", 9). -spec wibble(J) -> J. wibble(N) -> N. -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> echo( begin _pipe = 123, _pipe@1 = wibble(_pipe), wibble(_pipe@1) end, nil, 3 ). % ...omitted code from `templates/echo.erl`... ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__echo__record_update_printed_by_echo_is_wrapped_in_begin_end_block.snap ================================================ --- source: compiler-core/src/erlang/tests/echo.rs expression: "\npub type Wobble { Wobble(id: Int, name: String) }\n\npub fn main() {\n let wobble = Wobble(1, \"wobble\")\n echo Wobble(..wobble, id: 1)\n}\n" --- ----- SOURCE CODE pub type Wobble { Wobble(id: Int, name: String) } pub fn main() { let wobble = Wobble(1, "wobble") echo Wobble(..wobble, id: 1) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -export_type([wobble/0]). -type wobble() :: {wobble, integer(), binary()}. -file("project/test/my/mod.gleam", 4). -spec main() -> wobble(). main() -> Wobble = {wobble, 1, <<"wobble"/utf8>>}, echo({wobble, 1, erlang:element(3, Wobble)}, nil, 6). % ...omitted code from `templates/echo.erl`... ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__attribute_erlang.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\n@external(erlang, \"one\", \"one\")\npub fn one(x: Int) -> Int {\n todo\n}\n" --- ----- SOURCE CODE @external(erlang, "one", "one") pub fn one(x: Int) -> Int { todo } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([one/1]). -file("project/test/my/mod.gleam", 3). -spec one(integer()) -> integer(). one(X) -> one:one(X). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__attribute_javascript.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\n@external(javascript, \"./one.mjs\", \"one\")\npub fn one(x: Int) -> Int {\n todo\n}\n" --- ----- SOURCE CODE @external(javascript, "./one.mjs", "one") pub fn one(x: Int) -> Int { todo } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([one/1]). -file("project/test/my/mod.gleam", 3). -spec one(integer()) -> integer(). one(X) -> erlang:error(#{gleam_error => todo, message => <<"`todo` expression evaluated. This code has not yet been implemented."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"one"/utf8>>, line => 4}). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__both_externals_no_valid_impl.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\n@external(javascript, \"one\", \"one\")\npub fn js() -> Nil\n\n@external(erlang, \"one\", \"one\")\npub fn erl() -> Nil\n\npub fn should_not_be_generated() {\n js()\n erl()\n}\n" --- ----- SOURCE CODE @external(javascript, "one", "one") pub fn js() -> Nil @external(erlang, "one", "one") pub fn erl() -> Nil pub fn should_not_be_generated() { js() erl() } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([erl/0]). -file("project/test/my/mod.gleam", 6). -spec erl() -> nil. erl() -> one:one(). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__discarded_arg_in_external_are_passed_correctly.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\n@external(erlang, \"wibble\", \"wobble\")\npub fn woo(_a: a) -> Nil\n" --- ----- SOURCE CODE @external(erlang, "wibble", "wobble") pub fn woo(_a: a) -> Nil ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([woo/1]). -file("project/test/my/mod.gleam", 3). -spec woo(any()) -> nil. woo(_a) -> wibble:wobble(_a). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__elixir.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\npub fn main() {\n #(do, do())\n}\n\n@external(erlang, \"Elixir.String\", \"main\")\nfn do() -> Int\n" --- ----- SOURCE CODE pub fn main() { #(do, do()) } @external(erlang, "Elixir.String", "main") fn do() -> Int ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> {fun(() -> integer()), integer()}. main() -> {fun 'Elixir.String':main/0, 'Elixir.String':main()}. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__erlang_and_javascript.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\n@external(erlang, \"one\", \"one\")\n@external(javascript, \"./one.mjs\", \"one\")\npub fn one(x: Int) -> Int {\n todo\n}\n" --- ----- SOURCE CODE @external(erlang, "one", "one") @external(javascript, "./one.mjs", "one") pub fn one(x: Int) -> Int { todo } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([one/1]). -file("project/test/my/mod.gleam", 4). -spec one(integer()) -> integer(). one(X) -> one:one(X). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__hole_parameter_erlang.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\n@external(erlang, \"one\", \"one\")\npub fn one(x: List(_)) -> Int {\n todo\n}\n" --- ----- SOURCE CODE @external(erlang, "one", "one") pub fn one(x: List(_)) -> Int { todo } ----- ERROR error: Unexpected type hole ┌─ /src/one/two.gleam:3:20 │ 3 │ pub fn one(x: List(_)) -> Int { │ ^ I need to know what this is We need to know the exact type here so type holes cannot be used. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__hole_parameter_javascript.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\n@external(javascript, \"one\", \"one\")\npub fn one(x: List(_)) -> Int {\n todo\n}\n" --- ----- SOURCE CODE @external(javascript, "one", "one") pub fn one(x: List(_)) -> Int { todo } ----- ERROR error: Unexpected type hole ┌─ /src/one/two.gleam:3:20 │ 3 │ pub fn one(x: List(_)) -> Int { │ ^ I need to know what this is We need to know the exact type here so type holes cannot be used. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__hole_return_erlang.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\n@external(erlang, \"one\", \"one\")\npub fn one(x: List(Int)) -> List(_) {\n todo\n}\n" --- ----- SOURCE CODE @external(erlang, "one", "one") pub fn one(x: List(Int)) -> List(_) { todo } ----- ERROR error: Unexpected type hole ┌─ /src/one/two.gleam:3:34 │ 3 │ pub fn one(x: List(Int)) -> List(_) { │ ^ I need to know what this is We need to know the exact type here so type holes cannot be used. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__hole_return_javascript.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\n@external(javascript, \"one\", \"one\")\npub fn one(x: List(Int)) -> List(_) {\n todo\n}\n" --- ----- SOURCE CODE @external(javascript, "one", "one") pub fn one(x: List(Int)) -> List(_) { todo } ----- ERROR error: Unexpected type hole ┌─ /src/one/two.gleam:3:34 │ 3 │ pub fn one(x: List(Int)) -> List(_) { │ ^ I need to know what this is We need to know the exact type here so type holes cannot be used. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__inlining_external_functions_from_another_module.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "import atom\npub fn main() {\n atom.make(\"ok\")\n}\n" --- ----- SOURCE CODE import atom pub fn main() { atom.make("ok") } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> atom:atom_(). main() -> erlang:binary_to_atom(<<"ok"/utf8>>). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__integration_test1_3.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\n@external(erlang, \"Elixir.MyApp\", \"run\")\npub fn run() -> Int\n" --- ----- SOURCE CODE @external(erlang, "Elixir.MyApp", "run") pub fn run() -> Int ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([run/0]). -file("project/test/my/mod.gleam", 3). -spec run() -> integer(). run() -> 'Elixir.MyApp':run(). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__integration_test7.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\n@external(erlang, \"try\", \"and\")\npub fn receive() -> Int\npub fn catch(x) { receive() }\n" --- ----- SOURCE CODE @external(erlang, "try", "and") pub fn receive() -> Int pub fn catch(x) { receive() } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export(['receive'/0, 'catch'/1]). -file("project/test/my/mod.gleam", 3). -spec 'receive'() -> integer(). 'receive'() -> 'try':'and'(). -file("project/test/my/mod.gleam", 4). -spec 'catch'(any()) -> integer(). 'catch'(X) -> 'try':'and'(). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__javascript_only.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\npub fn should_be_generated(x: Int) -> Int {\n x\n}\n\n@external(javascript, \"one\", \"one\")\npub fn should_not_be_generated(x: Int) -> Int\n" --- ----- SOURCE CODE pub fn should_be_generated(x: Int) -> Int { x } @external(javascript, "one", "one") pub fn should_not_be_generated(x: Int) -> Int ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([should_be_generated/1]). -file("project/test/my/mod.gleam", 2). -spec should_be_generated(integer()) -> integer(). should_be_generated(X) -> X. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__javascript_only_indirect.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\npub fn should_be_generated(x: Int) -> Int {\n x\n}\n\n@external(javascript, \"one\", \"one\")\npub fn should_not_be_generated(x: Int) -> Int\n\npub fn also_should_not_be_generated() {\n should_not_be_generated(1)\n |> should_be_generated\n}\n" --- ----- SOURCE CODE pub fn should_be_generated(x: Int) -> Int { x } @external(javascript, "one", "one") pub fn should_not_be_generated(x: Int) -> Int pub fn also_should_not_be_generated() { should_not_be_generated(1) |> should_be_generated } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([should_be_generated/1]). -file("project/test/my/mod.gleam", 2). -spec should_be_generated(integer()) -> integer(). should_be_generated(X) -> X. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__multiple_discarded_args_in_external_are_passed_correctly.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\n@external(erlang, \"wibble\", \"wobble\")\npub fn woo(_: a, _: b) -> Nil\n" --- ----- SOURCE CODE @external(erlang, "wibble", "wobble") pub fn woo(_: a, _: b) -> Nil ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([woo/2]). -file("project/test/my/mod.gleam", 3). -spec woo(any(), any()) -> nil. woo(Argument, Argument@1) -> wibble:wobble(Argument, Argument@1). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__multiple_discarded_args_in_external_are_passed_correctly_2.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\n@external(erlang, \"wibble\", \"wobble\")\npub fn woo(__: a, _two: b) -> Nil\n" --- ----- SOURCE CODE @external(erlang, "wibble", "wobble") pub fn woo(__: a, _two: b) -> Nil ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([woo/2]). -file("project/test/my/mod.gleam", 3). -spec woo(any(), any()) -> nil. woo(Argument, _two) -> wibble:wobble(Argument, _two). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__no_body.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\n@external(erlang, \"one\", \"one\")\npub fn one(x: Int) -> Int\n" --- ----- SOURCE CODE @external(erlang, "one", "one") pub fn one(x: Int) -> Int ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([one/1]). -file("project/test/my/mod.gleam", 3). -spec one(integer()) -> integer(). one(X) -> one:one(X). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__no_body_or_implementation.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\npub fn one(x: Int) -> Float\n" --- ----- SOURCE CODE pub fn one(x: Int) -> Float ----- ERROR error: Function without an implementation ┌─ /src/one/two.gleam:2:1 │ 2 │ pub fn one(x: Int) -> Float │ ^^^^^^^^^^^^^^^^^^ We can't compile this function as it doesn't have an implementation. Add a body or an external implementation using the `@external` attribute. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__no_gleam_impl_no_annotations_function_fault_tolerance.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\n@external(erlang, \"one\", \"two\")\npub fn no_impl()\n\npub type X = UnknownType\n" --- ----- SOURCE CODE @external(erlang, "one", "two") pub fn no_impl() pub type X = UnknownType ----- ERROR error: Missing type annotation ┌─ /src/one/two.gleam:3:1 │ 3 │ pub fn no_impl() │ ^^^^^^^^^^^^^^^^ A return annotation is missing from this function. Functions with external implementations must have type annotations so we can tell what type of values they accept and return. error: Unknown type ┌─ /src/one/two.gleam:5:14 │ 5 │ pub type X = UnknownType │ ^^^^^^^^^^^ The type `UnknownType` is not defined or imported in this module. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__no_target_supported_function_fault_tolerance.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\n// This will error for having no support on this platform\n@external(erlang, \"one\", \"two\")\npub fn no_impl() -> Int\n\npub fn main() {\n // This will due to no_impl not having an appropriate implementation for the\n // target, NOT because it doesn't exist. The analyser should still know about\n // it, even though it is invalid.\n no_impl()\n}\n" --- ----- SOURCE CODE // This will error for having no support on this platform @external(erlang, "one", "two") pub fn no_impl() -> Int pub fn main() { // This will due to no_impl not having an appropriate implementation for the // target, NOT because it doesn't exist. The analyser should still know about // it, even though it is invalid. no_impl() } ----- ERROR error: Unsupported target ┌─ /src/one/two.gleam:4:1 │ 4 │ pub fn no_impl() -> Int │ ^^^^^^^^^^^^^^^^ The `no_impl` function is public but doesn't have an implementation for the JavaScript target. All public functions of a package must be able to compile for a module to be valid. error: Unsupported target ┌─ /src/one/two.gleam:10:3 │ 10 │ no_impl() │ ^^^^^^^ This value is not available as it is defined using externals, and there is no implementation for the JavaScript target. Hint: Did you mean to build for a different target? ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__no_type_annotation_for_parameter.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\n@external(erlang, \"one\", \"one\")\npub fn one(x: Int, y) -> Int {\n todo\n}\n" --- ----- SOURCE CODE @external(erlang, "one", "one") pub fn one(x: Int, y) -> Int { todo } ----- ERROR error: Missing type annotation ┌─ /src/one/two.gleam:3:20 │ 3 │ pub fn one(x: Int, y) -> Int { │ ^ A parameter annotation is missing from this function. Functions with external implementations must have type annotations so we can tell what type of values they accept and return. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__no_type_annotation_for_return.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\n@external(erlang, \"one\", \"one\")\npub fn one(x: Int) {\n todo\n}\n" --- ----- SOURCE CODE @external(erlang, "one", "one") pub fn one(x: Int) { todo } ----- ERROR error: Missing type annotation ┌─ /src/one/two.gleam:3:1 │ 3 │ pub fn one(x: Int) { │ ^^^^^^^^^^^^^^^^^^ A return annotation is missing from this function. Functions with external implementations must have type annotations so we can tell what type of values they accept and return. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__private.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\npub fn main() {\n do()\n}\n\n@external(erlang, \"library\", \"main\")\nfn do() -> Int\n" --- ----- SOURCE CODE pub fn main() { do() } @external(erlang, "library", "main") fn do() -> Int ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> library:main(). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__private_external_function_calls.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\n@external(erlang, \"m\", \"f\")\nfn go(x x: Int, y y: Int) -> Int\n\npub fn x() { go(x: 1, y: 2) go(y: 3, x: 4) }" --- ----- SOURCE CODE @external(erlang, "m", "f") fn go(x x: Int, y y: Int) -> Int pub fn x() { go(x: 1, y: 2) go(y: 3, x: 4) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([x/0]). -file("project/test/my/mod.gleam", 5). -spec x() -> integer(). x() -> m:f(1, 2), m:f(4, 3). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__private_local_function_references.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\n@external(erlang, \"m\", \"f\")\nfn go(x: Int, y: Int) -> Int\npub fn x() { go }\n" --- ----- SOURCE CODE @external(erlang, "m", "f") fn go(x: Int, y: Int) -> Int pub fn x() { go } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([x/0]). -file("project/test/my/mod.gleam", 4). -spec x() -> fun((integer(), integer()) -> integer()). x() -> fun m:f/2. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__public_elixir.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\n@external(erlang, \"Elixir.String\", \"main\")\npub fn do() -> Int\n" --- ----- SOURCE CODE @external(erlang, "Elixir.String", "main") pub fn do() -> Int ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([do/0]). -file("project/test/my/mod.gleam", 3). -spec do() -> integer(). do() -> 'Elixir.String':main(). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__public_local_function_calls.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "\n@external(erlang, \"m\", \"f\")\npub fn go(x x: Int, y y: Int) -> Int\npub fn x() { go(x: 1, y: 2) go(y: 3, x: 4) }\n" --- ----- SOURCE CODE @external(erlang, "m", "f") pub fn go(x x: Int, y y: Int) -> Int pub fn x() { go(x: 1, y: 2) go(y: 3, x: 4) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/2, x/0]). -file("project/test/my/mod.gleam", 3). -spec go(integer(), integer()) -> integer(). go(X, Y) -> m:f(X, Y). -file("project/test/my/mod.gleam", 4). -spec x() -> integer(). x() -> m:f(1, 2), m:f(4, 3). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__reference_to_imported_elixir_external_fn.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "import my_app\npub fn main() {\n let x = my_app.run\n id(my_app.run)\n}\nfn id(x) { x }\n" --- ----- SOURCE CODE import my_app pub fn main() { let x = my_app.run id(my_app.run) } fn id(x) { x } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 6). -spec id(K) -> K. id(X) -> X. -file("project/test/my/mod.gleam", 2). -spec main() -> fun(() -> integer()). main() -> X = fun 'Elixir.MyApp':run/0, id(fun 'Elixir.MyApp':run/0). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__unqualified_inlining_external_functions_from_another_module.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "import atom.{make}\npub fn main() {\n make(\"ok\")\n}\n" --- ----- SOURCE CODE import atom.{make} pub fn main() { make("ok") } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> atom:atom_(). main() -> erlang:binary_to_atom(<<"ok"/utf8>>). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__external_fn__unqualified_reference_to_imported_elixir_external_fn.snap ================================================ --- source: compiler-core/src/erlang/tests/external_fn.rs expression: "import my_app.{run}\npub fn main() {\n let x = run\n id(run)\n}\nfn id(x) { x }\n" --- ----- SOURCE CODE import my_app.{run} pub fn main() { let x = run id(run) } fn id(x) { x } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 6). -spec id(K) -> K. id(X) -> X. -file("project/test/my/mod.gleam", 2). -spec main() -> fun(() -> integer()). main() -> X = fun 'Elixir.MyApp':run/0, id(fun 'Elixir.MyApp':run/0). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__functions__function_as_value.snap ================================================ --- source: compiler-core/src/erlang/tests/functions.rs expression: "\nfn other() {\n Nil\n}\n\npub fn main() {\n other\n}\n" --- ----- SOURCE CODE fn other() { Nil } pub fn main() { other } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec other() -> nil. other() -> nil. -file("project/test/my/mod.gleam", 6). -spec main() -> fun(() -> nil). main() -> fun other/0. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__functions__function_called.snap ================================================ --- source: compiler-core/src/erlang/tests/functions.rs expression: "\npub fn main() {\n main()\n}\n" --- ----- SOURCE CODE pub fn main() { main() } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> any(). main() -> main(). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__functions__labelled_argument_ordering.snap ================================================ --- source: compiler-core/src/erlang/tests/functions.rs expression: "\ntype A { A }\ntype B { B }\ntype C { C }\ntype D { D }\n\nfn wibble(a a: A, b b: B, c c: C, d d: D) {\n Nil\n}\n\npub fn main() {\n wibble(A, C, D, b: B)\n wibble(A, C, D, b: B)\n wibble(B, C, D, a: A)\n wibble(B, C, a: A, d: D)\n wibble(B, C, d: D, a: A)\n wibble(B, D, a: A, c: C)\n wibble(B, D, c: C, a: A)\n wibble(C, D, b: B, a: A)\n}\n" --- ----- SOURCE CODE type A { A } type B { B } type C { C } type D { D } fn wibble(a a: A, b b: B, c c: C, d d: D) { Nil } pub fn main() { wibble(A, C, D, b: B) wibble(A, C, D, b: B) wibble(B, C, D, a: A) wibble(B, C, a: A, d: D) wibble(B, C, d: D, a: A) wibble(B, D, a: A, c: C) wibble(B, D, c: C, a: A) wibble(C, D, b: B, a: A) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -export_type([a/0, b/0, c/0, d/0]). -type a() :: a. -type b() :: b. -type c() :: c. -type d() :: d. -file("project/test/my/mod.gleam", 7). -spec wibble(a(), b(), c(), d()) -> nil. wibble(A, B, C, D) -> nil. -file("project/test/my/mod.gleam", 11). -spec main() -> nil. main() -> wibble(a, b, c, d), wibble(a, b, c, d), wibble(a, b, c, d), wibble(a, b, c, d), wibble(a, b, c, d), wibble(a, b, c, d), wibble(a, b, c, d), wibble(a, b, c, d). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__functions__nested_aliased_imported_function_as_value.snap ================================================ --- source: compiler-core/src/erlang/tests/functions.rs expression: "\nimport some/other.{wibble as wobble}\n\npub fn main() {\n wobble\n}\n" --- ----- SOURCE CODE import some/other.{wibble as wobble} pub fn main() { wobble } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 4). -spec main() -> fun(() -> nil). main() -> fun some@other:wibble/0. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__functions__nested_aliased_imported_function_called.snap ================================================ --- source: compiler-core/src/erlang/tests/functions.rs expression: "\nimport some/other.{wibble as wobble}\n\npub fn main() {\n wobble()\n}\n" --- ----- SOURCE CODE import some/other.{wibble as wobble} pub fn main() { wobble() } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 4). -spec main() -> nil. main() -> some@other:wibble(). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__functions__nested_imported_function_as_value.snap ================================================ --- source: compiler-core/src/erlang/tests/functions.rs expression: "\nimport some/other\n\npub fn main() {\n other.wibble\n}\n" --- ----- SOURCE CODE import some/other pub fn main() { other.wibble } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 4). -spec main() -> fun(() -> nil). main() -> fun some@other:wibble/0. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__functions__nested_imported_function_called.snap ================================================ --- source: compiler-core/src/erlang/tests/functions.rs expression: "\nimport some/other\n\npub fn main() {\n other.wibble()\n}\n" --- ----- SOURCE CODE import some/other pub fn main() { other.wibble() } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 4). -spec main() -> nil. main() -> some@other:wibble(). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__functions__nested_unqualified_imported_function_as_value.snap ================================================ --- source: compiler-core/src/erlang/tests/functions.rs expression: "\nimport some/other.{wibble}\n\npub fn main() {\n wibble\n}\n" --- ----- SOURCE CODE import some/other.{wibble} pub fn main() { wibble } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 4). -spec main() -> fun(() -> nil). main() -> fun some@other:wibble/0. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__functions__nested_unqualified_imported_function_called.snap ================================================ --- source: compiler-core/src/erlang/tests/functions.rs expression: "\nimport some/other.{wibble}\n\npub fn main() {\n wibble()\n}\n" --- ----- SOURCE CODE import some/other.{wibble} pub fn main() { wibble() } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 4). -spec main() -> nil. main() -> some@other:wibble(). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__functions__unused_private_functions.snap ================================================ --- source: compiler-core/src/erlang/tests/functions.rs expression: "\npub fn main() -> Int {\n used()\n}\n\nfn used() -> Int {\n 123\n}\n\nfn unused1() -> Int {\n unused2()\n}\n\nfn unused2() -> Int {\n used()\n}\n\nfn unused3() -> Int {\n used()\n}\n" --- ----- SOURCE CODE pub fn main() -> Int { used() } fn used() -> Int { 123 } fn unused1() -> Int { unused2() } fn unused2() -> Int { used() } fn unused3() -> Int { used() } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 6). -spec used() -> integer(). used() -> 123. -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> used(). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__clause_guards.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub fn main(args) {\n case args {\n x if x == args -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main(args) { case args { x if x == args -> 1 _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -file("project/test/my/mod.gleam", 2). -spec main(any()) -> integer(). main(Args) -> case Args of X when X =:= Args -> 1; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__clause_guards20.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub fn main() {\n let x = 0.123\n case x {\n _ if 0.123 <. x -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main() { let x = 0.123 case x { _ if 0.123 <. x -> 1 _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> X = 0.123, case X of _ when 0.123 < X -> 1; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__clause_guards21.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub fn main(x) {\n case x {\n _ if x == [1, 2, 3] -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { _ if x == [1, 2, 3] -> 1 _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -file("project/test/my/mod.gleam", 2). -spec main(list(integer())) -> integer(). main(X) -> case X of _ when X =:= [1, 2, 3] -> 1; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__clause_guards22.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub fn main() {\n let x = 0\n case x {\n 0 -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main() { let x = 0 case x { 0 -> 1 _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> X = 0, case X of 0 -> 1; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__clause_guards23.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub fn main() {\n let x = #(1, 2, 3)\n case x {\n _ if x == #(1, 2, 3) -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main() { let x = #(1, 2, 3) case x { _ if x == #(1, 2, 3) -> 1 _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> X = {1, 2, 3}, case X of _ when X =:= {1, 2, 3} -> 1; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__clause_guards24.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub fn main() {\n let x = #(1, 2, 3)\n case x {\n _ if x == #(1, 2, 3) -> 1\n _ if x == #(2, 3, 4) -> 2\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main() { let x = #(1, 2, 3) case x { _ if x == #(1, 2, 3) -> 1 _ if x == #(2, 3, 4) -> 2 _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> X = {1, 2, 3}, case X of _ when X =:= {1, 2, 3} -> 1; _ when X =:= {2, 3, 4} -> 2; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__clause_guards25.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub fn main() {\n let x = 0\n case x {\n _ if x == 0 -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main() { let x = 0 case x { _ if x == 0 -> 1 _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> X = 0, case X of _ when X =:= 0 -> 1; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__clause_guards26.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub fn main() {\n let x = 0\n case x {\n _ if 0 < x -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main() { let x = 0 case x { _ if 0 < x -> 1 _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> X = 0, case X of _ when 0 < X -> 1; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__clause_guards27.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub fn main() {\n case \"test\" {\n x if x == \"test\" -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case "test" { x if x == "test" -> 1 _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> case <<"test"/utf8>> of X when X =:= <<"test"/utf8>> -> 1; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__clause_guards28.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\n type Test { Test(x: Int, y: Float) }\n pub fn main() {\n let x = Test(1, 3.0)\n case x {\n _ if x == Test(1, 1.0) -> 1\n _ if x == Test(y: 2.0, x: 2) -> 2\n _ if x != Test(2, 3.0) -> 2\n _ -> 0\n }\n }\n" --- ----- SOURCE CODE type Test { Test(x: Int, y: Float) } pub fn main() { let x = Test(1, 3.0) case x { _ if x == Test(1, 1.0) -> 1 _ if x == Test(y: 2.0, x: 2) -> 2 _ if x != Test(2, 3.0) -> 2 _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -export_type([test/0]). -type test() :: {test, integer(), float()}. -file("project/test/my/mod.gleam", 3). -spec main() -> integer(). main() -> X = {test, 1, 3.0}, case X of _ when X =:= {test, 1, 1.0} -> 1; _ when X =:= {test, 2, 2.0} -> 2; _ when X =/= {test, 2, 3.0} -> 2; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__clause_guards29.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub fn main() {\n case 0.1, 1.0 {\n x, y if x <. y -> 1\n _, _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case 0.1, 1.0 { x, y if x <. y -> 1 _, _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> case {0.1, 1.0} of {X, Y} when X < Y -> 1; {_, _} -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__clause_guards30.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub fn main() {\n case 0.1, 1.0 {\n x, y if x <=. y -> 1\n _, _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case 0.1, 1.0 { x, y if x <=. y -> 1 _, _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> case {0.1, 1.0} of {X, Y} when X =< Y -> 1; {_, _} -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__clause_guards31.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub fn main(args) {\n case args {\n [x] | [x, _] if x -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main(args) { case args { [x] | [x, _] if x -> 1 _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -file("project/test/my/mod.gleam", 2). -spec main(list(boolean())) -> integer(). main(Args) -> case Args of [X] when X -> 1; [X, _] when X -> 1; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__clause_guards32.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub fn main() {\n let wibble = \"wobble\"\n case wibble {\n x if x == \"wob\" <> \"ble\" -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main() { let wibble = "wobble" case wibble { x if x == "wob" <> "ble" -> 1 _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> Wibble = <<"wobble"/utf8>>, case Wibble of X when X =:= <<"wob"/utf8, "ble"/utf8>> -> 1; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__clause_guards_1.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub fn main(args) {\n case args {\n x if {x != x} == {args == args} -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main(args) { case args { x if {x != x} == {args == args} -> 1 _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -file("project/test/my/mod.gleam", 2). -spec main(any()) -> integer(). main(Args) -> case Args of X when (X =/= X) =:= (Args =:= Args) -> 1; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__clause_guards_10.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub fn main() {\n let x = 0.123\n case x {\n _ if x == 3.14 -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main() { let x = 0.123 case x { _ if x == 3.14 -> 1 _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> X = 0.123, case X of _ when X =:= 3.14 -> 1; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__clause_guards_2.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub fn main(args) {\n case args {\n x if x && x || x == x && x -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main(args) { case args { x if x && x || x == x && x -> 1 _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -file("project/test/my/mod.gleam", 2). -spec main(boolean()) -> integer(). main(Args) -> case Args of X when (X andalso X) orelse ((X =:= X) andalso X) -> 1; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__clause_guards_3.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub fn main() {\n case 1, 0 {\n x, y if x > y -> 1\n _, _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case 1, 0 { x, y if x > y -> 1 _, _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> case {1, 0} of {X, Y} when X > Y -> 1; {_, _} -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__clause_guards_4.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub fn main() {\n case 1, 0 {\n x, y if x >= y -> 1\n _, _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case 1, 0 { x, y if x >= y -> 1 _, _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> case {1, 0} of {X, Y} when X >= Y -> 1; {_, _} -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__clause_guards_5.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub fn main() {\n case 1, 0 {\n x, y if x < y -> 1\n _, _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case 1, 0 { x, y if x < y -> 1 _, _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> case {1, 0} of {X, Y} when X < Y -> 1; {_, _} -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__clause_guards_6.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub fn main() {\n case 1, 0 {\n x, y if x <= y -> 1\n _, _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case 1, 0 { x, y if x <= y -> 1 _, _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> case {1, 0} of {X, Y} when X =< Y -> 1; {_, _} -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__clause_guards_7.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub fn main() {\n case 1.0, 0.1 {\n x, y if x >. y -> 1\n _, _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case 1.0, 0.1 { x, y if x >. y -> 1 _, _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> case {1.0, 0.1} of {X, Y} when X > Y -> 1; {_, _} -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__clause_guards_8.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub fn main() {\n case 1.0, 0.1 {\n x, y if x >=. y -> 1\n _, _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case 1.0, 0.1 { x, y if x >=. y -> 1 _, _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> case {1.0, 0.1} of {X, Y} when X >= Y -> 1; {_, _} -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__clause_guards_9.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub fn main() {\n let x = 0.123\n case x {\n 99.9854 -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main() { let x = 0.123 case x { 99.9854 -> 1 _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> X = 0.123, case X of 99.9854 -> 1; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__constants_in_guards.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub const string_value = \"constant value\"\npub const float_value = 3.14\npub const int_value = 42\npub const tuple_value = #(1, 2.0, \"3\")\npub const list_value = [1, 2, 3]\n\npub fn main(arg) {\n let _ = list_value\n case arg {\n #(w, x, y, z) if w == tuple_value && x == string_value && y >. float_value && z == int_value -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub const string_value = "constant value" pub const float_value = 3.14 pub const int_value = 42 pub const tuple_value = #(1, 2.0, "3") pub const list_value = [1, 2, 3] pub fn main(arg) { let _ = list_value case arg { #(w, x, y, z) if w == tuple_value && x == string_value && y >. float_value && z == int_value -> 1 _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -file("project/test/my/mod.gleam", 8). -spec main({{integer(), float(), binary()}, binary(), float(), integer()}) -> integer(). main(Arg) -> _ = [1, 2, 3], case Arg of {W, X, Y, Z} when (((W =:= {1, 2.0, <<"3"/utf8>>}) andalso (X =:= <<"constant value"/utf8>>)) andalso (Y > 3.14)) andalso (Z =:= 42) -> 1; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__constants_in_guards1.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub const list = [1, 2, 3]\n\npub fn main(arg) {\n case arg {\n _ if arg == list -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub const list = [1, 2, 3] pub fn main(arg) { case arg { _ if arg == list -> 1 _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -file("project/test/my/mod.gleam", 4). -spec main(list(integer())) -> integer(). main(Arg) -> case Arg of _ when Arg =:= [1, 2, 3] -> 1; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__field_access.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\n pub type Person {\n Person(username: String, name: String, age: Int)\n }\n \n pub fn main() {\n let given_name = \"jack\"\n let raiden = Person(\"raiden\", \"jack\", 31)\n \n case given_name {\n name if name == raiden.name -> \"It's jack\"\n _ -> \"It's not jack\"\n }\n }\n " --- ----- SOURCE CODE pub type Person { Person(username: String, name: String, age: Int) } pub fn main() { let given_name = "jack" let raiden = Person("raiden", "jack", 31) case given_name { name if name == raiden.name -> "It's jack" _ -> "It's not jack" } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -export_type([person/0]). -type person() :: {person, binary(), binary(), integer()}. -file("project/test/my/mod.gleam", 6). -spec main() -> binary(). main() -> Given_name = <<"jack"/utf8>>, Raiden = {person, <<"raiden"/utf8>>, <<"jack"/utf8>>, 31}, case Given_name of Name when Name =:= erlang:element(3, Raiden) -> <<"It's jack"/utf8>>; _ -> <<"It's not jack"/utf8>> end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__module_access.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\n import hero\n pub fn main() {\n let name = \"Tony Stark\"\n case name {\n n if n == hero.ironman.name -> True\n _ -> False\n }\n }\n " --- ----- SOURCE CODE import hero pub fn main() { let name = "Tony Stark" case name { n if n == hero.ironman.name -> True _ -> False } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 3). -spec main() -> boolean(). main() -> Name = <<"Tony Stark"/utf8>>, case Name of N when N =:= erlang:element(2, {hero, <<"Tony Stark"/utf8>>}) -> true; _ -> false end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__module_list_access.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\n import hero\n pub fn main() {\n let names = [\"Tony Stark\", \"Bruce Wayne\"]\n case names {\n n if n == hero.heroes -> True\n _ -> False\n }\n }\n " --- ----- SOURCE CODE import hero pub fn main() { let names = ["Tony Stark", "Bruce Wayne"] case names { n if n == hero.heroes -> True _ -> False } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 3). -spec main() -> boolean(). main() -> Names = [<<"Tony Stark"/utf8>>, <<"Bruce Wayne"/utf8>>], case Names of N when N =:= [<<"Tony Stark"/utf8>>, <<"Bruce Wayne"/utf8>>] -> true; _ -> false end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__module_nested_access.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\n import hero\n pub fn main() {\n let name = \"Bruce Wayne\"\n case name {\n n if n == hero.batman.secret_identity.name -> True\n _ -> False\n }\n }\n " --- ----- SOURCE CODE import hero pub fn main() { let name = "Bruce Wayne" case name { n if n == hero.batman.secret_identity.name -> True _ -> False } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 3). -spec main() -> boolean(). main() -> Name = <<"Bruce Wayne"/utf8>>, case Name of N when N =:= erlang:element( 2, erlang:element(2, {hero, {person, <<"Bruce Wayne"/utf8>>}}) ) -> true; _ -> false end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__module_string_access.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\n import hero\n pub fn main() {\n let name = \"Tony Stark\"\n case name {\n n if n == hero.ironman -> True\n _ -> False\n }\n }\n " --- ----- SOURCE CODE import hero pub fn main() { let name = "Tony Stark" case name { n if n == hero.ironman -> True _ -> False } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 3). -spec main() -> boolean(). main() -> Name = <<"Tony Stark"/utf8>>, case Name of N when N =:= <<"Tony Stark"/utf8>> -> true; _ -> false end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__module_tuple_access.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\n import hero\n pub fn main() {\n let name = \"Tony Stark\"\n case name {\n n if n == hero.hero.1 -> True\n _ -> False\n }\n }\n " --- ----- SOURCE CODE import hero pub fn main() { let name = "Tony Stark" case name { n if n == hero.hero.1 -> True _ -> False } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 3). -spec main() -> boolean(). main() -> Name = <<"Tony Stark"/utf8>>, case Name of N when N =:= erlang:element( 2, {<<"ironman"/utf8>>, <<"Tony Stark"/utf8>>} ) -> true; _ -> false end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__nested_record_access.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub type A {\n A(b: B)\n}\n\npub type B {\n B(c: C)\n}\n\npub type C {\n C(d: Bool)\n}\n\npub fn a(a: A) {\n case a {\n _ if a.b.c.d -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub type A { A(b: B) } pub type B { B(c: C) } pub type C { C(d: Bool) } pub fn a(a: A) { case a { _ if a.b.c.d -> 1 _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([a/1]). -export_type([a/0, b/0, c/0]). -type a() :: {a, b()}. -type b() :: {b, c()}. -type c() :: {c, boolean()}. -file("project/test/my/mod.gleam", 14). -spec a(a()) -> integer(). a(A) -> case A of _ when erlang:element(2, erlang:element(2, erlang:element(2, A))) -> 1; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__only_guards.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub const string_value = \"constant value\"\n\npub fn main(arg) {\n case arg {\n _ if arg == string_value -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub const string_value = "constant value" pub fn main(arg) { case arg { _ if arg == string_value -> 1 _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -file("project/test/my/mod.gleam", 4). -spec main(binary()) -> integer(). main(Arg) -> case Arg of _ when Arg =:= <<"constant value"/utf8>> -> 1; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__only_guards1.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub const bits = <<1, \"ok\":utf8, 3, 4:50>>\n\npub fn main(arg) {\n case arg {\n _ if arg == bits -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub const bits = <<1, "ok":utf8, 3, 4:50>> pub fn main(arg) { case arg { _ if arg == bits -> 1 _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -file("project/test/my/mod.gleam", 4). -spec main(bitstring()) -> integer(). main(Arg) -> case Arg of _ when Arg =:= <<1, "ok"/utf8, 3, 4:50>> -> 1; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__only_guards2.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub const constant = #(1, 2.0)\n\npub fn main(arg) {\n case arg {\n _ if arg == constant -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub const constant = #(1, 2.0) pub fn main(arg) { case arg { _ if arg == constant -> 1 _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -file("project/test/my/mod.gleam", 4). -spec main({integer(), float()}) -> integer(). main(Arg) -> case Arg of _ when Arg =:= {1, 2.0} -> 1; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__guards__only_guards3.snap ================================================ --- source: compiler-core/src/erlang/tests/guards.rs expression: "\npub const float_value = 3.14\n\npub fn main(arg) {\n case arg {\n _ if arg >. float_value -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub const float_value = 3.14 pub fn main(arg) { case arg { _ if arg >. float_value -> 1 _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -file("project/test/my/mod.gleam", 4). -spec main(float()) -> integer(). main(Arg) -> case Arg of _ when Arg > 3.14 -> 1; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__inlining__blocks_get_preserved_when_needed.snap ================================================ --- source: compiler-core/src/erlang/tests/inlining.rs expression: "\npub fn main() {\n { 4 |> make_adder }(6)\n}\n\nfn make_adder(a) {\n fn(b) { a + b }\n}\n" --- ----- SOURCE CODE pub fn main() { { 4 |> make_adder }(6) } fn make_adder(a) { fn(b) { a + b } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 6). -spec make_adder(integer()) -> fun((integer()) -> integer()). make_adder(A) -> fun(B) -> A + B end. -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> begin _pipe = 4, make_adder(_pipe) end(6). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__inlining__blocks_get_preserved_when_needed2.snap ================================================ --- source: compiler-core/src/erlang/tests/inlining.rs expression: "\npub fn main() {\n fn(x) { 1 + x }(2) * 3\n}\n" --- ----- SOURCE CODE pub fn main() { fn(x) { 1 + x }(2) * 3 } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> (1 + 2) * 3. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__inlining__do_not_inline_parameters_that_have_side_effects.snap ================================================ --- source: compiler-core/src/erlang/tests/inlining.rs expression: "\nimport gleam/result\n\npub fn main() {\n result.map(Ok(10), do_side_effects())\n}\n\nfn do_side_effects() {\n let function = fn(x) { x + 1 }\n panic as \"Side effects\"\n function\n}\n" --- ----- SOURCE CODE import gleam/result pub fn main() { result.map(Ok(10), do_side_effects()) } fn do_side_effects() { let function = fn(x) { x + 1 } panic as "Side effects" function } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 8). -spec do_side_effects() -> fun((integer()) -> integer()). do_side_effects() -> Function = fun(X) -> X + 1 end, erlang:error(#{gleam_error => panic, message => <<"Side effects"/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"do_side_effects"/utf8>>, line => 10}), Function. -file("project/test/my/mod.gleam", 4). -spec main() -> {ok, integer()} | {error, any()}. main() -> begin F = do_side_effects(), case {ok, 10} of {ok, Value} -> {ok, F(Value)}; {error, Error} -> {error, Error} end end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__inlining__do_not_inline_parameters_used_more_than_once.snap ================================================ --- source: compiler-core/src/erlang/tests/inlining.rs expression: "\nimport testing\n\npub fn main() {\n testing.always_inline(True)\n}\n" --- ----- SOURCE CODE import testing pub fn main() { testing.always_inline(True) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 4). -spec main() -> boolean(). main() -> begin Something = true, case Something of true -> Something; false -> false end end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__inlining__inline_anonymous_function_call.snap ================================================ --- source: compiler-core/src/erlang/tests/inlining.rs expression: "\npub fn main() {\n fn(a, b) { #(a, b) }(42, False)\n}\n" --- ----- SOURCE CODE pub fn main() { fn(a, b) { #(a, b) }(42, False) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> {integer(), boolean()}. main() -> {42, false}. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__inlining__inline_anonymous_function_in_pipe.snap ================================================ --- source: compiler-core/src/erlang/tests/inlining.rs expression: "\npub fn main() {\n 1 |> fn(x) { x + 1 } |> fn(y) { y * y }\n}\n" --- ----- SOURCE CODE pub fn main() { 1 |> fn(x) { x + 1 } |> fn(y) { y * y } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> _pipe = 1, _pipe@1 = _pipe + 1, begin Y = _pipe@1, Y * Y end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__inlining__inline_function_capture_in_pipe.snap ================================================ --- source: compiler-core/src/erlang/tests/inlining.rs expression: "\npub fn main() {\n 1 |> add(4, _)\n}\n\nfn add(a, b) { a + b }\n" --- ----- SOURCE CODE pub fn main() { 1 |> add(4, _) } fn add(a, b) { a + b } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 6). -spec add(integer(), integer()) -> integer(). add(A, B) -> A + B. -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> _pipe = 1, add(4, _pipe). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__inlining__inline_function_which_calls_other_function.snap ================================================ --- source: compiler-core/src/erlang/tests/inlining.rs expression: "\nimport testing\n\npub fn main() {\n testing.always_inline(Ok(10), Error)\n}\n" --- ----- SOURCE CODE import testing pub fn main() { testing.always_inline(Ok(10), Error) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 4). -spec main() -> {ok, any()} | {error, integer()}. main() -> case {ok, 10} of {ok, Value} -> {error, Value}; {error, Error} -> {error, Error} end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__inlining__inline_function_with_use.snap ================================================ --- source: compiler-core/src/erlang/tests/inlining.rs expression: "\nimport gleam/bool\n\npub fn divide(a, b) {\n use <- bool.guard(when: b == 0, return: 0)\n a / b\n}\n" --- ----- SOURCE CODE import gleam/bool pub fn divide(a, b) { use <- bool.guard(when: b == 0, return: 0) a / b } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([divide/2]). -file("project/test/my/mod.gleam", 4). -spec divide(integer(), integer()) -> integer(). divide(A, B) -> case B =:= 0 of true -> 0; false -> case B of 0 -> 0; Gleam@denominator -> A div Gleam@denominator end end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__inlining__inline_function_with_use_and_anonymous.snap ================================================ --- source: compiler-core/src/erlang/tests/inlining.rs expression: "\nimport gleam/bool\n\npub fn divide(a, b) {\n use <- bool.lazy_guard(b == 0, fn() { panic as \"Cannot divide by 0\" })\n a / b\n}\n" --- ----- SOURCE CODE import gleam/bool pub fn divide(a, b) { use <- bool.lazy_guard(b == 0, fn() { panic as "Cannot divide by 0" }) a / b } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([divide/2]). -file("project/test/my/mod.gleam", 4). -spec divide(integer(), integer()) -> integer(). divide(A, B) -> case B =:= 0 of true -> erlang:error(#{gleam_error => panic, message => <<"Cannot divide by 0"/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"divide"/utf8>>, line => 5}); false -> case B of 0 -> 0; Gleam@denominator -> A div Gleam@denominator end end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__inlining__inline_function_with_use_becomes_tail_recursive.snap ================================================ --- source: compiler-core/src/erlang/tests/inlining.rs expression: "\nimport gleam/bool\n\npub fn count(from: Int, to: Int) -> Int {\n use <- bool.guard(when: from >= to, return: from)\n echo from\n count(from + 1, to)\n}\n" --- ----- SOURCE CODE import gleam/bool pub fn count(from: Int, to: Int) -> Int { use <- bool.guard(when: from >= to, return: from) echo from count(from + 1, to) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([count/2]). -file("project/test/my/mod.gleam", 4). -spec count(integer(), integer()) -> integer(). count(From, To) -> case From >= To of true -> From; false -> echo(From, nil, 6), count(From + 1, To) end. % ...omitted code from `templates/echo.erl`... ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__inlining__inline_higher_order_function.snap ================================================ --- source: compiler-core/src/erlang/tests/inlining.rs expression: "\nimport gleam/result\n\npub fn main() {\n result.map(over: Ok(10), with: double)\n}\n\nfn double(x) { x + x }\n" --- ----- SOURCE CODE import gleam/result pub fn main() { result.map(over: Ok(10), with: double) } fn double(x) { x + x } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 8). -spec double(integer()) -> integer(). double(X) -> X + X. -file("project/test/my/mod.gleam", 4). -spec main() -> {ok, integer()} | {error, any()}. main() -> case {ok, 10} of {ok, Value} -> {ok, double(Value)}; {error, Error} -> {error, Error} end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__inlining__inline_higher_order_function_anonymous.snap ================================================ --- source: compiler-core/src/erlang/tests/inlining.rs expression: "\nimport gleam/result\n\npub fn main() {\n result.try(Ok(10), fn(value) {\n Ok({ value + 2 } * 4)\n })\n}\n" --- ----- SOURCE CODE import gleam/result pub fn main() { result.try(Ok(10), fn(value) { Ok({ value + 2 } * 4) }) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 4). -spec main() -> {ok, integer()} | {error, any()}. main() -> case {ok, 10} of {ok, Value} -> {ok, (Value + 2) * 4}; {error, Error} -> {error, Error} end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__inlining__inline_higher_order_function_with_capture.snap ================================================ --- source: compiler-core/src/erlang/tests/inlining.rs expression: "\nimport gleam/result\n\npub fn main() {\n result.try(Ok(10), divide(_, 2))\n}\n\nfn divide(a: Int, b: Int) -> Result(Int, Nil) {\n case a % b {\n 0 -> Ok(a / b)\n _ -> Error(Nil)\n }\n}\n" --- ----- SOURCE CODE import gleam/result pub fn main() { result.try(Ok(10), divide(_, 2)) } fn divide(a: Int, b: Int) -> Result(Int, Nil) { case a % b { 0 -> Ok(a / b) _ -> Error(Nil) } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 8). -spec divide(integer(), integer()) -> {ok, integer()} | {error, nil}. divide(A, B) -> case case B of 0 -> 0; Gleam@denominator -> A rem Gleam@denominator end of 0 -> {ok, case B of 0 -> 0; Gleam@denominator@1 -> A div Gleam@denominator@1 end}; _ -> {error, nil} end. -file("project/test/my/mod.gleam", 4). -spec main() -> {ok, integer()} | {error, nil}. main() -> case {ok, 10} of {ok, Value} -> divide(Value, 2); {error, Error} -> {error, Error} end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__inlining__inline_shadowed_variable.snap ================================================ --- source: compiler-core/src/erlang/tests/inlining.rs expression: "\npub fn main() {\n let a = 10\n let b = 20\n\n fn(x) {\n let a = 7\n x + a\n }(a + b)\n\n a\n}\n" --- ----- SOURCE CODE pub fn main() { let a = 10 let b = 20 fn(x) { let a = 7 x + a }(a + b) a } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> A = 10, B = 20, begin _inline_a_0 = 7, (A + B) + _inline_a_0 end, A. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__inlining__inline_shadowed_variable_nested.snap ================================================ --- source: compiler-core/src/erlang/tests/inlining.rs expression: "\npub fn sum(a, b) {\n fn(x) {\n let a = 7\n fn(y) {\n let a = 10\n y - a\n }(x + a)\n\n a\n }(a + b)\n\n a\n}\n" --- ----- SOURCE CODE pub fn sum(a, b) { fn(x) { let a = 7 fn(y) { let a = 10 y - a }(x + a) a }(a + b) a } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([sum/2]). -file("project/test/my/mod.gleam", 2). -spec sum(integer(), integer()) -> integer(). sum(A, B) -> begin _inline_a_0 = 7, begin _inline_a_1 = 10, ((A + B) + _inline_a_0) - _inline_a_1 end, _inline_a_0 end, A. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__inlining__inline_variable_shadowed_in_case_pattern.snap ================================================ --- source: compiler-core/src/erlang/tests/inlining.rs expression: "\npub fn sum() {\n let a = 10\n let b = 20\n\n fn(x) {\n case 7, 8 {\n a, b -> a + b + x\n }\n }(a + b)\n\n a + b\n}\n" --- ----- SOURCE CODE pub fn sum() { let a = 10 let b = 20 fn(x) { case 7, 8 { a, b -> a + b + x } }(a + b) a + b } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([sum/0]). -file("project/test/my/mod.gleam", 2). -spec sum() -> integer(). sum() -> A = 10, B = 20, case {7, 8} of {_inline_a_0, _inline_b_1} -> (_inline_a_0 + _inline_b_1) + (A + B) end, A + B. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__inlining__inline_variable_shadowing_case_pattern.snap ================================================ --- source: compiler-core/src/erlang/tests/inlining.rs expression: "\npub fn sum() {\n case 1, 2 {\n a, b -> fn(x) {\n let a = 7\n x + a\n }(a + b)\n }\n}\n" --- ----- SOURCE CODE pub fn sum() { case 1, 2 { a, b -> fn(x) { let a = 7 x + a }(a + b) } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([sum/0]). -file("project/test/my/mod.gleam", 2). -spec sum() -> integer(). sum() -> case {1, 2} of {A, B} -> _inline_a_0 = 7, (A + B) + _inline_a_0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__inlining__inline_variable_shadowing_parameter.snap ================================================ --- source: compiler-core/src/erlang/tests/inlining.rs expression: "\npub fn sum(a, b) {\n fn(x) {\n let a = 7\n x + a\n }(a + b)\n\n a\n}\n" --- ----- SOURCE CODE pub fn sum(a, b) { fn(x) { let a = 7 x + a }(a + b) a } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([sum/2]). -file("project/test/my/mod.gleam", 2). -spec sum(integer(), integer()) -> integer(). sum(A, B) -> begin _inline_a_0 = 7, (A + B) + _inline_a_0 end, A. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__inlining__inlining_works_properly_with_record_updates.snap ================================================ --- source: compiler-core/src/erlang/tests/inlining.rs expression: "\nimport gleam/result\n\npub type Wibble {\n Wibble(a: Int, b: Int)\n}\n\npub fn main() {\n let w = Wibble(1, 2)\n use b <- result.map(Ok(3))\n Wibble(..w, b:)\n}\n" --- ----- SOURCE CODE import gleam/result pub type Wibble { Wibble(a: Int, b: Int) } pub fn main() { let w = Wibble(1, 2) use b <- result.map(Ok(3)) Wibble(..w, b:) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -export_type([wibble/0]). -type wibble() :: {wibble, integer(), integer()}. -file("project/test/my/mod.gleam", 8). -spec main() -> {ok, wibble()} | {error, any()}. main() -> W = {wibble, 1, 2}, case {ok, 3} of {ok, Value} -> {ok, {wibble, erlang:element(2, W), Value}}; {error, Error} -> {error, Error} end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__inlining__inlining_works_through_blocks.snap ================================================ --- source: compiler-core/src/erlang/tests/inlining.rs expression: "\npub fn main() {\n { fn(x) { Ok(x + 1) } }(41)\n}\n" --- ----- SOURCE CODE pub fn main() { { fn(x) { Ok(x + 1) } }(41) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> {ok, integer()} | {error, any()}. main() -> {ok, 41 + 1}. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__inlining__parameters_from_nested_functions_are_correctly_inlined.snap ================================================ --- source: compiler-core/src/erlang/tests/inlining.rs expression: "\nimport gleam/result\n\npub fn halve_all(a, b, c) {\n use x <- result.try(divide(a, 2))\n use y <- result.try(divide(b, 2))\n use z <- result.map(divide(c, 2))\n\n #(x, y, z)\n}\n\nfn divide(a, b) {\n case a % b {\n 0 -> Ok(a / b)\n _ -> Error(Nil)\n }\n}\n" --- ----- SOURCE CODE import gleam/result pub fn halve_all(a, b, c) { use x <- result.try(divide(a, 2)) use y <- result.try(divide(b, 2)) use z <- result.map(divide(c, 2)) #(x, y, z) } fn divide(a, b) { case a % b { 0 -> Ok(a / b) _ -> Error(Nil) } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([halve_all/3]). -file("project/test/my/mod.gleam", 12). -spec divide(integer(), integer()) -> {ok, integer()} | {error, nil}. divide(A, B) -> case case B of 0 -> 0; Gleam@denominator -> A rem Gleam@denominator end of 0 -> {ok, case B of 0 -> 0; Gleam@denominator@1 -> A div Gleam@denominator@1 end}; _ -> {error, nil} end. -file("project/test/my/mod.gleam", 4). -spec halve_all(integer(), integer(), integer()) -> {ok, {integer(), integer(), integer()}} | {error, nil}. halve_all(A, B, C) -> case divide(A, 2) of {ok, Value} -> X = Value, case divide(B, 2) of {ok, _inline_value_0} -> Y = _inline_value_0, case divide(C, 2) of {ok, _inline_value_1} -> {ok, {X, Y, _inline_value_1}}; {error, Error} -> {error, Error} end; {error, _inline_error_2} -> {error, _inline_error_2} end; {error, _inline_error_3} -> {error, _inline_error_3} end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__assignment_pattern.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "pub fn go() {\n let assert 123 as x = 123\n x\n}" --- ----- SOURCE CODE pub fn go() { let assert 123 as x = 123 x } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 1). -spec go() -> integer(). go() -> X@1 = case 123 of 123 = X -> X; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"go"/utf8>>, line => 2, value => _assert_fail, start => 16, 'end' => 41, pattern_start => 27, pattern_end => 35}) end, X@1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__bit_array_assignment_discard.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "\npub fn main() {\n let assert <<_ as number>> = <<10>>\n number\n}\n" --- ----- SOURCE CODE pub fn main() { let assert <<_ as number>> = <<10>> number } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> Number@1 = case <<10>> of <> -> Number; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 3, value => _assert_fail, start => 19, 'end' => 54, pattern_start => 30, pattern_end => 45}) end, Number@1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__bit_array_assignment_float.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "\npub fn main() {\n let assert <<3.14 as pi:float>> = <<3.14>>\n pi\n}\n" --- ----- SOURCE CODE pub fn main() { let assert <<3.14 as pi:float>> = <<3.14>> pi } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> float(). main() -> Pi@1 = case <<3.14/float>> of <> when Pi =:= 3.14 -> Pi; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 3, value => _assert_fail, start => 19, 'end' => 61, pattern_start => 30, pattern_end => 50}) end, Pi@1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__bit_array_assignment_int.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "\npub fn main() {\n let assert <<1 as a>> = <<1>>\n a\n}\n" --- ----- SOURCE CODE pub fn main() { let assert <<1 as a>> = <<1>> a } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> A@1 = case <<1>> of <> when A =:= 1 -> A; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 3, value => _assert_fail, start => 19, 'end' => 48, pattern_start => 30, pattern_end => 40}) end, A@1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__bit_array_assignment_string.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "\npub fn main() {\n let assert <<\"Hello, world!\" as message:utf8>> = <<\"Hello, world!\">>\n message\n}\n" --- ----- SOURCE CODE pub fn main() { let assert <<"Hello, world!" as message:utf8>> = <<"Hello, world!">> message } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> binary(). main() -> Message@1 = case <<"Hello, world!"/utf8>> of <> when Message =:= <<"Hello, world!"/utf8>> -> Message; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 3, value => _assert_fail, start => 19, 'end' => 87, pattern_start => 30, pattern_end => 65}) end, Message@1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__bit_array_pattern.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "pub fn go() {\n let assert <> = <<123>>\n a + b + c\n}" --- ----- SOURCE CODE pub fn go() { let assert <> = <<123>> a + b + c } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 1). -spec go() -> integer(). go() -> {A@1, B@1, C@1} = case <<123>> of <> -> {A, B, C}; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"go"/utf8>>, line => 2, value => _assert_fail, start => 16, 'end' => 54, pattern_start => 27, pattern_end => 44}) end, (A@1 + B@1) + C@1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__constructor_pattern.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "pub fn go() {\n let assert Ok(x) = Error(Nil)\n x\n}" --- ----- SOURCE CODE pub fn go() { let assert Ok(x) = Error(Nil) x } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 1). -spec go() -> any(). go() -> X@1 = case {error, nil} of {ok, X} -> X; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"go"/utf8>>, line => 2, value => _assert_fail, start => 16, 'end' => 45, pattern_start => 27, pattern_end => 32}) end, X@1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__constructor_pattern_with_multiple_variables.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "\npub type Wibble {\n Wibble(Int, Float)\n}\n\npub fn go() {\n let assert Wibble(x, 2.0 as y) = Wibble(1, 2.0)\n x\n}" --- ----- SOURCE CODE pub type Wibble { Wibble(Int, Float) } pub fn go() { let assert Wibble(x, 2.0 as y) = Wibble(1, 2.0) x } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -export_type([wibble/0]). -type wibble() :: {wibble, integer(), float()}. -file("project/test/my/mod.gleam", 6). -spec go() -> integer(). go() -> {X@1, Y@1} = case {wibble, 1, 2.0} of {wibble, X, 2.0 = Y} -> {X, Y}; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"go"/utf8>>, line => 7, value => _assert_fail, start => 59, 'end' => 106, pattern_start => 70, pattern_end => 89}) end, X@1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__discard_pattern.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "pub fn go() {\n let assert _ = 123\n}" --- ----- SOURCE CODE pub fn go() { let assert _ = 123 } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 1). -spec go() -> integer(). go() -> _ = 123. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__float_pattern.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "pub fn go() {\n let assert 1.5 = 5.1\n}" --- ----- SOURCE CODE pub fn go() { let assert 1.5 = 5.1 } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 1). -spec go() -> float(). go() -> _assert_subject = 5.1, case _assert_subject of 1.5 -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"go"/utf8>>, line => 2, value => _assert_fail, start => 16, 'end' => 36, pattern_start => 27, pattern_end => 30}) end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__int_pattern.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "pub fn go() {\n let assert 1 = 2\n}" --- ----- SOURCE CODE pub fn go() { let assert 1 = 2 } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 1). -spec go() -> integer(). go() -> _assert_subject = 2, case _assert_subject of 1 -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"go"/utf8>>, line => 2, value => _assert_fail, start => 16, 'end' => 32, pattern_start => 27, pattern_end => 28}) end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__just_variable.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "pub fn go() {\n let assert x = Ok(1)\n x\n}" --- ----- SOURCE CODE pub fn go() { let assert x = Ok(1) x } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 1). -spec go() -> {ok, integer()} | {error, any()}. go() -> X = {ok, 1}, X. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__let_assert_at_end_of_block.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "\npub fn go() {\n let result = Ok(10)\n let x = {\n let assert Ok(_) = result\n }\n x\n}" --- ----- SOURCE CODE pub fn go() { let result = Ok(10) let x = { let assert Ok(_) = result } x } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 2). -spec go() -> {ok, integer()} | {error, any()}. go() -> Result = {ok, 10}, X = begin case Result of {ok, _} -> Result; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"go"/utf8>>, line => 5, value => _assert_fail, start => 53, 'end' => 78, pattern_start => 64, pattern_end => 69}) end end, X. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__let_assert_should_not_use_redefined_variable.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "\nfn split_once(x: String, y: String) -> Result(#(String, String), String) {\n Ok(#(x, y))\n}\n\npub fn main() {\n let string = \"Hello, world!\"\n let assert Ok(#(prefix, string)) = split_once(string, \"\\n\")\n as { \"Failed to split: \" <> string }\n}\n " snapshot_kind: text --- ----- SOURCE CODE fn split_once(x: String, y: String) -> Result(#(String, String), String) { Ok(#(x, y)) } pub fn main() { let string = "Hello, world!" let assert Ok(#(prefix, string)) = split_once(string, "\n") as { "Failed to split: " <> string } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec split_once(binary(), binary()) -> {ok, {binary(), binary()}} | {error, binary()}. split_once(X, Y) -> {ok, {X, Y}}. -file("project/test/my/mod.gleam", 6). -spec main() -> {ok, {binary(), binary()}} | {error, binary()}. main() -> String = <<"Hello, world!"/utf8>>, _assert_subject = split_once(String, <<"\n"/utf8>>), case _assert_subject of {ok, {Prefix, String@1}} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => (<<"Failed to split: "/utf8, String/binary>>), file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 8, value => _assert_fail, start => 148, 'end' => 207, pattern_start => 159, pattern_end => 180}) end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__list_pattern.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "pub fn go() {\n let assert [1, x, 3] = [1, 2, 3]\n x\n}" --- ----- SOURCE CODE pub fn go() { let assert [1, x, 3] = [1, 2, 3] x } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 1). -spec go() -> integer(). go() -> X@1 = case [1, 2, 3] of [1, X, 3] -> X; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"go"/utf8>>, line => 2, value => _assert_fail, start => 16, 'end' => 48, pattern_start => 27, pattern_end => 36}) end, X@1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__list_pattern_with_multiple_variables.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "pub fn go() {\n let assert [a, b, c] = [1, 2, 3]\n a + b + c\n}" --- ----- SOURCE CODE pub fn go() { let assert [a, b, c] = [1, 2, 3] a + b + c } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 1). -spec go() -> integer(). go() -> {A@1, B@1, C@1} = case [1, 2, 3] of [A, B, C] -> {A, B, C}; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"go"/utf8>>, line => 2, value => _assert_fail, start => 16, 'end' => 48, pattern_start => 27, pattern_end => 36}) end, (A@1 + B@1) + C@1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__message.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "\npub fn unwrap_or_panic(value) {\n let assert Ok(inner) = value as \"Oops, there was an error\"\n inner\n}\n" --- ----- SOURCE CODE pub fn unwrap_or_panic(value) { let assert Ok(inner) = value as "Oops, there was an error" inner } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([unwrap_or_panic/1]). -file("project/test/my/mod.gleam", 2). -spec unwrap_or_panic({ok, K} | {error, any()}) -> K. unwrap_or_panic(Value) -> Inner@1 = case Value of {ok, Inner} -> Inner; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Oops, there was an error"/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"unwrap_or_panic"/utf8>>, line => 3, value => _assert_fail, start => 35, 'end' => 63, pattern_start => 46, pattern_end => 55}) end, Inner@1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__more_than_one_var.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "pub fn go(x) {\n let assert [1, a, b, c] = x\n [a, b, c]\n}" --- ----- SOURCE CODE pub fn go(x) { let assert [1, a, b, c] = x [a, b, c] } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/1]). -file("project/test/my/mod.gleam", 1). -spec go(list(integer())) -> list(integer()). go(X) -> {A@1, B@1, C@1} = case X of [1, A, B, C] -> {A, B, C}; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"go"/utf8>>, line => 2, value => _assert_fail, start => 17, 'end' => 44, pattern_start => 28, pattern_end => 40}) end, [A@1, B@1, C@1]. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__one_var.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "pub fn go() {\n let assert Ok(y) = Ok(1)\n y\n}" --- ----- SOURCE CODE pub fn go() { let assert Ok(y) = Ok(1) y } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 1). -spec go() -> integer(). go() -> Y@1 = case {ok, 1} of {ok, Y} -> Y; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"go"/utf8>>, line => 2, value => _assert_fail, start => 16, 'end' => 40, pattern_start => 27, pattern_end => 32}) end, Y@1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__pattern_let.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "pub fn go(x) {\n let assert [1 as a, b, c] = x\n [a, b, c]\n}" --- ----- SOURCE CODE pub fn go(x) { let assert [1 as a, b, c] = x [a, b, c] } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/1]). -file("project/test/my/mod.gleam", 1). -spec go(list(integer())) -> list(integer()). go(X) -> {A@1, B@1, C@1} = case X of [1 = A, B, C] -> {A, B, C}; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"go"/utf8>>, line => 2, value => _assert_fail, start => 17, 'end' => 46, pattern_start => 28, pattern_end => 42}) end, [A@1, B@1, C@1]. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__reference_earlier_segment.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "\npub fn main() {\n let assert <> = <<3, 1, 2, 3>>\n bytes\n}\n" --- ----- SOURCE CODE pub fn main() { let assert <> = <<3, 1, 2, 3>> bytes } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> {Length@1, Bytes@1} = case <<3, 1, 2, 3>> of <> -> {Length, Bytes}; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 3, value => _assert_fail, start => 19, 'end' => 85, pattern_start => 30, pattern_end => 68}) end, Bytes@1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__string_pattern.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "pub fn go() {\n let assert \"Hello!\" = \"Hel\" <> \"lo!\"\n}" --- ----- SOURCE CODE pub fn go() { let assert "Hello!" = "Hel" <> "lo!" } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 1). -spec go() -> binary(). go() -> _assert_subject = <<"Hel"/utf8, "lo!"/utf8>>, case _assert_subject of <<"Hello!"/utf8>> -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"go"/utf8>>, line => 2, value => _assert_fail, start => 16, 'end' => 52, pattern_start => 27, pattern_end => 35}) end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__string_prefix_pattern.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "pub fn go() {\n let assert \"Hello \" <> name = \"Hello John\"\n name\n}" --- ----- SOURCE CODE pub fn go() { let assert "Hello " <> name = "Hello John" name } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 1). -spec go() -> binary(). go() -> Name@1 = case <<"Hello John"/utf8>> of <<"Hello "/utf8, Name/binary>> -> Name; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"go"/utf8>>, line => 2, value => _assert_fail, start => 16, 'end' => 58, pattern_start => 27, pattern_end => 43}) end, Name@1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__string_prefix_pattern_with_prefix_binding.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "pub fn go() {\n let assert \"Hello \" as greeting <> name = \"Hello John\"\n #(greeting, name)\n}" --- ----- SOURCE CODE pub fn go() { let assert "Hello " as greeting <> name = "Hello John" #(greeting, name) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 1). -spec go() -> {binary(), binary()}. go() -> {Name@1, Greeting@1} = case <<"Hello John"/utf8>> of <<"Hello "/utf8, Name/binary>> -> {Name, Greeting}; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"go"/utf8>>, line => 2, value => _assert_fail, start => 16, 'end' => 70, pattern_start => 27, pattern_end => 55}) end, Greeting = <<"Hello "/utf8>>, {Greeting@1, Name@1}. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__tuple_pattern.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "pub fn go() {\n let assert #(a, b, c) = #(1, 2, 3)\n a + b + c\n}" --- ----- SOURCE CODE pub fn go() { let assert #(a, b, c) = #(1, 2, 3) a + b + c } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 1). -spec go() -> integer(). go() -> {A, B, C} = {1, 2, 3}, (A + B) + C. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__variable_message.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "\npub fn expect(value, message) {\n let assert Ok(inner) = value as message\n inner\n}\n" --- ----- SOURCE CODE pub fn expect(value, message) { let assert Ok(inner) = value as message inner } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([expect/2]). -file("project/test/my/mod.gleam", 2). -spec expect({ok, L} | {error, any()}, binary()) -> L. expect(Value, Message) -> Inner@1 = case Value of {ok, Inner} -> Inner; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => Message, file => <>, module => <<"my/mod"/utf8>>, function => <<"expect"/utf8>>, line => 3, value => _assert_fail, start => 35, 'end' => 63, pattern_start => 46, pattern_end => 55}) end, Inner@1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__let_assert__variable_rewrites.snap ================================================ --- source: compiler-core/src/erlang/tests/let_assert.rs expression: "pub fn go() {\n let assert Ok(y) = Ok(1)\n let assert Ok(y) = Ok(1)\n y\n}" --- ----- SOURCE CODE pub fn go() { let assert Ok(y) = Ok(1) let assert Ok(y) = Ok(1) y } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 1). -spec go() -> integer(). go() -> Y@1 = case {ok, 1} of {ok, Y} -> Y; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"go"/utf8>>, line => 2, value => _assert_fail, start => 16, 'end' => 40, pattern_start => 27, pattern_end => 32}) end, Y@3 = case {ok, 1} of {ok, Y@2} -> Y@2; _assert_fail@1 -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"go"/utf8>>, line => 3, value => _assert_fail@1, start => 43, 'end' => 67, pattern_start => 54, pattern_end => 59}) end, Y@3. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__numbers__int_negation.snap ================================================ --- source: compiler-core/src/erlang/tests/numbers.rs expression: "\npub fn main() {\n let a = 3\n let b = -a\n}\n" --- ----- SOURCE CODE pub fn main() { let a = 3 let b = -a } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> A = 3, B = - A. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__numbers__numbers_with_scientific_notation.snap ================================================ --- source: compiler-core/src/erlang/tests/numbers.rs expression: "\nconst i = 100.001e223\nconst j = -100.001e-223\n\npub fn main() {\n i\n j\n}\n" --- ----- SOURCE CODE const i = 100.001e223 const j = -100.001e-223 pub fn main() { i j } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 5). -spec main() -> float(). main() -> 100.001e223, -100.001e-223. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__numbers__numbers_with_underscores.snap ================================================ --- source: compiler-core/src/erlang/tests/numbers.rs expression: "\npub fn main() {\n 100_000\n 100_000.00101\n}\n" --- ----- SOURCE CODE pub fn main() { 100_000 100_000.00101 } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> float(). main() -> 100000, 100000.00101. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__numbers__numbers_with_underscores1.snap ================================================ --- source: compiler-core/src/erlang/tests/numbers.rs expression: "\nconst i = 100_000\nconst f = 100_000.00101\npub fn main() {\n i\n f\n}\n" --- ----- SOURCE CODE const i = 100_000 const f = 100_000.00101 pub fn main() { i f } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 4). -spec main() -> float(). main() -> 100000, 100000.00101. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__numbers__numbers_with_underscores2.snap ================================================ --- source: compiler-core/src/erlang/tests/numbers.rs expression: "\npub fn main() {\n let assert 100_000 = 1\n let assert 100_000.00101 = 1.\n 1\n}\n" --- ----- SOURCE CODE pub fn main() { let assert 100_000 = 1 let assert 100_000.00101 = 1. 1 } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> case 1 of 100000 -> nil; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 3, value => _assert_fail, start => 19, 'end' => 41, pattern_start => 30, pattern_end => 37}) end, case 1.0 of 100000.00101 -> nil; _assert_fail@1 -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 4, value => _assert_fail@1, start => 44, 'end' => 73, pattern_start => 55, pattern_end => 68}) end, 1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__numbers__repeated_int_negation.snap ================================================ --- source: compiler-core/src/erlang/tests/numbers.rs expression: "\npub fn main() {\n let a = 3\n let b = --a\n}\n" --- ----- SOURCE CODE pub fn main() { let a = 3 let b = --a } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> A = 3, B = - - A. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__numbers__zero_b_in_hex.snap ================================================ --- source: compiler-core/src/erlang/tests/numbers.rs expression: "\npub fn main() {\n 0xffe0bb\n}\n" --- ----- SOURCE CODE pub fn main() { 0xffe0bb } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> 16#ffe0bb. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__panic__panic_as.snap ================================================ --- source: compiler-core/src/erlang/tests/panic.rs expression: "\npub fn main() {\n panic as \"wibble\"\n}\n" --- ----- SOURCE CODE pub fn main() { panic as "wibble" } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> any(). main() -> erlang:error(#{gleam_error => panic, message => <<"wibble"/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 3}). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__panic__panic_as_function.snap ================================================ --- source: compiler-core/src/erlang/tests/panic.rs expression: "\npub fn retstring() {\n \"wibble\"\n}\npub fn main() {\n panic as { retstring() <> \"wobble\" }\n}\n" --- ----- SOURCE CODE pub fn retstring() { "wibble" } pub fn main() { panic as { retstring() <> "wobble" } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([retstring/0, main/0]). -file("project/test/my/mod.gleam", 2). -spec retstring() -> binary(). retstring() -> <<"wibble"/utf8>>. -file("project/test/my/mod.gleam", 5). -spec main() -> any(). main() -> erlang:error(#{gleam_error => panic, message => (<<(retstring())/binary, "wobble"/utf8>>), file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 6}). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__panic__piped.snap ================================================ --- source: compiler-core/src/erlang/tests/panic.rs expression: "\npub fn main() {\n \"lets\"\n |> panic\n}\n " --- ----- SOURCE CODE pub fn main() { "lets" |> panic } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> any(). main() -> _pipe = <<"lets"/utf8>>, (erlang:error(#{gleam_error => panic, message => <<"`panic` expression evaluated."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 4}))(_pipe). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__panic__piped_chain.snap ================================================ --- source: compiler-core/src/erlang/tests/panic.rs expression: "\n pub fn main() {\n \"lets\"\n |> panic as \"pipe\"\n |> panic as \"other panic\"\n }\n " --- ----- SOURCE CODE pub fn main() { "lets" |> panic as "pipe" |> panic as "other panic" } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> any(). main() -> _pipe = <<"lets"/utf8>>, _pipe@1 = (erlang:error(#{gleam_error => panic, message => <<"pipe"/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 4}))(_pipe), (erlang:error(#{gleam_error => panic, message => <<"other panic"/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 5}))(_pipe@1). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__panic__plain.snap ================================================ --- source: compiler-core/src/erlang/tests/panic.rs expression: "\npub fn main() {\n panic\n}\n" --- ----- SOURCE CODE pub fn main() { panic } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> any(). main() -> erlang:error(#{gleam_error => panic, message => <<"`panic` expression evaluated."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 3}). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__patterns__alternative_patterns.snap ================================================ --- source: compiler-core/src/erlang/tests/patterns.rs expression: "\npub fn main() {\n let duplicate_name = 1\n\n case 1 {\n 1 | 2 -> {\n let duplicate_name = duplicate_name + 1\n duplicate_name\n }\n _ -> 0\n }\n}" --- ----- SOURCE CODE pub fn main() { let duplicate_name = 1 case 1 { 1 | 2 -> { let duplicate_name = duplicate_name + 1 duplicate_name } _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> Duplicate_name = 1, case 1 of 1 -> Duplicate_name@1 = Duplicate_name + 1, Duplicate_name@1; 2 -> Duplicate_name@1 = Duplicate_name + 1, Duplicate_name@1; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__patterns__alternative_patterns1.snap ================================================ --- source: compiler-core/src/erlang/tests/patterns.rs expression: "\npub fn main() {\n case Ok(1) {\n Ok(duplicate_name) | Error(duplicate_name) -> duplicate_name\n }\n}" --- ----- SOURCE CODE pub fn main() { case Ok(1) { Ok(duplicate_name) | Error(duplicate_name) -> duplicate_name } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> case {ok, 1} of {ok, Duplicate_name} -> Duplicate_name; {error, Duplicate_name} -> Duplicate_name end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__patterns__alternative_patterns2.snap ================================================ --- source: compiler-core/src/erlang/tests/patterns.rs expression: "\npub fn main() {\n let duplicate_name = 1\n\n case 1 {\n 1 | 2 if duplicate_name == 1 -> duplicate_name\n _ -> 0\n }\n}" --- ----- SOURCE CODE pub fn main() { let duplicate_name = 1 case 1 { 1 | 2 if duplicate_name == 1 -> duplicate_name _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> Duplicate_name = 1, case 1 of 1 when Duplicate_name =:= 1 -> Duplicate_name; 2 when Duplicate_name =:= 1 -> Duplicate_name; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__patterns__alternative_patterns3.snap ================================================ --- source: compiler-core/src/erlang/tests/patterns.rs expression: "\npub const constant = Ok(1)\n\npub fn main(arg) {\n let _ = constant\n case arg {\n _ if arg == constant -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub const constant = Ok(1) pub fn main(arg) { let _ = constant case arg { _ if arg == constant -> 1 _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -file("project/test/my/mod.gleam", 4). -spec main({ok, integer()} | {error, any()}) -> integer(). main(Arg) -> _ = {ok, 1}, case Arg of _ when Arg =:= {ok, 1} -> 1; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__patterns__pattern_as.snap ================================================ --- source: compiler-core/src/erlang/tests/patterns.rs expression: "pub fn a(x) {\n case x {\n Ok(1 as y) -> 1\n _ -> 0\n }\n}" --- ----- SOURCE CODE pub fn a(x) { case x { Ok(1 as y) -> 1 _ -> 0 } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([a/1]). -file("project/test/my/mod.gleam", 1). -spec a({ok, integer()} | {error, any()}) -> integer(). a(X) -> case X of {ok, 1 = Y} -> 1; _ -> 0 end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__patterns__string_prefix_as_pattern_with_assertion.snap ================================================ --- source: compiler-core/src/erlang/tests/patterns.rs expression: "pub fn a(x) {\n let assert \"a\" as a <> rest = \"wibble\"\n a\n}" --- ----- SOURCE CODE pub fn a(x) { let assert "a" as a <> rest = "wibble" a } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([a/1]). -file("project/test/my/mod.gleam", 1). -spec a(any()) -> binary(). a(X) -> {Rest@1, A@1} = case <<"wibble"/utf8>> of <<"a"/utf8, Rest/binary>> -> {Rest, A}; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"a"/utf8>>, line => 2, value => _assert_fail, start => 16, 'end' => 54, pattern_start => 27, pattern_end => 43}) end, A = <<"a"/utf8>>, A@1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__patterns__string_prefix_as_pattern_with_list.snap ================================================ --- source: compiler-core/src/erlang/tests/patterns.rs expression: "pub fn a(x) {\n case x {\n [\"a\" as a <> _, \"b\" as b <> _] -> a <> b\n _ -> \"\"\n }\n}" --- ----- SOURCE CODE pub fn a(x) { case x { ["a" as a <> _, "b" as b <> _] -> a <> b _ -> "" } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([a/1]). -file("project/test/my/mod.gleam", 1). -spec a(list(binary())) -> binary(). a(X) -> case X of [<<"a"/utf8, _/binary>>, <<"b"/utf8, _/binary>>] -> A = <<"a"/utf8>>, B = <<"b"/utf8>>, <>; _ -> <<""/utf8>> end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__patterns__string_prefix_as_pattern_with_multiple_subjects.snap ================================================ --- source: compiler-core/src/erlang/tests/patterns.rs expression: "pub fn a(x) {\n case x, x {\n _, \"a\" as a <> _ -> a\n _, _ -> \"a\"\n }\n}" --- ----- SOURCE CODE pub fn a(x) { case x, x { _, "a" as a <> _ -> a _, _ -> "a" } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([a/1]). -file("project/test/my/mod.gleam", 1). -spec a(binary()) -> binary(). a(X) -> case {X, X} of {_, <<"a"/utf8, _/binary>>} -> A = <<"a"/utf8>>, A; {_, _} -> <<"a"/utf8>> end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__patterns__string_prefix_as_pattern_with_multiple_subjects_and_guard.snap ================================================ --- source: compiler-core/src/erlang/tests/patterns.rs expression: "pub fn a(x) {\n case x, x {\n _, \"a\" as a <> rest if rest == \"a\" -> a\n _, _ -> \"a\"\n }\n}" --- ----- SOURCE CODE pub fn a(x) { case x, x { _, "a" as a <> rest if rest == "a" -> a _, _ -> "a" } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([a/1]). -file("project/test/my/mod.gleam", 1). -spec a(binary()) -> binary(). a(X) -> case {X, X} of {_, <<"a"/utf8, Rest/binary>>} when Rest =:= <<"a"/utf8>> -> A = <<"a"/utf8>>, A; {_, _} -> <<"a"/utf8>> end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__pipes__block_expr_into_pipe.snap ================================================ --- source: compiler-core/src/erlang/tests/pipes.rs expression: "fn id(a) { a }\npub fn main() {\n {\n let x = 1\n x\n }\n |> id\n}" --- ----- SOURCE CODE fn id(a) { a } pub fn main() { { let x = 1 x } |> id } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 1). -spec id(I) -> I. id(A) -> A. -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> _pipe = begin X = 1, X end, id(_pipe). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__pipes__call_pipeline_result.snap ================================================ --- source: compiler-core/src/erlang/tests/pipes.rs expression: "\npub fn main() {\n { 1 |> add }(1)\n}\n\npub fn add(x) {\n fn(y) { x + y }\n}\n" --- ----- SOURCE CODE pub fn main() { { 1 |> add }(1) } pub fn add(x) { fn(y) { x + y } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([add/1, main/0]). -file("project/test/my/mod.gleam", 6). -spec add(integer()) -> fun((integer()) -> integer()). add(X) -> fun(Y) -> X + Y end. -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> begin _pipe = 1, add(_pipe) end(1). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__pipes__clever_pipe_rewriting.snap ================================================ --- source: compiler-core/src/erlang/tests/pipes.rs expression: "\npub fn apply(f: fn(a) -> b, a: a) { a |> f }\n" --- ----- SOURCE CODE pub fn apply(f: fn(a) -> b, a: a) { a |> f } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([apply/2]). -file("project/test/my/mod.gleam", 2). -spec apply(fun((I) -> J), I) -> J. apply(F, A) -> _pipe = A, F(_pipe). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__pipes__clever_pipe_rewriting1.snap ================================================ --- source: compiler-core/src/erlang/tests/pipes.rs expression: "\npub fn apply(f: fn(a, Int) -> b, a: a) { a |> f(1) }\n" --- ----- SOURCE CODE pub fn apply(f: fn(a, Int) -> b, a: a) { a |> f(1) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([apply/2]). -file("project/test/my/mod.gleam", 2). -spec apply(fun((I, integer()) -> J), I) -> J. apply(F, A) -> _pipe = A, F(_pipe, 1). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__pipes__multiple_pipes.snap ================================================ --- source: compiler-core/src/erlang/tests/pipes.rs expression: "\npub fn main() {\n 1 |> x |> x\n 2 |> x |> x\n}\n\nfn x(x) { x }\n" --- ----- SOURCE CODE pub fn main() { 1 |> x |> x 2 |> x |> x } fn x(x) { x } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 7). -spec x(J) -> J. x(X) -> X. -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> _pipe = 1, _pipe@1 = x(_pipe), x(_pipe@1), _pipe@2 = 2, _pipe@3 = x(_pipe@2), x(_pipe@3). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__pipes__pipe_in_call.snap ================================================ --- source: compiler-core/src/erlang/tests/pipes.rs expression: "\npub fn main() {\n 123\n |> two(\n 1 |> two(2),\n _,\n )\n}\n\npub fn two(a, b) {\n a\n}\n" --- ----- SOURCE CODE pub fn main() { 123 |> two( 1 |> two(2), _, ) } pub fn two(a, b) { a } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([two/2, main/0]). -file("project/test/my/mod.gleam", 10). -spec two(J, any()) -> J. two(A, B) -> A. -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> _pipe = 123, two( begin _pipe@1 = 1, two(_pipe@1, 2) end, _pipe ). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__pipes__pipe_in_case_subject.snap ================================================ --- source: compiler-core/src/erlang/tests/pipes.rs expression: "pub fn x(f) {\n case 1 |> f {\n x -> x\n }\n}" --- ----- SOURCE CODE pub fn x(f) { case 1 |> f { x -> x } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([x/1]). -file("project/test/my/mod.gleam", 1). -spec x(fun((integer()) -> L)) -> L. x(F) -> case begin _pipe = 1, F(_pipe) end of X -> X end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__pipes__pipe_in_eq.snap ================================================ --- source: compiler-core/src/erlang/tests/pipes.rs expression: "fn id(x) {\n x\n}\n\npub fn main() {\n 1 == 1 |> id\n}" --- ----- SOURCE CODE fn id(x) { x } pub fn main() { 1 == 1 |> id } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 1). -spec id(I) -> I. id(X) -> X. -file("project/test/my/mod.gleam", 5). -spec main() -> boolean(). main() -> 1 =:= begin _pipe = 1, id(_pipe) end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__pipes__pipe_in_list.snap ================================================ --- source: compiler-core/src/erlang/tests/pipes.rs expression: "pub fn x(f) {\n [\n 1 |> f\n ]\n}" --- ----- SOURCE CODE pub fn x(f) { [ 1 |> f ] } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([x/1]). -file("project/test/my/mod.gleam", 1). -spec x(fun((integer()) -> L)) -> list(L). x(F) -> [begin _pipe = 1, F(_pipe) end]. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__pipes__pipe_in_record_update.snap ================================================ --- source: compiler-core/src/erlang/tests/pipes.rs expression: "pub type X {\n X(a: Int, b: Int)\n}\n\nfn id(x) {\n x\n}\n\npub fn main(x) {\n X(..x, a: 1 |> id)\n}" --- ----- SOURCE CODE pub type X { X(a: Int, b: Int) } fn id(x) { x } pub fn main(x) { X(..x, a: 1 |> id) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -export_type([x/0]). -type x() :: {x, integer(), integer()}. -file("project/test/my/mod.gleam", 5). -spec id(I) -> I. id(X) -> X. -file("project/test/my/mod.gleam", 9). -spec main(x()) -> x(). main(X) -> {x, begin _pipe = 1, id(_pipe) end, erlang:element(3, X)}. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__pipes__pipe_in_tuple.snap ================================================ --- source: compiler-core/src/erlang/tests/pipes.rs expression: "pub fn x(f) {\n #(\n 1 |> f\n )\n}" --- ----- SOURCE CODE pub fn x(f) { #( 1 |> f ) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([x/1]). -file("project/test/my/mod.gleam", 1). -spec x(fun((integer()) -> K)) -> {K}. x(F) -> {begin _pipe = 1, F(_pipe) end}. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__basic.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "record_definition(\"PetCat\",\n&[(\"name\", type_::tuple(vec![])), (\"is_cute\", type_::tuple(vec![]))])" --- -record(pet_cat, {name :: {}, is_cute :: {}}). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__const_record_update_generic_respecialization.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs assertion_line: 421 expression: "\npub type Box(a) {\n Box(name: String, value: a)\n}\n\npub const base = Box(\"score\", 50)\npub const updated = Box(..base, value: \"Hello\")\n\npub fn main() {\n #(base, updated)\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub type Box(a) { Box(name: String, value: a) } pub const base = Box("score", 50) pub const updated = Box(..base, value: "Hello") pub fn main() { #(base, updated) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -export_type([box/1]). -type box(I) :: {box, binary(), I}. -file("project/test/my/mod.gleam", 9). -spec main() -> {box(integer()), box(binary())}. main() -> {{box, <<"score"/utf8>>, 50}, {box, <<"score"/utf8>>, <<"Hello"/utf8>>}}. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__constant_record_update_with_unlabelled_fields.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "\npub type Wibble {\n Wibble(Int, Float, b: Bool, s: String)\n}\n\npub const record = Wibble(1, 3.14, True, \"Hello\")\npub const updated = Wibble(..record, b: False)\n\npub fn main() {\n updated\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(Int, Float, b: Bool, s: String) } pub const record = Wibble(1, 3.14, True, "Hello") pub const updated = Wibble(..record, b: False) pub fn main() { updated } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -export_type([wibble/0]). -type wibble() :: {wibble, integer(), float(), boolean(), binary()}. -file("project/test/my/mod.gleam", 9). -spec main() -> wibble(). main() -> {wibble, 1, 3.14, false, <<"Hello"/utf8>>}. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__imported_qualified_constructor_as_fn_name_escape.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "import other_module\n\npub fn main() {\n other_module.Let\n}" --- ----- SOURCE CODE import other_module pub fn main() { other_module.Let } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 3). -spec main() -> fun((integer()) -> other_module:'let'()). main() -> fun(Field@0) -> {'let', Field@0} end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__long_definition_formatting.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "record_definition(\"PetCat\",\n&[(\"name\", type_::generic_var(1)), (\"is_cute\", type_::unbound_var(1)),\n(\"linked\", type_::link(type_::int())),\n(\"whatever\",\ntype_::list(type_::tuple(vec![type_::nil(),\ntype_::list(type_::tuple(vec![type_::nil(), type_::nil(), type_::nil()])),\ntype_::nil(),\ntype_::list(type_::tuple(vec![type_::nil(), type_::nil(), type_::nil()])),\ntype_::nil(),\ntype_::list(type_::tuple(vec![type_::nil(), type_::nil(),\ntype_::nil()])),]))),])" --- -record(pet_cat, { name :: any(), is_cute :: any(), linked :: integer(), whatever :: list({nil, list({nil, nil, nil}), nil, list({nil, nil, nil}), nil, list({nil, nil, nil})}) }). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__module_types.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "record_definition(\"PetCat\",\n&[(\"name\",\nArc::new(Type::Named\n{\n publicity: Publicity::Public, package: \"package\".into(), module:\n module_name, name: \"my_type\".into(), args: vec![], inferred_variant: None,\n}))])" --- -record(pet_cat, {name :: name:my_type()}). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__nested_record_update.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "pub type Wibble {\n Wibble(a: Int, b: Wobble, c: Int)\n}\n\npub type Wobble {\n Wobble(a: Int, b: Int)\n}\n\npub fn main() {\n let base = Wibble(1, Wobble(2, 3), 4)\n Wibble(..base, b: Wobble(..base.b, b: 5))\n}" --- ----- SOURCE CODE pub type Wibble { Wibble(a: Int, b: Wobble, c: Int) } pub type Wobble { Wobble(a: Int, b: Int) } pub fn main() { let base = Wibble(1, Wobble(2, 3), 4) Wibble(..base, b: Wobble(..base.b, b: 5)) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -export_type([wibble/0, wobble/0]). -type wibble() :: {wibble, integer(), wobble(), integer()}. -type wobble() :: {wobble, integer(), integer()}. -file("project/test/my/mod.gleam", 9). -spec main() -> wibble(). main() -> Base = {wibble, 1, {wobble, 2, 3}, 4}, {wibble, erlang:element(2, Base), begin _record = erlang:element(3, Base), {wobble, erlang:element(2, _record), 5} end, erlang:element(4, Base)}. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__nested_record_update_with_blocks.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "pub type A { A(b: B) }\npub type B { B(c: C) }\npub type C { C(val: Int) }\n\npub fn main(a: A) {\n A(..a, b: {\n B(..a.b, c: {\n C(..a.b.c, val: 0)\n })\n })\n}" --- ----- SOURCE CODE pub type A { A(b: B) } pub type B { B(c: C) } pub type C { C(val: Int) } pub fn main(a: A) { A(..a, b: { B(..a.b, c: { C(..a.b.c, val: 0) }) }) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -export_type([a/0, b/0, c/0]). -type a() :: {a, b()}. -type b() :: {b, c()}. -type c() :: {c, integer()}. -file("project/test/my/mod.gleam", 5). -spec main(a()) -> a(). main(A) -> {a, begin _record = erlang:element(2, A), {b, begin _record@1 = erlang:element(2, erlang:element(2, A)), {c, 0} end} end}. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__pipe_update_subject.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "pub type Thing {\n Thing(a: Int, b: Int)\n}\n\npub fn identity(x) { x }\n\npub fn main() {\n let thing = Thing(1, 2)\n Thing(..thing |> identity, b: 1000)\n}" --- ----- SOURCE CODE pub type Thing { Thing(a: Int, b: Int) } pub fn identity(x) { x } pub fn main() { let thing = Thing(1, 2) Thing(..thing |> identity, b: 1000) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([identity/1, main/0]). -export_type([thing/0]). -type thing() :: {thing, integer(), integer()}. -file("project/test/my/mod.gleam", 5). -spec identity(I) -> I. identity(X) -> X. -file("project/test/my/mod.gleam", 7). -spec main() -> thing(). main() -> Thing = {thing, 1, 2}, _record = begin _pipe = Thing, identity(_pipe) end, {thing, erlang:element(2, _record), 1000}. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__private_unused_records.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "type A { A(inner: Int) }\ntype B { B(String) }\ntype C { C(Int) }\n\npub fn main(x: Int) -> Int {\n let a = A(x)\n a.inner\n}\n" --- ----- SOURCE CODE type A { A(inner: Int) } type B { B(String) } type C { C(Int) } pub fn main(x: Int) -> Int { let a = A(x) a.inner } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -export_type([a/0, b/0, c/0]). -type a() :: {a, integer()}. -type b() :: {b, binary()}. -type c() :: {c, integer()}. -file("project/test/my/mod.gleam", 5). -spec main(integer()) -> integer(). main(X) -> A = {a, X}, erlang:element(2, A). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__record_access_block.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "pub type Thing {\n Thing(a: Int, b: Int)\n}\n\npub fn main() {\n {\n let thing = Thing(1, 2)\n thing\n }.a\n}" --- ----- SOURCE CODE pub type Thing { Thing(a: Int, b: Int) } pub fn main() { { let thing = Thing(1, 2) thing }.a } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -export_type([thing/0]). -type thing() :: {thing, integer(), integer()}. -file("project/test/my/mod.gleam", 5). -spec main() -> integer(). main() -> erlang:element( 2, begin Thing = {thing, 1, 2}, Thing end ). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__record_accessor_multiple_variants.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "\npub type Person {\n Teacher(name: String, title: String)\n Student(name: String, age: Int)\n}\npub fn get_name(person: Person) { person.name }" --- ----- SOURCE CODE pub type Person { Teacher(name: String, title: String) Student(name: String, age: Int) } pub fn get_name(person: Person) { person.name } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([get_name/1]). -export_type([person/0]). -type person() :: {teacher, binary(), binary()} | {student, binary(), integer()}. -file("project/test/my/mod.gleam", 6). -spec get_name(person()) -> binary(). get_name(Person) -> erlang:element(2, Person). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__record_accessor_multiple_variants_parameterised_types.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "\npub type Person {\n Teacher(name: String, age: List(Int), title: String)\n Student(name: String, age: List(Int))\n}\npub fn get_name(person: Person) { person.name }\npub fn get_age(person: Person) { person.age }" --- ----- SOURCE CODE pub type Person { Teacher(name: String, age: List(Int), title: String) Student(name: String, age: List(Int)) } pub fn get_name(person: Person) { person.name } pub fn get_age(person: Person) { person.age } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([get_name/1, get_age/1]). -export_type([person/0]). -type person() :: {teacher, binary(), list(integer()), binary()} | {student, binary(), list(integer())}. -file("project/test/my/mod.gleam", 6). -spec get_name(person()) -> binary(). get_name(Person) -> erlang:element(2, Person). -file("project/test/my/mod.gleam", 7). -spec get_age(person()) -> list(integer()). get_age(Person) -> erlang:element(3, Person). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__record_accessor_multiple_variants_positions_other_than_first.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "\npub type Person {\n Teacher(name: String, age: Int, title: String)\n Student(name: String, age: Int)\n}\npub fn get_name(person: Person) { person.name }\npub fn get_age(person: Person) { person.age }" --- ----- SOURCE CODE pub type Person { Teacher(name: String, age: Int, title: String) Student(name: String, age: Int) } pub fn get_name(person: Person) { person.name } pub fn get_age(person: Person) { person.age } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([get_name/1, get_age/1]). -export_type([person/0]). -type person() :: {teacher, binary(), integer(), binary()} | {student, binary(), integer()}. -file("project/test/my/mod.gleam", 6). -spec get_name(person()) -> binary(). get_name(Person) -> erlang:element(2, Person). -file("project/test/my/mod.gleam", 7). -spec get_age(person()) -> integer(). get_age(Person) -> erlang:element(3, Person). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__record_accessor_multiple_with_first_position_different_types.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "\npub type Person {\n Teacher(name: Nil, age: Int)\n Student(name: String, age: Int)\n}\npub fn get_age(person: Person) { person.age }" --- ----- SOURCE CODE pub type Person { Teacher(name: Nil, age: Int) Student(name: String, age: Int) } pub fn get_age(person: Person) { person.age } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([get_age/1]). -export_type([person/0]). -type person() :: {teacher, nil, integer()} | {student, binary(), integer()}. -file("project/test/my/mod.gleam", 6). -spec get_age(person()) -> integer(). get_age(Person) -> erlang:element(3, Person). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__record_accessors.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "\npub type Person { Person(name: String, age: Int) }\npub fn get_age(person: Person) { person.age }\npub fn get_name(person: Person) { person.name }\n" --- ----- SOURCE CODE pub type Person { Person(name: String, age: Int) } pub fn get_age(person: Person) { person.age } pub fn get_name(person: Person) { person.name } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([get_age/1, get_name/1]). -export_type([person/0]). -type person() :: {person, binary(), integer()}. -file("project/test/my/mod.gleam", 3). -spec get_age(person()) -> integer(). get_age(Person) -> erlang:element(3, Person). -file("project/test/my/mod.gleam", 4). -spec get_name(person()) -> binary(). get_name(Person) -> erlang:element(2, Person). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__record_constants.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "pub type Test { A }\nconst some_test = A\npub fn a() { A }" --- ----- SOURCE CODE pub type Test { A } const some_test = A pub fn a() { A } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([a/0]). -export_type([test/0]). -type test() :: a. -file("project/test/my/mod.gleam", 3). -spec a() -> test(). a() -> a. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__record_spread.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "\npub type Triple {\n Triple(a: Int, b: Int, c: Int)\n}\n\npub fn main() {\n let triple = Triple(1,2,3)\n let Triple(the_a, ..) = triple\n the_a\n}\n" --- ----- SOURCE CODE pub type Triple { Triple(a: Int, b: Int, c: Int) } pub fn main() { let triple = Triple(1,2,3) let Triple(the_a, ..) = triple the_a } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -export_type([triple/0]). -type triple() :: {triple, integer(), integer(), integer()}. -file("project/test/my/mod.gleam", 6). -spec main() -> integer(). main() -> Triple = {triple, 1, 2, 3}, {triple, The_a, _, _} = Triple, The_a. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__record_spread1.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "\npub type Triple {\n Triple(a: Int, b: Int, c: Int)\n}\n\npub fn main() {\n let triple = Triple(1,2,3)\n let Triple(b: the_b, ..) = triple\n the_b\n}\n" --- ----- SOURCE CODE pub type Triple { Triple(a: Int, b: Int, c: Int) } pub fn main() { let triple = Triple(1,2,3) let Triple(b: the_b, ..) = triple the_b } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -export_type([triple/0]). -type triple() :: {triple, integer(), integer(), integer()}. -file("project/test/my/mod.gleam", 6). -spec main() -> integer(). main() -> Triple = {triple, 1, 2, 3}, {triple, _, The_b, _} = Triple, The_b. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__record_spread2.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "\npub type Triple {\n Triple(a: Int, b: Int, c: Int)\n}\n\npub fn main() {\n let triple = Triple(1,2,3)\n let Triple(the_a, c: the_c, ..) = triple\n the_c\n}\n" --- ----- SOURCE CODE pub type Triple { Triple(a: Int, b: Int, c: Int) } pub fn main() { let triple = Triple(1,2,3) let Triple(the_a, c: the_c, ..) = triple the_c } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -export_type([triple/0]). -type triple() :: {triple, integer(), integer(), integer()}. -file("project/test/my/mod.gleam", 6). -spec main() -> integer(). main() -> Triple = {triple, 1, 2, 3}, {triple, The_a, _, The_c} = Triple, The_c. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__record_spread3.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "\npub type Triple {\n Triple(a: Int, b: Int, c: Int)\n}\n\npub fn main() {\n let triple = Triple(1,2,3)\n case triple {\n Triple(b: the_b, ..) -> the_b\n }\n}\n" --- ----- SOURCE CODE pub type Triple { Triple(a: Int, b: Int, c: Int) } pub fn main() { let triple = Triple(1,2,3) case triple { Triple(b: the_b, ..) -> the_b } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -export_type([triple/0]). -type triple() :: {triple, integer(), integer(), integer()}. -file("project/test/my/mod.gleam", 6). -spec main() -> integer(). main() -> Triple = {triple, 1, 2, 3}, case Triple of {triple, _, The_b, _} -> The_b end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__record_update_with_unlabelled_fields.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "\npub type Wibble {\n Wibble(Int, Float, b: Bool, s: String)\n}\n\npub fn main() {\n let record = Wibble(1, 3.14, True, \"Hello\")\n Wibble(..record, b: False)\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(Int, Float, b: Bool, s: String) } pub fn main() { let record = Wibble(1, 3.14, True, "Hello") Wibble(..record, b: False) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -export_type([wibble/0]). -type wibble() :: {wibble, integer(), float(), boolean(), binary()}. -file("project/test/my/mod.gleam", 6). -spec main() -> wibble(). main() -> Record = {wibble, 1, 3.14, true, <<"Hello"/utf8>>}, {wibble, erlang:element(2, Record), erlang:element(3, Record), false, erlang:element(5, Record)}. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__record_updates.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "\npub type Person { Person(name: String, age: Int) }\n\npub fn main() {\n let p = Person(\"Quinn\", 27)\n let new_p = Person(..p, age: 28)\n new_p\n}\n" --- ----- SOURCE CODE pub type Person { Person(name: String, age: Int) } pub fn main() { let p = Person("Quinn", 27) let new_p = Person(..p, age: 28) new_p } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -export_type([person/0]). -type person() :: {person, binary(), integer()}. -file("project/test/my/mod.gleam", 4). -spec main() -> person(). main() -> P = {person, <<"Quinn"/utf8>>, 27}, New_p = {person, erlang:element(2, P), 28}, New_p. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__record_updates1.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "\npub type Person { Person(name: String, age: Int) }\n\npub fn main() {\n let p = Person(\"Quinn\", 27)\n let new_p = Person(..p, age: p.age + 1)\n new_p\n}\n" --- ----- SOURCE CODE pub type Person { Person(name: String, age: Int) } pub fn main() { let p = Person("Quinn", 27) let new_p = Person(..p, age: p.age + 1) new_p } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -export_type([person/0]). -type person() :: {person, binary(), integer()}. -file("project/test/my/mod.gleam", 4). -spec main() -> person(). main() -> P = {person, <<"Quinn"/utf8>>, 27}, New_p = {person, erlang:element(2, P), erlang:element(3, P) + 1}, New_p. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__record_updates2.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "\npub type Person { Person(name: String, age: Int) }\n\npub fn main() {\n let p = Person(\"Quinn\", 27)\n let new_p = Person(..p, age: 28, name: \"Riley\")\n new_p\n}\n" --- ----- SOURCE CODE pub type Person { Person(name: String, age: Int) } pub fn main() { let p = Person("Quinn", 27) let new_p = Person(..p, age: 28, name: "Riley") new_p } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -export_type([person/0]). -type person() :: {person, binary(), integer()}. -file("project/test/my/mod.gleam", 4). -spec main() -> person(). main() -> P = {person, <<"Quinn"/utf8>>, 27}, New_p = {person, <<"Riley"/utf8>>, 28}, New_p. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__record_updates3.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "\npub type Person { Person(name: String, age: Int) }\n\npub fn main() {\n let new_p = Person(..return_person(), age: 28)\n new_p\n}\n\nfn return_person() {\n Person(\"Quinn\", 27)\n}\n" --- ----- SOURCE CODE pub type Person { Person(name: String, age: Int) } pub fn main() { let new_p = Person(..return_person(), age: 28) new_p } fn return_person() { Person("Quinn", 27) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -export_type([person/0]). -type person() :: {person, binary(), integer()}. -file("project/test/my/mod.gleam", 9). -spec return_person() -> person(). return_person() -> {person, <<"Quinn"/utf8>>, 27}. -file("project/test/my/mod.gleam", 4). -spec main() -> person(). main() -> New_p = begin _record = return_person(), {person, erlang:element(2, _record), 28} end, New_p. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__record_updates4.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "\npub type Car { Car(make: String, model: String, driver: Person) }\npub type Person { Person(name: String, age: Int) }\n\npub fn main() {\n let car = Car(make: \"Amphicar\", model: \"Model 770\", driver: Person(name: \"John Doe\", age: 27))\n let new_p = Person(..car.driver, age: 28)\n new_p\n}\n" --- ----- SOURCE CODE pub type Car { Car(make: String, model: String, driver: Person) } pub type Person { Person(name: String, age: Int) } pub fn main() { let car = Car(make: "Amphicar", model: "Model 770", driver: Person(name: "John Doe", age: 27)) let new_p = Person(..car.driver, age: 28) new_p } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -export_type([car/0, person/0]). -type car() :: {car, binary(), binary(), person()}. -type person() :: {person, binary(), integer()}. -file("project/test/my/mod.gleam", 5). -spec main() -> person(). main() -> Car = {car, <<"Amphicar"/utf8>>, <<"Model 770"/utf8>>, {person, <<"John Doe"/utf8>>, 27}}, New_p = begin _record = erlang:element(4, Car), {person, erlang:element(2, _record), 28} end, New_p. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__reserve_words.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "record_definition(\"div\",\n&[(\"receive\", type_::int()), (\"catch\", type_::tuple(vec![])),\n(\"unreserved\", type_::tuple(vec![]))])" --- -record('div', {'receive' :: integer(), 'catch' :: {}, unreserved :: {}}). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__records__type_vars.snap ================================================ --- source: compiler-core/src/erlang/tests/records.rs expression: "record_definition(\"PetCat\",\n&[(\"name\", type_::generic_var(1)), (\"is_cute\", type_::unbound_var(1)),\n(\"linked\", type_::link(type_::int()))])" --- -record(pet_cat, {name :: any(), is_cute :: any(), linked :: integer()}). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__reserved__build_in_erlang_type_escaping.snap ================================================ --- source: compiler-core/src/erlang/tests/reserved.rs expression: pub type Map --- ----- SOURCE CODE pub type Map ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export_type([map_/0]). -type map_() :: any(). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__reserved__escape_erlang_reserved_keywords_in_type_names.snap ================================================ --- source: compiler-core/src/erlang/tests/reserved.rs expression: "pub type After { TestAfter }\npub type And { TestAnd }\npub type Andalso { TestAndAlso }\npub type Band { TestBAnd }\npub type Begin { TestBegin }\npub type Bnot { TestBNot }\npub type Bor { TestBOr }\npub type Bsl { TestBsl }\npub type Bsr { TestBsr }\npub type Bxor { TestBXor }\npub type Case { TestCase }\npub type Catch { TestCatch }\npub type Cond { TestCond }\npub type Div { TestDiv }\npub type End { TestEnd }\npub type Fun { TestFun }\npub type If { TestIf }\npub type Let { TestLet }\npub type Maybe { TestMaybe }\npub type Not { TestNot }\npub type Of { TestOf }\npub type Or { TestOr }\npub type Orelse { TestOrElse }\npub type Query { TestQuery }\npub type Receive { TestReceive }\npub type Rem { TestRem }\npub type Try { TestTry }\npub type When { TestWhen }\npub type Xor { TestXor }" --- ----- SOURCE CODE pub type After { TestAfter } pub type And { TestAnd } pub type Andalso { TestAndAlso } pub type Band { TestBAnd } pub type Begin { TestBegin } pub type Bnot { TestBNot } pub type Bor { TestBOr } pub type Bsl { TestBsl } pub type Bsr { TestBsr } pub type Bxor { TestBXor } pub type Case { TestCase } pub type Catch { TestCatch } pub type Cond { TestCond } pub type Div { TestDiv } pub type End { TestEnd } pub type Fun { TestFun } pub type If { TestIf } pub type Let { TestLet } pub type Maybe { TestMaybe } pub type Not { TestNot } pub type Of { TestOf } pub type Or { TestOr } pub type Orelse { TestOrElse } pub type Query { TestQuery } pub type Receive { TestReceive } pub type Rem { TestRem } pub type Try { TestTry } pub type When { TestWhen } pub type Xor { TestXor } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export_type(['after'/0, 'and'/0, 'andalso'/0, 'band'/0, 'begin'/0, 'bnot'/0, 'bor'/0, 'bsl'/0, 'bsr'/0, 'bxor'/0, 'case'/0, 'catch'/0, 'cond'/0, 'div'/0, 'end'/0, 'fun'/0, 'if'/0, 'let'/0, 'maybe'/0, 'not'/0, 'of'/0, 'or'/0, 'orelse'/0, 'query'/0, 'receive'/0, 'rem'/0, 'try'/0, 'when'/0, 'xor'/0]). -type 'after'() :: test_after. -type 'and'() :: test_and. -type 'andalso'() :: test_and_also. -type 'band'() :: test_b_and. -type 'begin'() :: test_begin. -type 'bnot'() :: test_b_not. -type 'bor'() :: test_b_or. -type 'bsl'() :: test_bsl. -type 'bsr'() :: test_bsr. -type 'bxor'() :: test_b_xor. -type 'case'() :: test_case. -type 'catch'() :: test_catch. -type 'cond'() :: test_cond. -type 'div'() :: test_div. -type 'end'() :: test_end. -type 'fun'() :: test_fun. -type 'if'() :: test_if. -type 'let'() :: test_let. -type 'maybe'() :: test_maybe. -type 'not'() :: test_not. -type 'of'() :: test_of. -type 'or'() :: test_or. -type 'orelse'() :: test_or_else. -type 'query'() :: test_query. -type 'receive'() :: test_receive. -type 'rem'() :: test_rem. -type 'try'() :: test_try. -type 'when'() :: test_when. -type 'xor'() :: test_xor. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__ascii_as_unicode_escape_sequence.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn y() -> String {\n \"\\u{79}\"\n}\n" --- ----- SOURCE CODE pub fn y() -> String { "\u{79}" } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([y/0]). -file("project/test/my/mod.gleam", 2). -spec y() -> binary(). y() -> <<"\x{79}"/utf8>>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__assert_const_concat.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\nconst cute = \"cute\"\nconst cute_bee = cute <> \"bee\"\n\npub fn main() {\n cute_bee\n}\n" --- ----- SOURCE CODE const cute = "cute" const cute_bee = cute <> "bee" pub fn main() { cute_bee } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 5). -spec main() -> binary(). main() -> <<"cute"/utf8, "bee"/utf8>>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__assert_const_concat_many_strings.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\nconst big_concat = \"a\" <> \"b\" <> \"c\" <> \"d\" <> \"e\" <> \"f\" <> \"g\" <> \"h\" <> \"i\" <> \"j\" <> \"k\" <> \"l\" <> \"m\" <> \"n\" <> \"o\" <> \"p\" <> \"q\" <> \"r\" <> \"s\" <> \"t\" <> \"u\" <> \"v\" <> \"w\" <> \"x\" <> \"y\" <> \"z\"\n\npub fn main() {\n big_concat\n}\n" --- ----- SOURCE CODE const big_concat = "a" <> "b" <> "c" <> "d" <> "e" <> "f" <> "g" <> "h" <> "i" <> "j" <> "k" <> "l" <> "m" <> "n" <> "o" <> "p" <> "q" <> "r" <> "s" <> "t" <> "u" <> "v" <> "w" <> "x" <> "y" <> "z" pub fn main() { big_concat } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 4). -spec main() -> binary(). main() -> <<"a"/utf8, "b"/utf8, "c"/utf8, "d"/utf8, "e"/utf8, "f"/utf8, "g"/utf8, "h"/utf8, "i"/utf8, "j"/utf8, "k"/utf8, "l"/utf8, "m"/utf8, "n"/utf8, "o"/utf8, "p"/utf8, "q"/utf8, "r"/utf8, "s"/utf8, "t"/utf8, "u"/utf8, "v"/utf8, "w"/utf8, "x"/utf8, "y"/utf8, "z"/utf8>>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__assert_const_concat_many_strings_in_list.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\nconst big_concat_list = [\"a\" <> \"b\" <> \"c\" <> \"d\" <> \"e\" <> \"f\" <> \"g\" <> \"h\" <> \"i\" <> \"j\" <> \"k\" <> \"l\" <> \"m\" <> \"n\" <> \"o\" <> \"p\" <> \"q\" <> \"r\" <> \"s\" <> \"t\" <> \"u\" <> \"v\" <> \"w\" <> \"x\" <> \"y\" <> \"z\"]\n\npub fn main() {\n big_concat_list\n}\n" --- ----- SOURCE CODE const big_concat_list = ["a" <> "b" <> "c" <> "d" <> "e" <> "f" <> "g" <> "h" <> "i" <> "j" <> "k" <> "l" <> "m" <> "n" <> "o" <> "p" <> "q" <> "r" <> "s" <> "t" <> "u" <> "v" <> "w" <> "x" <> "y" <> "z"] pub fn main() { big_concat_list } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 4). -spec main() -> list(binary()). main() -> [<<"a"/utf8, "b"/utf8, "c"/utf8, "d"/utf8, "e"/utf8, "f"/utf8, "g"/utf8, "h"/utf8, "i"/utf8, "j"/utf8, "k"/utf8, "l"/utf8, "m"/utf8, "n"/utf8, "o"/utf8, "p"/utf8, "q"/utf8, "r"/utf8, "s"/utf8, "t"/utf8, "u"/utf8, "v"/utf8, "w"/utf8, "x"/utf8, "y"/utf8, "z"/utf8>>]. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__assert_const_concat_other_const_concat.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\nconst cute_bee = \"cute\" <> \"bee\"\nconst cute_cute_bee_buzz = cute_bee <> \"buzz\"\n\npub fn main() {\n cute_cute_bee_buzz\n}\n" --- ----- SOURCE CODE const cute_bee = "cute" <> "bee" const cute_cute_bee_buzz = cute_bee <> "buzz" pub fn main() { cute_cute_bee_buzz } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 5). -spec main() -> binary(). main() -> <<"cute"/utf8, "bee"/utf8, "buzz"/utf8>>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__assert_string_prefix.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn main(x) {\n let assert \"m-\" <> rest = x\n rest\n}\n" --- ----- SOURCE CODE pub fn main(x) { let assert "m-" <> rest = x rest } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -file("project/test/my/mod.gleam", 2). -spec main(binary()) -> binary(). main(X) -> Rest@1 = case X of <<"m-"/utf8, Rest/binary>> -> Rest; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 3, value => _assert_fail, start => 20, 'end' => 47, pattern_start => 31, pattern_end => 43}) end, Rest@1. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__assert_string_prefix_discar.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn main(x) {\n let assert \"m-\" <> _ = x\n}\n" --- ----- SOURCE CODE pub fn main(x) { let assert "m-" <> _ = x } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -file("project/test/my/mod.gleam", 2). -spec main(binary()) -> binary(). main(X) -> case X of <<"m-"/utf8, _/binary>> -> X; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 3, value => _assert_fail, start => 20, 'end' => 44, pattern_start => 31, pattern_end => 40}) end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__concat.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn go(x, y) {\n x <> y\n}\n" --- ----- SOURCE CODE pub fn go(x, y) { x <> y } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/2]). -file("project/test/my/mod.gleam", 2). -spec go(binary(), binary()) -> binary(). go(X, Y) -> <>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__concat_3_variables.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn go(x, y, z) {\n x <> y <> z\n}\n" --- ----- SOURCE CODE pub fn go(x, y, z) { x <> y <> z } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/3]). -file("project/test/my/mod.gleam", 2). -spec go(binary(), binary(), binary()) -> binary(). go(X, Y, Z) -> <<<>/binary, Z/binary>>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__concat_constant.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\nconst a = \"Hello, \"\nconst b = \"Joe!\"\n\npub fn go() {\n a <> b\n}\n" --- ----- SOURCE CODE const a = "Hello, " const b = "Joe!" pub fn go() { a <> b } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 5). -spec go() -> binary(). go() -> <<"Hello, "/utf8, "Joe!"/utf8>>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__concat_constant_fn.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\nconst cs = s\n\nfn s() {\n \"s\"\n}\n\npub fn go() {\n cs() <> cs()\n}\n" --- ----- SOURCE CODE const cs = s fn s() { "s" } pub fn go() { cs() <> cs() } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 4). -spec s() -> binary(). s() -> <<"s"/utf8>>. -file("project/test/my/mod.gleam", 8). -spec go() -> binary(). go() -> <<(s())/binary, (s())/binary>>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__concat_function_call.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\nfn x() {\n \"\"\n}\n\npub fn go() {\n x() <> x()\n}\n" --- ----- SOURCE CODE fn x() { "" } pub fn go() { x() <> x() } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 2). -spec x() -> binary(). x() -> <<""/utf8>>. -file("project/test/my/mod.gleam", 6). -spec go() -> binary(). go() -> <<(x())/binary, (x())/binary>>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__discard_concat_rest_pattern.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn go(x) {\n case x {\n \"Hello, \" <> _ -> Nil\n _ -> Nil\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { "Hello, " <> _ -> Nil _ -> Nil } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/1]). -file("project/test/my/mod.gleam", 2). -spec go(binary()) -> nil. go(X) -> case X of <<"Hello, "/utf8, _/binary>> -> nil; _ -> nil end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__not_unicode_escape_sequence.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn not_unicode_escape_sequence() -> String {\n \"\\\\u{03a9}\"\n}\n" --- ----- SOURCE CODE pub fn not_unicode_escape_sequence() -> String { "\\u{03a9}" } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([not_unicode_escape_sequence/0]). -file("project/test/my/mod.gleam", 2). -spec not_unicode_escape_sequence() -> binary(). not_unicode_escape_sequence() -> <<"\\u{03a9}"/utf8>>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__not_unicode_escape_sequence2.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn not_unicode_escape_sequence() -> String {\n \"\\\\\\\\u{03a9}\"\n}\n" --- ----- SOURCE CODE pub fn not_unicode_escape_sequence() -> String { "\\\\u{03a9}" } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([not_unicode_escape_sequence/0]). -file("project/test/my/mod.gleam", 2). -spec not_unicode_escape_sequence() -> binary(). not_unicode_escape_sequence() -> <<"\\\\u{03a9}"/utf8>>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__pipe_concat.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\nfn id(x) {\n x\n}\n\npub fn main() {\n { \"\" |> id } <> { \"\" |> id }\n}\n" --- ----- SOURCE CODE fn id(x) { x } pub fn main() { { "" |> id } <> { "" |> id } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec id(I) -> I. id(X) -> X. -file("project/test/my/mod.gleam", 6). -spec main() -> binary(). main() -> <<(begin _pipe = <<""/utf8>>, id(_pipe) end)/binary, (begin _pipe@1 = <<""/utf8>>, id(_pipe@1) end)/binary>>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__rest_variable_rewriting.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn go(x) {\n case x {\n \"Hello, \" <> x -> x\n _ -> \"Unknown\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { "Hello, " <> x -> x _ -> "Unknown" } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/1]). -file("project/test/my/mod.gleam", 2). -spec go(binary()) -> binary(). go(X) -> case X of <<"Hello, "/utf8, X@1/binary>> -> X@1; _ -> <<"Unknown"/utf8>> end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__string_of_number_concat.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn go(x) {\n x <> \"1\"\n}\n" --- ----- SOURCE CODE pub fn go(x) { x <> "1" } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/1]). -file("project/test/my/mod.gleam", 2). -spec go(binary()) -> binary(). go(X) -> <>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__string_prefix.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn go(x) {\n case x {\n \"Hello, \" <> name -> name\n _ -> \"Unknown\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { "Hello, " <> name -> name _ -> "Unknown" } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/1]). -file("project/test/my/mod.gleam", 2). -spec go(binary()) -> binary(). go(X) -> case X of <<"Hello, "/utf8, Name/binary>> -> Name; _ -> <<"Unknown"/utf8>> end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__string_prefix_assignment.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn go(x) {\n case x {\n \"Hello, \" as greeting <> name -> greeting\n _ -> \"Unknown\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { "Hello, " as greeting <> name -> greeting _ -> "Unknown" } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/1]). -file("project/test/my/mod.gleam", 2). -spec go(binary()) -> binary(). go(X) -> case X of <<"Hello, "/utf8, Name/binary>> -> Greeting = <<"Hello, "/utf8>>, Greeting; _ -> <<"Unknown"/utf8>> end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__string_prefix_assignment_not_unicode_escape_sequence.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn go(x) {\n let _ = case x {\n \"\\\\u{9}\" as start <> rest -> \"test\"\n \"\\\\u{000009}\" as start <> rest -> \"test\"\n \"\\\\u{21}\" as start <> rest -> \"test\"\n \"\\\\u{100}\" as start <> rest -> \"test\"\n \"\\\\u{1000}\" as start <> rest -> \"test\"\n \"\\\\u{1F600}\" as start <> rest -> \"test\"\n \"\\\\u{1f600}\" as start <> rest -> \"test\"\n \"\\\\u{01F600}\" as start <> rest -> \"test\"\n \"\\\\u{01f600}\" as start <> rest -> \"test\"\n \"\\\\u{9} \\\\u{000009} \\\\u{21} \\\\u{100} \\\\u{1000} \\\\u{1F600} \\\\u{01F600}\" as start <> rest -> \"test\"\n _ -> \"Unknown\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { let _ = case x { "\\u{9}" as start <> rest -> "test" "\\u{000009}" as start <> rest -> "test" "\\u{21}" as start <> rest -> "test" "\\u{100}" as start <> rest -> "test" "\\u{1000}" as start <> rest -> "test" "\\u{1F600}" as start <> rest -> "test" "\\u{1f600}" as start <> rest -> "test" "\\u{01F600}" as start <> rest -> "test" "\\u{01f600}" as start <> rest -> "test" "\\u{9} \\u{000009} \\u{21} \\u{100} \\u{1000} \\u{1F600} \\u{01F600}" as start <> rest -> "test" _ -> "Unknown" } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/1]). -file("project/test/my/mod.gleam", 2). -spec go(binary()) -> binary(). go(X) -> _ = case X of <<"\\u{9}"/utf8, Rest/binary>> -> Start = <<"\\u{9}"/utf8>>, <<"test"/utf8>>; <<"\\u{000009}"/utf8, Rest@1/binary>> -> Start@1 = <<"\\u{000009}"/utf8>>, <<"test"/utf8>>; <<"\\u{21}"/utf8, Rest@2/binary>> -> Start@2 = <<"\\u{21}"/utf8>>, <<"test"/utf8>>; <<"\\u{100}"/utf8, Rest@3/binary>> -> Start@3 = <<"\\u{100}"/utf8>>, <<"test"/utf8>>; <<"\\u{1000}"/utf8, Rest@4/binary>> -> Start@4 = <<"\\u{1000}"/utf8>>, <<"test"/utf8>>; <<"\\u{1F600}"/utf8, Rest@5/binary>> -> Start@5 = <<"\\u{1F600}"/utf8>>, <<"test"/utf8>>; <<"\\u{1f600}"/utf8, Rest@6/binary>> -> Start@6 = <<"\\u{1f600}"/utf8>>, <<"test"/utf8>>; <<"\\u{01F600}"/utf8, Rest@7/binary>> -> Start@7 = <<"\\u{01F600}"/utf8>>, <<"test"/utf8>>; <<"\\u{01f600}"/utf8, Rest@8/binary>> -> Start@8 = <<"\\u{01f600}"/utf8>>, <<"test"/utf8>>; <<"\\u{9} \\u{000009} \\u{21} \\u{100} \\u{1000} \\u{1F600} \\u{01F600}"/utf8, Rest@9/binary>> -> Start@9 = <<"\\u{9} \\u{000009} \\u{21} \\u{100} \\u{1000} \\u{1F600} \\u{01F600}"/utf8>>, <<"test"/utf8>>; _ -> <<"Unknown"/utf8>> end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__string_prefix_assignment_with_escape_sequences.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn go(x) {\n let _ = case x {\n \"\\f\" as start <> rest -> \"test\"\n \"\\n\" as start <> rest -> \"test\"\n \"\\r\" as start <> rest -> \"test\"\n \"\\t\" as start <> rest -> \"test\"\n \"\\\"\" as start <> rest -> \"test\"\n \"\\\\\" as start <> rest -> \"test\"\n \"\\f \\n \\r \\t \\\" \\\\\" as start <> rest -> \"control chars with prefix assignment\"\n \"\\u{9}\" as start <> rest -> \"test\"\n \"\\u{000009}\" as start <> rest -> \"test\"\n \"\\u{21}\" as start <> rest -> \"test\"\n \"\\u{100}\" as start <> rest -> \"test\"\n \"\\u{1000}\" as start <> rest -> \"test\"\n \"\\u{1F600}\" as start <> rest -> \"test\"\n \"\\u{1f600}\" as start <> rest -> \"test\"\n \"\\u{01F600}\" as start <> rest -> \"test\"\n \"\\u{01f600}\" as start <> rest -> \"test\"\n \"\\u{9} \\u{000009} \\u{21} \\u{100} \\u{1000} \\u{1F600} \\u{01F600}\" as start <> rest -> \"test\"\n _ -> \"Unknown\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { let _ = case x { "\f" as start <> rest -> "test" "\n" as start <> rest -> "test" "\r" as start <> rest -> "test" "\t" as start <> rest -> "test" "\"" as start <> rest -> "test" "\\" as start <> rest -> "test" "\f \n \r \t \" \\" as start <> rest -> "control chars with prefix assignment" "\u{9}" as start <> rest -> "test" "\u{000009}" as start <> rest -> "test" "\u{21}" as start <> rest -> "test" "\u{100}" as start <> rest -> "test" "\u{1000}" as start <> rest -> "test" "\u{1F600}" as start <> rest -> "test" "\u{1f600}" as start <> rest -> "test" "\u{01F600}" as start <> rest -> "test" "\u{01f600}" as start <> rest -> "test" "\u{9} \u{000009} \u{21} \u{100} \u{1000} \u{1F600} \u{01F600}" as start <> rest -> "test" _ -> "Unknown" } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/1]). -file("project/test/my/mod.gleam", 2). -spec go(binary()) -> binary(). go(X) -> _ = case X of <<"\f"/utf8, Rest/binary>> -> Start = <<"\f"/utf8>>, <<"test"/utf8>>; <<"\n"/utf8, Rest@1/binary>> -> Start@1 = <<"\n"/utf8>>, <<"test"/utf8>>; <<"\r"/utf8, Rest@2/binary>> -> Start@2 = <<"\r"/utf8>>, <<"test"/utf8>>; <<"\t"/utf8, Rest@3/binary>> -> Start@3 = <<"\t"/utf8>>, <<"test"/utf8>>; <<"\""/utf8, Rest@4/binary>> -> Start@4 = <<"\""/utf8>>, <<"test"/utf8>>; <<"\\"/utf8, Rest@5/binary>> -> Start@5 = <<"\\"/utf8>>, <<"test"/utf8>>; <<"\f \n \r \t \" \\"/utf8, Rest@6/binary>> -> Start@6 = <<"\f \n \r \t \" \\"/utf8>>, <<"control chars with prefix assignment"/utf8>>; <<"\x{9}"/utf8, Rest@7/binary>> -> Start@7 = <<"\x{9}"/utf8>>, <<"test"/utf8>>; <<"\x{000009}"/utf8, Rest@8/binary>> -> Start@8 = <<"\x{000009}"/utf8>>, <<"test"/utf8>>; <<"\x{21}"/utf8, Rest@9/binary>> -> Start@9 = <<"\x{21}"/utf8>>, <<"test"/utf8>>; <<"\x{100}"/utf8, Rest@10/binary>> -> Start@10 = <<"\x{100}"/utf8>>, <<"test"/utf8>>; <<"\x{1000}"/utf8, Rest@11/binary>> -> Start@11 = <<"\x{1000}"/utf8>>, <<"test"/utf8>>; <<"\x{1F600}"/utf8, Rest@12/binary>> -> Start@12 = <<"\x{1F600}"/utf8>>, <<"test"/utf8>>; <<"\x{1f600}"/utf8, Rest@13/binary>> -> Start@13 = <<"\x{1f600}"/utf8>>, <<"test"/utf8>>; <<"\x{01F600}"/utf8, Rest@14/binary>> -> Start@14 = <<"\x{01F600}"/utf8>>, <<"test"/utf8>>; <<"\x{01f600}"/utf8, Rest@15/binary>> -> Start@15 = <<"\x{01f600}"/utf8>>, <<"test"/utf8>>; <<"\x{9} \x{000009} \x{21} \x{100} \x{1000} \x{1F600} \x{01F600}"/utf8, Rest@16/binary>> -> Start@16 = <<"\x{9} \x{000009} \x{21} \x{100} \x{1000} \x{1F600} \x{01F600}"/utf8>>, <<"test"/utf8>>; _ -> <<"Unknown"/utf8>> end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__string_prefix_assignment_with_guard.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn go(x) {\n case x {\n \"Hello, \" as greeting <> name if name == \"Dude\" -> greeting <> \"Mate\"\n \"Hello, \" as greeting <> name -> greeting\n _ -> \"Unknown\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { "Hello, " as greeting <> name if name == "Dude" -> greeting <> "Mate" "Hello, " as greeting <> name -> greeting _ -> "Unknown" } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/1]). -file("project/test/my/mod.gleam", 2). -spec go(binary()) -> binary(). go(X) -> case X of <<"Hello, "/utf8, Name/binary>> when Name =:= <<"Dude"/utf8>> -> Greeting = <<"Hello, "/utf8>>, <>; <<"Hello, "/utf8, Name@1/binary>> -> Greeting@1 = <<"Hello, "/utf8>>, Greeting@1; _ -> <<"Unknown"/utf8>> end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__string_prefix_assignment_with_multiple_subjects.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn go(x) {\n case x {\n \"1\" as digit <> _ | \"2\" as digit <> _ -> digit\n _ -> \"Unknown\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { "1" as digit <> _ | "2" as digit <> _ -> digit _ -> "Unknown" } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/1]). -file("project/test/my/mod.gleam", 2). -spec go(binary()) -> binary(). go(X) -> case X of <<"1"/utf8, _/binary>> -> Digit = <<"1"/utf8>>, Digit; <<"2"/utf8, _/binary>> -> Digit = <<"2"/utf8>>, Digit; _ -> <<"Unknown"/utf8>> end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__string_prefix_not_unicode_escape_sequence.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn go(x) {\n let _ = case x {\n \"\\\\u{9}\" <> rest -> \"test\"\n \"\\\\u{000009}\" <> rest -> \"test\"\n \"\\\\u{21}\" <> rest -> \"test\"\n \"\\\\u{100}\" <> rest -> \"test\"\n \"\\\\u{1000}\" <> rest -> \"test\"\n \"\\\\u{1F600}\" <> rest -> \"test\"\n \"\\\\u{1f600}\" <> rest -> \"test\"\n \"\\\\u{01F600}\" <> rest -> \"test\"\n \"\\\\u{01f600}\" <> rest -> \"test\"\n \"\\\\u{9} \\\\u{000009} \\\\u{21} \\\\u{100} \\\\u{1000} \\\\u{1F600} \\\\u{01F600}\" <> rest -> \"test\"\n _ -> \"Unknown\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { let _ = case x { "\\u{9}" <> rest -> "test" "\\u{000009}" <> rest -> "test" "\\u{21}" <> rest -> "test" "\\u{100}" <> rest -> "test" "\\u{1000}" <> rest -> "test" "\\u{1F600}" <> rest -> "test" "\\u{1f600}" <> rest -> "test" "\\u{01F600}" <> rest -> "test" "\\u{01f600}" <> rest -> "test" "\\u{9} \\u{000009} \\u{21} \\u{100} \\u{1000} \\u{1F600} \\u{01F600}" <> rest -> "test" _ -> "Unknown" } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/1]). -file("project/test/my/mod.gleam", 2). -spec go(binary()) -> binary(). go(X) -> _ = case X of <<"\\u{9}"/utf8, Rest/binary>> -> <<"test"/utf8>>; <<"\\u{000009}"/utf8, Rest@1/binary>> -> <<"test"/utf8>>; <<"\\u{21}"/utf8, Rest@2/binary>> -> <<"test"/utf8>>; <<"\\u{100}"/utf8, Rest@3/binary>> -> <<"test"/utf8>>; <<"\\u{1000}"/utf8, Rest@4/binary>> -> <<"test"/utf8>>; <<"\\u{1F600}"/utf8, Rest@5/binary>> -> <<"test"/utf8>>; <<"\\u{1f600}"/utf8, Rest@6/binary>> -> <<"test"/utf8>>; <<"\\u{01F600}"/utf8, Rest@7/binary>> -> <<"test"/utf8>>; <<"\\u{01f600}"/utf8, Rest@8/binary>> -> <<"test"/utf8>>; <<"\\u{9} \\u{000009} \\u{21} \\u{100} \\u{1000} \\u{1F600} \\u{01F600}"/utf8, Rest@9/binary>> -> <<"test"/utf8>>; _ -> <<"Unknown"/utf8>> end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__string_prefix_shadowing.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn go(x) {\n case x {\n \"Hello, \" as x <> name -> x\n _ -> \"Unknown\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { "Hello, " as x <> name -> x _ -> "Unknown" } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/1]). -file("project/test/my/mod.gleam", 2). -spec go(binary()) -> binary(). go(X) -> case X of <<"Hello, "/utf8, Name/binary>> -> X@1 = <<"Hello, "/utf8>>, X@1; _ -> <<"Unknown"/utf8>> end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__string_prefix_with_escape_sequences.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn go(x) {\n let _ = case x {\n \"\\f\" <> rest -> \"test\"\n \"\\n\" <> rest -> \"test\"\n \"\\r\" <> rest -> \"test\"\n \"\\t\" <> rest -> \"test\"\n \"\\\"\" <> rest -> \"test\"\n \"\\\\\" <> rest -> \"test\"\n \"\\f \\n \\r \\t \\\" \\\\\" <> rest -> \"control chars with prefix assignment\"\n \"\\u{9}\" <> rest -> \"test\"\n \"\\u{000009}\" <> rest -> \"test\"\n \"\\u{21}\" <> rest -> \"test\"\n \"\\u{100}\" <> rest -> \"test\"\n \"\\u{1000}\" <> rest -> \"test\"\n \"\\u{1F600}\" <> rest -> \"test\"\n \"\\u{1f600}\" <> rest -> \"test\"\n \"\\u{01F600}\" <> rest -> \"test\"\n \"\\u{01f600}\" <> rest -> \"test\"\n \"\\u{9} \\u{000009} \\u{21} \\u{100} \\u{1000} \\u{1F600} \\u{01F600}\" <> rest -> \"test\"\n _ -> \"Unknown\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { let _ = case x { "\f" <> rest -> "test" "\n" <> rest -> "test" "\r" <> rest -> "test" "\t" <> rest -> "test" "\"" <> rest -> "test" "\\" <> rest -> "test" "\f \n \r \t \" \\" <> rest -> "control chars with prefix assignment" "\u{9}" <> rest -> "test" "\u{000009}" <> rest -> "test" "\u{21}" <> rest -> "test" "\u{100}" <> rest -> "test" "\u{1000}" <> rest -> "test" "\u{1F600}" <> rest -> "test" "\u{1f600}" <> rest -> "test" "\u{01F600}" <> rest -> "test" "\u{01f600}" <> rest -> "test" "\u{9} \u{000009} \u{21} \u{100} \u{1000} \u{1F600} \u{01F600}" <> rest -> "test" _ -> "Unknown" } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/1]). -file("project/test/my/mod.gleam", 2). -spec go(binary()) -> binary(). go(X) -> _ = case X of <<"\f"/utf8, Rest/binary>> -> <<"test"/utf8>>; <<"\n"/utf8, Rest@1/binary>> -> <<"test"/utf8>>; <<"\r"/utf8, Rest@2/binary>> -> <<"test"/utf8>>; <<"\t"/utf8, Rest@3/binary>> -> <<"test"/utf8>>; <<"\""/utf8, Rest@4/binary>> -> <<"test"/utf8>>; <<"\\"/utf8, Rest@5/binary>> -> <<"test"/utf8>>; <<"\f \n \r \t \" \\"/utf8, Rest@6/binary>> -> <<"control chars with prefix assignment"/utf8>>; <<"\x{9}"/utf8, Rest@7/binary>> -> <<"test"/utf8>>; <<"\x{000009}"/utf8, Rest@8/binary>> -> <<"test"/utf8>>; <<"\x{21}"/utf8, Rest@9/binary>> -> <<"test"/utf8>>; <<"\x{100}"/utf8, Rest@10/binary>> -> <<"test"/utf8>>; <<"\x{1000}"/utf8, Rest@11/binary>> -> <<"test"/utf8>>; <<"\x{1F600}"/utf8, Rest@12/binary>> -> <<"test"/utf8>>; <<"\x{1f600}"/utf8, Rest@13/binary>> -> <<"test"/utf8>>; <<"\x{01F600}"/utf8, Rest@14/binary>> -> <<"test"/utf8>>; <<"\x{01f600}"/utf8, Rest@15/binary>> -> <<"test"/utf8>>; <<"\x{9} \x{000009} \x{21} \x{100} \x{1000} \x{1F600} \x{01F600}"/utf8, Rest@16/binary>> -> <<"test"/utf8>>; _ -> <<"Unknown"/utf8>> end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__unicode1.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn emoji() -> String {\n \"\\u{1f600}\"\n}\n" --- ----- SOURCE CODE pub fn emoji() -> String { "\u{1f600}" } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([emoji/0]). -file("project/test/my/mod.gleam", 2). -spec emoji() -> binary(). emoji() -> <<"\x{1f600}"/utf8>>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__unicode2.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn y_with_dieresis() -> String {\n \"\\u{0308}y\"\n}\n" --- ----- SOURCE CODE pub fn y_with_dieresis() -> String { "\u{0308}y" } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([y_with_dieresis/0]). -file("project/test/my/mod.gleam", 2). -spec y_with_dieresis() -> binary(). y_with_dieresis() -> <<"\x{0308}y"/utf8>>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__unicode3.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn y_with_dieresis_with_slash() -> String {\n \"\\\\\\u{0308}y\"\n}\n" --- ----- SOURCE CODE pub fn y_with_dieresis_with_slash() -> String { "\\\u{0308}y" } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([y_with_dieresis_with_slash/0]). -file("project/test/my/mod.gleam", 2). -spec y_with_dieresis_with_slash() -> binary(). y_with_dieresis_with_slash() -> <<"\\\x{0308}y"/utf8>>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__unicode_concat1.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn main(x) -> String {\n x <> \"\\u{0308}\"\n}\n" --- ----- SOURCE CODE pub fn main(x) -> String { x <> "\u{0308}" } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -file("project/test/my/mod.gleam", 2). -spec main(binary()) -> binary(). main(X) -> <>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__unicode_concat2.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn main(x) -> String {\n x <> \"\\\\u{0308}\"\n}\n" --- ----- SOURCE CODE pub fn main(x) -> String { x <> "\\u{0308}" } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -file("project/test/my/mod.gleam", 2). -spec main(binary()) -> binary(). main(X) -> <>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__unicode_concat3.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn main(x) -> String {\n x <> \"\\\\\\u{0308}\"\n}\n" --- ----- SOURCE CODE pub fn main(x) -> String { x <> "\\\u{0308}" } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -file("project/test/my/mod.gleam", 2). -spec main(binary()) -> binary(). main(X) -> <>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__strings__unicode_escape_sequence_6_digits.snap ================================================ --- source: compiler-core/src/erlang/tests/strings.rs expression: "\npub fn unicode_escape_sequence_6_digits() -> String {\n \"\\u{10abcd}\"\n}\n" --- ----- SOURCE CODE pub fn unicode_escape_sequence_6_digits() -> String { "\u{10abcd}" } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([unicode_escape_sequence_6_digits/0]). -file("project/test/my/mod.gleam", 2). -spec unicode_escape_sequence_6_digits() -> binary(). unicode_escape_sequence_6_digits() -> <<"\x{10abcd}"/utf8>>. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__todo__named.snap ================================================ --- source: compiler-core/src/erlang/tests/todo.rs expression: "\npub fn main() {\n todo as \"testing\"\n}\n" --- ----- SOURCE CODE pub fn main() { todo as "testing" } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> any(). main() -> erlang:error(#{gleam_error => todo, message => <<"testing"/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 3}). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__todo__piped.snap ================================================ --- source: compiler-core/src/erlang/tests/todo.rs expression: "\n pub fn main() {\n \"lets\"\n |> todo as \"pipe\"\n |> todo as \"other todo\"\n }\n " --- ----- SOURCE CODE pub fn main() { "lets" |> todo as "pipe" |> todo as "other todo" } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> any(). main() -> _pipe = <<"lets"/utf8>>, _pipe@1 = (erlang:error(#{gleam_error => todo, message => <<"pipe"/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 4}))(_pipe), (erlang:error(#{gleam_error => todo, message => <<"other todo"/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 5}))(_pipe@1). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__todo__plain.snap ================================================ --- source: compiler-core/src/erlang/tests/todo.rs expression: "\npub fn main() {\n todo\n}\n" --- ----- SOURCE CODE pub fn main() { todo } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> any(). main() -> erlang:error(#{gleam_error => todo, message => <<"`todo` expression evaluated. This code has not yet been implemented."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 3}). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__todo__todo_as.snap ================================================ --- source: compiler-core/src/erlang/tests/todo.rs expression: "\npub fn main() {\n todo as \"wibble\"\n}\n" --- ----- SOURCE CODE pub fn main() { todo as "wibble" } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> any(). main() -> erlang:error(#{gleam_error => todo, message => <<"wibble"/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 3}). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__todo__todo_as_function.snap ================================================ --- source: compiler-core/src/erlang/tests/todo.rs expression: "\npub fn retstring() {\n \"wibble\"\n}\npub fn main() {\n todo as { retstring() <> \"wobble\" }\n}\n" --- ----- SOURCE CODE pub fn retstring() { "wibble" } pub fn main() { todo as { retstring() <> "wobble" } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([retstring/0, main/0]). -file("project/test/my/mod.gleam", 2). -spec retstring() -> binary(). retstring() -> <<"wibble"/utf8>>. -file("project/test/my/mod.gleam", 5). -spec main() -> any(). main() -> erlang:error(#{gleam_error => todo, message => (<<(retstring())/binary, "wobble"/utf8>>), file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 6}). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__type_params__custom_type_named_args_count_once.snap ================================================ --- source: compiler-core/src/erlang/tests/type_params.rs expression: "\n pub type Wibble(a, b) {\n Wibble(a, b)\n }\n\n pub fn wibble() -> Wibble(a, a) {\n todo\n }\n " --- ----- SOURCE CODE pub type Wibble(a, b) { Wibble(a, b) } pub fn wibble() -> Wibble(a, a) { todo } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([wibble/0]). -export_type([wibble/2]). -type wibble(I, J) :: {wibble, I, J}. -file("project/test/my/mod.gleam", 6). -spec wibble() -> wibble(K, K). wibble() -> erlang:error(#{gleam_error => todo, message => <<"`todo` expression evaluated. This code has not yet been implemented."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"wibble"/utf8>>, line => 7}). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__type_params__custom_type_nested_named_args_count_once.snap ================================================ --- source: compiler-core/src/erlang/tests/type_params.rs expression: "\n pub type Wibble(a, b) {\n Wibble(a, b)\n }\n\n pub fn wibble() -> Wibble(a, Wibble(a, b)) {\n todo\n }\n " --- ----- SOURCE CODE pub type Wibble(a, b) { Wibble(a, b) } pub fn wibble() -> Wibble(a, Wibble(a, b)) { todo } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([wibble/0]). -export_type([wibble/2]). -type wibble(I, J) :: {wibble, I, J}. -file("project/test/my/mod.gleam", 6). -spec wibble() -> wibble(K, wibble(K, any())). wibble() -> erlang:error(#{gleam_error => todo, message => <<"`todo` expression evaluated. This code has not yet been implemented."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"wibble"/utf8>>, line => 7}). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__type_params__custom_type_nested_result_type_count_once.snap ================================================ --- source: compiler-core/src/erlang/tests/type_params.rs expression: "\n pub type Wibble(a) {\n Oops\n }\n\n pub fn wibble() -> Result(a, Wibble(a)) {\n todo\n }\n " --- ----- SOURCE CODE pub type Wibble(a) { Oops } pub fn wibble() -> Result(a, Wibble(a)) { todo } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([wibble/0]). -export_type([wibble/1]). -type wibble(I) :: oops | {gleam_phantom, I}. -file("project/test/my/mod.gleam", 6). -spec wibble() -> {ok, any()} | {error, wibble(any())}. wibble() -> erlang:error(#{gleam_error => todo, message => <<"`todo` expression evaluated. This code has not yet been implemented."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"wibble"/utf8>>, line => 7}). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__type_params__custom_type_tuple_type_params_count_twice.snap ================================================ --- source: compiler-core/src/erlang/tests/type_params.rs expression: "\n pub type Wibble(a, b) {\n Wibble(a, b)\n }\n\n pub fn wibble() -> #(a, Wibble(a, b)) {\n todo\n }\n " --- ----- SOURCE CODE pub type Wibble(a, b) { Wibble(a, b) } pub fn wibble() -> #(a, Wibble(a, b)) { todo } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([wibble/0]). -export_type([wibble/2]). -type wibble(I, J) :: {wibble, I, J}. -file("project/test/my/mod.gleam", 6). -spec wibble() -> {K, wibble(K, any())}. wibble() -> erlang:error(#{gleam_error => todo, message => <<"`todo` expression evaluated. This code has not yet been implemented."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"wibble"/utf8>>, line => 7}). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__type_params__nested_result_type_count_once.snap ================================================ --- source: compiler-core/src/erlang/tests/type_params.rs expression: "\n pub fn wibble() -> Result(a, Result(a, b)) {\n todo\n }\n " --- ----- SOURCE CODE pub fn wibble() -> Result(a, Result(a, b)) { todo } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([wibble/0]). -file("project/test/my/mod.gleam", 2). -spec wibble() -> {ok, any()} | {error, {ok, any()} | {error, any()}}. wibble() -> erlang:error(#{gleam_error => todo, message => <<"`todo` expression evaluated. This code has not yet been implemented."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"wibble"/utf8>>, line => 3}). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__type_params__result_type_count_once.snap ================================================ --- source: compiler-core/src/erlang/tests/type_params.rs expression: "\n pub fn wibble() -> Result(a, a) {\n todo\n }\n " --- ----- SOURCE CODE pub fn wibble() -> Result(a, a) { todo } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([wibble/0]). -file("project/test/my/mod.gleam", 2). -spec wibble() -> {ok, any()} | {error, any()}. wibble() -> erlang:error(#{gleam_error => todo, message => <<"`todo` expression evaluated. This code has not yet been implemented."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"wibble"/utf8>>, line => 3}). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__type_params__result_type_inferred_count_once.snap ================================================ --- source: compiler-core/src/erlang/tests/type_params.rs expression: "\n pub fn wibble() {\n let assert Ok(_) = wobble()\n }\n\n pub type Wobble(a) {\n Wobble\n }\n\n pub fn wobble() -> Result(a, Wobble(a)) {\n todo\n }\n " --- ----- SOURCE CODE pub fn wibble() { let assert Ok(_) = wobble() } pub type Wobble(a) { Wobble } pub fn wobble() -> Result(a, Wobble(a)) { todo } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([wobble/0, wibble/0]). -export_type([wobble/1]). -type wobble(I) :: wobble | {gleam_phantom, I}. -file("project/test/my/mod.gleam", 10). -spec wobble() -> {ok, any()} | {error, wobble(any())}. wobble() -> erlang:error(#{gleam_error => todo, message => <<"`todo` expression evaluated. This code has not yet been implemented."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"wobble"/utf8>>, line => 11}). -file("project/test/my/mod.gleam", 2). -spec wibble() -> {ok, any()} | {error, wobble(any())}. wibble() -> _assert_subject = wobble(), case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"wibble"/utf8>>, line => 3, value => _assert_fail, start => 39, 'end' => 66, pattern_start => 50, pattern_end => 55}) end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__type_params__tuple_type_params_count_twice.snap ================================================ --- source: compiler-core/src/erlang/tests/type_params.rs expression: "\n pub fn wibble() -> #(a, b) {\n todo\n }\n " --- ----- SOURCE CODE pub fn wibble() -> #(a, b) { todo } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([wibble/0]). -file("project/test/my/mod.gleam", 2). -spec wibble() -> {any(), any()}. wibble() -> erlang:error(#{gleam_error => todo, message => <<"`todo` expression evaluated. This code has not yet been implemented."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"wibble"/utf8>>, line => 3}). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__use___arity_1.snap ================================================ --- source: compiler-core/src/erlang/tests/use_.rs expression: "\npub fn main() {\n use <- pair()\n 123\n}\n\nfn pair(f) {\n let x = f()\n #(x, x)\n}\n" --- ----- SOURCE CODE pub fn main() { use <- pair() 123 } fn pair(f) { let x = f() #(x, x) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 7). -spec pair(fun(() -> L)) -> {L, L}. pair(F) -> X = F(), {X, X}. -file("project/test/my/mod.gleam", 2). -spec main() -> {integer(), integer()}. main() -> pair(fun() -> 123 end). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__use___arity_2.snap ================================================ --- source: compiler-core/src/erlang/tests/use_.rs expression: "\npub fn main() {\n use <- pair(1.0)\n 123\n}\n\nfn pair(x, f) {\n let y = f()\n #(x, y)\n}\n" --- ----- SOURCE CODE pub fn main() { use <- pair(1.0) 123 } fn pair(x, f) { let y = f() #(x, y) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 7). -spec pair(J, fun(() -> M)) -> {J, M}. pair(X, F) -> Y = F(), {X, Y}. -file("project/test/my/mod.gleam", 2). -spec main() -> {float(), integer()}. main() -> pair(1.0, fun() -> 123 end). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__use___arity_3.snap ================================================ --- source: compiler-core/src/erlang/tests/use_.rs expression: "\npub fn main() {\n use <- trip(1.0, \"\")\n 123\n}\n\nfn trip(x, y, f) {\n let z = f()\n #(x, y, z)\n}\n" --- ----- SOURCE CODE pub fn main() { use <- trip(1.0, "") 123 } fn trip(x, y, f) { let z = f() #(x, y, z) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 7). -spec trip(J, K, fun(() -> N)) -> {J, K, N}. trip(X, Y, F) -> Z = F(), {X, Y, Z}. -file("project/test/my/mod.gleam", 2). -spec main() -> {float(), binary(), integer()}. main() -> trip(1.0, <<""/utf8>>, fun() -> 123 end). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__use___no_callback_body.snap ================================================ --- source: compiler-core/src/erlang/tests/use_.rs expression: "\npub fn main() {\n let thingy = fn(f) { f() }\n use <- thingy()\n}\n" --- ----- SOURCE CODE pub fn main() { let thingy = fn(f) { f() } use <- thingy() } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> any(). main() -> Thingy = fun(F) -> F() end, Thingy(fun() -> erlang:error(#{gleam_error => todo, message => <<"`todo` expression evaluated. This code has not yet been implemented."/utf8>>, file => <>, module => <<"my/mod"/utf8>>, function => <<"main"/utf8>>, line => 4}) end). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__use___pipeline_that_returns_fn.snap ================================================ --- source: compiler-core/src/erlang/tests/use_.rs expression: "\npub fn main() {\n use <- 1 |> add\n 1\n}\n\npub fn add(x) {\n fn(f) { f() + x }\n}\n" --- ----- SOURCE CODE pub fn main() { use <- 1 |> add 1 } pub fn add(x) { fn(f) { f() + x } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([add/1, main/0]). -file("project/test/my/mod.gleam", 7). -spec add(integer()) -> fun((fun(() -> integer())) -> integer()). add(X) -> fun(F) -> F() + X end. -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> begin _pipe = 1, add(_pipe) end(fun() -> 1 end). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__variables__anon_external_fun_name_escaping.snap ================================================ --- source: compiler-core/src/erlang/tests/variables.rs expression: "\n@external(erlang, \"one.two\", \"three.four\")\nfn func() -> Nil\n\npub fn main() {\n func\n}" --- ----- SOURCE CODE @external(erlang, "one.two", "three.four") fn func() -> Nil pub fn main() { func } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 5). -spec main() -> fun(() -> nil). main() -> fun 'one.two':'three.four'/0. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__variables__blocks_are_scopes.snap ================================================ --- source: compiler-core/src/erlang/tests/variables.rs expression: "\npub fn main() {\n let x = 1\n {\n let x = 2\n }\n x\n}\n" --- ----- SOURCE CODE pub fn main() { let x = 1 { let x = 2 } x } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/0]). -file("project/test/my/mod.gleam", 2). -spec main() -> integer(). main() -> X = 1, begin X@1 = 2 end, X. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__variables__discarded.snap ================================================ --- source: compiler-core/src/erlang/tests/variables.rs expression: "pub fn go() {\n let _r = 1\n let _r = 2\n Nil\n}" --- ----- SOURCE CODE pub fn go() { let _r = 1 let _r = 2 Nil } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/0]). -file("project/test/my/mod.gleam", 1). -spec go() -> nil. go() -> _ = 1, _ = 2, nil. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__variables__module_const_vars.snap ================================================ --- source: compiler-core/src/erlang/tests/variables.rs expression: "const int = 42\nconst int_alias = int\npub fn use_int_alias() { int_alias }\n\nfn int_identity(i: Int) { i }\nconst int_identity_alias: fn(Int) -> Int = int_identity\npub fn use_int_identity_alias() { int_identity_alias(42) }\n\nconst compound: #(Int, fn(Int) -> Int, fn(Int) -> Int) = #(int, int_identity, int_identity_alias)\npub fn use_compound() { compound.1(compound.0) }\n" --- ----- SOURCE CODE const int = 42 const int_alias = int pub fn use_int_alias() { int_alias } fn int_identity(i: Int) { i } const int_identity_alias: fn(Int) -> Int = int_identity pub fn use_int_identity_alias() { int_identity_alias(42) } const compound: #(Int, fn(Int) -> Int, fn(Int) -> Int) = #(int, int_identity, int_identity_alias) pub fn use_compound() { compound.1(compound.0) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([use_int_alias/0, use_int_identity_alias/0, use_compound/0]). -file("project/test/my/mod.gleam", 5). -spec int_identity(integer()) -> integer(). int_identity(I) -> I. -file("project/test/my/mod.gleam", 3). -spec use_int_alias() -> integer(). use_int_alias() -> 42. -file("project/test/my/mod.gleam", 7). -spec use_int_identity_alias() -> integer(). use_int_identity_alias() -> int_identity(42). -file("project/test/my/mod.gleam", 10). -spec use_compound() -> integer(). use_compound() -> (erlang:element(2, {42, fun int_identity/1, fun int_identity/1}))( erlang:element(1, {42, fun int_identity/1, fun int_identity/1}) ). ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__variables__shadow_and_call.snap ================================================ --- source: compiler-core/src/erlang/tests/variables.rs expression: "\npub fn main(x) {\n fn(x) { x }(x)\n}\n" --- ----- SOURCE CODE pub fn main(x) { fn(x) { x }(x) } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -file("project/test/my/mod.gleam", 2). -spec main(K) -> K. main(X) -> X. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__variables__shadow_let.snap ================================================ --- source: compiler-core/src/erlang/tests/variables.rs expression: "\npub fn go(a) {\n case a {\n 99 -> {\n let a = a\n 1\n }\n _ -> a\n }\n}" --- ----- SOURCE CODE pub fn go(a) { case a { 99 -> { let a = a 1 } _ -> a } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([go/1]). -file("project/test/my/mod.gleam", 2). -spec go(integer()) -> integer(). go(A) -> case A of 99 -> A@1 = A, 1; _ -> A end. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__variables__shadow_param.snap ================================================ --- source: compiler-core/src/erlang/tests/variables.rs expression: "pub fn main(board) {\nfn(board) { board }\n board\n}" --- ----- SOURCE CODE pub fn main(board) { fn(board) { board } board } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -file("project/test/my/mod.gleam", 1). -spec main(I) -> I. main(Board) -> fun(Board@1) -> Board@1 end, Board. ================================================ FILE: compiler-core/src/erlang/tests/snapshots/gleam_core__erlang__tests__variables__shadow_pipe.snap ================================================ --- source: compiler-core/src/erlang/tests/variables.rs expression: "\npub fn main(x) {\n x\n |> fn(x) { x }\n}\n" --- ----- SOURCE CODE pub fn main(x) { x |> fn(x) { x } } ----- COMPILED ERLANG -module(my@mod). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "project/test/my/mod.gleam"). -export([main/1]). -file("project/test/my/mod.gleam", 2). -spec main(K) -> K. main(X) -> _pipe = X, _pipe. ================================================ FILE: compiler-core/src/erlang/tests/strings.rs ================================================ use crate::assert_erl; #[test] fn unicode1() { assert_erl!( r#" pub fn emoji() -> String { "\u{1f600}" } "#, ); } #[test] fn unicode2() { assert_erl!( r#" pub fn y_with_dieresis() -> String { "\u{0308}y" } "#, ); } #[test] fn unicode_concat1() { assert_erl!( r#" pub fn main(x) -> String { x <> "\u{0308}" } "#, ); } #[test] fn unicode_concat2() { assert_erl!( r#" pub fn main(x) -> String { x <> "\\u{0308}" } "#, ); } #[test] fn unicode_concat3() { assert_erl!( r#" pub fn main(x) -> String { x <> "\\\u{0308}" } "#, ); } #[test] fn not_unicode_escape_sequence() { // '\u'-s must be converted to '\x' in the Erlang codegen. // but '\\u'-s mustn't. assert_erl!( r#" pub fn not_unicode_escape_sequence() -> String { "\\u{03a9}" } "#, ); } #[test] fn not_unicode_escape_sequence2() { assert_erl!( r#" pub fn not_unicode_escape_sequence() -> String { "\\\\u{03a9}" } "#, ); } #[test] fn unicode3() { assert_erl!( r#" pub fn y_with_dieresis_with_slash() -> String { "\\\u{0308}y" } "#, ); } #[test] fn unicode_escape_sequence_6_digits() { assert_erl!( r#" pub fn unicode_escape_sequence_6_digits() -> String { "\u{10abcd}" } "#, ); } #[test] fn ascii_as_unicode_escape_sequence() { assert_erl!( r#" pub fn y() -> String { "\u{79}" } "#, ) } #[test] fn concat() { assert_erl!( r#" pub fn go(x, y) { x <> y } "#, ); } #[test] fn concat_3_variables() { assert_erl!( r#" pub fn go(x, y, z) { x <> y <> z } "#, ); } #[test] fn string_prefix() { assert_erl!( r#" pub fn go(x) { case x { "Hello, " <> name -> name _ -> "Unknown" } } "#, ); } #[test] fn string_prefix_assignment() { assert_erl!( r#" pub fn go(x) { case x { "Hello, " as greeting <> name -> greeting _ -> "Unknown" } } "#, ) } #[test] fn string_prefix_assignment_with_guard() { assert_erl!( r#" pub fn go(x) { case x { "Hello, " as greeting <> name if name == "Dude" -> greeting <> "Mate" "Hello, " as greeting <> name -> greeting _ -> "Unknown" } } "#, ) } // https://github.com/gleam-lang/gleam/issues/3126 #[test] fn string_prefix_assignment_with_escape_sequences() { assert_erl!( r#" pub fn go(x) { let _ = case x { "\f" as start <> rest -> "test" "\n" as start <> rest -> "test" "\r" as start <> rest -> "test" "\t" as start <> rest -> "test" "\"" as start <> rest -> "test" "\\" as start <> rest -> "test" "\f \n \r \t \" \\" as start <> rest -> "control chars with prefix assignment" "\u{9}" as start <> rest -> "test" "\u{000009}" as start <> rest -> "test" "\u{21}" as start <> rest -> "test" "\u{100}" as start <> rest -> "test" "\u{1000}" as start <> rest -> "test" "\u{1F600}" as start <> rest -> "test" "\u{1f600}" as start <> rest -> "test" "\u{01F600}" as start <> rest -> "test" "\u{01f600}" as start <> rest -> "test" "\u{9} \u{000009} \u{21} \u{100} \u{1000} \u{1F600} \u{01F600}" as start <> rest -> "test" _ -> "Unknown" } } "#, ) } #[test] fn string_prefix_with_escape_sequences() { assert_erl!( r#" pub fn go(x) { let _ = case x { "\f" <> rest -> "test" "\n" <> rest -> "test" "\r" <> rest -> "test" "\t" <> rest -> "test" "\"" <> rest -> "test" "\\" <> rest -> "test" "\f \n \r \t \" \\" <> rest -> "control chars with prefix assignment" "\u{9}" <> rest -> "test" "\u{000009}" <> rest -> "test" "\u{21}" <> rest -> "test" "\u{100}" <> rest -> "test" "\u{1000}" <> rest -> "test" "\u{1F600}" <> rest -> "test" "\u{1f600}" <> rest -> "test" "\u{01F600}" <> rest -> "test" "\u{01f600}" <> rest -> "test" "\u{9} \u{000009} \u{21} \u{100} \u{1000} \u{1F600} \u{01F600}" <> rest -> "test" _ -> "Unknown" } } "#, ) } #[test] fn string_prefix_assignment_not_unicode_escape_sequence() { assert_erl!( r#" pub fn go(x) { let _ = case x { "\\u{9}" as start <> rest -> "test" "\\u{000009}" as start <> rest -> "test" "\\u{21}" as start <> rest -> "test" "\\u{100}" as start <> rest -> "test" "\\u{1000}" as start <> rest -> "test" "\\u{1F600}" as start <> rest -> "test" "\\u{1f600}" as start <> rest -> "test" "\\u{01F600}" as start <> rest -> "test" "\\u{01f600}" as start <> rest -> "test" "\\u{9} \\u{000009} \\u{21} \\u{100} \\u{1000} \\u{1F600} \\u{01F600}" as start <> rest -> "test" _ -> "Unknown" } } "#, ) } #[test] fn string_prefix_not_unicode_escape_sequence() { assert_erl!( r#" pub fn go(x) { let _ = case x { "\\u{9}" <> rest -> "test" "\\u{000009}" <> rest -> "test" "\\u{21}" <> rest -> "test" "\\u{100}" <> rest -> "test" "\\u{1000}" <> rest -> "test" "\\u{1F600}" <> rest -> "test" "\\u{1f600}" <> rest -> "test" "\\u{01F600}" <> rest -> "test" "\\u{01f600}" <> rest -> "test" "\\u{9} \\u{000009} \\u{21} \\u{100} \\u{1000} \\u{1F600} \\u{01F600}" <> rest -> "test" _ -> "Unknown" } } "#, ) } // https://github.com/gleam-lang/gleam/issues/2471 #[test] fn string_prefix_assignment_with_multiple_subjects() { assert_erl!( r#" pub fn go(x) { case x { "1" as digit <> _ | "2" as digit <> _ -> digit _ -> "Unknown" } } "#, ) } #[test] fn string_prefix_shadowing() { assert_erl!( r#" pub fn go(x) { case x { "Hello, " as x <> name -> x _ -> "Unknown" } } "#, ) } #[test] fn rest_variable_rewriting() { // This test checks that the variable on the right hand side of <> has // it's name written correctly when it shadows an existing variable assert_erl!( r#" pub fn go(x) { case x { "Hello, " <> x -> x _ -> "Unknown" } } "#, ); } #[test] fn discard_concat_rest_pattern() { // We can discard the right hand side, it parses and type checks ok assert_erl!( r#" pub fn go(x) { case x { "Hello, " <> _ -> Nil _ -> Nil } } "#, ); } #[test] fn string_of_number_concat() { assert_erl!( r#" pub fn go(x) { x <> "1" } "#, ); } #[test] fn concat_function_call() { assert_erl!( r#" fn x() { "" } pub fn go() { x() <> x() } "#, ); } #[test] fn concat_constant() { assert_erl!( r#" const a = "Hello, " const b = "Joe!" pub fn go() { a <> b } "#, ); } #[test] fn concat_constant_fn() { assert_erl!( r#" const cs = s fn s() { "s" } pub fn go() { cs() <> cs() } "#, ); } #[test] fn pipe_concat() { assert_erl!( r#" fn id(x) { x } pub fn main() { { "" |> id } <> { "" |> id } } "#, ); } #[test] fn assert_string_prefix() { assert_erl!( r#" pub fn main(x) { let assert "m-" <> rest = x rest } "#, ); } #[test] fn assert_string_prefix_discar() { assert_erl!( r#" pub fn main(x) { let assert "m-" <> _ = x } "#, ); } #[test] fn assert_const_concat() { assert_erl!( r#" const cute = "cute" const cute_bee = cute <> "bee" pub fn main() { cute_bee } "# ); } #[test] fn assert_const_concat_many_strings() { assert_erl!( r#" const big_concat = "a" <> "b" <> "c" <> "d" <> "e" <> "f" <> "g" <> "h" <> "i" <> "j" <> "k" <> "l" <> "m" <> "n" <> "o" <> "p" <> "q" <> "r" <> "s" <> "t" <> "u" <> "v" <> "w" <> "x" <> "y" <> "z" pub fn main() { big_concat } "# ); } #[test] fn assert_const_concat_many_strings_in_list() { assert_erl!( r#" const big_concat_list = ["a" <> "b" <> "c" <> "d" <> "e" <> "f" <> "g" <> "h" <> "i" <> "j" <> "k" <> "l" <> "m" <> "n" <> "o" <> "p" <> "q" <> "r" <> "s" <> "t" <> "u" <> "v" <> "w" <> "x" <> "y" <> "z"] pub fn main() { big_concat_list } "# ); } #[test] fn assert_const_concat_other_const_concat() { assert_erl!( r#" const cute_bee = "cute" <> "bee" const cute_cute_bee_buzz = cute_bee <> "buzz" pub fn main() { cute_cute_bee_buzz } "# ); } ================================================ FILE: compiler-core/src/erlang/tests/todo.rs ================================================ use crate::assert_erl; #[test] fn plain() { assert_erl!( r#" pub fn main() { todo } "# ); } #[test] fn todo_as() { assert_erl!( r#" pub fn main() { todo as "wibble" } "# ); } #[test] fn named() { assert_erl!( r#" pub fn main() { todo as "testing" } "# ); } #[test] fn todo_as_function() { assert_erl!( r#" pub fn retstring() { "wibble" } pub fn main() { todo as { retstring() <> "wobble" } } "# ); } #[test] fn piped() { assert_erl!( r#" pub fn main() { "lets" |> todo as "pipe" |> todo as "other todo" } "# ); } ================================================ FILE: compiler-core/src/erlang/tests/type_params.rs ================================================ use crate::assert_erl; #[test] fn result_type_inferred_count_once() { assert_erl!( " pub fn wibble() { let assert Ok(_) = wobble() } pub type Wobble(a) { Wobble } pub fn wobble() -> Result(a, Wobble(a)) { todo } " ); } #[test] fn result_type_count_once() { assert_erl!( " pub fn wibble() -> Result(a, a) { todo } " ); } #[test] fn nested_result_type_count_once() { assert_erl!( " pub fn wibble() -> Result(a, Result(a, b)) { todo } " ); } #[test] fn custom_type_nested_result_type_count_once() { assert_erl!( " pub type Wibble(a) { Oops } pub fn wibble() -> Result(a, Wibble(a)) { todo } " ); } #[test] fn tuple_type_params_count_twice() { assert_erl!( " pub fn wibble() -> #(a, b) { todo } " ); } #[test] fn custom_type_named_args_count_once() { assert_erl!( " pub type Wibble(a, b) { Wibble(a, b) } pub fn wibble() -> Wibble(a, a) { todo } " ); } #[test] fn custom_type_nested_named_args_count_once() { assert_erl!( " pub type Wibble(a, b) { Wibble(a, b) } pub fn wibble() -> Wibble(a, Wibble(a, b)) { todo } " ); } #[test] fn custom_type_tuple_type_params_count_twice() { assert_erl!( " pub type Wibble(a, b) { Wibble(a, b) } pub fn wibble() -> #(a, Wibble(a, b)) { todo } " ); } ================================================ FILE: compiler-core/src/erlang/tests/use_.rs ================================================ use crate::assert_erl; #[test] fn arity_1() { assert_erl!( r#" pub fn main() { use <- pair() 123 } fn pair(f) { let x = f() #(x, x) } "#, ) } #[test] fn arity_2() { assert_erl!( r#" pub fn main() { use <- pair(1.0) 123 } fn pair(x, f) { let y = f() #(x, y) } "#, ) } #[test] fn arity_3() { assert_erl!( r#" pub fn main() { use <- trip(1.0, "") 123 } fn trip(x, y, f) { let z = f() #(x, y, z) } "#, ) } #[test] fn no_callback_body() { assert_erl!( r#" pub fn main() { let thingy = fn(f) { f() } use <- thingy() } "# ); } // https://github.com/gleam-lang/gleam/issues/1863 #[test] fn pipeline_that_returns_fn() { assert_erl!( r#" pub fn main() { use <- 1 |> add 1 } pub fn add(x) { fn(f) { f() + x } } "# ); } ================================================ FILE: compiler-core/src/erlang/tests/variables.rs ================================================ use crate::assert_erl; #[test] fn shadow_let() { // https://github.com/gleam-lang/gleam/issues/333 assert_erl!( r#" pub fn go(a) { case a { 99 -> { let a = a 1 } _ -> a } }"# ); } #[test] fn shadow_param() { // https://github.com/gleam-lang/gleam/issues/772 assert_erl!( "pub fn main(board) { fn(board) { board } board }" ); } #[test] fn shadow_and_call() { // https://github.com/gleam-lang/gleam/issues/762 assert_erl!( r#" pub fn main(x) { fn(x) { x }(x) } "# ); } #[test] fn shadow_pipe() { assert_erl!( r#" pub fn main(x) { x |> fn(x) { x } } "# ); } #[test] fn discarded() { // https://github.com/gleam-lang/gleam/issues/788 assert_erl!( r#"pub fn go() { let _r = 1 let _r = 2 Nil }"# ); } #[test] fn anon_external_fun_name_escaping() { // https://github.com/gleam-lang/gleam/issues/1418 assert_erl!( r#" @external(erlang, "one.two", "three.four") fn func() -> Nil pub fn main() { func }"# ); } #[test] fn module_const_vars() { assert_erl!( "const int = 42 const int_alias = int pub fn use_int_alias() { int_alias } fn int_identity(i: Int) { i } const int_identity_alias: fn(Int) -> Int = int_identity pub fn use_int_identity_alias() { int_identity_alias(42) } const compound: #(Int, fn(Int) -> Int, fn(Int) -> Int) = #(int, int_identity, int_identity_alias) pub fn use_compound() { compound.1(compound.0) } " ) } #[test] fn blocks_are_scopes() { assert_erl!( " pub fn main() { let x = 1 { let x = 2 } x } " ) } ================================================ FILE: compiler-core/src/erlang/tests.rs ================================================ use std::time::SystemTime; use camino::Utf8PathBuf; use crate::analyse::TargetSupport; use crate::config::PackageConfig; use crate::type_::PRELUDE_MODULE_NAME; use crate::warning::WarningEmitter; use crate::{build, inline}; use crate::{ build::{Origin, Target}, erlang::module, line_numbers::LineNumbers, uid::UniqueIdGenerator, warning::TypeWarningEmitter, }; use camino::Utf8Path; mod assert; mod bit_arrays; mod case; mod conditional_compilation; mod consts; mod custom_types; mod documentation; mod echo; mod external_fn; mod functions; mod guards; mod inlining; mod let_assert; mod numbers; mod panic; mod patterns; mod pipes; mod records; mod reserved; mod strings; mod todo; mod type_params; mod use_; mod variables; pub fn compile_test_project( src: &str, src_path: &str, dependencies: Vec<(&str, &str, &str)>, ) -> String { let mut modules = im::HashMap::new(); let ids = UniqueIdGenerator::new(); // DUPE: preludeinsertion // TODO: Currently we do this here and also in the tests. It would be better // to have one place where we create all this required state for use in each // place. let _ = modules.insert( PRELUDE_MODULE_NAME.into(), crate::type_::build_prelude(&ids), ); let mut direct_dependencies = std::collections::HashMap::from_iter(vec![]); for (dep_package, dep_name, dep_src) in dependencies { let mut dep_config = PackageConfig::default(); dep_config.name = dep_package.into(); let parsed = crate::parse::parse_module( Utf8PathBuf::from("test/path"), dep_src, &WarningEmitter::null(), ) .expect("dep syntax error"); let mut ast = parsed.module; ast.name = dep_name.into(); let line_numbers = LineNumbers::new(dep_src); let dep = crate::analyse::ModuleAnalyzerConstructor::<()> { target: Target::Erlang, ids: &ids, origin: Origin::Src, importable_modules: &modules, warnings: &TypeWarningEmitter::null(), direct_dependencies: &std::collections::HashMap::new(), dev_dependencies: &std::collections::HashSet::new(), target_support: TargetSupport::NotEnforced, package_config: &dep_config, } .infer_module(ast, line_numbers, "".into()) .expect("should successfully infer dep Erlang"); let _ = modules.insert(dep_name.into(), dep.type_info); let _ = direct_dependencies.insert(dep_package.into(), ()); } let path = Utf8PathBuf::from(src_path); let parsed = crate::parse::parse_module(path.clone(), src, &WarningEmitter::null()) .expect("syntax error"); let mut config = PackageConfig::default(); config.name = "thepackage".into(); let mut ast = parsed.module; ast.name = "my/mod".into(); let line_numbers = LineNumbers::new(src); let ast = crate::analyse::ModuleAnalyzerConstructor::<()> { target: Target::Erlang, ids: &ids, origin: Origin::Src, importable_modules: &modules, warnings: &TypeWarningEmitter::null(), direct_dependencies: &direct_dependencies, dev_dependencies: &std::collections::HashSet::new(), target_support: TargetSupport::NotEnforced, package_config: &config, } .infer_module(ast, line_numbers, path.clone()) .expect("should successfully infer root Erlang"); let ast = inline::module(ast, &modules); // After building everything we still need to attach the module comments, to // do that we're reusing the `attach_doc_and_module_comments` that's used // for the real thing. We just have to make a placeholder module wrapping // the parsed ast and call the function! let mut built_module = build::Module { name: "my/mod".into(), code: src.into(), mtime: SystemTime::UNIX_EPOCH, input_path: path, origin: Origin::Src, ast, extra: parsed.extra, dependencies: vec![], }; let root = Utf8Path::new("/root"); built_module.attach_doc_and_module_comments(); let line_numbers = LineNumbers::new(src); module(&built_module.ast, &line_numbers, root) .unwrap() .replace( std::include_str!("../../templates/echo.erl"), "% ...omitted code from `templates/echo.erl`...", ) } #[macro_export] macro_rules! assert_erl { ($(($dep_package:expr, $dep_name:expr, $dep_src:expr)),+, $src:literal $(,)?) => {{ let compiled = $crate::erlang::tests::compile_test_project( $src, "/root/project/test/my/mod.gleam", vec![$(($dep_package, $dep_name, $dep_src)),*], ); let output = format!( "----- SOURCE CODE\n{}\n\n----- COMPILED ERLANG\n{}", $src, compiled ); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }}; ($src:expr $(,)?) => {{ let compiled = $crate::erlang::tests::compile_test_project( $src, "/root/project/test/my/mod.gleam", Vec::new(), ); let output = format!( "----- SOURCE CODE\n{}\n\n----- COMPILED ERLANG\n{}", $src, compiled, ); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }}; } #[test] fn integration_test() { assert_erl!( r#"pub fn go() { let x = #(100000000000000000, #(2000000000, 3000000000000, 40000000000), 50000, 6000000000) x }"# ); } #[test] fn integration_test0_1() { assert_erl!( r#"pub fn go() { let y = 1 let y = 2 y }"# ); } #[test] fn integration_test0_2() { // hex, octal, and binary literals assert_erl!( r#"pub fn go() { let fifteen = 0xF let nine = 0o11 let ten = 0b1010 fifteen }"# ); } #[test] fn integration_test0_3() { assert_erl!( r#"pub fn go() { let y = 1 let y = 2 y }"# ); } #[test] fn integration_test1() { assert_erl!(r#"pub fn t() { True }"#); } #[test] fn integration_test1_1() { assert_erl!( r#"pub type Money { Pound(Int) } pub fn pound(x) { Pound(x) }"# ); } #[test] fn integration_test1_2() { assert_erl!(r#"pub fn loop() { loop() }"#); } #[test] fn integration_test1_4() { assert_erl!( r#"fn inc(x) { x + 1 } pub fn go() { 1 |> inc |> inc |> inc }"# ); } #[test] fn integration_test1_5() { assert_erl!( r#"fn add(x, y) { x + y } pub fn go() { 1 |> add(_, 1) |> add(2, _) |> add(_, 3) }"# ); } #[test] fn integration_test1_6() { assert_erl!( r#"pub fn and(x, y) { x && y } pub fn or(x, y) { x || y } pub fn remainder(x, y) { x % y } pub fn fdiv(x, y) { x /. y } "# ); } #[test] fn integration_test2() { assert_erl!( r#"pub fn second(list) { case list { [x, y] -> y z -> 1 } } pub fn tail(list) { case list { [x, ..xs] -> xs z -> list } } "# ); } #[test] fn integration_test5() { assert_erl!( "pub fn tail(list) { case list { [x, ..] -> x _ -> 0 } }" ); } #[test] fn integration_test6() { assert_erl!(r#"pub fn x() { let x = 1 let x = x + 1 x }"#); } #[test] fn integration_test8() { // Translation of Float-specific BinOp into variable-type Erlang term comparison. assert_erl!(r#"pub fn x() { 1. <. 2.3 }"#); } #[test] fn integration_test9() { // Custom type creation assert_erl!( r#"pub type Pair(x, y) { Pair(x: x, y: y) } pub fn x() { Pair(1, 2) Pair(3., 4.) }"# ); } #[test] fn integration_test10() { assert_erl!( r#"pub type Null { Null } pub fn x() { Null }"# ); } #[test] fn integration_test3() { assert_erl!( r#"pub type Point { Point(x: Int, y: Int) } pub fn y() { fn() { Point }()(4, 6) }"# ); } #[test] fn integration_test11() { assert_erl!( r#"pub type Point { Point(x: Int, y: Int) } pub fn x() { Point(x: 4, y: 6) Point(y: 1, x: 9) }"# ); } #[test] fn integration_test12() { assert_erl!( r#"pub type Point { Point(x: Int, y: Int) } pub fn x(y) { let Point(a, b) = y a }"# ); } //https://github.com/gleam-lang/gleam/issues/1106 #[test] fn integration_test13() { assert_erl!( r#"pub type State{ Start(Int) End(Int) } pub fn build(constructor : fn(Int) -> a) -> a { constructor(1) } pub fn main() { build(End) }"# ); } #[test] fn integration_test16() { assert_erl!( r#"fn go(x xx, y yy) { xx } pub fn x() { go(x: 1, y: 2) go(y: 3, x: 4) }"# ); } #[test] fn integration_test17() { // https://github.com/gleam-lang/gleam/issues/289 assert_erl!( r#" pub type User { User(id: Int, name: String, age: Int) } pub fn create_user(user_id) { User(age: 22, id: user_id, name: "") } "# ); } #[test] fn integration_test18() { assert_erl!(r#"pub fn run() { case 1, 2 { a, b -> a } }"#); } #[test] fn integration_test19() { assert_erl!( r#"pub type X { X(x: Int, y: Float) } pub fn x() { X(x: 1, y: 2.) X(y: 3., x: 4) }"# ); } #[test] fn integration_test20() { assert_erl!( r#" pub fn go(a) { let a = a + 1 a } "# ); } #[test] fn integration_test21() { assert_erl!( r#" pub fn go(a) { let a = 1 a } "# ); } #[test] fn integration_test22() { // https://github.com/gleam-lang/gleam/issues/358 assert_erl!( r#" pub fn factory(f, i) { f(i) } pub type Box { Box(i: Int) } pub fn main() { factory(Box, 0) } "# ); } #[test] fn integration_test23() { // https://github.com/gleam-lang/gleam/issues/384 assert_erl!( r#" pub fn main(args) { case args { _ -> { let a = 1 a } } let a = 2 a } "# ); } #[test] fn binop_parens() { // Parentheses are added for binop subexpressions assert_erl!( r#" pub fn main() { let a = 2 * {3 + 1} / 2 let b = 5 + 3 / 3 * 2 - 6 * 4 b } "# ); } #[test] fn field_access_function_call() { // Parentheses are added when calling functions returned by record access assert_erl!( r#" pub type FnBox { FnBox(f: fn(Int) -> Int) } pub fn main() { let b = FnBox(f: fn(x) { x }) b.f(5) } "# ); } #[test] fn field_access_function_call1() { // Parentheses are added when calling functions returned by tuple access assert_erl!( r#" pub fn main() { let t = #(fn(x) { x }) t.0(5) } "# ); } // https://github.com/gleam-lang/gleam/issues/777 #[test] fn block_assignment() { assert_erl!( r#" pub fn main() { let x = { 1 2 } x } "# ); } #[test] fn recursive_type() { // TODO: we should be able to generalise `id` and we should be // able to handle recursive types. Either of these type features // would make this module type check OK. assert_erl!( r#" fn id(x) { x } pub fn main() { id(id) } "# ); } #[test] fn tuple_access_in_guard() { assert_erl!( r#" pub fn main() { let key = 10 let x = [#(10, 2), #(1, 2)] case x { [first, ..rest] if first.0 == key -> "ok" _ -> "ko" } } "# ); } #[test] fn variable_name_underscores_preserved() { assert_erl!( "pub fn a(name_: String) -> String { let name__ = name_ let name = name__ let one_1 = 1 let one1 = one_1 name }" ); } #[test] fn allowed_string_escapes() { assert_erl!(r#"pub fn a() { "\n" "\r" "\t" "\\" "\"" "\\^" }"#); } // https://github.com/gleam-lang/gleam/issues/1006 #[test] fn keyword_constructors() { assert_erl!("pub type X { Div }"); } // https://github.com/gleam-lang/gleam/issues/1006 #[test] fn keyword_constructors1() { assert_erl!("pub type X { Fun(Int) }"); } #[test] fn discard_in_assert() { assert_erl!( "pub fn x(y) { let assert Ok(_) = y 1 }" ); } // https://github.com/gleam-lang/gleam/issues/1424 #[test] fn operator_pipe_right_hand_side() { assert_erl!( "fn id(x) { x } pub fn bool_expr(x, y) { y || x |> id }" ); } #[test] fn negation() { assert_erl!( "pub fn negate(x) { !x }" ) } #[test] fn negation_block() { assert_erl!( "pub fn negate(x) { !{ 123 x } }" ) } // https://github.com/gleam-lang/gleam/issues/1655 #[test] fn tail_maybe_expr_block() { assert_erl!( "pub fn a() { let fake_tap = fn(x) { x } let b = [99] [ 1, 2, ..b |> fake_tap ] } " ); } // https://github.com/gleam-lang/gleam/issues/1587 #[test] fn guard_variable_rewriting() { assert_erl!( "pub fn main() { case 1.0 { a if a <. 0.0 -> { let a = a a } _ -> 0.0 } } " ) } // https://github.com/gleam-lang/gleam/issues/1816 #[test] fn function_argument_shadowing() { assert_erl!( "pub fn main(a) { Box } pub type Box { Box(Int) } " ) } // https://github.com/gleam-lang/gleam/issues/2156 #[test] fn dynamic() { assert_erl!("pub type Dynamic") } // https://github.com/gleam-lang/gleam/issues/2166 #[test] fn inline_const_pattern_option() { assert_erl!( "pub fn main() { let fifteen = 15 let x = <<5:size(sixteen)>> case x { <<5:size(sixteen)>> -> <<5:size(sixteen)>> <<6:size(fifteen)>> -> <<5:size(fifteen)>> _ -> <<>> } } pub const sixteen = 16" ) } // https://github.com/gleam-lang/gleam/issues/2349 #[test] fn positive_zero() { assert_erl!( " pub fn main() { 0.0 } " ) } // https://github.com/gleam-lang/gleam/issues/3073 #[test] fn scientific_notation() { assert_erl!( " pub fn main() { 1.0e6 1.e6 } " ); } // https://github.com/gleam-lang/gleam/issues/3304 #[test] fn type_named_else() { assert_erl!( " pub type Else { Else } pub fn main() { Else } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn type_named_module_info() { assert_erl!( " pub type ModuleInfo { ModuleInfo } pub fn main() { ModuleInfo } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn function_named_module_info() { assert_erl!( " pub fn module_info() { 1 } pub fn main() { module_info() } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn function_named_module_info_imported() { assert_erl!( ( "some_module", "some_module", " pub fn module_info() { 1 } " ), " import some_module pub fn main() { some_module.module_info() } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn function_named_module_info_imported_qualified() { assert_erl!( ( "some_module", "some_module", " pub fn module_info() { 1 } " ), " import some_module.{module_info} pub fn main() { module_info() } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn constant_named_module_info() { assert_erl!( " pub const module_info = 1 pub fn main() { module_info } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn constant_named_module_info_imported() { assert_erl!( ( "some_module", "some_module", " pub const module_info = 1 " ), " import some_module pub fn main() { some_module.module_info } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn constant_named_module_info_imported_qualified() { assert_erl!( ( "some_module", "some_module", " pub const module_info = 1 " ), " import some_module.{module_info} pub fn main() { module_info } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn constant_named_module_info_with_function_inside() { assert_erl!( " pub fn function() { 1 } pub const module_info = function pub fn main() { module_info() } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn constant_named_module_info_with_function_inside_imported() { assert_erl!( ( "some_module", "some_module", " pub fn function() { 1 } pub const module_info = function " ), " import some_module pub fn main() { some_module.module_info() } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn constant_named_module_info_with_function_inside_imported_qualified() { assert_erl!( ( "some_module", "some_module", " pub fn function() { 1 } pub const module_info = function " ), " import some_module.{module_info} pub fn main() { module_info() } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn function_named_module_info_in_constant() { assert_erl!( " pub fn module_info() { 1 } pub const constant = module_info pub fn main() { constant() } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn function_named_module_info_in_constant_imported() { assert_erl!( ( "some_module", "some_module", " pub fn module_info() { 1 } pub const constant = module_info " ), " import some_module pub fn main() { some_module.constant() } " ); } // https://github.com/gleam-lang/gleam/issues/3382 #[test] fn function_named_module_info_in_constant_imported_qualified() { assert_erl!( ( "some_module", "some_module", " pub fn module_info() { 1 } pub const constant = module_info " ), " import some_module.{constant} pub fn main() { constant() } " ); } // https://github.com/gleam-lang/gleam/issues/3648 #[test] fn windows_file_escaping_bug() { let src = "pub fn main() { Nil }"; let path = "C:\\root\\project\\test\\my\\mod.gleam"; let output = compile_test_project(src, path, Vec::new()); insta::assert_snapshot!(insta::internals::AutoName, output, src); } // https://github.com/gleam-lang/gleam/issues/3315 #[test] fn bit_pattern_shadowing() { assert_erl!( " pub fn main() { let code = <<\"hello world\":utf8>> let pre = 1 case code { <> -> pre _ -> panic } } " ); } #[test] fn float_division_by_literal_zero() { assert_erl!( " pub fn main() { 1.0 /. 0.0 } " ); } #[test] fn float_division_by_literal_non_zero() { assert_erl!( " pub fn main() { 1.0 /. 2.0 } " ); } ================================================ FILE: compiler-core/src/erlang.rs ================================================ // TODO: Refactor this module to be methods on structs rather than free // functions with a load of arguments. See the JavaScript code generator and the // formatter for examples. mod pattern; #[cfg(test)] mod tests; use crate::build::{Target, module_erlang_name}; use crate::erlang::pattern::{PatternPrinter, StringPatternAssignment}; use crate::strings::{convert_string_escape_chars, to_snake_case}; use crate::type_::is_prelude_module; use crate::{ Result, ast::{Function, *}, docvec, line_numbers::LineNumbers, pretty::*, type_::{ ModuleValueConstructor, PatternConstructor, Type, TypeVar, TypedCallArg, ValueConstructor, ValueConstructorVariant, }, }; use camino::Utf8Path; use ecow::{EcoString, eco_format}; use itertools::Itertools; use regex::{Captures, Regex}; use std::collections::HashSet; use std::sync::OnceLock; use std::{collections::HashMap, ops::Deref, str::FromStr, sync::Arc}; use vec1::Vec1; const INDENT: isize = 4; const MAX_COLUMNS: isize = 80; fn module_name_atom(module: &str) -> Document<'static> { atom_string(module.replace('/', "@").into()) } #[derive(Debug, Clone)] struct Env<'a> { module: &'a str, function: &'a str, line_numbers: &'a LineNumbers, needs_function_docs: bool, echo_used: bool, current_scope_vars: im::HashMap, erl_function_scope_vars: im::HashMap, } impl<'env> Env<'env> { pub fn new(module: &'env str, function: &'env str, line_numbers: &'env LineNumbers) -> Self { let vars: im::HashMap<_, _> = std::iter::once(("_".into(), 0)).collect(); Self { current_scope_vars: vars.clone(), erl_function_scope_vars: vars, needs_function_docs: false, echo_used: false, line_numbers, function, module, } } pub fn local_var_name<'a>(&mut self, name: &str) -> Document<'a> { match self.current_scope_vars.get(name) { None => { let _ = self.current_scope_vars.insert(name.to_string(), 0); let _ = self.erl_function_scope_vars.insert(name.to_string(), 0); variable_name(name).to_doc() } Some(0) => variable_name(name).to_doc(), Some(n) => { use std::fmt::Write; let mut name = variable_name(name); write!(name, "@{n}").expect("pushing number suffix to name"); name.to_doc() } } } pub fn next_local_var_name<'a>(&mut self, name: &str) -> Document<'a> { let next = self.erl_function_scope_vars.get(name).map_or(0, |i| i + 1); let _ = self.erl_function_scope_vars.insert(name.to_string(), next); let _ = self.current_scope_vars.insert(name.to_string(), next); self.local_var_name(name) } } pub fn records(module: &TypedModule) -> Vec<(&str, String)> { module .definitions .custom_types .iter() .filter(|custom_type| { custom_type.publicity.is_public() && !module .unused_definition_positions .contains(&custom_type.location.start) }) .flat_map(|custom_type| &custom_type.constructors) .filter(|constructor| !constructor.arguments.is_empty()) .filter_map(|constructor| { constructor .arguments .iter() .map( |RecordConstructorArg { label, ast: _, location: _, type_, .. }| { label .as_ref() .map(|(_, label)| (label.as_str(), type_.clone())) }, ) .collect::>>() .map(|fields| (constructor.name.as_str(), fields)) }) .map(|(name, fields)| (name, record_definition(name, &fields))) .collect() } pub fn record_definition(name: &str, fields: &[(&str, Arc)]) -> String { let name = to_snake_case(name); let type_printer = TypePrinter::new("").var_as_any(); let fields = fields.iter().map(move |(name, type_)| { let type_ = type_printer.print(type_); docvec![atom_string((*name).into()), " :: ", type_.group()] }); let fields = break_("", "") .append(join(fields, break_(",", ", "))) .nest(INDENT) .append(break_("", "")) .group(); docvec!["-record(", atom_string(name), ", {", fields, "}).", line()] .to_pretty_string(MAX_COLUMNS) } pub fn module<'a>( module: &'a TypedModule, line_numbers: &'a LineNumbers, root: &'a Utf8Path, ) -> Result { Ok(module_document(module, line_numbers, root)?.to_pretty_string(MAX_COLUMNS)) } fn module_document<'a>( module: &'a TypedModule, line_numbers: &'a LineNumbers, root: &'a Utf8Path, ) -> Result> { let mut exports = vec![]; let mut type_defs = vec![]; let mut type_exports = vec![]; let header = "-module(" .to_doc() .append(module.erlang_name()) .append(").") .append(line()); // We need to know which private functions are referenced in importable // constants so that we can export them anyway in the generated Erlang. // This is because otherwise when the constant is used in another module it // would result in an error as it tries to reference this private function. let overridden_publicity = find_private_functions_referenced_in_importable_constants(module); for function in &module.definitions.functions { register_function_exports(function, &mut exports, &overridden_publicity); } for custom_type in &module.definitions.custom_types { register_custom_type_exports(custom_type, &mut type_exports, &mut type_defs, &module.name); } let exports = match (!exports.is_empty(), !type_exports.is_empty()) { (false, false) => return Ok(header), (true, false) => "-export([" .to_doc() .append(join(exports, ", ".to_doc())) .append("]).") .append(lines(2)), (true, true) => "-export([" .to_doc() .append(join(exports, ", ".to_doc())) .append("]).") .append(line()) .append("-export_type([") .to_doc() .append(join(type_exports, ", ".to_doc())) .append("]).") .append(lines(2)), (false, true) => "-export_type([" .to_doc() .append(join(type_exports, ", ".to_doc())) .append("]).") .append(lines(2)), }; let type_defs = if type_defs.is_empty() { nil() } else { join(type_defs, lines(2)).append(lines(2)) }; let src_path_full = &module.type_info.src_path; let src_path_relative = EcoString::from( src_path_full .strip_prefix(root) .unwrap_or(src_path_full) .as_str(), ) .replace("\\", "\\\\"); let mut needs_function_docs = false; let mut echo_used = false; let mut statements = vec![]; for function in &module.definitions.functions { if let Some((statement_document, env)) = module_function( function, &module.name, module.type_info.is_internal, line_numbers, src_path_relative.clone(), &module.unused_definition_positions, ) { needs_function_docs = needs_function_docs || env.needs_function_docs; echo_used = echo_used || env.echo_used; statements.push(statement_document); } } let module_doc = if module.type_info.is_internal { Some(hidden_module_doc().append(lines(2))) } else if module.documentation.is_empty() { None } else { Some(module_doc(&module.documentation).append(lines(2))) }; // We're going to need the documentation directives if any of the module's // functions need it, or if the module has a module comment that we want to // include in the generated Erlang source, or if the module is internal. let needs_doc_directive = needs_function_docs || module_doc.is_some(); let documentation_directive = if needs_doc_directive { "-if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif." .to_doc() .append(lines(2)) } else { nil() }; let module = docvec![ header, "-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).", line(), "-define(FILEPATH, \"", src_path_relative, "\").", line(), exports, documentation_directive, module_doc, type_defs, join(statements, lines(2)), ]; let module = if echo_used { module .append(lines(2)) .append(std::include_str!("../templates/echo.erl").to_doc()) } else { module }; Ok(module.append(line())) } fn register_function_exports( function: &TypedFunction, exports: &mut Vec>, overridden_publicity: &im::HashSet, ) { let Function { publicity, name: Some((_, name)), arguments, implementations, .. } = function else { return; }; // If the function isn't for this target then don't attempt to export it if implementations.supports(Target::Erlang) && (publicity.is_importable() || overridden_publicity.contains(name)) { let function_name = escape_erlang_existing_name(name); exports.push( atom_string(function_name.into()) .append("/") .append(arguments.len()), ) } } fn register_custom_type_exports( custom_type: &TypedCustomType, type_exports: &mut Vec>, type_defs: &mut Vec>, module_name: &str, ) { let TypedCustomType { name, constructors, opaque, typed_parameters, external_erlang, .. } = custom_type; // Erlang doesn't allow phantom type variables in type definitions but gleam does // so we check the type declaratinon against its constroctors and generate a phantom // value that uses the unused type variables. let type_var_usages = collect_type_var_usages(HashMap::new(), typed_parameters); let mut constructor_var_usages = HashMap::new(); for c in constructors { constructor_var_usages = collect_type_var_usages(constructor_var_usages, c.arguments.iter().map(|a| &a.type_)); } let phantom_vars: Vec<_> = type_var_usages .keys() .filter(|&id| !constructor_var_usages.contains_key(id)) .sorted() .map(|&id| Type::Var { type_: Arc::new(std::cell::RefCell::new(TypeVar::Generic { id })), }) .collect(); let phantom_vars_constructor = if !phantom_vars.is_empty() { let type_printer = TypePrinter::new(module_name); Some(tuple( std::iter::once("gleam_phantom".to_doc()) .chain(phantom_vars.iter().map(|pv| type_printer.print(pv))), )) } else { None }; // Type Exports type_exports.push( erl_safe_type_name(to_snake_case(name)) .to_doc() .append("/") .append(typed_parameters.len()), ); // Type definitions let definition = if constructors.is_empty() { if let Some((module, external_type, _location)) = external_erlang { let printer = TypePrinter::new(module_name); docvec![ module, ":", external_type, "(", join( typed_parameters .iter() .map(|parameter| printer.print(parameter)), ", ".to_doc() ), ")" ] } else { let constructors = std::iter::once("any()".to_doc()).chain(phantom_vars_constructor); join(constructors, break_(" |", " | ")) } } else { let constructors = constructors .iter() .map(|constructor| { let name = atom_string(to_snake_case(&constructor.name)); if constructor.arguments.is_empty() { name } else { let type_printer = TypePrinter::new(module_name); let arguments = constructor .arguments .iter() .map(|argument| type_printer.print(&argument.type_)); tuple(std::iter::once(name).chain(arguments)) } }) .chain(phantom_vars_constructor); join(constructors, break_(" |", " | ")) } .nest(INDENT); let type_printer = TypePrinter::new(module_name); let params = join( typed_parameters .iter() .map(|type_| type_printer.print(type_)), ", ".to_doc(), ); let doc = if *opaque { "-opaque " } else { "-type " } .to_doc() .append(erl_safe_type_name(to_snake_case(name))) .append("(") .append(params) .append(") :: ") .append(definition) .group() .append("."); type_defs.push(doc); } fn module_function<'a>( function: &'a TypedFunction, module: &'a str, is_internal_module: bool, line_numbers: &'a LineNumbers, src_path: EcoString, unused_definition_positions: &HashSet, ) -> Option<(Document<'a>, Env<'a>)> { // We don't generate any code for unused functions. if unused_definition_positions.contains(&function.location.start) { return None; } // Private external functions don't need to render anything, the underlying // Erlang implementation is used directly at the call site. if function.external_erlang.is_some() && function.publicity.is_private() { return None; } // If the function has no suitable Erlang implementation then there is nothing // to generate for it. if !function.implementations.supports(Target::Erlang) { return None; } let (_, function_name) = function .name .as_ref() .expect("A module's function must be named"); let function_name = escape_erlang_existing_name(function_name); let file_attribute = file_attribute(src_path, function, line_numbers); let mut env = Env::new(module, function_name, line_numbers); let var_usages = collect_type_var_usages( HashMap::new(), std::iter::once(&function.return_type).chain(function.arguments.iter().map(|a| &a.type_)), ); let type_printer = TypePrinter::new(module).with_var_usages(&var_usages); let arguments_spec = function .arguments .iter() .map(|a| type_printer.print(&a.type_)); let return_spec = type_printer.print(&function.return_type); let spec = fun_spec(function_name, arguments_spec, return_spec); let arguments = if function.external_erlang.is_some() { external_fun_arguments(&function.arguments, &mut env) } else { fun_arguments(&function.arguments, &mut env) }; let body = function .external_erlang .as_ref() .map(|(module, function, _location)| { docvec![ atom(module), ":", atom(escape_erlang_existing_name(function)), arguments.clone() ] }) .unwrap_or_else(|| statement_sequence(&function.body, &mut env)); let attributes = file_attribute; let attributes = if is_internal_module || function.publicity.is_internal() { // If a function is marked as internal or comes from an internal module // we want to hide its documentation in the Erlang shell! // So the doc directive will look like this: `-doc(false).` env.needs_function_docs = true; docvec![attributes, line(), hidden_function_doc()] } else { match &function.documentation { Some((_, documentation)) => { env.needs_function_docs = true; let doc_lines = documentation .trim_end() .split('\n') .map(EcoString::from) .collect_vec(); docvec![attributes, line(), function_doc(&doc_lines)] } _ => attributes, } }; Some(( docvec![ attributes, line(), spec, atom_string(escape_erlang_existing_name(function_name).into()), arguments, " ->", line().append(body).nest(INDENT).group(), ".", ], env, )) } fn file_attribute<'a>( path: EcoString, function: &'a TypedFunction, line_numbers: &'a LineNumbers, ) -> Document<'a> { let line = line_numbers.line_number(function.location.start); docvec!["-file(\"", path, "\", ", line, ")."] } enum DocCommentKind { Module, Function, } enum DocCommentContent<'a> { String(&'a Vec), False, } fn hidden_module_doc<'a>() -> Document<'a> { doc_attribute(DocCommentKind::Module, DocCommentContent::False) } fn module_doc<'a>(content: &Vec) -> Document<'a> { doc_attribute(DocCommentKind::Module, DocCommentContent::String(content)) } fn hidden_function_doc<'a>() -> Document<'a> { doc_attribute(DocCommentKind::Function, DocCommentContent::False) } fn function_doc<'a>(content: &Vec) -> Document<'a> { doc_attribute(DocCommentKind::Function, DocCommentContent::String(content)) } fn doc_attribute<'a>(kind: DocCommentKind, content: DocCommentContent<'_>) -> Document<'a> { let prefix = match kind { DocCommentKind::Module => "?MODULEDOC", DocCommentKind::Function => "?DOC", }; match content { DocCommentContent::False => prefix.to_doc().append("(false)."), DocCommentContent::String(doc_lines) => { let is_multiline_doc_comment = doc_lines.len() > 1; let doc_lines = join( doc_lines.iter().map(|line| { let line = line.replace("\\", "\\\\").replace("\"", "\\\""); docvec!["\"", line, "\\n\""] }), line(), ); if is_multiline_doc_comment { let nested_documentation = docvec![line(), doc_lines].nest(INDENT); docvec![prefix, "(", nested_documentation, line(), ")."] } else { docvec![prefix, "(", doc_lines, ")."] } } } } fn external_fun_arguments<'a>(arguments: &'a [TypedArg], env: &mut Env<'a>) -> Document<'a> { wrap_arguments(arguments.iter().map(|argument| { let name = match &argument.names { ArgNames::Discard { name, .. } | ArgNames::LabelledDiscard { name, .. } | ArgNames::Named { name, .. } | ArgNames::NamedLabelled { name, .. } => name, }; if name.chars().all(|c| c == '_') { env.next_local_var_name("argument") } else { env.next_local_var_name(name) } })) } fn fun_arguments<'a>(arguments: &'a [TypedArg], env: &mut Env<'a>) -> Document<'a> { wrap_arguments(arguments.iter().map(|argument| match &argument.names { ArgNames::Discard { .. } | ArgNames::LabelledDiscard { .. } => "_".to_doc(), ArgNames::Named { name, .. } | ArgNames::NamedLabelled { name, .. } => { env.next_local_var_name(name) } })) } fn wrap_arguments<'a, I>(arguments: I) -> Document<'a> where I: IntoIterator>, { break_("", "") .append(join(arguments, break_(",", ", "))) .nest(INDENT) .append(break_("", "")) .surround("(", ")") .group() } fn fun_spec<'a>( name: &'a str, arguments: impl IntoIterator>, return_: Document<'a>, ) -> Document<'a> { "-spec " .to_doc() .append(atom(name)) .append(wrap_arguments(arguments)) .append(" -> ") .append(return_) .append(".") .append(line()) .group() } fn atom_string(value: EcoString) -> Document<'static> { escape_atom_string(value).to_doc() } fn atom_pattern() -> &'static Regex { static ATOM_PATTERN: OnceLock = OnceLock::new(); ATOM_PATTERN.get_or_init(|| Regex::new(r"^[a-z][a-z0-9_@]*$").expect("atom RE regex")) } fn atom(value: &str) -> Document<'_> { if is_erlang_reserved_word(value) { // Escape because of keyword collision eco_format!("'{value}'").to_doc() } else if atom_pattern().is_match(value) { // No need to escape EcoString::from(value).to_doc() } else { // Escape because of characters contained eco_format!("'{value}'").to_doc() } } pub fn escape_atom_string(value: EcoString) -> EcoString { if is_erlang_reserved_word(&value) { // Escape because of keyword collision eco_format!("'{value}'") } else if atom_pattern().is_match(&value) { value } else { // Escape because of characters contained eco_format!("'{value}'") } } fn unicode_escape_sequence_pattern() -> &'static Regex { static PATTERN: OnceLock = OnceLock::new(); PATTERN.get_or_init(|| { Regex::new(r#"(\\+)(u)"#).expect("Unicode escape sequence regex cannot be constructed") }) } fn string_inner(value: &str) -> Document<'_> { let content = unicode_escape_sequence_pattern() // `\\u`-s should not be affected, so that "\\u..." is not converted to // "\\x...". That's why capturing groups is used to exclude cases that // shouldn't be replaced. .replace_all(value, |caps: &Captures<'_>| { let slashes = caps.get(1).map_or("", |m| m.as_str()); if slashes.len().is_multiple_of(2) { format!("{slashes}u") } else { format!("{slashes}x") } }); EcoString::from(content).to_doc() } fn string(value: &str) -> Document<'_> { string_inner(value).surround("<<\"", "\"/utf8>>") } fn string_length_utf8_bytes(str: &EcoString) -> usize { convert_string_escape_chars(str).len() } fn tuple<'a>(elements: impl IntoIterator>) -> Document<'a> { join(elements, break_(",", ", ")) .nest(INDENT) .surround("{", "}") .group() } fn const_string_concatenate_bit_array<'a>( elements: impl IntoIterator>, ) -> Document<'a> { join(elements, break_(",", ", ")) .nest(INDENT) .surround("<<", ">>") .group() } fn const_string_concatenate<'a>( left: &'a TypedConstant, right: &'a TypedConstant, env: &mut Env<'a>, ) -> Document<'a> { let left = const_string_concatenate_argument(left, env); let right = const_string_concatenate_argument(right, env); const_string_concatenate_bit_array([left, right]) } fn const_string_concatenate_inner<'a>( left: &'a TypedConstant, right: &'a TypedConstant, env: &mut Env<'a>, ) -> Document<'a> { let left = const_string_concatenate_argument(left, env); let right = const_string_concatenate_argument(right, env); join([left, right], break_(",", ", ")) } fn const_string_concatenate_argument<'a>( value: &'a TypedConstant, env: &mut Env<'a>, ) -> Document<'a> { match value { Constant::String { value, .. } => docvec!['"', string_inner(value), "\"/utf8"], Constant::Var { constructor: Some(constructor), .. } => match &constructor.variant { ValueConstructorVariant::ModuleConstant { literal: Constant::String { value, .. }, .. } => docvec!['"', string_inner(value), "\"/utf8"], ValueConstructorVariant::ModuleConstant { literal: Constant::StringConcatenation { left, right, .. }, .. } => const_string_concatenate_inner(left, right, env), ValueConstructorVariant::LocalVariable { .. } | ValueConstructorVariant::ModuleConstant { .. } | ValueConstructorVariant::ModuleFn { .. } | ValueConstructorVariant::Record { .. } => const_inline(value, env), }, Constant::StringConcatenation { left, right, .. } => { const_string_concatenate_inner(left, right, env) } Constant::Int { .. } | Constant::Float { .. } | Constant::Tuple { .. } | Constant::List { .. } | Constant::Record { .. } | Constant::RecordUpdate { .. } | Constant::BitArray { .. } | Constant::Var { .. } | Constant::Invalid { .. } => const_inline(value, env), } } fn string_concatenate<'a>( left: &'a TypedExpr, right: &'a TypedExpr, env: &mut Env<'a>, ) -> Document<'a> { let left = string_concatenate_argument(left, env); let right = string_concatenate_argument(right, env); bit_array([left, right]) } fn string_concatenate_argument<'a>(value: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> { match value { TypedExpr::Var { constructor: ValueConstructor { variant: ValueConstructorVariant::ModuleConstant { literal: Constant::String { value, .. }, .. }, .. }, .. } | TypedExpr::String { value, .. } => docvec!['"', string_inner(value), "\"/utf8"], TypedExpr::Var { name, constructor: ValueConstructor { variant: ValueConstructorVariant::LocalVariable { .. }, .. }, .. } => docvec![env.local_var_name(name), "/binary"], TypedExpr::BinOp { name: BinOp::Concatenate, .. } => docvec![expr(value, env), "/binary"], TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => docvec!["(", maybe_block_expr(value, env), ")/binary"], } } fn bit_array<'a>(elements: impl IntoIterator>) -> Document<'a> { join(elements, break_(",", ", ")) .nest(INDENT) .surround("<<", ">>") .group() } fn const_segment<'a>( value: &'a TypedConstant, options: &'a [TypedConstantBitArraySegmentOption], env: &mut Env<'a>, ) -> Document<'a> { let value_is_a_string_literal = matches!(value, Constant::String { .. }); let create_document = |env: &mut Env<'a>| { match value { // Skip the normal <> surrounds Constant::String { value, .. } => value.to_doc().surround("\"", "\""), // As normal Constant::Int { .. } | Constant::Float { .. } | Constant::BitArray { .. } => { const_inline(value, env) } // Wrap anything else in parentheses Constant::Tuple { .. } | Constant::List { .. } | Constant::Record { .. } | Constant::RecordUpdate { .. } | Constant::Var { .. } | Constant::StringConcatenation { .. } | Constant::Invalid { .. } => const_inline(value, env).surround("(", ")"), } }; let size = |value: &'a TypedConstant, env: &mut Env<'a>| { if let Constant::Int { .. } = value { Some(":".to_doc().append(const_inline(value, env))) } else { Some( ":".to_doc() .append(const_inline(value, env).surround("(", ")")), ) } }; let unit = |value: &'a u8| Some(eco_format!("unit:{value}").to_doc()); bit_array_segment( create_document, options, size, unit, value_is_a_string_literal, false, env, ) } enum Position { Tail, NotTail, } fn statement<'a>( statement: &'a TypedStatement, env: &mut Env<'a>, position: Position, ) -> Document<'a> { match statement { Statement::Expression(e) => expr(e, env), Statement::Assignment(a) => assignment(a, env, position), Statement::Use(use_) => expr(&use_.call, env), Statement::Assert(a) => assert(a, env), } } fn expr_segment<'a>( value: &'a TypedExpr, options: &'a [BitArrayOption], env: &mut Env<'a>, ) -> Document<'a> { let value_is_a_string_literal = matches!(value, TypedExpr::String { .. }); let create_document = |env: &mut Env<'a>| { match value { // Skip the normal <> surrounds and set the string literal flag TypedExpr::String { value, .. } => string_inner(value).surround("\"", "\""), // As normal TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::Var { .. } | TypedExpr::BitArray { .. } => expr(value, env), // Wrap anything else in parentheses TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => expr(value, env).surround("(", ")"), } }; let size = |expression: &'a TypedExpr, env: &mut Env<'a>| { if let TypedExpr::Int { value, .. } = expression { let v = value.replace("_", ""); let v = u64::from_str(&v).unwrap_or(0); Some(eco_format!(":{v}").to_doc()) } else { let inner_expr = maybe_block_expr(expression, env).surround("(", ")"); // The value of size must be a non-negative integer, we use lists:max here to ensure // it is at least 0; let value_guard = ":(lists:max([" .to_doc() .append(inner_expr) .append(", 0]))") .group(); Some(value_guard) } }; let unit = |value: &'a u8| Some(eco_format!("unit:{value}").to_doc()); bit_array_segment( create_document, options, size, unit, value_is_a_string_literal, false, env, ) } fn bit_array_segment<'a, Value: 'a, CreateDoc, SizeToDoc, UnitToDoc, State>( mut create_document: CreateDoc, options: &'a [BitArrayOption], mut size_to_doc: SizeToDoc, mut unit_to_doc: UnitToDoc, value_is_a_string_literal: bool, value_is_a_discard: bool, state: &mut State, ) -> Document<'a> where CreateDoc: FnMut(&mut State) -> Document<'a>, SizeToDoc: FnMut(&'a Value, &mut State) -> Option>, UnitToDoc: FnMut(&'a u8) -> Option>, { let mut size: Option> = None; let mut unit: Option> = None; let mut others = Vec::new(); // Erlang only allows valid codepoint integers to be used as values for utf segments // We want to support <> for all string variables, but <> is invalid // To work around this we use the binary type specifier for these segments instead let override_type = if !value_is_a_string_literal && !value_is_a_discard { Some("binary") } else { None }; for option in options { use BitArrayOption as Opt; if !others.is_empty() && !matches!(option, Opt::Size { .. } | Opt::Unit { .. }) { others.push("-".to_doc()); } match option { Opt::Utf8 { .. } => others.push(override_type.unwrap_or("utf8").to_doc()), Opt::Utf16 { .. } => others.push(override_type.unwrap_or("utf16").to_doc()), Opt::Utf32 { .. } => others.push(override_type.unwrap_or("utf32").to_doc()), Opt::Int { .. } => others.push("integer".to_doc()), Opt::Float { .. } => others.push("float".to_doc()), Opt::Bytes { .. } => others.push("binary".to_doc()), Opt::Bits { .. } => others.push("bitstring".to_doc()), Opt::Utf8Codepoint { .. } => others.push("utf8".to_doc()), Opt::Utf16Codepoint { .. } => others.push("utf16".to_doc()), Opt::Utf32Codepoint { .. } => others.push("utf32".to_doc()), Opt::Signed { .. } => others.push("signed".to_doc()), Opt::Unsigned { .. } => others.push("unsigned".to_doc()), Opt::Big { .. } => others.push("big".to_doc()), Opt::Little { .. } => others.push("little".to_doc()), Opt::Native { .. } => others.push("native".to_doc()), Opt::Size { value, .. } => size = size_to_doc(value, state), Opt::Unit { value, .. } => unit = unit_to_doc(value), } } let mut document = create_document(state); document = document.append(size); let others_is_empty = others.is_empty(); if !others_is_empty { document = document.append("/").append(others); } if unit.is_some() { if !others_is_empty { document = document.append("-").append(unit) } else { document = document.append("/").append(unit) } } document } fn block<'a>(statements: &'a Vec1, env: &mut Env<'a>) -> Document<'a> { if statements.len() == 1 && let Statement::Expression(expression) = statements.first() && !needs_begin_end_wrapping(expression) { return docvec!['(', expr(expression, env), ')']; } let vars = env.current_scope_vars.clone(); let document = statement_sequence(statements, env); env.current_scope_vars = vars; begin_end(document) } fn statement_sequence<'a>(statements: &'a [TypedStatement], env: &mut Env<'a>) -> Document<'a> { let count = statements.len(); let mut documents = Vec::with_capacity(count * 3); for (i, expression) in statements.iter().enumerate() { let position = if i + 1 == count { Position::Tail } else { Position::NotTail }; documents.push(statement(expression, env, position).group()); if i + 1 < count { // This isn't the final expression so add the delimeters documents.push(",".to_doc()); documents.push(line()); } } if count == 1 { documents.to_doc() } else { documents.to_doc().force_break() } } fn float_div<'a>(left: &'a TypedExpr, right: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> { if right.is_non_zero_compile_time_number() { return binop_exprs(left, "/", right, env); } else if right.is_zero_compile_time_number() { return "+0.0".to_doc(); } let left = expr(left, env); let right = expr(right, env); let denominator = env.next_local_var_name("gleam@denominator"); let clauses = docvec![ line(), "+0.0 -> +0.0;", line(), "-0.0 -> -0.0;", line(), denominator.clone(), " -> ", binop_documents(left, "/", denominator) ]; docvec!["case ", right, " of", clauses.nest(INDENT), line(), "end"] } fn int_div<'a>( left: &'a TypedExpr, right: &'a TypedExpr, op: &'static str, env: &mut Env<'a>, ) -> Document<'a> { if right.is_non_zero_compile_time_number() { return binop_exprs(left, op, right, env); } // If we have a constant value divided by zero then it's safe to replace it // directly with 0. if left.is_literal() && right.is_zero_compile_time_number() { return "0".to_doc(); } let left = expr(left, env); let right = expr(right, env); let denominator = env.next_local_var_name("gleam@denominator"); let clauses = docvec![ line(), "0 -> 0;", line(), denominator.clone(), " -> ", binop_documents(left, op, denominator) ]; docvec!["case ", right, " of", clauses.nest(INDENT), line(), "end"] } fn bin_op<'a>( name: &'a BinOp, left: &'a TypedExpr, right: &'a TypedExpr, env: &mut Env<'a>, ) -> Document<'a> { let op = match name { BinOp::And => "andalso", BinOp::Or => "orelse", BinOp::LtInt | BinOp::LtFloat => "<", BinOp::LtEqInt | BinOp::LtEqFloat => "=<", BinOp::Eq => "=:=", BinOp::NotEq => "/=", BinOp::GtInt | BinOp::GtFloat => ">", BinOp::GtEqInt | BinOp::GtEqFloat => ">=", BinOp::AddInt => "+", BinOp::AddFloat => "+", BinOp::SubInt => "-", BinOp::SubFloat => "-", BinOp::MultInt => "*", BinOp::MultFloat => "*", BinOp::DivFloat => return float_div(left, right, env), BinOp::DivInt => return int_div(left, right, "div", env), BinOp::RemainderInt => return int_div(left, right, "rem", env), BinOp::Concatenate => return string_concatenate(left, right, env), }; binop_exprs(left, op, right, env) } fn binop_exprs<'a>( left: &'a TypedExpr, op: &'static str, right: &'a TypedExpr, env: &mut Env<'a>, ) -> Document<'a> { let left = if let TypedExpr::BinOp { .. } = left { expr(left, env).surround("(", ")") } else { maybe_block_expr(left, env) }; let right = if let TypedExpr::BinOp { .. } = right { expr(right, env).surround("(", ")") } else { maybe_block_expr(right, env) }; binop_documents(left, op, right) } fn binop_documents<'a>(left: Document<'a>, op: &'static str, right: Document<'a>) -> Document<'a> { left.append(break_("", " ")) .append(op) .group() .append(" ") .append(right) } fn let_assert<'a>( value: &'a TypedExpr, pattern: &'a TypedPattern, environment: &mut Env<'a>, message: Option<&'a TypedExpr>, position: Position, location: SrcSpan, ) -> Document<'a> { // If the pattern will never fail, like a tuple or a simple variable, we // simply treat it as if it were a `let` assignment. if pattern.always_matches() { return let_(value, pattern, environment); } let message = match message { Some(message) => expr(message, environment), None => string("Pattern match failed, no pattern matched the value."), }; let subject = maybe_block_expr(value, environment); // The code we generated for a `let assert` assignment looks something like // this. For this Gleam code: // // ```gleam // let assert [a, b, c] = [1, 2, 3] // ``` // // We generate (roughly) the following Erlang: // // ```erlang // {A, B, C} = case [1, 2, 3] of // [A, B, C] -> {A, B, C}; // _ -> erlang:error(...) // end. // ``` // This is the most efficient way to properly extract all the required // variables from the pattern. However, if the `let assert` assignment is // the last in a block, like this: // // ```gleam // let x = { // let assert [a, b, c] = [1, 2, 3] // } // ``` // // The generated Erlang code will end up assigning the value `#(1, 2, 3)` // to the variable `x`, instead of `[1, 2, 3]`. In this case, we must // generate slightly different code. Since we know we won't be using the // bound variables anywhere (there is nothing else in this scope to // reference them), we can safely remove the assignment from the generated // code, and generate the following: // // ```erlang // X = begin // _assert_subject = [1, 2, 3] // case _assert_subject of // [A, B, C] -> _assert_subject; // _ -> erlang:error(...) // end // end. // ``` // // That correctly assigns `[1, 2, 3]` to the `x` variable. // let is_tail = match position { Position::Tail => true, Position::NotTail => false, }; let (subject_assignment, subject) = if is_tail && !value.is_var() { let variable = environment.next_local_var_name(ASSERT_SUBJECT_VARIABLE); let assignment = docvec![variable.clone(), " = ", subject, ",", line()]; (assignment, variable) } else { (nil(), subject) }; let mut pattern_printer = PatternPrinter::new(environment); let pattern_document = pattern_printer.print(pattern); let PatternPrinter { environment, variables, guards, assignments, } = pattern_printer; let assignments_map = assignments .iter() .map(|assignment| (assignment.gleam_name.clone(), assignment)) .collect(); let clause_guard = optional_clause_guard(None, guards, environment, &assignments_map); let value_document = match variables.as_slice() { _ if is_tail => subject.clone(), [] => "nil".to_doc(), [variable] => environment.local_var_name(variable), variables => { let variables = variables .iter() .map(|variable| environment.local_var_name(variable)); docvec![ break_("{", "{"), join(variables, break_(",", ", ")).nest(INDENT), "}" ] .group() } }; let assignment = match variables.as_slice() { _ if is_tail => nil(), [] => nil(), [variable] => environment.next_local_var_name(variable).append(" = "), variables => { let variables = variables .iter() .map(|variable| environment.next_local_var_name(variable)); docvec![ break_("{", "{"), join(variables, break_(",", ", ")).nest(INDENT), "} = " ] .group() } }; let clauses = docvec![ pattern_document, clause_guard, " -> ", value_document, ";", line(), environment.next_local_var_name(ASSERT_FAIL_VARIABLE), " ->", docvec![ line(), erlang_error( "let_assert", &message, location, vec![ ("value", environment.local_var_name(ASSERT_FAIL_VARIABLE)), ("start", location.start.to_doc()), ("'end'", value.location().end.to_doc()), ("pattern_start", pattern.location().start.to_doc()), ("pattern_end", pattern.location().end.to_doc()), ], environment, ) .nest(INDENT) ] .nest(INDENT) ]; let assignments = if assignments.is_empty() { nil() } else { docvec![ ",", line(), join( assignments .iter() .map(|assignment| assignment.to_assignment_doc()), ",".to_doc().append(line()) ) ] }; docvec![ subject_assignment, assignment, "case ", subject, " of", docvec![line(), clauses].nest(INDENT), line(), "end", assignments, ] } fn let_<'a>( value: &'a TypedExpr, pattern: &'a TypedPattern, environment: &mut Env<'a>, ) -> Document<'a> { let body = maybe_block_expr(value, environment).group(); PatternPrinter::new(environment) .print(pattern) .append(" = ") .append(body) } fn float<'a>(value: &str) -> Document<'a> { let mut value = value.replace('_', ""); if value.ends_with('.') { value.push('0') } match value.split('.').collect_vec().as_slice() { ["0", "0"] => "+0.0".to_doc(), [before_dot, after_dot] if after_dot.starts_with('e') => { eco_format!("{before_dot}.0{after_dot}").to_doc() } _ => EcoString::from(value).to_doc(), } } fn expr_list<'a>( elements: &'a [TypedExpr], tail: &'a Option>, env: &mut Env<'a>, ) -> Document<'a> { let elements = join( elements .iter() .map(|element| maybe_block_expr(element, env)), break_(",", ", "), ); list( elements, tail.as_ref().map(|element| maybe_block_expr(element, env)), ) } fn list<'a>(elements: Document<'a>, tail: Option>) -> Document<'a> { let elements = match tail { Some(tail) if elements.is_empty() => return tail.to_doc(), Some(tail) => elements.append(break_(" |", " | ")).append(tail), None => elements, }; elements.to_doc().nest(INDENT).surround("[", "]").group() } fn var<'a>(name: &'a str, constructor: &'a ValueConstructor, env: &mut Env<'a>) -> Document<'a> { match &constructor.variant { ValueConstructorVariant::Record { name: record_name, .. } => match constructor.type_.deref() { Type::Fn { arguments, .. } => { let chars = incrementing_arguments_list(arguments.len()); "fun(" .to_doc() .append(chars.clone()) .append(") -> {") .append(atom_string(to_snake_case(record_name))) .append(", ") .append(chars) .append("} end") } Type::Named { .. } | Type::Var { .. } | Type::Tuple { .. } => { atom_string(to_snake_case(record_name)) } }, ValueConstructorVariant::LocalVariable { .. } => env.local_var_name(name), ValueConstructorVariant::ModuleConstant { literal, .. } => const_inline(literal, env), ValueConstructorVariant::ModuleFn { arity, external_erlang: Some((module, name)), .. } if module == env.module => function_reference(None, name, *arity), ValueConstructorVariant::ModuleFn { arity, external_erlang: Some((module, name)), .. } => function_reference(Some(module), name, *arity), ValueConstructorVariant::ModuleFn { arity, module, .. } if module == env.module => { function_reference(None, name, *arity) } ValueConstructorVariant::ModuleFn { arity, module, name, .. } => function_reference(Some(module), name, *arity), } } fn function_reference<'a>(module: Option<&'a str>, name: &'a str, arity: usize) -> Document<'a> { match module { None => "fun ".to_doc(), Some(module) => "fun ".to_doc().append(module_name_atom(module)).append(":"), } .append(atom(escape_erlang_existing_name(name))) .append("/") .append(arity) } fn int<'a>(value: &str) -> Document<'a> { let mut value = value.replace('_', ""); if value.starts_with("0x") { value.replace_range(..2, "16#"); } else if value.starts_with("0o") { value.replace_range(..2, "8#"); } else if value.starts_with("0b") { value.replace_range(..2, "2#"); } EcoString::from(value).to_doc() } fn const_inline<'a>(literal: &'a TypedConstant, env: &mut Env<'a>) -> Document<'a> { match literal { Constant::Int { value, .. } => int(value), Constant::Float { value, .. } => float(value), Constant::String { value, .. } => string(value), Constant::Tuple { elements, .. } => { tuple(elements.iter().map(|element| const_inline(element, env))) } Constant::List { elements, tail, .. } => { let tail_elements = tail .as_deref() .and_then(|tail| tail.list_elements()) .unwrap_or_default(); join( elements .iter() .chain(tail_elements) .map(|element| const_inline(element, env)), break_(",", ", "), ) .nest(INDENT) .surround("[", "]") .group() } Constant::BitArray { segments, .. } => bit_array( segments .iter() .map(|s| const_segment(&s.value, &s.options, env)), ), Constant::Record { tag, type_, arguments, .. } if arguments.is_empty() => match type_.deref() { Type::Fn { arguments, .. } => record_constructor_function(tag, arguments.len()), Type::Named { .. } | Type::Var { .. } | Type::Tuple { .. } => { atom_string(to_snake_case(tag)) } }, Constant::Record { tag, arguments, .. } => { // Record updates are fully expanded during type checking, so we just handle arguments let arguments_doc = arguments .iter() .map(|argument| const_inline(&argument.value, env)); let tag = atom_string(to_snake_case(tag)); tuple(std::iter::once(tag).chain(arguments_doc)) } Constant::Var { name, constructor, .. } => var( name, constructor .as_ref() .expect("This is guaranteed to hold a value."), env, ), Constant::StringConcatenation { left, right, .. } => { const_string_concatenate(left, right, env) } Constant::RecordUpdate { .. } => panic!("record updates should not reach code generation"), Constant::Invalid { .. } => panic!("invalid constants should not reach code generation"), } } fn record_constructor_function(tag: &EcoString, arity: usize) -> Document<'_> { let chars = incrementing_arguments_list(arity); "fun(" .to_doc() .append(chars.clone()) .append(") -> {") .append(atom_string(to_snake_case(tag))) .append(", ") .append(chars) .append("} end") } fn clause<'a>(clause: &'a TypedClause, environment: &mut Env<'a>) -> Document<'a> { let Clause { guard, pattern, alternative_patterns, then, .. } = clause; // These are required to get the alternative patterns working properly. // Simply rendering the duplicate erlang clauses breaks the variable // rewriting because each pattern would define different (rewritten) // variables names. let initial_erlang_vars = environment.erl_function_scope_vars.clone(); let initial_scope_vars = environment.current_scope_vars.clone(); let mut branches_docs = Vec::with_capacity(alternative_patterns.len() + 1); for patterns in std::iter::once(pattern).chain(alternative_patterns) { // Erlang doesn't support alternative patterns, so we turn each // alternative into a branch of its own. // For each alternative, before generating the body, we need to reset // the variables in scope to what they are before the case expression, // so that a branch will not interfere with the other ones! environment.erl_function_scope_vars = initial_erlang_vars.clone(); environment.current_scope_vars = initial_scope_vars.clone(); let mut pattern_printer = PatternPrinter::new(environment); let pattern = match patterns.as_slice() { [pattern] => pattern_printer.print(pattern), _ => tuple(patterns.iter().map(|pattern| { pattern_printer.reset_variables(); pattern_printer.print(pattern) })), }; let PatternPrinter { environment, guards, variables: _, assignments, } = pattern_printer; let assignments_map = assignments .iter() .map(|assignment| (assignment.gleam_name.clone(), assignment)) .collect(); let guard = optional_clause_guard(guard.as_ref(), guards, environment, &assignments_map); let then = clause_consequence(then, assignments, environment).group(); branches_docs.push(docvec![ pattern, guard, " ->", docvec![line(), then].nest(INDENT), ]); } join(branches_docs, ";".to_doc().append(lines(2))) } fn clause_consequence<'a>( consequence: &'a TypedExpr, // Further assignments that the pattern might need to introduce at the start // of the new block. assignments: Vec>, env: &mut Env<'a>, ) -> Document<'a> { let assignment_doc = if assignments.is_empty() { nil() } else { let separator = ",".to_doc().append(line()); join( assignments .iter() .map(|assignment| assignment.to_assignment_doc()), separator.clone(), ) .append(separator) }; let consequence = if let TypedExpr::Block { statements, .. } = consequence { statement_sequence(statements, env) } else { expr(consequence, env) }; assignment_doc.append(consequence) } fn optional_clause_guard<'a>( guard: Option<&'a TypedClauseGuard>, additional_guards: Vec>, env: &mut Env<'a>, assignments: &HashMap>, ) -> Document<'a> { let guard_doc = guard.map(|guard| bare_clause_guard(guard, env, assignments)); let guards_count = guard_doc.iter().len() + additional_guards.len(); let guards_docs = additional_guards.into_iter().chain(guard_doc).map(|guard| { if guards_count > 1 { guard.surround("(", ")") } else { guard } }); let doc = join(guards_docs, " andalso ".to_doc()); if doc.is_empty() { doc } else { " when ".to_doc().append(doc) } } fn bare_clause_guard<'a>( guard: &'a TypedClauseGuard, env: &mut Env<'a>, assignments: &HashMap>, ) -> Document<'a> { match guard { ClauseGuard::Block { value, .. } => { bare_clause_guard(value, env, assignments).surround("(", ")") } ClauseGuard::Not { expression, .. } => { docvec!["not ", bare_clause_guard(expression, env, assignments)] } ClauseGuard::BinaryOperator { operator, left, right, .. } => { let left_document = clause_guard(left, env, assignments); let right_document = clause_guard(right, env, assignments); let operator = match operator { BinOp::Or => "orelse", BinOp::And => "andalso", BinOp::Eq => "=:=", BinOp::NotEq => "=/=", BinOp::GtInt | BinOp::GtFloat => ">", BinOp::GtEqInt | BinOp::GtEqFloat => ">=", BinOp::LtInt | BinOp::LtFloat => "<", BinOp::LtEqInt | BinOp::LtEqFloat => "=<", BinOp::AddInt | BinOp::AddFloat => "+", BinOp::SubInt | BinOp::SubFloat => "-", BinOp::MultInt | BinOp::MultFloat => "*", BinOp::DivFloat => "/", BinOp::DivInt => "div", BinOp::RemainderInt => "rem", BinOp::Concatenate => { return clause_guard_string_concatenate(left, right, env, assignments); } }; docvec![left_document, " ", operator, " ", right_document] } // Only local variables are supported and the typer ensures that all // ClauseGuard::Vars are local variables ClauseGuard::Var { name, .. } => { // If we're referencing a variable introduced by a string pattern // assignment we need to replace it with its actual literal value: // in the generated code the variable is only defined later, so // just referencing its name would result in an error. assignments .get(name) .map(|assignment| assignment.literal_value.clone()) .unwrap_or_else(|| env.local_var_name(name)) } ClauseGuard::TupleIndex { tuple, index, .. } => tuple_index_inline(tuple, *index, env), ClauseGuard::FieldAccess { container, index, .. } => tuple_index_inline(container, index.expect("Unable to find index") + 1, env), ClauseGuard::ModuleSelect { literal, .. } => const_inline(literal, env), ClauseGuard::Constant(constant) => const_inline(constant, env), } } fn clause_guard_string_concatenate<'a>( left: &'a TypedClauseGuard, right: &'a TypedClauseGuard, env: &mut Env<'a>, assignments: &HashMap>, ) -> Document<'a> { let left = clause_guard_string_concatenate_argument(left, env, assignments); let right = clause_guard_string_concatenate_argument(right, env, assignments); bit_array([left, right]) } fn clause_guard_string_concatenate_argument<'a>( guard: &'a TypedClauseGuard, env: &mut Env<'a>, assignments: &HashMap>, ) -> Document<'a> { match guard { ClauseGuard::Constant(Constant::String { value, .. }) => { docvec!['"', string_inner(value), "\"/utf8"] } ClauseGuard::Constant(Constant::StringConcatenation { left, right, .. }) => { const_string_concatenate_inner(left, right, env) } ClauseGuard::ModuleSelect { literal, .. } => match literal { Constant::String { value, .. } => docvec!['"', string_inner(value), "\"/utf8"], Constant::StringConcatenation { left, right, .. } => { const_string_concatenate_inner(left, right, env) } Constant::Int { .. } | Constant::Float { .. } | Constant::Tuple { .. } | Constant::List { .. } | Constant::Record { .. } | Constant::RecordUpdate { .. } | Constant::BitArray { .. } | Constant::Var { .. } | Constant::Invalid { .. } => docvec!["(", const_inline(literal, env), ")/binary"], }, ClauseGuard::Var { name, .. } => assignments .get(name) .map(|assignment| docvec![assignment.literal_value.clone(), "/binary"]) .unwrap_or_else(|| docvec![env.local_var_name(name), "/binary"]), ClauseGuard::BinaryOperator { operator: BinOp::Concatenate, left, right, .. } => docvec![ clause_guard_string_concatenate(left, right, env, assignments), "/binary" ], ClauseGuard::Block { .. } | ClauseGuard::BinaryOperator { .. } | ClauseGuard::Not { .. } | ClauseGuard::TupleIndex { .. } | ClauseGuard::FieldAccess { .. } | ClauseGuard::Constant(_) => docvec![ clause_guard(guard, env, assignments).surround("(", ")"), "/binary" ], } } fn tuple_index_inline<'a>( tuple: &'a TypedClauseGuard, index: u64, env: &mut Env<'a>, ) -> Document<'a> { let index_doc = eco_format!("{}", (index + 1)).to_doc(); let tuple_doc = bare_clause_guard(tuple, env, &HashMap::new()); "erlang:element" .to_doc() .append(wrap_arguments([index_doc, tuple_doc])) } fn clause_guard<'a>( guard: &'a TypedClauseGuard, env: &mut Env<'a>, assignments: &HashMap>, ) -> Document<'a> { match guard { // Binary operators are wrapped in parens ClauseGuard::BinaryOperator { .. } => "(" .to_doc() .append(bare_clause_guard(guard, env, assignments)) .append(")"), // Other expressions are not ClauseGuard::Constant(_) | ClauseGuard::Not { .. } | ClauseGuard::Var { .. } | ClauseGuard::TupleIndex { .. } | ClauseGuard::FieldAccess { .. } | ClauseGuard::ModuleSelect { .. } | ClauseGuard::Block { .. } => bare_clause_guard(guard, env, assignments), } } fn clauses<'a>(cs: &'a [TypedClause], env: &mut Env<'a>) -> Document<'a> { join( cs.iter().map(|c| { let vars = env.current_scope_vars.clone(); let erl = clause(c, env); env.current_scope_vars = vars; // Reset the known variables now the clauses' scope has ended erl }), ";".to_doc().append(lines(2)), ) } fn case<'a>(subjects: &'a [TypedExpr], cs: &'a [TypedClause], env: &mut Env<'a>) -> Document<'a> { let subjects_doc = if subjects.len() == 1 { let subject = subjects .first() .expect("erl case printing of single subject"); maybe_block_expr(subject, env).group() } else { tuple( subjects .iter() .map(|element| maybe_block_expr(element, env)), ) }; "case " .to_doc() .append(subjects_doc) .append(" of") .append(line().append(clauses(cs, env)).nest(INDENT)) .append(line()) .append("end") .group() } fn call<'a>(fun: &'a TypedExpr, arguments: &'a [TypedCallArg], env: &mut Env<'a>) -> Document<'a> { docs_arguments_call( fun, arguments .iter() .map(|argument| maybe_block_expr(&argument.value, env)) .collect(), env, ) } fn module_fn_with_arguments<'a>( module: &'a str, name: &'a str, arguments: Vec>, env: &Env<'a>, ) -> Document<'a> { let name = escape_erlang_existing_name(name); let arguments = wrap_arguments(arguments); if module == env.module { atom(name).append(arguments) } else { atom_string(module.replace('/', "@").into()) .append(":") .append(atom(name)) .append(arguments) } } fn docs_arguments_call<'a>( fun: &'a TypedExpr, mut arguments: Vec>, env: &mut Env<'a>, ) -> Document<'a> { match fun { TypedExpr::ModuleSelect { constructor: ModuleValueConstructor::Record { name, .. }, .. } | TypedExpr::Var { constructor: ValueConstructor { variant: ValueConstructorVariant::Record { name, .. }, .. }, .. } => tuple(std::iter::once(atom_string(to_snake_case(name))).chain(arguments)), TypedExpr::Var { constructor: ValueConstructor { variant: ValueConstructorVariant::ModuleFn { external_erlang: Some((module, name)), .. } | ValueConstructorVariant::ModuleFn { module, name, .. }, .. }, .. } => module_fn_with_arguments(module, name, arguments, env), // Match against a Constant::Var that contains a function. // We want this to be emitted like a normal function call, not a function variable // substitution. TypedExpr::Var { constructor: ValueConstructor { variant: ValueConstructorVariant::ModuleConstant { literal: Constant::Var { constructor: Some(constructor), .. }, .. }, .. }, .. } if constructor.variant.is_module_fn() => match &constructor.variant { ValueConstructorVariant::ModuleFn { external_erlang: Some((module, name)), .. } | ValueConstructorVariant::ModuleFn { module, name, .. } => { module_fn_with_arguments(module, name, arguments, env) } ValueConstructorVariant::LocalVariable { .. } | ValueConstructorVariant::ModuleConstant { .. } | ValueConstructorVariant::Record { .. } => { unreachable!("The above clause guard ensures that this is a module fn") } }, TypedExpr::ModuleSelect { constructor: ModuleValueConstructor::Fn { external_erlang: Some((module, name)), .. } | ModuleValueConstructor::Fn { module, name, .. }, .. } => { let arguments = wrap_arguments(arguments); let name = escape_erlang_existing_name(name); // We use the constructor Fn variant's `module` and function `name`. // It would also be valid to use the module and label as in the // Gleam code, but using the variant can result in an optimisation // in which the target function is used for `external fn`s, removing // one layer of wrapping. // This also enables an optimisation in the Erlang compiler in which // some Erlang BIFs can be replaced with literals if their arguments // are literals, such as `binary_to_atom`. atom_string(module_erlang_name(module)) .append(":") .append(atom_string(name.into())) .append(arguments) } TypedExpr::Fn { kind, body, .. } if kind.is_capture() => { if let Statement::Expression(TypedExpr::Call { fun, arguments: inner_arguments, .. }) = body.first() { let mut merged_arguments = Vec::with_capacity(inner_arguments.len()); for arg in inner_arguments { if let TypedExpr::Var { name, .. } = &arg.value && name == CAPTURE_VARIABLE { merged_arguments.push(arguments.swap_remove(0)) } else { merged_arguments.push(maybe_block_expr(&arg.value, env)) } } docs_arguments_call(fun, merged_arguments, env) } else { panic!("Erl printing: Capture was not a call") } } TypedExpr::Fn { .. } | TypedExpr::Call { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::TupleIndex { .. } => { let arguments = wrap_arguments(arguments); expr(fun, env).surround("(", ")").append(arguments) } TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::List { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => { let arguments = wrap_arguments(arguments); maybe_block_expr(fun, env).append(arguments) } } } fn record_update<'a>( record: &'a Option>, constructor: &'a TypedExpr, arguments: &'a [TypedCallArg], env: &mut Env<'a>, ) -> Document<'a> { let vars = env.current_scope_vars.clone(); let document = match record.as_ref() { Some(record) => docvec![ assignment(record, env, Position::NotTail), ",", line(), call(constructor, arguments, env) ], None => call(constructor, arguments, env), }; env.current_scope_vars = vars; document } /// Wrap a document in begin end /// fn begin_end(document: Document<'_>) -> Document<'_> { docvec!["begin", line().append(document).nest(INDENT), line(), "end"].force_break() } fn maybe_block_expr<'a>(expression: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> { if needs_begin_end_wrapping(expression) { begin_end(expr(expression, env)) } else { expr(expression, env) } } fn needs_begin_end_wrapping(expression: &TypedExpr) -> bool { match expression { // Record updates are 1 expression if there's no assignment, multiple otherwise. TypedExpr::RecordUpdate { record_assignment, .. } => record_assignment.is_some(), TypedExpr::Pipeline { .. } => true, TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::Block { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Echo { .. } | TypedExpr::Panic { .. } | TypedExpr::BitArray { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => false, } } fn todo<'a>(message: Option<&'a TypedExpr>, location: SrcSpan, env: &mut Env<'a>) -> Document<'a> { let message = match message { Some(m) => expr(m, env), None => string("`todo` expression evaluated. This code has not yet been implemented."), }; erlang_error("todo", &message, location, vec![], env) } fn panic<'a>(location: SrcSpan, message: Option<&'a TypedExpr>, env: &mut Env<'a>) -> Document<'a> { let message = match message { Some(m) => expr(m, env), None => string("`panic` expression evaluated."), }; erlang_error("panic", &message, location, vec![], env) } fn echo<'a>( body: Document<'a>, message: Option<&'a TypedExpr>, location: &SrcSpan, env: &mut Env<'a>, ) -> Document<'a> { env.echo_used = true; let message = message .as_ref() .map(|message| maybe_block_expr(message, env)) .unwrap_or("nil".to_doc()); "echo".to_doc().append(wrap_arguments(vec![ body, message, env.line_numbers.line_number(location.start).to_doc(), ])) } fn erlang_error<'a>( name: &'a str, message: &Document<'a>, location: SrcSpan, fields: Vec<(&'a str, Document<'a>)>, env: &Env<'a>, ) -> Document<'a> { let mut fields_doc = docvec![ "gleam_error => ", name, ",", line(), "message => ", message.clone(), ",", line(), "file => <>,", line(), "module => ", env.module.to_doc().surround("<<\"", "\"/utf8>>"), ",", line(), "function => ", string(env.function), ",", line(), "line => ", env.line_numbers.line_number(location.start), ]; for (key, value) in fields { fields_doc = fields_doc .append(",") .append(line()) .append(key) .append(" => ") .append(value); } let error = docvec!["#{", fields_doc.group().nest(INDENT), "}"]; docvec!["erlang:error", wrap_arguments([error.group()])] } fn expr<'a>(expression: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> { match expression { TypedExpr::Todo { message: label, location, .. } => todo(label.as_deref(), *location, env), TypedExpr::Panic { location, message, .. } => panic(*location, message.as_deref(), env), TypedExpr::Echo { expression, location, message, .. } => { let expression = expression .as_ref() .expect("echo with no expression outside of pipe"); let expression = maybe_block_expr(expression, env); echo(expression, message.as_deref(), location, env) } TypedExpr::Int { value, .. } => int(value), TypedExpr::Float { value, .. } => float(value), TypedExpr::String { value, .. } => string(value), TypedExpr::Pipeline { first_value, assignments, finally, .. } => pipeline(first_value, assignments, finally, env), TypedExpr::Block { statements, .. } => block(statements, env), TypedExpr::TupleIndex { tuple, index, .. } => tuple_index(tuple, *index, env), TypedExpr::Var { name, constructor, .. } => var(name, constructor, env), TypedExpr::Fn { arguments, body, .. } => fun(arguments, body, env), TypedExpr::NegateBool { value, .. } => negate_with("not ", value, env), TypedExpr::NegateInt { value, .. } => negate_with("- ", value, env), TypedExpr::List { elements, tail, .. } => expr_list(elements, tail, env), TypedExpr::Call { fun, arguments, .. } => call(fun, arguments, env), TypedExpr::ModuleSelect { constructor: ModuleValueConstructor::Record { name, arity: 0, .. }, .. } => atom_string(to_snake_case(name)), TypedExpr::ModuleSelect { constructor: ModuleValueConstructor::Constant { literal, .. }, .. } => const_inline(literal, env), TypedExpr::ModuleSelect { constructor: ModuleValueConstructor::Record { name, arity, .. }, .. } => record_constructor_function(name, *arity as usize), TypedExpr::ModuleSelect { type_, constructor: ModuleValueConstructor::Fn { external_erlang: Some((module, name)), .. } | ModuleValueConstructor::Fn { module, name, .. }, .. } => module_select_fn(type_.clone(), module, name), TypedExpr::RecordAccess { record, index, .. } => tuple_index(record, index + 1, env), TypedExpr::PositionalAccess { record, index, .. } => tuple_index(record, index + 1, env), TypedExpr::RecordUpdate { record_assignment, constructor, arguments, .. } => record_update(record_assignment, constructor, arguments, env), TypedExpr::Case { subjects, clauses, .. } => case(subjects, clauses, env), TypedExpr::BinOp { name, left, right, .. } => bin_op(name, left, right, env), TypedExpr::Tuple { elements, .. } => tuple( elements .iter() .map(|element| maybe_block_expr(element, env)), ), TypedExpr::BitArray { segments, .. } => bit_array( segments .iter() .map(|s| expr_segment(&s.value, &s.options, env)), ), TypedExpr::Invalid { .. } => panic!("invalid expressions should not reach code generation"), } } fn pipeline<'a>( first_value: &'a TypedPipelineAssignment, assignments: &'a [(TypedPipelineAssignment, PipelineAssignmentKind)], finally: &'a TypedExpr, env: &mut Env<'a>, ) -> Document<'a> { let mut documents = Vec::with_capacity((assignments.len() + 1) * 3); let all_assignments = std::iter::once(first_value) .chain(assignments.iter().map(|(assignment, _kind)| assignment)); let echo_doc = |var_name: &Option>, message: Option<&'a TypedExpr>, location: &SrcSpan, env: &mut Env<'a>| { let name = var_name .to_owned() .expect("echo with no previous step in a pipe"); echo(name, message, location, env) }; let vars = env.current_scope_vars.clone(); let mut prev_local_var_name = None; for a in all_assignments { // An echo in a pipeline won't result in an assignment, instead it // just prints the previous variable assigned in the pipeline. if let TypedExpr::Echo { expression: None, message, location, .. } = a.value.as_ref() { documents.push(echo_doc( &prev_local_var_name, message.as_deref(), location, env, )) } else { // Otherwise we assign the intermediate pipe value to a variable. let body = maybe_block_expr(&a.value, env).group(); let name = env.next_local_var_name(&a.name); prev_local_var_name = Some(name.clone()); documents.push(docvec![name, " = ", body]); }; documents.push(",".to_doc()); documents.push(line()); } if let TypedExpr::Echo { expression: None, message, location, .. } = finally { documents.push(echo_doc( &prev_local_var_name, message.as_deref(), location, env, )) } else { documents.push(expr(finally, env)) } env.current_scope_vars = vars; documents.to_doc() } fn assignment<'a>( assignment: &'a TypedAssignment, env: &mut Env<'a>, position: Position, ) -> Document<'a> { match &assignment.kind { AssignmentKind::Let | AssignmentKind::Generated => { let_(&assignment.value, &assignment.pattern, env) } AssignmentKind::Assert { message, location, .. } => let_assert( &assignment.value, &assignment.pattern, env, message.as_ref(), position, *location, ), } } fn assert<'a>(assert: &'a TypedAssert, env: &mut Env<'a>) -> Document<'a> { let Assert { value, location, message, } = assert; let message = match message { Some(message) => expr(message, env), None => string("Assertion failed."), }; let mut assignments = Vec::new(); let (subject, mut fields) = match value { TypedExpr::Call { fun, arguments, .. } => { assert_call(fun, arguments, &mut assignments, env) } TypedExpr::BinOp { name, left, right, .. } => { let operator = match name { BinOp::And => { return assert_and(left, right, message, *location, env); } BinOp::Or => { return assert_or(left, right, message, *location, env); } BinOp::Eq => "=:=", BinOp::NotEq => "/=", BinOp::LtInt | BinOp::LtFloat => "<", BinOp::LtEqInt | BinOp::LtEqFloat => "=<", BinOp::GtInt | BinOp::GtFloat => ">", BinOp::GtEqInt | BinOp::GtEqFloat => ">=", BinOp::AddInt | BinOp::AddFloat | BinOp::SubInt | BinOp::SubFloat | BinOp::MultInt | BinOp::MultFloat | BinOp::DivInt | BinOp::DivFloat | BinOp::RemainderInt | BinOp::Concatenate => { panic!("Non-boolean operators cannot appear here in well-typed code") } }; let left_document = assign_to_variable(left, &mut assignments, env); let right_document = assign_to_variable(right, &mut assignments, env); ( binop_documents(left_document.clone(), operator, right_document.clone()), vec![ ("kind", atom("binary_operator")), ("operator", atom(name.name())), ( "left", asserted_expression( AssertExpression::from_expression(left), Some(left_document), left.location(), ), ), ( "right", asserted_expression( AssertExpression::from_expression(right), Some(right_document), right.location(), ), ), ], ) } TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => ( maybe_block_expr(value, env), vec![ ("kind", atom("expression")), ( "expression", asserted_expression( AssertExpression::from_expression(value), Some("false".to_doc()), value.location(), ), ), ], ), }; fields.push(("start", location.start.to_doc())); fields.push(("'end'", value.location().end.to_doc())); fields.push(("expression_start", value.location().start.to_doc())); let clauses = docvec![ line(), "true -> nil;", line(), "false -> ", erlang_error("assert", &message, *location, fields, env), ]; docvec![ assignments, "case ", subject, " of", clauses.nest(INDENT), line(), "end" ] } fn assert_call<'a>( function: &'a TypedExpr, arguments: &'a Vec>, assignments: &mut Vec>, env: &mut Env<'a>, ) -> (Document<'a>, Vec<(&'static str, Document<'a>)>) { let argument_variables = arguments .iter() .map(|argument| assign_to_variable(&argument.value, assignments, env)) .collect_vec(); let arguments = join( argument_variables .iter() .zip(arguments) .map(|(variable, argument)| { asserted_expression( AssertExpression::from_expression(&argument.value), Some(variable.clone()), argument.location(), ) }), break_(",", ", "), ) .nest(INDENT) .surround("[", "]"); ( docs_arguments_call(function, argument_variables, env), vec![("kind", atom("function_call")), ("arguments", arguments)], ) } /// In Gleam, the `&&` operator is short-circuiting, meaning that we can't /// pre-evaluate both sides of it, and use them in the exception that is /// thrown. /// Instead, we need to implement this short-circuiting logic ourself. /// /// If we short-circuit, we must leave the second expression unevaluated, /// and signal that using the `unevaluated` variant, as detailed in the /// exception format. For the first expression, we know it must be `false`, /// otherwise we would have continued by evaluating the second expression. /// /// Similarly, if we do evaluate the second expression and fail, we know /// that the first expression must have evaluated to `true`, and the second /// to `false`. This way, we avoid needing to evaluate either expression /// twice. /// /// The generated code then looks something like this: /// ```erlang /// case expr1 of /// true -> case expr2 of /// true -> true; /// false -> /// end; /// false -> /// end /// ``` /// fn assert_and<'a>( left: &'a TypedExpr, right: &'a TypedExpr, message: Document<'a>, location: SrcSpan, env: &mut Env<'a>, ) -> Document<'a> { let left_kind = AssertExpression::from_expression(left); let right_kind = AssertExpression::from_expression(right); let fields_if_short_circuiting = vec![ ("kind", atom("binary_operator")), ("operator", atom("&&")), ( "left", asserted_expression(left_kind, Some("false".to_doc()), left.location()), ), ( "right", asserted_expression(AssertExpression::Unevaluated, None, right.location()), ), ("start", location.start.to_doc()), ("'end'", right.location().end.to_doc()), ("expression_start", left.location().start.to_doc()), ]; let fields = vec![ ("kind", atom("binary_operator")), ("operator", atom("&&")), ( "left", asserted_expression(left_kind, Some("true".to_doc()), left.location()), ), ( "right", asserted_expression(right_kind, Some("false".to_doc()), right.location()), ), ("start", location.start.to_doc()), ("'end'", right.location().end.to_doc()), ("expression_start", left.location().start.to_doc()), ]; let right_clauses = docvec![ line(), "true -> nil;", line(), "false -> ", erlang_error("assert", &message, location, fields, env), ]; let left_clauses = docvec![ line(), "true -> ", docvec![ "case ", maybe_block_expr(right, env), " of", right_clauses.nest(INDENT), line(), "end" ] .nest(INDENT), ";", line(), "false -> ", erlang_error( "assert", &message, location, fields_if_short_circuiting, env ), ]; docvec![ "case ", maybe_block_expr(left, env), " of", left_clauses.nest(INDENT), line(), "end" ] } /// Similar to `&&`, `||` is also short-circuiting in Gleam. However, if `||` /// short-circuits, that's because the first expression evaluated to `true`, /// meaning the whole assertion succeeds. This allows us to directly use Erlang's /// `orelse` operator as the subject of the `case` expression. /// /// The only difference is that due to the nature of `||`, if the assertion fails, /// we know that both sides must have evaluated to `false`, so we don't /// need to store the values of them in variables beforehand. fn assert_or<'a>( left: &'a TypedExpr, right: &'a TypedExpr, message: Document<'a>, location: SrcSpan, env: &mut Env<'a>, ) -> Document<'a> { let fields = vec![ ("kind", atom("binary_operator")), ("operator", atom("||")), ( "left", asserted_expression( AssertExpression::from_expression(left), Some("false".to_doc()), left.location(), ), ), ( "right", asserted_expression( AssertExpression::from_expression(right), Some("false".to_doc()), right.location(), ), ), ("start", location.start.to_doc()), ("'end'", right.location().end.to_doc()), ("expression_start", left.location().start.to_doc()), ]; let clauses = docvec![ line(), "true -> nil;", line(), "false -> ", erlang_error("assert", &message, location, fields, env), ]; docvec![ "case ", docvec![ maybe_block_expr(left, env), " orelse ", maybe_block_expr(right, env) ] .nest(INDENT), " of", clauses.nest(INDENT), line(), "end" ] } fn assign_to_variable<'a>( value: &'a TypedExpr, assignments: &mut Vec>, env: &mut Env<'a>, ) -> Document<'a> { if value.is_var() { expr(value, env) } else { let value = maybe_block_expr(value, env); let variable = env.next_local_var_name(ASSERT_SUBJECT_VARIABLE); let definition = docvec![variable.clone(), " = ", value, ",", line()]; assignments.push(definition); variable } } #[derive(Debug, Clone, Copy)] enum AssertExpression { Literal, Expression, Unevaluated, } impl AssertExpression { fn from_expression(expression: &TypedExpr) -> Self { if expression.is_literal() { Self::Literal } else { Self::Expression } } } fn asserted_expression( kind: AssertExpression, value: Option>, location: SrcSpan, ) -> Document<'_> { let kind = match kind { AssertExpression::Literal => atom("literal"), AssertExpression::Expression => atom("expression"), AssertExpression::Unevaluated => atom("unevaluated"), }; let start = location.start.to_doc(); let end = location.end.to_doc(); let value_field = if let Some(value) = value { docvec!["value => ", value, ",", line()] } else { nil() }; let fields_doc = docvec![ "kind => ", kind, ",", line(), value_field, "start => ", start, ",", line(), // `end` is a keyword in Erlang, so we have to quote it "'end' => ", end, line(), ]; "#{".to_doc() .append(fields_doc.group().nest(INDENT)) .append("}") } fn negate_with<'a>(op: &'static str, value: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> { docvec![op, maybe_block_expr(value, env)] } fn tuple_index<'a>(tuple: &'a TypedExpr, index: u64, env: &mut Env<'a>) -> Document<'a> { let index_doc = eco_format!("{}", (index + 1)).to_doc(); let tuple_doc = maybe_block_expr(tuple, env); "erlang:element" .to_doc() .append(wrap_arguments([index_doc, tuple_doc])) } fn module_select_fn<'a>(type_: Arc, module_name: &'a str, label: &'a str) -> Document<'a> { match crate::type_::collapse_links(type_).as_ref() { Type::Fn { arguments, .. } => function_reference(Some(module_name), label, arguments.len()), Type::Named { .. } | Type::Var { .. } | Type::Tuple { .. } => module_name_atom(module_name) .append(":") .append(atom(label)) .append("()"), } } fn fun<'a>( arguments: &'a [TypedArg], body: &'a [TypedStatement], env: &mut Env<'a>, ) -> Document<'a> { let current_scope_vars = env.current_scope_vars.clone(); let doc = "fun" .to_doc() .append(fun_arguments(arguments, env).append(" ->")) .append( break_("", " ") .append(statement_sequence(body, env)) .nest(INDENT), ) .append(break_("", " ")) .append("end") .group(); env.current_scope_vars = current_scope_vars; doc } fn incrementing_arguments_list(arity: usize) -> EcoString { let arguments = (0..arity).map(|c| format!("Field@{c}")); Itertools::intersperse(arguments, ", ".into()) .collect::() .into() } fn variable_name(name: &str) -> EcoString { let mut chars = name.chars(); let first_char = chars.next(); let first_uppercased = first_char.into_iter().flat_map(char::to_uppercase); first_uppercased.chain(chars).collect::() } /// When rendering a type variable to an erlang type spec we need all type variables with the /// same id to end up with the same name in the generated erlang. /// This function converts a usize into base 26 A-Z for this purpose. fn id_to_type_var(id: u64) -> Document<'static> { if id < 26 { let mut name = EcoString::from(""); name.push(char::from_u32((id % 26 + 65) as u32).expect("id_to_type_var 0")); return name.to_doc(); } let mut name = vec![]; let mut last_char = id; while last_char >= 26 { name.push(char::from_u32((last_char % 26 + 65) as u32).expect("id_to_type_var 1")); last_char /= 26; } name.push(char::from_u32((last_char % 26 + 64) as u32).expect("id_to_type_var 2")); name.reverse(); name.into_iter().collect::().to_doc() } pub fn is_erlang_reserved_word(name: &str) -> bool { matches!( name, "!" | "receive" | "bnot" | "div" | "rem" | "band" | "bor" | "bxor" | "bsl" | "bsr" | "not" | "and" | "or" | "xor" | "orelse" | "andalso" | "when" | "end" | "fun" | "try" | "catch" | "after" | "begin" | "let" | "query" | "cond" | "if" | "of" | "case" | "maybe" | "else" ) } // Includes shell_default & user_default which are looked for by the erlang shell pub fn is_erlang_standard_library_module(name: &str) -> bool { matches!( name, "array" | "base64" | "beam_lib" | "binary" | "c" | "calendar" | "dets" | "dict" | "digraph" | "digraph_utils" | "epp" | "erl_anno" | "erl_eval" | "erl_expand_records" | "erl_id_trans" | "erl_internal" | "erl_lint" | "erl_parse" | "erl_pp" | "erl_scan" | "erl_tar" | "ets" | "file_sorter" | "filelib" | "filename" | "gb_sets" | "gb_trees" | "gen_event" | "gen_fsm" | "gen_server" | "gen_statem" | "io" | "io_lib" | "lists" | "log_mf_h" | "maps" | "math" | "ms_transform" | "orddict" | "ordsets" | "pool" | "proc_lib" | "proplists" | "qlc" | "queue" | "rand" | "random" | "re" | "sets" | "shell" | "shell_default" | "shell_docs" | "slave" | "sofs" | "string" | "supervisor" | "supervisor_bridge" | "sys" | "timer" | "unicode" | "uri_string" | "user_default" | "win32reg" | "zip" ) } // Includes the functions that are autogenerated by erlang itself pub fn escape_erlang_existing_name(name: &str) -> &str { match name { "module_info" => "moduleInfo", _ => name, } } // A TypeVar can either be rendered as an actual type variable such as `A` or `B`, // or it can be rendered as `any()` depending on how many usages it has. If it // has only 1 usage it is an `any()` type. If it has more than 1 usage it is a // type variable. This function gathers usages for this determination. // // Examples: // fn(a) -> String // `a` is `any()` // fn() -> Result(a, b) // `a` and `b` are `any()` // fn(a) -> a // `a` is a type var fn collect_type_var_usages<'a>( mut ids: HashMap, types: impl IntoIterator>, ) -> HashMap { for type_ in types { type_var_ids(type_, &mut ids); } ids } fn result_type_var_ids(ids: &mut HashMap, arg_ok: &Type, arg_err: &Type) { let mut ok_ids = HashMap::new(); type_var_ids(arg_ok, &mut ok_ids); let mut err_ids = HashMap::new(); type_var_ids(arg_err, &mut err_ids); let mut result_counts = ok_ids; for (id, count) in err_ids { let _ = result_counts .entry(id) .and_modify(|current_count| { if *current_count < count { *current_count = count; } }) .or_insert(count); } for (id, count) in result_counts { let _ = ids .entry(id) .and_modify(|current_count| { *current_count += count; }) .or_insert(count); } } fn type_var_ids(type_: &Type, ids: &mut HashMap) { match type_ { Type::Var { type_ } => match type_.borrow().deref() { TypeVar::Generic { id, .. } | TypeVar::Unbound { id, .. } => { let count = ids.entry(*id).or_insert(0); *count += 1; } TypeVar::Link { type_ } => type_var_ids(type_, ids), }, Type::Named { arguments, module, name, .. } => match arguments[..] { [ref arg_ok, ref arg_err] if is_prelude_module(module) && name == "Result" => { result_type_var_ids(ids, arg_ok, arg_err) } _ => { for argument in arguments { type_var_ids(argument, ids) } } }, Type::Fn { arguments, return_ } => { for argument in arguments { type_var_ids(argument, ids) } type_var_ids(return_, ids); } Type::Tuple { elements } => { for element in elements { type_var_ids(element, ids) } } } } fn erl_safe_type_name(mut name: EcoString) -> EcoString { if matches!( name.as_str(), "any" | "arity" | "atom" | "binary" | "bitstring" | "boolean" | "byte" | "char" | "dynamic" | "float" | "function" | "identifier" | "integer" | "iodata" | "iolist" | "list" | "map" | "maybe_improper_list" | "mfa" | "module" | "neg_integer" | "nil" | "no_return" | "node" | "non_neg_integer" | "none" | "nonempty_improper_list" | "nonempty_list" | "nonempty_string" | "number" | "pid" | "port" | "pos_integer" | "reference" | "string" | "term" | "timeout" | "tuple" ) { name.push('_'); name } else { escape_atom_string(name) } } #[derive(Debug)] struct TypePrinter<'a> { var_as_any: bool, current_module: &'a str, var_usages: Option<&'a HashMap>, } impl<'a> TypePrinter<'a> { fn new(current_module: &'a str) -> Self { Self { current_module, var_usages: None, var_as_any: false, } } pub fn with_var_usages(mut self, var_usages: &'a HashMap) -> Self { self.var_usages = Some(var_usages); self } pub fn print(&self, type_: &Type) -> Document<'static> { match type_ { Type::Var { type_ } => self.print_var(&type_.borrow()), Type::Named { name, module, arguments, .. } if is_prelude_module(module) => self.print_prelude_type(name, arguments), Type::Named { name, module, arguments, .. } => self.print_type_app(module, name, arguments), Type::Fn { arguments, return_ } => self.print_fn(arguments, return_), Type::Tuple { elements } => tuple(elements.iter().map(|element| self.print(element))), } } fn print_var(&self, type_: &TypeVar) -> Document<'static> { match type_ { TypeVar::Generic { .. } | TypeVar::Unbound { .. } if self.var_as_any => { "any()".to_doc() } TypeVar::Generic { id, .. } | TypeVar::Unbound { id, .. } => match &self.var_usages { Some(usages) => match usages.get(id) { Some(&0) => nil(), Some(&1) => "any()".to_doc(), _ => id_to_type_var(*id), }, None => id_to_type_var(*id), }, TypeVar::Link { type_ } => self.print(type_), } } fn print_prelude_type(&self, name: &str, arguments: &[Arc]) -> Document<'static> { match name { "Nil" => "nil".to_doc(), "Int" | "UtfCodepoint" => "integer()".to_doc(), "String" => "binary()".to_doc(), "Bool" => "boolean()".to_doc(), "Float" => "float()".to_doc(), "BitArray" => "bitstring()".to_doc(), "List" => { let arg0 = self.print(arguments.first().expect("print_prelude_type list")); "list(".to_doc().append(arg0).append(")") } "Result" => match arguments { [arg_ok, arg_err] => { let ok = tuple(["ok".to_doc(), self.print(arg_ok)]); let error = tuple(["error".to_doc(), self.print(arg_err)]); docvec![ok, break_(" |", " | "), error].nest(INDENT).group() } _ => panic!("print_prelude_type result expects ok and err"), }, // Getting here should mean we either forgot a built-in type or there is a // compiler error name => panic!("{name} is not a built-in type."), } } fn print_type_app( &self, module: &str, name: &str, arguments: &[Arc], ) -> Document<'static> { let arguments = join( arguments.iter().map(|argument| self.print(argument)), ", ".to_doc(), ); let name = erl_safe_type_name(to_snake_case(name)).to_doc(); if self.current_module == module { docvec![name, "(", arguments, ")"] } else { docvec![module_name_atom(module), ":", name, "(", arguments, ")"] } } fn print_fn(&self, arguments: &[Arc], return_: &Type) -> Document<'static> { let arguments = join( arguments.iter().map(|argument| self.print(argument)), ", ".to_doc(), ); let return_ = self.print(return_); "fun((" .to_doc() .append(arguments) .append(") -> ") .append(return_) .append(")") } /// Print type vars as `any()`. fn var_as_any(mut self) -> Self { self.var_as_any = true; self } } fn find_private_functions_referenced_in_importable_constants( module: &TypedModule, ) -> im::HashSet { let mut overridden_publicity = im::HashSet::new(); for constant in &module.definitions.constants { if constant.publicity.is_importable() { find_referenced_private_functions(&constant.value, &mut overridden_publicity) } } overridden_publicity } fn find_referenced_private_functions( constant: &TypedConstant, already_found: &mut im::HashSet, ) { match constant { Constant::Invalid { .. } => panic!("invalid constants should not reach code generation"), Constant::RecordUpdate { .. } => { panic!("record updates should not reach code generation") } Constant::Int { .. } | Constant::Float { .. } | Constant::String { .. } | Constant::BitArray { .. } => (), TypedConstant::Var { name, constructor, .. } => { if let Some(ValueConstructor { type_, .. }) = constructor.as_deref() && let Type::Fn { .. } = **type_ { let _ = already_found.insert(name.clone()); } } TypedConstant::Record { arguments, .. } => arguments .iter() .for_each(|argument| find_referenced_private_functions(&argument.value, already_found)), TypedConstant::StringConcatenation { left, right, .. } => { find_referenced_private_functions(left, already_found); find_referenced_private_functions(right, already_found); } Constant::Tuple { elements, .. } | Constant::List { elements, .. } => elements .iter() .for_each(|element| find_referenced_private_functions(element, already_found)), } } ================================================ FILE: compiler-core/src/error/snapshots/gleam_core__error__tests__shell_program_not_found_bun_linux_other.snap ================================================ --- source: compiler-core/src/error/tests.rs expression: "err[0].text" --- The program `bun` was not found. Is it installed? Documentation for installing Bun can be viewed here: https://bun.sh/docs/installation/ ================================================ FILE: compiler-core/src/error/snapshots/gleam_core__error__tests__shell_program_not_found_bun_linux_ubuntu.snap ================================================ --- source: compiler-core/src/error/tests.rs expression: "err[0].text" --- The program `bun` was not found. Is it installed? Documentation for installing Bun can be viewed here: https://bun.sh/docs/installation/ ================================================ FILE: compiler-core/src/error/snapshots/gleam_core__error__tests__shell_program_not_found_bun_macos_other.snap ================================================ --- source: compiler-core/src/error/tests.rs expression: "err[0].text" --- The program `bun` was not found. Is it installed? You can install Bun via homebrew: brew install oven-sh/bun/bun Documentation for installing Bun can be viewed here: https://bun.sh/docs/installation/ ================================================ FILE: compiler-core/src/error/snapshots/gleam_core__error__tests__shell_program_not_found_deno_linux_other.snap ================================================ --- source: compiler-core/src/error/tests.rs expression: "err[0].text" --- The program `deno` was not found. Is it installed? Documentation for installing Deno can be viewed here: https://docs.deno.com/runtime/getting_started/installation/ ================================================ FILE: compiler-core/src/error/snapshots/gleam_core__error__tests__shell_program_not_found_deno_linux_ubuntu.snap ================================================ --- source: compiler-core/src/error/tests.rs expression: "err[0].text" --- The program `deno` was not found. Is it installed? Documentation for installing Deno can be viewed here: https://docs.deno.com/runtime/getting_started/installation/ ================================================ FILE: compiler-core/src/error/snapshots/gleam_core__error__tests__shell_program_not_found_deno_macos_other.snap ================================================ --- source: compiler-core/src/error/tests.rs expression: "err[0].text" --- The program `deno` was not found. Is it installed? You can install Deno via homebrew: brew install deno Documentation for installing Deno can be viewed here: https://docs.deno.com/runtime/getting_started/installation/ ================================================ FILE: compiler-core/src/error/snapshots/gleam_core__error__tests__shell_program_not_found_elixir_linux_other.snap ================================================ --- source: compiler-core/src/error/tests.rs expression: "err[0].text" --- The program `elixir` was not found. Is it installed? Documentation for installing Elixir can be viewed here: https://elixir-lang.org/install.html ================================================ FILE: compiler-core/src/error/snapshots/gleam_core__error__tests__shell_program_not_found_elixir_linux_ubuntu.snap ================================================ --- source: compiler-core/src/error/tests.rs expression: "err[0].text" --- The program `elixir` was not found. Is it installed? You can install Elixir via apt: sudo apt install elixir Documentation for installing Elixir can be viewed here: https://elixir-lang.org/install.html ================================================ FILE: compiler-core/src/error/snapshots/gleam_core__error__tests__shell_program_not_found_elixir_macos_other.snap ================================================ --- source: compiler-core/src/error/tests.rs expression: "err[0].text" --- The program `elixir` was not found. Is it installed? You can install Elixir via homebrew: brew install elixir Documentation for installing Elixir can be viewed here: https://elixir-lang.org/install.html ================================================ FILE: compiler-core/src/error/snapshots/gleam_core__error__tests__shell_program_not_found_erlc_linux_other.snap ================================================ --- source: compiler-core/src/error/tests.rs expression: "err[0].text" --- The program `erlc` was not found. Is it installed? Documentation for installing Erlang can be viewed here: https://gleam.run/getting-started/installing/ ================================================ FILE: compiler-core/src/error/snapshots/gleam_core__error__tests__shell_program_not_found_erlc_linux_ubuntu.snap ================================================ --- source: compiler-core/src/error/tests.rs expression: "err[0].text" --- The program `erlc` was not found. Is it installed? Documentation for installing Erlang can be viewed here: https://gleam.run/getting-started/installing/ ================================================ FILE: compiler-core/src/error/snapshots/gleam_core__error__tests__shell_program_not_found_erlc_macos_other.snap ================================================ --- source: compiler-core/src/error/tests.rs expression: "err[0].text" --- The program `erlc` was not found. Is it installed? You can install Erlang via homebrew: brew install erlang Documentation for installing Erlang can be viewed here: https://gleam.run/getting-started/installing/ ================================================ FILE: compiler-core/src/error/snapshots/gleam_core__error__tests__shell_program_not_found_git_linux_other.snap ================================================ --- source: compiler-core/src/error/tests.rs expression: "err[0].text" --- The program `git` was not found. Is it installed? Documentation for installing Git can be viewed here: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git ================================================ FILE: compiler-core/src/error/snapshots/gleam_core__error__tests__shell_program_not_found_git_linux_ubuntu.snap ================================================ --- source: compiler-core/src/error/tests.rs expression: "err[0].text" --- The program `git` was not found. Is it installed? You can install Git via apt: sudo apt install git Documentation for installing Git can be viewed here: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git ================================================ FILE: compiler-core/src/error/snapshots/gleam_core__error__tests__shell_program_not_found_git_macos_other.snap ================================================ --- source: compiler-core/src/error/tests.rs expression: "err[0].text" --- The program `git` was not found. Is it installed? You can install Git via homebrew: brew install git Documentation for installing Git can be viewed here: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git ================================================ FILE: compiler-core/src/error/snapshots/gleam_core__error__tests__shell_program_not_found_node_linux_other.snap ================================================ --- source: compiler-core/src/error/tests.rs expression: "err[0].text" --- The program `node` was not found. Is it installed? Documentation for installing Node.js via package manager can be viewed here: https://nodejs.org/en/download/package-manager/all/ ================================================ FILE: compiler-core/src/error/snapshots/gleam_core__error__tests__shell_program_not_found_node_linux_ubuntu.snap ================================================ --- source: compiler-core/src/error/tests.rs expression: "err[0].text" --- The program `node` was not found. Is it installed? Documentation for installing Node.js via package manager can be viewed here: https://nodejs.org/en/download/package-manager/all/ ================================================ FILE: compiler-core/src/error/snapshots/gleam_core__error__tests__shell_program_not_found_node_macos_other.snap ================================================ --- source: compiler-core/src/error/tests.rs expression: "err[0].text" --- The program `node` was not found. Is it installed? You can install Node.js via homebrew: brew install node Documentation for installing Node.js via package manager can be viewed here: https://nodejs.org/en/download/package-manager/all/ ================================================ FILE: compiler-core/src/error/snapshots/gleam_core__error__tests__shell_program_not_found_rebar3_linux_other.snap ================================================ --- source: compiler-core/src/error/tests.rs expression: "err[0].text" --- The program `rebar3` was not found. Is it installed? Documentation for installing Rebar3 can be viewed here: https://rebar3.org/docs/getting-started/ ================================================ FILE: compiler-core/src/error/snapshots/gleam_core__error__tests__shell_program_not_found_rebar3_linux_ubuntu.snap ================================================ --- source: compiler-core/src/error/tests.rs expression: "err[0].text" --- The program `rebar3` was not found. Is it installed? Documentation for installing Rebar3 can be viewed here: https://rebar3.org/docs/getting-started/ ================================================ FILE: compiler-core/src/error/snapshots/gleam_core__error__tests__shell_program_not_found_rebar3_macos_other.snap ================================================ --- source: compiler-core/src/error/tests.rs expression: "err[0].text" --- The program `rebar3` was not found. Is it installed? You can install Rebar3 via homebrew: brew install rebar3 Documentation for installing Rebar3 can be viewed here: https://rebar3.org/docs/getting-started/ ================================================ FILE: compiler-core/src/error/tests.rs ================================================ use super::*; use insta::assert_snapshot; #[test] fn test_shell_program_not_found_error() { let cmds = vec!["erlc", "rebar3", "deno", "elixir", "node", "bun", "git"]; let oses = vec!["macos", "linux"]; let distros = vec!["ubuntu", "other"]; for cmd in &cmds { for os in &oses { if os != &"linux" { let err = Error::ShellProgramNotFound { program: cmd.to_string(), os: parse_os(os, "other"), } .to_diagnostics(); assert_snapshot!( format!("shell_program_not_found_{cmd}_{os}_other"), err[0].text ); } else { for distro in &distros { let err = Error::ShellProgramNotFound { program: cmd.to_string(), os: parse_os(os, distro), } .to_diagnostics(); assert_snapshot!( format!("shell_program_not_found_{cmd}_{os}_{distro}"), err[0].text ); } } } } } ================================================ FILE: compiler-core/src/error.rs ================================================ #![allow(clippy::unwrap_used, clippy::expect_used)] use crate::bit_array::UnsupportedOption; use crate::build::{Origin, Outcome, Runtime, Target}; use crate::dependency::{PackageFetcher, ResolutionError}; use crate::diagnostic::{Diagnostic, ExtraLabel, Label, Location}; use crate::derivation_tree::DerivationTreePrinter; use crate::parse::error::ParseErrorDetails; use crate::strings::{to_snake_case, to_upper_camel_case}; use crate::type_::collapse_links; use crate::type_::error::{ IncorrectArityContext, InvalidImportKind, MissingAnnotation, ModuleValueUsageContext, Named, RecordField, UnexpectedLabelledArgKind, UnknownField, UnknownTypeHint, UnsafeRecordUpdateReason, }; use crate::type_::printer::{Names, Printer}; use crate::type_::{FieldAccessUsage, error::PatternMatchKind}; use crate::{ast::BinOp, parse::error::ParseErrorType, type_::Type}; use crate::{bit_array, diagnostic::Level, type_::UnifyErrorSituation}; use ecow::EcoString; use hexpm::version::Version; use itertools::Itertools; use std::borrow::Cow; use std::fmt::{Debug, Display}; use std::io::Write; use std::path::PathBuf; use std::sync::Arc; use termcolor::Buffer; use thiserror::Error; use vec1::Vec1; use camino::{Utf8Path, Utf8PathBuf}; pub type Name = EcoString; pub type Result = std::result::Result; #[cfg(test)] pub mod tests; macro_rules! wrap_format { ($($tts:tt)*) => { wrap(&format!($($tts)*)) } } #[derive(Debug, Clone, Eq, PartialEq)] pub struct UnknownImportDetails { pub module: Name, pub location: crate::ast::SrcSpan, pub path: Utf8PathBuf, pub src: EcoString, pub modules: Vec, } #[derive(Debug, Clone, Eq, PartialEq)] pub struct ImportCycleLocationDetails { pub location: crate::ast::SrcSpan, pub path: Utf8PathBuf, pub src: EcoString, } /// Describes where a defined module comes from. #[derive(Debug, Clone, Eq, PartialEq)] pub struct DefinedModuleOrigin { /// The package defining the module pub package_name: EcoString, /// The path to the module pub path: Utf8PathBuf, } #[derive(Debug, Eq, PartialEq, Error, Clone)] pub enum Error { #[error("failed to parse Gleam source code")] Parse { path: Utf8PathBuf, src: EcoString, error: Box, }, #[error("type checking failed")] Type { path: Utf8PathBuf, src: EcoString, errors: Vec1, names: Box, }, #[error("unknown import {import}")] UnknownImport { import: EcoString, // Boxed to prevent this variant from being overly large details: Box, }, #[error("duplicate module {module}")] DuplicateModule { module: Name, first: DefinedModuleOrigin, second: DefinedModuleOrigin, }, #[error("duplicate source file {file}")] DuplicateSourceFile { file: String }, #[error("duplicate native Erlang module {module}")] DuplicateNativeErlangModule { module: Name, first: Utf8PathBuf, second: Utf8PathBuf, }, #[error("gleam module {module} clashes with native file of same name")] ClashingGleamModuleAndNativeFileName { module: Name, gleam_file: Utf8PathBuf, native_file: Utf8PathBuf, }, #[error("cyclical module imports")] ImportCycle { modules: Vec1<(EcoString, ImportCycleLocationDetails)>, }, #[error("cyclical package dependencies")] PackageCycle { packages: Vec }, #[error("{action:?} {path:?} failed: {err:?}")] FileIo { kind: FileKind, action: FileIoAction, path: Utf8PathBuf, err: Option, }, #[error("Non Utf-8 Path: {path}")] NonUtf8Path { path: PathBuf }, #[error("{error}")] GitInitialization { error: String }, #[error("io operation failed")] StandardIo { action: StandardIoAction, err: Option, }, #[error("source code incorrectly formatted")] Format { problem_files: Vec }, #[error("Hex error: {0}")] Hex(String), #[error("{error}")] ExpandTar { error: String }, #[error("{err}")] AddTar { path: Utf8PathBuf, err: String }, #[error("{0}")] TarFinish(String), #[error("{0}")] Gzip(String), #[error("shell program `{program}` not found")] ShellProgramNotFound { program: String, os: OS }, #[error("shell program `{program}` failed")] ShellCommand { program: String, reason: ShellCommandFailureReason, }, #[error("{name} is not a valid project name")] InvalidProjectName { name: String, reason: InvalidProjectNameReason, }, #[error("{module} is not a valid module name")] InvalidModuleName { module: String }, #[error("{module} is not module")] ModuleDoesNotExist { module: EcoString, suggestion: Option, }, #[error("{module} does not have a main function")] ModuleDoesNotHaveMainFunction { module: EcoString, origin: Origin }, #[error("{module} does not have a public main function")] MainFunctionIsPrivate { module: EcoString }, #[error("{module}'s main function has the wrong arity so it can not be run")] MainFunctionHasWrongArity { module: EcoString, arity: usize }, #[error("{module}'s main function does not support the current target")] MainFunctionDoesNotSupportTarget { module: EcoString, target: Target }, #[error("{input} is not a valid version. {error}")] InvalidVersionFormat { input: String, error: String }, #[error("incompatible locked version. {error}")] IncompatibleLockedVersion { error: String }, #[error("project root already exists")] ProjectRootAlreadyExist { path: String }, #[error("File(s) already exist in {}", file_names.iter().map(|x| x.as_str()).join(", "))] OutputFilesAlreadyExist { file_names: Vec }, #[error("Packages not exist: {}", packages.iter().join(", "))] RemovedPackagesNotExist { packages: Vec }, #[error("Packages to update not exist: {}", packages.iter().join(", "))] PackagesToUpdateNotExist { packages: Vec }, #[error("unable to find project root")] UnableToFindProjectRoot { path: String }, #[error("gleam.toml version {toml_ver} does not match .app version {app_ver}")] VersionDoesNotMatch { toml_ver: String, app_ver: String }, #[error("warnings are not permitted")] ForbiddenWarnings { count: usize }, #[error("Invalid runtime for target {target:?}: {invalid_runtime:?}")] InvalidRuntime { target: Target, invalid_runtime: Runtime, }, #[error("package downloading failed: {error}")] DownloadPackageError { package_name: String, package_version: String, error: String, }, #[error("{0}")] Http(String), #[error("Failed to create canonical path for package {0}")] DependencyCanonicalizationFailed(String), #[error("Could not find versions that satisfy dependency requirements")] DependencyResolutionNoSolution { root_package_name: EcoString, derivation_tree: Box, String>>>, }, #[error("Dependency resolution failed: {0}")] DependencyResolutionError(String), #[error("The package {0} is listed in dependencies and dev_dependencies")] DuplicateDependency(EcoString), #[error("Expected package {expected} at path {path} but found {found} instead")] WrongDependencyProvided { path: Utf8PathBuf, expected: String, found: String, }, #[error("The package {package} is provided multiple times, as {source_1} and {source_2}")] ProvidedDependencyConflict { package: String, source_1: String, source_2: String, }, #[error("The package was missing required fields for publishing")] MissingHexPublishFields { description_missing: bool, licence_missing: bool, }, #[error("Dependency {package:?} has not been published to Hex")] PublishNonHexDependencies { package: String }, #[error("The package {package} uses unsupported build tools {build_tools:?}")] UnsupportedBuildTool { package: String, build_tools: Vec, }, #[error("Opening docs at {path} failed: {error}")] FailedToOpenDocs { path: Utf8PathBuf, error: String }, #[error( "The package {package} requires a Gleam version satisfying \ {required_version} and you are using v{gleam_version}" )] IncompatibleCompilerVersion { package: String, required_version: String, gleam_version: String, }, #[error("The --javascript-prelude flag must be given when compiling to JavaScript")] JavaScriptPreludeRequired, #[error("The modules {unfinished:?} contain todo expressions and so cannot be published")] CannotPublishTodo { unfinished: Vec }, #[error("The modules {unfinished:?} contain todo expressions and so cannot be published")] CannotPublishEcho { unfinished: Vec }, #[error( "The modules {unfinished:?} contain internal types in their public API so cannot be published" )] CannotPublishLeakedInternalType { unfinished: Vec }, #[error("The modules {unfinished:?} are empty and so cannot be published")] CannotPublishEmptyModules { unfinished: Vec }, #[error("Publishing packages with an invalid README is not permitted")] CannotPublishWithInvalidReadme { reason: InvalidReadmeReason }, #[error("Publishing packages to reserve names is not permitted")] HexPackageSquatting, #[error("The package includes the default main function so cannot be published")] CannotPublishWithDefaultMain { package_name: EcoString }, #[error("Corrupt manifest.toml")] CorruptManifest, #[error("The Gleam module {path} would overwrite the Erlang module {name}")] GleamModuleWouldOverwriteStandardErlangModule { name: EcoString, path: Utf8PathBuf }, #[error("Version already published")] HexPublishReplaceRequired { version: String }, #[error("Insufficient permissions to publish {name} {version}")] HexPublishAccessDenied { name: String, version: String }, #[error("The gleam version constraint is wrong and so cannot be published")] CannotPublishWrongVersion { minimum_required_version: SmallVersion, wrongfully_allowed_version: SmallVersion, }, #[error("Failed to encrypt local Hex API key")] FailedToEncryptLocalHexApiKey { detail: String }, #[error("Failed to decrypt local Hex API key")] FailedToDecryptLocalHexApiKey { detail: String }, #[error("Cannot add a package with the same name as a dependency")] CannotAddSelfAsDependency { name: EcoString }, #[error("Incorrect Hex one-time-password")] IncorrectHexOneTimePassword, } #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum InvalidReadmeReason { Missing, Empty, Default, } // A wrapper that ignores the inner value for equality: #[derive(Debug, Clone)] pub struct NeverEqual(pub T); impl PartialEq for NeverEqual { fn eq(&self, _other: &Self) -> bool { false } } impl Eq for NeverEqual {} /// This is to make clippy happy and not make the error variant too big by /// storing an entire `hexpm::version::Version` in the error. /// /// This is enough to report wrong Gleam compiler versions. /// #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct SmallVersion { major: u8, minor: u8, patch: u8, } impl Display for SmallVersion { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&format!("{}.{}.{}", self.major, self.minor, self.patch)) } } impl SmallVersion { pub fn from_hexpm(version: Version) -> Self { Self { major: version.major as u8, minor: version.minor as u8, patch: version.patch as u8, } } } #[derive(Debug, Clone, Eq, PartialEq, Copy)] pub enum OS { Linux(Distro), MacOS, Windows, Other, } #[derive(Debug, Clone, Eq, PartialEq, Copy)] pub enum Distro { Ubuntu, Debian, Other, } pub fn parse_os(os: &str, distro: &str) -> OS { match os { "macos" => OS::MacOS, "windows" => OS::Windows, "linux" => OS::Linux(parse_linux_distribution(distro)), _ => OS::Other, } } pub fn parse_linux_distribution(distro: &str) -> Distro { match distro { "ubuntu" => Distro::Ubuntu, "debian" => Distro::Debian, _ => Distro::Other, } } #[derive(Debug, Eq, PartialEq, Clone)] pub enum ShellCommandFailureReason { /// When we don't have any context about the failure Unknown, /// When the actual running of the command failed for some reason. IoError(std::io::ErrorKind), /// When the shell command returned an error status ShellCommandError(String), } impl Error { pub fn http(error: E) -> Error where E: std::error::Error, { Self::Http(error.to_string()) } pub fn hex(error: hexpm::ApiError) -> Error { match error { hexpm::ApiError::Json(_) | hexpm::ApiError::Io(_) | hexpm::ApiError::RateLimited | hexpm::ApiError::InvalidCredentials | hexpm::ApiError::UnexpectedResponse(_, _) | hexpm::ApiError::InvalidPackageNameFormat(_) | hexpm::ApiError::IncorrectPayloadSignature | hexpm::ApiError::InvalidProtobuf(_) | hexpm::ApiError::InvalidVersionFormat(_) | hexpm::ApiError::NotFound | hexpm::ApiError::InvalidVersionRequirementFormat(_) | hexpm::ApiError::IncorrectChecksum | hexpm::ApiError::Forbidden | hexpm::ApiError::NotReplacing | hexpm::ApiError::LateModification | hexpm::ApiError::OAuthTimeout | hexpm::ApiError::OAuthAccessDenied | hexpm::ApiError::ExpiredToken | hexpm::ApiError::OAuthRefreshTokenRejected => Self::Hex(error.to_string()), hexpm::ApiError::IncorrectOneTimePassword => Self::IncorrectHexOneTimePassword, } } pub fn add_tar(path: P, error: E) -> Error where P: AsRef, E: std::error::Error, { Self::AddTar { path: path.as_ref().to_path_buf(), err: error.to_string(), } } pub fn finish_tar(error: E) -> Error where E: std::error::Error, { Self::TarFinish(error.to_string()) } pub fn dependency_resolution_failed( error: ResolutionError<'_, T>, root_package_name: EcoString, ) -> Error { match error { ResolutionError::NoSolution(derivation_tree) => Self::DependencyResolutionNoSolution { root_package_name, derivation_tree: Box::new(NeverEqual(derivation_tree)), }, ResolutionError::ErrorRetrievingDependencies { package, version, source, } => Self::DependencyResolutionError(format!( "An error occurred while trying to retrieve dependencies of {package}@{version}: {source}", )), ResolutionError::ErrorChoosingVersion { package, source } => { Self::DependencyResolutionError(format!( "An error occurred while choosing the version of {package}: {source}", )) } ResolutionError::ErrorInShouldCancel(err) => Self::DependencyResolutionError(format!( "Dependency resolution was cancelled. {err}" )), } } pub fn expand_tar(error: E) -> Error where E: std::error::Error, { Self::ExpandTar { error: error.to_string(), } } } impl From for Outcome { fn from(error: Error) -> Self { Outcome::TotalFailure(error) } } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum InvalidProjectNameReason { Format, FormatNotLowercase, GleamPrefix, ErlangReservedWord, ErlangStandardLibraryModule, GleamReservedWord, GleamReservedModule, } pub fn format_invalid_project_name_error( name: &str, reason: &InvalidProjectNameReason, with_suggestion: &Option, ) -> String { let reason_message = match reason { InvalidProjectNameReason::ErlangReservedWord => "is a reserved word in Erlang.", InvalidProjectNameReason::ErlangStandardLibraryModule => { "is a standard library module in Erlang." } InvalidProjectNameReason::GleamReservedWord => "is a reserved word in Gleam.", InvalidProjectNameReason::GleamReservedModule => "is a reserved module name in Gleam.", InvalidProjectNameReason::FormatNotLowercase => { "does not have the correct format. Project names \ may only contain lowercase letters." } InvalidProjectNameReason::Format => { "does not have the correct format. Project names \ must start with a lowercase letter and may only contain lowercase letters, \ numbers and underscores." } InvalidProjectNameReason::GleamPrefix => { "has the reserved prefix `gleam_`. \ This prefix is intended for official Gleam packages only." } }; match with_suggestion { Some(suggested_name) => wrap_format!( "We were not able to create your project as `{}` {} Would you like to name your project '{}' instead?", name, reason_message, suggested_name ), None => wrap_format!( "We were not able to create your project as `{}` {} Please try again with a different project name.", name, reason_message ), } } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum StandardIoAction { Read, Write, } impl StandardIoAction { fn text(&self) -> &'static str { match self { StandardIoAction::Read => "read from", StandardIoAction::Write => "write to", } } } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum FileIoAction { Link, Open, Copy, Read, Parse, Delete, // Rename, Create, WriteTo, Canonicalise, UpdatePermissions, FindParent, ReadMetadata, } impl FileIoAction { fn text(&self) -> &'static str { match self { FileIoAction::Link => "link", FileIoAction::Open => "open", FileIoAction::Copy => "copy", FileIoAction::Read => "read", FileIoAction::Parse => "parse", FileIoAction::Delete => "delete", // FileIoAction::Rename => "rename", FileIoAction::Create => "create", FileIoAction::WriteTo => "write to", FileIoAction::FindParent => "find the parent of", FileIoAction::Canonicalise => "canonicalise", FileIoAction::UpdatePermissions => "update permissions of", FileIoAction::ReadMetadata => "read metadata of", } } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum FileKind { File, Directory, } impl FileKind { fn text(&self) -> &'static str { match self { FileKind::File => "file", FileKind::Directory => "directory", } } } // https://github.com/rust-lang/rust/blob/03994e498df79aa1f97f7bbcfd52d57c8e865049/compiler/rustc_span/src/edit_distance.rs pub fn edit_distance(a: &str, b: &str, limit: usize) -> Option { let mut a = &a.chars().collect::>()[..]; let mut b = &b.chars().collect::>()[..]; if a.len() < b.len() { std::mem::swap(&mut a, &mut b); } let min_dist = a.len() - b.len(); // If we know the limit will be exceeded, we can return early. if min_dist > limit { return None; } // Strip common prefix. while !b.is_empty() && !a.is_empty() { let (b_first, b_rest) = b.split_last().expect("Failed to split 'b' slice"); let (a_first, a_rest) = a.split_last().expect("Failed to split 'a' slice"); if b_first == a_first { a = a_rest; b = b_rest; } else { break; } } // If either string is empty, the distance is the length of the other. // We know that `b` is the shorter string, so we don't need to check `a`. if b.is_empty() { return Some(min_dist); } let mut prev_prev = vec![usize::MAX; b.len() + 1]; let mut prev = (0..=b.len()).collect::>(); let mut current = vec![0; b.len() + 1]; // row by row for i in 1..=a.len() { if let Some(element) = current.get_mut(0) { *element = i; } let a_idx = i - 1; // column by column for j in 1..=b.len() { let b_idx = j - 1; // There is no cost to substitute a character with itself. let substitution_cost = match (a.get(a_idx), b.get(b_idx)) { (Some(&a_char), Some(&b_char)) => { if a_char == b_char { 0 } else { 1 } } _ => panic!("Index out of bounds"), }; let insertion = current.get(j - 1).map_or(usize::MAX, |&x| x + 1); if let Some(value) = current.get_mut(j) { *value = std::cmp::min( // deletion prev.get(j).map_or(usize::MAX, |&x| x + 1), std::cmp::min( // insertion insertion, // substitution prev.get(j - 1) .map_or(usize::MAX, |&x| x + substitution_cost), ), ); } if (i > 1) && (j > 1) && let (Some(&a_val), Some(&b_val_prev), Some(&a_val_prev), Some(&b_val)) = ( a.get(a_idx), b.get(b_idx - 1), a.get(a_idx - 1), b.get(b_idx), ) && (a_val == b_val_prev) && (a_val_prev == b_val) { // transposition if let Some(curr) = current.get_mut(j) && let Some(&prev_prev_val) = prev_prev.get(j - 2) { *curr = std::cmp::min(*curr, prev_prev_val + 1); } } } // Rotate the buffers, reusing the memory. [prev_prev, prev, current] = [prev, current, prev_prev]; } // `prev` because we already rotated the buffers. let distance = match prev.get(b.len()) { Some(&d) => d, None => usize::MAX, }; (distance <= limit).then_some(distance) } fn edit_distance_with_substrings(a: &str, b: &str, limit: usize) -> Option { let n = a.chars().count(); let m = b.chars().count(); // Check one isn't less than half the length of the other. If this is true then there is a // big difference in length. let big_len_diff = (n * 2) < m || (m * 2) < n; let len_diff = m.abs_diff(n); let distance = edit_distance(a, b, limit + len_diff)?; // This is the crux, subtracting length difference means exact substring matches will now be 0 let score = distance - len_diff; // If the score is 0 but the words have different lengths then it's a substring match not a full // word match let score = if score == 0 && len_diff > 0 && !big_len_diff { 1 // Exact substring match, but not a total word match so return non-zero } else if !big_len_diff { // Not a big difference in length, discount cost of length difference score + len_diff.div_ceil(2) } else { // A big difference in length, add back the difference in length to the score score + len_diff }; (score <= limit).then_some(score) } fn did_you_mean(name: &str, options: &[EcoString]) -> Option { // If only one option is given, return that option. // This seems to solve the `unknown_variable_3` test. if options.len() == 1 { return options .first() .map(|option| format!("Did you mean `{option}`?")); } // Check for case-insensitive matches. // This solves the comparison to small and single character terms, // such as the test on `type_vars_must_be_declared`. if let Some(exact_match) = options .iter() .find(|&option| option.eq_ignore_ascii_case(name)) { return Some(format!("Did you mean `{exact_match}`?")); } // Calculate the threshold as one third of the name's length, with a minimum of 1. let threshold = std::cmp::max(name.chars().count() / 3, 1); // Filter and sort options based on edit distance. options .iter() .filter(|&option| option != crate::ast::CAPTURE_VARIABLE) .sorted() .filter_map(|option| { edit_distance_with_substrings(option, name, threshold) .map(|distance| (option, distance)) }) .min_by_key(|&(_, distance)| distance) .map(|(option, _)| format!("Did you mean `{option}`?")) } fn to_ordinal(value: u32) -> String { match value % 10 { // All numbers starting with 1 end in `th` (11th, 12th, 13th, etc.) _ if value / 10 == 1 => format!("{value}th"), 1 => format!("{value}st"), 2 => format!("{value}nd"), 3 => format!("{value}rd"), _ => format!("{value}th"), } } impl Error { pub fn pretty_string(&self) -> String { let mut nocolor = Buffer::no_color(); self.pretty(&mut nocolor); String::from_utf8(nocolor.into_inner()).expect("Error printing produced invalid utf8") } pub fn pretty(&self, buffer: &mut Buffer) { for diagnostic in self.to_diagnostics() { diagnostic.write(buffer); writeln!(buffer).expect("write new line after diagnostic"); } } pub fn to_diagnostics(&self) -> Vec { use crate::type_::Error as TypeError; match self { Error::IncorrectHexOneTimePassword => { let text = "That two-factor authentication code was rejected by Hex, please try again. If you need to reconfigure your Hex two-factor security it can be done via the Hex website: https://hex.pm/dashboard/security " .into(); vec![Diagnostic { title: "Incorrect two-factor authentication code".into(), text, level: Level::Error, location: None, hint: None, }] } Error::HexPackageSquatting => { let text = "You appear to be attempting to reserve a name on Hex rather than publishing a working package. This is against the Hex terms of service and can result in package deletion or account suspension. " .into(); vec![Diagnostic { title: "Invalid Hex package".into(), text, level: Level::Error, location: None, hint: None, }] } Error::CannotPublishWithInvalidReadme { reason: InvalidReadmeReason::Default, } => { let text = "You appear to be attempting to publish a package with the default README generated by the `gleam new` command. That is meant as a placeholder and a published package should have its own carefully written README. " .into(); vec![Diagnostic { title: "Cannot publish with default README".into(), text, level: Level::Error, location: None, hint: Some( "Update your project's README to describe it before publishing".into(), ), }] } Error::CannotPublishWithInvalidReadme { reason: InvalidReadmeReason::Missing, } => { let text = "You appear to be attempting to publish a package with no README. All published packages should have one. " .into(); vec![Diagnostic { title: "Cannot publish with no README".into(), text, level: Level::Error, location: None, hint: Some("Add a README to your project before publishing.".into()), }] } Error::CannotPublishWithInvalidReadme { reason: InvalidReadmeReason::Empty, } => { let text = "You appear to be attempting to publish a package with an empty README. All published packages should have a non empty README. " .into(); vec![Diagnostic { title: "Cannot publish with empty README".into(), text, level: Level::Error, location: None, hint: Some( "Update your project's README to describe it before publishing".into(), ), }] } Error::CannotPublishWithDefaultMain { package_name } => { let text = wrap_format!( "Packages with the default main function cannot be published Remove or modify the main function that contains only: `io.println(\"Hello from {package_name}!\")`" ); vec![Diagnostic { title: "Cannot publish with default main function".into(), text, level: Level::Error, location: None, hint: None, }] } Error::InvalidProjectName { name, reason } => { let text = format_invalid_project_name_error(name, reason, &None); vec![Diagnostic { title: "Invalid project name".into(), text, hint: None, level: Level::Error, location: None, }] } Error::InvalidModuleName { module } => vec![Diagnostic { title: "Invalid module name".into(), text: format!( "`{module}` is not a valid module name. Module names can only contain lowercase letters, underscore, and forward slash and must not end with a slash." ), level: Level::Error, location: None, hint: None, }], Error::ModuleDoesNotExist { module, suggestion } => { let hint = match suggestion { Some(suggestion) => format!("Did you mean `{suggestion}`?"), None => format!("Try creating the file `src/{module}.gleam`."), }; vec![Diagnostic { title: "Module does not exist".into(), text: format!("Module `{module}` was not found."), level: Level::Error, location: None, hint: Some(hint), }] } Error::ModuleDoesNotHaveMainFunction { module, origin } => vec![Diagnostic { title: "Module does not have a main function".into(), text: format!( "`{module}` does not have a main function so the module can not be run." ), level: Level::Error, location: None, hint: Some(format!( "Add a public `main` function to `{}/{module}.gleam`.", origin.folder_name() )), }], Error::MainFunctionIsPrivate { module } => vec![Diagnostic { title: "Module does not have a public main function".into(), text: wrap_format!( "`{module}` has a main function, but it is private, so it cannot be run." ), level: Level::Error, location: None, hint: Some(wrap_format!( "Make the `main` function in the `{module}` module public." )), }], Error::MainFunctionDoesNotSupportTarget { module, target } => vec![Diagnostic { title: "Target not supported".into(), text: wrap_format!( "`{module}` has a main function, but it does not support the {target} \ target, so it cannot be run.", target = target.as_presentable_str(), ), level: Level::Error, location: None, hint: None, }], Error::MainFunctionHasWrongArity { module, arity } => vec![Diagnostic { title: "Main function has wrong arity".into(), text: wrap_format!( "`{module}:main` should have an arity of 0 to be run but its arity is {arity}." ), level: Level::Error, location: None, hint: Some("Change the function signature of main to `pub fn main() {}`.".into()), }], Error::ProjectRootAlreadyExist { path } => vec![Diagnostic { title: "Project folder already exists".into(), text: format!("Project folder root:\n\n {path}"), level: Level::Error, hint: None, location: None, }], Error::OutputFilesAlreadyExist { file_names } => vec![Diagnostic { title: format!( "{} already exist{} in target directory", if file_names.len() == 1 { "File" } else { "Files" }, if file_names.len() == 1 { "" } else { "s" } ), text: format!( "{} If you want to overwrite these files, delete them and run the command again. ", file_names .iter() .map(|name| format!(" - {}", name.as_str())) .join("\n") ), level: Level::Error, hint: None, location: None, }], Error::RemovedPackagesNotExist { packages } => vec![Diagnostic { title: "Package not found".into(), text: format!( "These packages are not dependencies of your package so they could not be removed. {} ", packages .iter() .map(|p| format!(" - {}", p.as_str())) .join("\n") ), level: Level::Error, hint: None, location: None, }], Error::PackagesToUpdateNotExist { packages } => vec![Diagnostic { title: "Packages to update not found".into(), text: format!( "These packages are not dependencies of your package so they could not be updated. {} ", packages .iter() .map(|p| format!(" - {}", p.as_str())) .join("\n") ), level: Level::Error, hint: None, location: None, }], Error::CannotPublishTodo { unfinished } => vec![Diagnostic { title: "Cannot publish unfinished code".into(), text: format!( "These modules contain todo expressions and cannot be published: {} Please remove them and try again. ", unfinished .iter() .map(|name| format!(" - {}", name.as_str())) .join("\n") ), level: Level::Error, hint: None, location: None, }], Error::CannotPublishEcho { unfinished } => vec![Diagnostic { title: "Cannot publish unfinished code".into(), text: format!( "These modules contain echo expressions and cannot be published: {} `echo` is only meant for debug printing, please remove them and try again. ", unfinished .iter() .map(|name| format!(" - {}", name.as_str())) .join("\n") ), level: Level::Error, hint: None, location: None, }], Error::CannotPublishWrongVersion { minimum_required_version, wrongfully_allowed_version, } => vec![Diagnostic { title: "Cannot publish package with wrong Gleam version range".into(), text: wrap(&format!( "Your package uses features that require at least v{minimum_required_version}. But the Gleam version range specified in your `gleam.toml` would allow this \ code to run on an earlier version like v{wrongfully_allowed_version}, \ resulting in compilation errors!" )), level: Level::Error, hint: Some(format!( "Remove the version constraint from your `gleam.toml` or update it to be: gleam = \">= {minimum_required_version}\"" )), location: None, }], Error::CannotPublishLeakedInternalType { unfinished } => vec![Diagnostic { title: "Cannot publish unfinished code".into(), text: format!( "These modules leak internal types in their public API and cannot be published: {} Please make sure internal types do not appear in public functions and try again. ", unfinished .iter() .map(|name| format!(" - {}", name.as_str())) .join("\n") ), level: Level::Error, hint: None, location: None, }], Error::CannotPublishEmptyModules { unfinished } => vec![Diagnostic { title: "Cannot publish empty modules".into(), text: wrap_format!( "These modules contain no public definitions and cannot be published: {} Please add public functions, types, or constants to these modules, or remove them and try again.", unfinished .iter() .map(|name| format!(" - {}", name.as_str())) .join("\n") ), level: Level::Error, hint: None, location: None, }], Error::UnableToFindProjectRoot { path } => { let text = wrap_format!( "We were unable to find gleam.toml. We searched in {path} and all parent directories." ); vec![Diagnostic { title: "Project not found".into(), text, hint: None, level: Level::Error, location: None, }] } Error::VersionDoesNotMatch { toml_ver, app_ver } => { let text = format!( "The version in gleam.toml \"{toml_ver}\" does not match the version in your app.src file \"{app_ver}\"." ); vec![Diagnostic { title: "Version does not match".into(), hint: None, text, level: Level::Error, location: None, }] } Error::ShellProgramNotFound { program, os } => { let mut text = format!("The program `{program}` was not found. Is it installed?"); match os { OS::MacOS => { fn brew_install(name: &str, pkg: &str) -> String { format!("\n\nYou can install {name} via homebrew: brew install {pkg}",) } match program.as_str() { "erl" | "erlc" | "escript" => { text.push_str(&brew_install("Erlang", "erlang")) } "rebar3" => text.push_str(&brew_install("Rebar3", "rebar3")), "deno" => text.push_str(&brew_install("Deno", "deno")), "elixir" => text.push_str(&brew_install("Elixir", "elixir")), "node" => text.push_str(&brew_install("Node.js", "node")), "bun" => text.push_str(&brew_install("Bun", "oven-sh/bun/bun")), "git" => text.push_str(&brew_install("Git", "git")), _ => (), } } OS::Linux(distro) => { fn apt_install(name: &str, pkg: &str) -> String { format!("\n\nYou can install {name} via apt: sudo apt install {pkg}") } match distro { Distro::Ubuntu | Distro::Debian => match program.as_str() { "elixir" => text.push_str(&apt_install("Elixir", "elixir")), "git" => text.push_str(&apt_install("Git", "git")), _ => (), }, Distro::Other => (), } } OS::Windows | OS::Other => (), } text.push('\n'); match program.as_str() { "erl" | "erlc" | "escript" => text.push_str( " Documentation for installing Erlang can be viewed here: https://gleam.run/getting-started/installing/", ), "rebar3" => text.push_str( " Documentation for installing Rebar3 can be viewed here: https://rebar3.org/docs/getting-started/", ), "deno" => text.push_str( " Documentation for installing Deno can be viewed here: https://docs.deno.com/runtime/getting_started/installation/", ), "elixir" => text.push_str( " Documentation for installing Elixir can be viewed here: https://elixir-lang.org/install.html", ), "node" => text.push_str( " Documentation for installing Node.js via package manager can be viewed here: https://nodejs.org/en/download/package-manager/all/", ), "bun" => text.push_str( " Documentation for installing Bun can be viewed here: https://bun.sh/docs/installation/", ), "git" => text.push_str( " Documentation for installing Git can be viewed here: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git", ), _ => (), } vec![Diagnostic { title: "Program not found".into(), text, hint: None, level: Level::Error, location: None, }] } Error::ShellCommand { program: command, reason: ShellCommandFailureReason::Unknown, } => { let text = format!("There was a problem when running the shell command `{command}`."); vec![Diagnostic { title: "Shell command failure".into(), text, hint: None, level: Level::Error, location: None, }] } Error::ShellCommand { program: command, reason: ShellCommandFailureReason::IoError(err), } => { let text = format!( "There was a problem when running the shell command `{}`. The error from the shell command library was: {}", command, std_io_error_kind_text(err) ); vec![Diagnostic { title: "Shell command failure".into(), text, hint: None, level: Level::Error, location: None, }] } Error::ShellCommand { program: command, reason: ShellCommandFailureReason::ShellCommandError(err), } => { let text = format!( "There was a problem when running the shell command `{command}`. The error from the shell command was: {err}" ); vec![Diagnostic { title: "Shell command failure".into(), text, hint: None, level: Level::Error, location: None, }] } Error::Gzip(detail) => { let text = format!( "There was a problem when applying gzip compression. This was error from the gzip library: {detail}" ); vec![Diagnostic { title: "Gzip compression failure".into(), text, hint: None, level: Level::Error, location: None, }] } Error::AddTar { path, err } => { let text = format!( "There was a problem when attempting to add the file {path} to a tar archive. This was error from the tar library: {err}" ); vec![Diagnostic { title: "Failure creating tar archive".into(), text, hint: None, level: Level::Error, location: None, }] } Error::ExpandTar { error } => { let text = format!( "There was a problem when attempting to expand a to a tar archive. This was error from the tar library: {error}" ); vec![Diagnostic { title: "Failure opening tar archive".into(), text, hint: None, level: Level::Error, location: None, }] } Error::TarFinish(detail) => { let text = format!( "There was a problem when creating a tar archive. This was error from the tar library: {detail}" ); vec![Diagnostic { title: "Failure creating tar archive".into(), text, hint: None, level: Level::Error, location: None, }] } Error::Hex(detail) => { let text = format!( "There was a problem when using the Hex API. This was error from the Hex client library: {detail}" ); vec![Diagnostic { title: "Hex API failure".into(), text, hint: None, level: Level::Error, location: None, }] } Error::DuplicateModule { module, first, second, } => { // If the conflicting modules come from the same package just // showing the same package name twice is not all that useful. // So we will show the path to each one! let should_show_path = first.package_name == second.package_name; let format_origin = |origin: &DefinedModuleOrigin| { if should_show_path { format!("at {}", origin.path) } else { format!("by the package {}", origin.package_name) } }; let text = format!( "The module `{module}` is defined multiple times. It is first defined {} It is defined a second time {}", format_origin(first), format_origin(second) ); vec![Diagnostic { title: "Duplicate module".into(), text, hint: None, level: Level::Error, location: None, }] } Error::ClashingGleamModuleAndNativeFileName { module, gleam_file, native_file, } => { let text = format!( "The Gleam module `{module}` is clashing with a native file with the same name: Gleam module: {gleam_file} Native file: {native_file} This is a problem because the Gleam module would be compiled to a file with the same name and extension, unintentionally overwriting the native file." ); vec![Diagnostic { title: "Gleam module clashes with native file".into(), text, hint: Some( "Consider renaming one of the files, such as by \ adding an `_ffi` suffix to the native file's name, and trying again." .into(), ), level: Level::Error, location: None, }] } Error::DuplicateSourceFile { file } => vec![Diagnostic { title: "Duplicate Source file".into(), text: format!("The file `{file}` is defined multiple times."), hint: None, level: Level::Error, location: None, }], Error::DuplicateNativeErlangModule { module, first, second, } => { let text = format!( "The native Erlang module `{module}` is defined multiple times. First: {first} Second: {second} Erlang modules must have unique names regardless of the subfolders where their `.erl` files are located." ); vec![Diagnostic { title: "Duplicate native Erlang module".into(), text, hint: Some("Rename one of the native Erlang modules and try again.".into()), level: Level::Error, location: None, }] } Error::FileIo { kind, action, path, err, } => { let err = match err { Some(e) => { format!("\nThe error message from the file IO library was:\n\n {e}\n") } None => "".into(), }; let mut text = format!( "An error occurred while trying to {} this {}: {} {}", action.text(), kind.text(), path, err, ); if cfg!(target_family = "windows") && action == &FileIoAction::Link { text.push_str(" Windows does not support symbolic links without developer mode or admin privileges. Please enable developer mode and try again. https://learn.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development#activate-developer-mode"); } vec![Diagnostic { title: "File IO failure".into(), text, hint: None, level: Level::Error, location: None, }] } Error::FailedToEncryptLocalHexApiKey { detail } => { let text = wrap_format!( "A problem was encountered \ encrypting the local Hex API key with the given password. The error from the encryption library was: {detail}" ); vec![Diagnostic { title: "Failed to encrypt data".into(), text, hint: None, level: Level::Error, location: None, }] } Error::FailedToDecryptLocalHexApiKey { detail } => { let text = wrap_format!( "Unable to decrypt the local Hex API key with the given password. The error from the encryption library was: {detail}" ); vec![Diagnostic { title: "Failed to decrypt local Hex API key".into(), text, hint: None, level: Level::Error, location: None, }] } Error::NonUtf8Path { path } => { let text = format!( "Encountered a non UTF-8 path '{}', but only UTF-8 paths are supported.", path.to_string_lossy() ); vec![Diagnostic { title: "Non UTF-8 Path Encountered".into(), text, level: Level::Error, location: None, hint: None, }] } Error::GitInitialization { error } => { let text = format!( "An error occurred while trying make a git repository for this project: {error}" ); vec![Diagnostic { title: "Failed to initialize git repository".into(), text, hint: None, level: Level::Error, location: None, }] } Error::Type { path, src, errors: error, names, } => error .iter() .map(|error| match error { TypeError::LiteralFloatOutOfRange { location, .. } => Diagnostic { title: "Float outside of valid range".into(), text: wrap( "This float value is too large to be represented by \ a floating point type: float values must be in the range -1.7976931348623157e308 \ - 1.7976931348623157e308.", ), hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, TypeError::InvalidImport { location, importing_module, imported_module, kind: InvalidImportKind::SrcImportingTest, } => { let text = wrap_format!( "The application module `{importing_module}` \ is importing the test module `{imported_module}`. Test modules are not included in production builds so application \ modules cannot import them. Perhaps move the `{imported_module}` \ module to the src directory.", ); Diagnostic { title: "App importing test module".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("Imported here".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::InvalidImport { location, importing_module, imported_module, kind: InvalidImportKind::SrcImportingDev, } => { let text = wrap_format!( "The application module `{importing_module}` \ is importing the development module `{imported_module}`. Development modules are not included in production builds so application \ modules cannot import them. Perhaps move the `{imported_module}` \ module to the src directory.", ); Diagnostic { title: "App importing dev module".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("Imported here".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::InvalidImport { location, importing_module, imported_module, kind: InvalidImportKind::DevImportingTest, } => { let text = wrap_format!( "The development module `{importing_module}` \ is importing the test module `{imported_module}`. Test modules should only contain test-related code, and not general development \ code. Perhaps move the `{imported_module}` module to the dev directory.", ); Diagnostic { title: "Dev importing test module".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("Imported here".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::UnknownLabels { unknown, valid, supplied, } => { let other_labels: Vec<_> = valid .iter() .filter(|label| !supplied.contains(label)) .cloned() .collect(); let title = if unknown.len() > 1 { "Unknown labels" } else { "Unknown label" } .into(); let mut labels = unknown.iter().map(|(label, location)| { let text = did_you_mean(label, &other_labels) .unwrap_or_else(|| "Unexpected label".into()); Label { text: Some(text), span: *location, } }); let label = labels.next().expect("Unknown labels first label"); let extra_labels = labels .map(|label| ExtraLabel { src_info: None, label, }) .collect(); let text = if valid.is_empty() { "This constructor does not accept any labelled arguments.".into() } else if other_labels.is_empty() { "You have already supplied all the labelled arguments that this constructor accepts." .into() } else { let mut label_text = String::from("It accepts these labels:\n"); for label in other_labels.iter().sorted() { label_text.push_str("\n "); label_text.push_str(label); } label_text }; Diagnostic { title, text, hint: None, level: Level::Error, location: Some(Location { label, path: path.clone(), src: src.clone(), extra_labels, }), } } TypeError::UnexpectedLabelledArg { location, label, kind, } => { let kind = match kind { UnexpectedLabelledArgKind::FunctionParameter => "function", UnexpectedLabelledArgKind::RecordConstructorArgument => { "record constructor" } }; let text = format!( "This argument has been given a label but the {kind} does not expect any. Please remove the label `{label}`." ); Diagnostic { title: "Unexpected labelled argument".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::PositionalArgumentAfterLabelled { location } => { let text = wrap( "This unlabeled argument has been \ supplied after a labelled argument. Once a labelled argument has been supplied all following arguments must also be labelled.", ); Diagnostic { title: "Unexpected positional argument".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::DuplicateImport { location, previous_location, name, } => { let text = format!( "`{name}` has been imported multiple times. Names in a Gleam module must be unique so one will need to be renamed." ); Diagnostic { title: "Duplicate import".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("Reimported here".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![ExtraLabel { src_info: None, label: Label { text: Some("First imported here".into()), span: *previous_location, }, }], }), } } TypeError::DuplicateName { location_a, location_b, name, .. } => { let (first_location, second_location) = if location_a.start < location_b.start { (location_a, location_b) } else { (location_b, location_a) }; let text = format!( "`{name}` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed." ); Diagnostic { title: "Duplicate definition".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("Redefined here".into()), span: *second_location, }, path: path.clone(), src: src.clone(), extra_labels: vec![ExtraLabel { src_info: None, label: Label { text: Some("First defined here".into()), span: *first_location, }, }], }), } } TypeError::DuplicateTypeName { name, location, previous_location, .. } => { let text = format!( "The type `{name}` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed." ); Diagnostic { title: "Duplicate type definition".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("Redefined here".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![ExtraLabel { src_info: None, label: Label { text: Some("First defined here".into()), span: *previous_location, }, }], }), } } TypeError::DuplicateField { location, label } => { let text = format!( "The label `{label}` has already been defined. Rename this label." ); Diagnostic { title: "Duplicate label".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::DuplicateArgument { location, label } => { let text = format!("The labelled argument `{label}` has already been supplied."); Diagnostic { title: "Duplicate argument".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::RecursiveType { location } => { let text = wrap( "I don't know how to work out what type this \ value has. It seems to be defined in terms of itself.", ); Diagnostic { title: "Recursive type".into(), text, hint: Some("Add some type annotations and try again.".into()), level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::NotFn { location, type_ } => { let mut printer = Printer::new(names); let text = format!( "This value is being called as a function but its type is:\n\n {}", printer.print_type(type_) ); Diagnostic { title: "Type mismatch".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::UnknownRecordField { usage, location, type_, label, fields, unknown_field: variants, } => { let mut printer = Printer::new(names); // Give a hint about what type this value has. let mut text = format!( "The value being accessed has this type:\n\n {}\n", printer.print_type(type_) ); // Give a hint about what record fields this value has, if any. if fields.is_empty() { if variants == &UnknownField::NoFields { text.push_str("\nIt does not have any fields."); } else { text.push_str( "\nIt does not have fields that are common \ across all variants.", ); } } else { text.push_str("\nIt has these accessible fields:\n"); } for field in fields.iter().sorted() { text.push_str("\n ."); text.push_str(field); } match variants { UnknownField::AppearsInAVariant => { let msg = wrap( "Note: The field you are trying to access is \ not defined consistently across all variants of this custom type. To fix this, \ ensure that all variants include the field with the same name, position, and \ type.", ); text.push_str("\n\n"); text.push_str(&msg); } UnknownField::AppearsInAnImpossibleVariant => { let msg = wrap( "Note: The field exists in this custom type \ but is not defined for the current variant. Ensure that you are accessing the \ field on a variant where it is valid.", ); text.push_str("\n\n"); text.push_str(&msg); } UnknownField::TrulyUnknown => (), UnknownField::NoFields => (), } // Give a hint about Gleam not having OOP methods if it // looks like they might be trying to call one. match usage { FieldAccessUsage::MethodCall => { let msg = wrap( "Gleam is not object oriented, so if you are trying \ to call a method on this value you may want to use the function syntax instead.", ); text.push_str("\n\n"); text.push_str(&msg); text.push_str("\n\n "); text.push_str(label); text.push_str("(value)"); } FieldAccessUsage::Other | FieldAccessUsage::RecordUpdate => (), } let label = did_you_mean(label, fields) .unwrap_or_else(|| "This field does not exist".into()); Diagnostic { title: "Unknown record field".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some(label), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::CouldNotUnify { location, expected, given, situation: Some(UnifyErrorSituation::Operator(op)), } => { let mut printer = Printer::new(names); let mut text = format!( "The {op} operator expects arguments of this type: {expected} But this argument has this type: {given}\n", op = op.name(), expected = printer.print_type(expected), given = printer.print_type(given), ); if let Some(hint) = hint_alternative_operator(op, given) { text.push('\n'); text.push_str("Hint: "); text.push_str(&hint); } Diagnostic { title: "Type mismatch".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::CouldNotUnify { location, expected, given, situation: Some(UnifyErrorSituation::PipeTypeMismatch), } => { // Remap the pipe function type into just the type expected by the pipe. let expected = expected .fn_types() .and_then(|(arguments, _)| arguments.first().cloned()); // Remap the argument as well, if it's a function. let given = given .fn_types() .and_then(|(arguments, _)| arguments.first().cloned()) .unwrap_or_else(|| given.clone()); let mut printer = Printer::new(names); let text = format!( "The argument is: {given} But function expects: {expected}", expected = expected .map(|v| printer.print_type(&v)) .unwrap_or_else(|| " No arguments".into()), given = printer.print_type(&given) ); Diagnostic { title: "Type mismatch".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some( "This function does not accept the piped type".into(), ), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::CouldNotUnify { location, expected, given, situation, } => { let mut printer = Printer::new(names); let mut text = if let Some(description) = situation.as_ref().and_then(|s| s.description()) { let mut text = description.to_string(); text.push('\n'); text.push('\n'); text } else { "".into() }; text.push_str("Expected type:\n\n "); text.push_str(&printer.print_type(expected)); text.push_str("\n\nFound type:\n\n "); text.push_str(&printer.print_type(given)); let (main_message_location, main_message_text, extra_labels) = match situation { // When the mismatch error comes from a case clause we want to highlight the // entire branch (pattern included) when reporting the error; in addition, // if the error could be resolved just by wrapping the value in an `Ok` // or `Error` we want to add an additional label with this hint below the // offending value. Some(UnifyErrorSituation::CaseClauseMismatch { clause_location, }) => (clause_location, None, vec![]), // In all other cases we just highlight the offending expression, optionally // adding the wrapping hint if it makes sense. Some(_) | None => { (location, hint_wrap_value_in_result(expected, given), vec![]) } }; Diagnostic { title: "Type mismatch".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: main_message_text, span: *main_message_location, }, path: path.clone(), src: src.clone(), extra_labels, }), } } TypeError::IncorrectTypeArity { location, expected, given: given_number, name, } => { let expected = match expected { 0 => "no type arguments".into(), 1 => "1 type argument".into(), _ => format!("{expected} type arguments"), }; let given = match given_number { 0 => "none", _ => &format!("{given_number}"), }; let text = wrap_format!( "`{name}` requires {expected} \ but {given} where provided." ); Diagnostic { title: "Incorrect arity".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some(format!("Expected {expected}, got {given_number}")), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::TypeUsedAsAConstructor { location, name } => { let text = wrap_format!( "`{name}` is a type with no parameters, but here it's \ being used as a type constructor." ); Diagnostic { title: "Type used as a type constructor".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("You can remove this".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::IncorrectArity { labels, location, context, expected, given, } => { let text = if labels.is_empty() { "".into() } else { let subject = match context { IncorrectArityContext::Pattern => "pattern", IncorrectArityContext::Function => "call", }; let labels = labels .iter() .map(|p| format!(" - {p}")) .sorted() .join("\n"); format!( "This {subject} accepts these additional labelled \ arguments:\n\n{labels}", ) }; let expected = match expected { 0 => "no arguments".into(), 1 => "1 argument".into(), _ => format!("{expected} arguments"), }; let label = format!("Expected {expected}, got {given}"); Diagnostic { title: "Incorrect arity".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some(label), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::UnnecessarySpreadOperator { location, arity } => { let text = wrap_format!( "This record has {arity} fields and you have already \ assigned variables to all of them." ); Diagnostic { title: "Unnecessary spread operator".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::UnsafeRecordUpdate { location, reason } => match reason { UnsafeRecordUpdateReason::UnknownVariant { constructed_variant, } => { let text = wrap_format!( "This value cannot be used to build an updated \ `{constructed_variant}` as it could be some other variant. Consider pattern matching on it with a case expression and then \ constructing a new record with its values." ); Diagnostic { title: "Unsafe record update".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some(format!( "I'm not sure this is always a `{constructed_variant}`" )), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } UnsafeRecordUpdateReason::WrongVariant { constructed_variant, spread_variant, } => { let text = wrap_format!( "This value is a `{spread_variant}` so \ it cannot be used to build a `{constructed_variant}`, even if they share some fields. Note: If you want to change one variant of a type into another, you should \ specify all fields explicitly instead of using the record update syntax." ); Diagnostic { title: "Incorrect record update".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some(format!("This is a `{spread_variant}`")), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } UnsafeRecordUpdateReason::IncompatibleFieldTypes { expected_field_type, record_field_type, record_variant, field, .. } => { let mut printer = Printer::new(names); let expected_field_type = printer.print_type(expected_field_type); let record_field_type = printer.print_type(record_field_type); let record_variant = printer.print_type(record_variant); let text = match field { RecordField::Labelled(label) => wrap_format!( "The `{label}` field \ of this value is a `{record_field_type}`, but the arguments given to the record \ update indicate that it should be a `{expected_field_type}`. Note: If the same type variable is used for multiple fields, all those fields \ need to be updated at the same time if their type changes." ), RecordField::Unlabelled(index) => wrap_format!( "The {} field \ of this value is a `{record_field_type}`, but the arguments given to the record \ update indicate that it should be a `{expected_field_type}`. Note: Unlabelled fields cannot be updated in a record update, so either add \ a label or use a record constructor.", to_ordinal(*index + 1), ), }; Diagnostic { title: "Incomplete record update".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some(format!("This is a `{record_variant}`")), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } }, TypeError::UnknownType { location, name, hint, } => { let label_text = match hint { UnknownTypeHint::AlternativeTypes(types) => did_you_mean(name, types), UnknownTypeHint::ValueInScopeWithSameName => None, }; let mut text = wrap_format!( "The type `{name}` is not defined or imported in this module." ); match hint { UnknownTypeHint::ValueInScopeWithSameName => { let hint = wrap_format!( "There is a value in scope with the name `{name}`, \ but no type in scope with that name." ); text.push('\n'); text.push_str(hint.as_str()); } UnknownTypeHint::AlternativeTypes(_) => {} }; Diagnostic { title: "Unknown type".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: label_text, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::UnknownVariable { location, variables, discarded_location, name, type_with_name_in_scope, } => { let title = String::from("Unknown variable"); if let Some(ignored_location) = discarded_location { let location = Location { label: Label { text: Some("So this is not in scope".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![ExtraLabel { src_info: None, label: Label { text: Some("This value is discarded".into()), span: *ignored_location, }, }], }; Diagnostic { title, text: "".into(), hint: Some(wrap_format!( "Change `_{name}` to `{name}` or reference another variable", )), level: Level::Error, location: Some(location), } } else { let text = if *type_with_name_in_scope { wrap_format!("`{name}` is a type, it cannot be used as a value.") } else { let is_first_char_uppercase = name.chars().next().is_some_and(char::is_uppercase); if is_first_char_uppercase { wrap_format!( "The custom type variant constructor \ `{name}` is not in scope here." ) } else { wrap_format!("The name `{name}` is not in scope here.") } }; Diagnostic { title, text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: did_you_mean(name, variables), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } } TypeError::PrivateTypeLeak { location, leaked } => { let mut printer = Printer::new(names); // TODO: be more precise. // - is being returned by this public function // - is taken as an argument by this public function // - is taken as an argument by this public enum constructor // etc let text = wrap_format!( "The following type is private, but is \ being used by this public export. {} Private types can only be used within the module that defines them.", printer.print_type(leaked), ); Diagnostic { title: "Private type used in public interface".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::UnknownModule { location, name, suggestions, } => Diagnostic { title: "Unknown module".into(), text: format!("No module has been found with the name `{name}`."), hint: suggestions .first() .map(|suggestion| suggestion.suggestion(name)), level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, TypeError::UnknownModuleType { location, name, module_name, type_constructors, value_with_same_name: imported_type_as_value, } => { let text = if *imported_type_as_value { format!("`{name}` is only a value, it cannot be imported as a type.") } else { format!("The module `{module_name}` does not have a `{name}` type.") }; Diagnostic { title: "Unknown module type".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: if *imported_type_as_value { Some(format!("Did you mean `{name}`?")) } else { did_you_mean(name, type_constructors) }, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::UnknownModuleValue { location, name, module_name, value_constructors, type_with_same_name: imported_value_as_type, context, } => { let text = if *imported_value_as_type { match context { ModuleValueUsageContext::UnqualifiedImport => wrap_format!( "`{name}` is only a type, it cannot be imported as a value." ), ModuleValueUsageContext::ModuleAccess => wrap_format!( "{module_name}.{name} is a type constructor, \ it cannot be used as a value" ), } } else { wrap_format!( "The module `{module_name}` does not have a `{name}` value." ) }; Diagnostic { title: "Unknown module value".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: if *imported_value_as_type && matches!( context, ModuleValueUsageContext::UnqualifiedImport ) { Some(format!("Did you mean `type {name}`?")) } else { did_you_mean(name, value_constructors) }, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::ModuleAliasUsedAsName { location, name } => { let text = wrap( "Modules are not values, so you cannot assign them \ to variables, pass them to functions, or anything else that you would do with a value.", ); Diagnostic { title: format!("Module `{name}` used as a value"), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::IncorrectNumClausePatterns { location, expected, given, } => { let subject = if *expected == 1 { "subject" } else { "subjects" }; let pattern = if *expected == 1 { "pattern" } else { "patterns" }; let text = wrap_format!( "This case expression has {expected} {subject}, \ but this pattern matches {given}. Each clause must have a pattern for every subject value.", ); Diagnostic { title: "Incorrect number of patterns".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some(format!( "Expected {expected} {pattern}, got {given}" )), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::NonLocalClauseGuardVariable { location, name } => { let text = wrap_format!( "Variables used in guards must be either defined in the \ function, or be an argument to the function. The variable \ `{name}` is not defined locally.", ); Diagnostic { title: "Invalid guard variable".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("Is not locally defined".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::ExtraVarInAlternativePattern { location, name } => { let text = wrap_format!( "All alternative patterns must define the same variables as \ the initial pattern. This variable `{name}` has not been previously defined.", ); Diagnostic { title: "Extra alternative pattern variable".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("Has not been previously defined".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::MissingVarInAlternativePattern { location, name } => { let text = wrap_format!( "All alternative patterns must define the same variables \ as the initial pattern, but the `{name}` variable is missing.", ); Diagnostic { title: "Missing alternative pattern variable".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some( "This does not define all required variables".into(), ), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::DuplicateVarInPattern { location, name } => { let text = wrap_format!( "Variables can only be used once per pattern. This \ variable `{name}` appears multiple times. If you used the same variable twice deliberately in order to check for equality \ please use a guard clause instead. e.g. (x, y) if x == y -> ...", ); Diagnostic { title: "Duplicate variable in pattern".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("This has already been used".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::OutOfBoundsTupleIndex { location, size: 0, .. } => Diagnostic { title: "Out of bounds tuple index".into(), text: "This tuple has no elements so it cannot be indexed at all.".into(), hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, TypeError::OutOfBoundsTupleIndex { location, index, size, } => { let text = wrap_format!( "The index being accessed for this tuple is {}, but this \ tuple has {} elements so the highest valid index is {}.", index, size, size - 1, ); Diagnostic { title: "Out of bounds tuple index".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("This index is too large".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::NotATuple { location, given } => { let mut printer = Printer::new(names); let text = format!( "To index into this value it needs to be a tuple, \ however it has this type: {}", printer.print_type(given), ); Diagnostic { title: "Type mismatch".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("This is not a tuple".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::NotATupleUnbound { location } => { let text = wrap( "To index into a tuple we need to \ know its size, but we don't know anything about this type yet. \ Please add some type annotations so we can continue.", ); Diagnostic { title: "Type mismatch".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("What type is this?".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::RecordAccessUnknownType { location } => { let text = wrap( "In order to access a record field \ we need to know what type it is, but I can't tell \ the type here. Try adding type annotations to your \ function and try again.", ); Diagnostic { title: "Unknown type for record access".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("I don't know what type this is".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::BitArraySegmentError { error, location } => { let (label, mut extra) = match error { bit_array::ErrorType::ConflictingTypeOptions { existing_type } => ( "This is an extra type specifier", vec![format!( "Hint: This segment already has the type {existing_type}." )], ), bit_array::ErrorType::ConflictingSignednessOptions { existing_signed, } => ( "This is an extra signedness specifier", vec![format!( "Hint: This segment already has a \ signedness of {existing_signed}." )], ), bit_array::ErrorType::ConflictingEndiannessOptions { existing_endianness, } => ( "This is an extra endianness specifier", vec![format!( "Hint: This segment already has an \ endianness of {existing_endianness}." )], ), bit_array::ErrorType::ConflictingSizeOptions => ( "This is an extra size specifier", vec!["Hint: This segment already has a size.".into()], ), bit_array::ErrorType::ConflictingUnitOptions => ( "This is an extra unit specifier", vec!["Hint: A BitArray segment can have at most 1 unit.".into()], ), bit_array::ErrorType::FloatWithSize => ( "Invalid float size", vec!["Hint: floats have an exact size of 16/32/64 bits.".into()], ), bit_array::ErrorType::InvalidEndianness => ( "This option is invalid here", vec![wrap( "Hint: signed and unsigned \ can only be used with int, float, utf16 and utf32 types.", )], ), bit_array::ErrorType::OptionNotAllowedInValue => ( "This option is only allowed in BitArray patterns", vec!["Hint: This option has no effect in BitArray values.".into()], ), bit_array::ErrorType::SignednessUsedOnNonInt { type_ } => ( "Signedness is only valid with int types", vec![format!("Hint: This segment has a type of {type_}")], ), bit_array::ErrorType::TypeDoesNotAllowSize { type_ } => ( "Size cannot be specified here", vec![format!("Hint: {type_} segments have an automatic size.")], ), bit_array::ErrorType::TypeDoesNotAllowUnit { type_ } => ( "Unit cannot be specified here", vec![wrap(&format!( "Hint: {type_} segments \ are sized based on their value and cannot have a unit." ))], ), bit_array::ErrorType::VariableUtfSegmentInPattern => ( "This cannot be a variable", vec![wrap( "Hint: in patterns utf8, utf16, and \ utf32 must be an exact string.", )], ), bit_array::ErrorType::SegmentMustHaveSize => ( "This segment has no size", vec![wrap( "Hint: Bit array segments without \ a size are only allowed at the end of a bin pattern.", )], ), bit_array::ErrorType::UnitMustHaveSize => ( "This needs an explicit size", vec![ "Hint: If you specify unit() you must also specify size()." .into(), ], ), bit_array::ErrorType::ConstantSizeNotPositive => { ("A constant size must be a positive number", vec![]) } bit_array::ErrorType::OptionNotSupportedForTarget { target, option: UnsupportedOption::NativeEndianness, } => ( "Unsupported endianness", vec![wrap_format!( "The {target} target does not support the `native` \ endianness option.", target = target.as_presentable_str(), )], ), bit_array::ErrorType::OptionNotSupportedForTarget { target, option: UnsupportedOption::UtfCodepointPattern, } => ( "UTF-codepoint pattern matching is not supported", vec![wrap_format!( "The {target} target does not support \ UTF-codepoint pattern matching.", target = target.as_presentable_str(), )], ), }; extra.push("See: https://tour.gleam.run/data-types/bit-arrays/".into()); let text = extra.join("\n"); Diagnostic { title: "Invalid bit array segment".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some(label.into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::RecordUpdateInvalidConstructor { location } => Diagnostic { title: "Invalid record constructor".into(), text: "Only record constructors can be used with the update syntax.".into(), hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("This is not a record constructor".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, TypeError::UnexpectedTypeHole { location } => Diagnostic { title: "Unexpected type hole".into(), text: "We need to know the exact type here so type holes cannot be used." .into(), hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("I need to know what this is".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, TypeError::ReservedModuleName { name } => { let text = format!( "The module name `{name}` is reserved. Try a different name for this module." ); Diagnostic { title: "Reserved module name".into(), text, hint: None, location: None, level: Level::Error, } } TypeError::KeywordInModuleName { name, keyword } => { let text = wrap_format!( "The module name `{name}` contains the keyword `{keyword}`, \ so importing it would be a syntax error. Try a different name for this module." ); Diagnostic { title: "Invalid module name".into(), text, hint: None, location: None, level: Level::Error, } } TypeError::NotExhaustivePatternMatch { location, unmatched, kind, } => { let mut text = match kind { PatternMatchKind::Case => { "This case expression does not match all possibilities. Each constructor must have a pattern that matches it or else it could crash." } PatternMatchKind::Assignment => { "This assignment does not match all possibilities. Either use a case expression with patterns for each possible value, or use `let assert` rather than `let`." } } .to_string(); text.push_str("\n\nThese values are not matched:\n\n"); for unmatched in unmatched { text.push_str(" - "); text.push_str(unmatched); text.push('\n'); } Diagnostic { title: "Not exhaustive pattern match".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::ArgumentNameAlreadyUsed { location, name } => Diagnostic { title: "Argument name already used".into(), text: format!( "Two `{name}` arguments have been defined for this function." ), hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, TypeError::UnlabelledAfterlabelled { location } => Diagnostic { title: "Unlabelled argument after labelled argument".into(), text: wrap( "All unlabelled arguments must come before any labelled arguments.", ), hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, TypeError::RecursiveTypeAlias { location, cycle } => { let mut text = "This type alias is defined in terms of itself.\n".into(); write_cycle(&mut text, cycle); text.push_str( "If we tried to compile this recursive type it would expand forever in a loop, and we'd never get the final type.", ); Diagnostic { title: "Type cycle".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::ExternalMissingAnnotation { location, kind } => { let kind = match kind { MissingAnnotation::Parameter => "parameter", MissingAnnotation::Return => "return", }; let text = format!( "A {kind} annotation is missing from this function. Functions with external implementations must have type annotations so we can tell what type of values they accept and return.", ); Diagnostic { title: "Missing type annotation".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::NoImplementation { location } => { let text = "We can't compile this function as it doesn't have an implementation. Add a body or an external implementation using the `@external` attribute." .into(); Diagnostic { title: "Function without an implementation".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::InvalidExternalJavascriptModule { location, name, module, } => { let text = wrap_format!( "The function `{name}` has an external JavaScript \ implementation but the module path `{module}` is not valid." ); Diagnostic { title: "Invalid JavaScript module".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::InvalidExternalJavascriptFunction { location, name, function, } => { let text = wrap_format!( "The function `{name}` has an external JavaScript \ implementation but the function name `{function}` is not valid." ); Diagnostic { title: "Invalid JavaScript function".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::InexhaustiveLetAssignment { location, missing } => { let mut text = wrap( "This assignment uses a pattern that does not \ match all possible values. If one of the other values \ is used then the assignment will crash. The missing patterns are:\n", ); for missing in missing { text.push_str("\n "); text.push_str(missing); } text.push('\n'); Diagnostic { title: "Inexhaustive pattern".into(), text, hint: Some( "Use a more general pattern or use `let assert` instead.".into(), ), level: Level::Error, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: Label { text: None, span: *location, }, extra_labels: Vec::new(), }), } } TypeError::InexhaustiveCaseExpression { location, missing } => { let mut text = wrap( "This case expression does not have a pattern \ for all possible values. If it is run on one of the \ values without a pattern then it will crash. The missing patterns are:\n", ); for missing in missing { text.push_str("\n "); text.push_str(missing); } Diagnostic { title: "Inexhaustive patterns".into(), text, hint: None, level: Level::Error, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: Label { text: None, span: *location, }, extra_labels: Vec::new(), }), } } TypeError::MissingCaseBody { location } => { let text = wrap("This case expression is missing its body."); Diagnostic { title: "Missing case body".into(), text, hint: None, level: Level::Error, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: Label { text: None, span: *location, }, extra_labels: Vec::new(), }), } } TypeError::UnsupportedExpressionTarget { location, target: current_target, } => { let text = wrap_format!( "This value is not available as it is defined using externals, \ and there is no implementation for the {} target.\n", match current_target { Target::Erlang => "Erlang", Target::JavaScript => "JavaScript", } ); let hint = wrap("Did you mean to build for a different target?"); Diagnostic { title: "Unsupported target".into(), text, hint: Some(hint), level: Level::Error, location: Some(Location { path: path.clone(), src: src.clone(), label: Label { text: None, span: *location, }, extra_labels: vec![], }), } } TypeError::UnsupportedPublicFunctionTarget { location, name, target, } => { let target = match target { Target::Erlang => "Erlang", Target::JavaScript => "JavaScript", }; let text = wrap_format!( "The `{name}` function is public but doesn't have an \ implementation for the {target} target. All public functions of a package \ must be able to compile for a module to be valid." ); Diagnostic { title: "Unsupported target".into(), text, hint: None, level: Level::Error, location: Some(Location { path: path.clone(), src: src.clone(), label: Label { text: None, span: *location, }, extra_labels: vec![], }), } } TypeError::UnusedTypeAliasParameter { location, name } => { let text = wrap_format!( "The type variable `{name}` is unused. It can be safely removed.", ); Diagnostic { title: "Unused type parameter".into(), text, hint: None, level: Level::Error, location: Some(Location { path: path.clone(), src: src.clone(), label: Label { text: None, span: *location, }, extra_labels: vec![], }), } } TypeError::DuplicateTypeParameter { location, name } => { let text = wrap_format!( "This definition has multiple type parameters named `{name}`. Rename or remove one of them.", ); Diagnostic { title: "Duplicate type parameter".into(), text, hint: None, level: Level::Error, location: Some(Location { path: path.clone(), src: src.clone(), label: Label { text: None, span: *location, }, extra_labels: vec![], }), } } TypeError::NotFnInUse { location, type_ } => { let mut printer = Printer::new(names); let text = wrap_format!( "In a use expression, there should be a function on \ the right hand side of `<-`, but this value has type: {} See: https://tour.gleam.run/advanced-features/use/", printer.print_type(type_) ); Diagnostic { title: "Type mismatch".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::UseFnDoesntTakeCallback { location, actual_type: None, } | TypeError::UseFnIncorrectArity { location, expected: 0, given: 1, } => { let text = wrap( "The function on the right of `<-` here \ takes no arguments, but it has to take at least \ one argument, a callback function. See: https://tour.gleam.run/advanced-features/use/", ); Diagnostic { title: "Incorrect arity".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("Expected no arguments, got 1".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::UseFnIncorrectArity { location, expected, given, } => { let expected_string = match expected { 0 => "no arguments".into(), 1 => "1 argument".into(), _ => format!("{expected} arguments"), }; let supplied_arguments = given - 1; let supplied_arguments_string = match supplied_arguments { 0 => "no arguments".into(), 1 => "1 argument".into(), _ => format!("{given} arguments"), }; let label = format!("Expected {expected_string}, got {given}"); let mut text: String = format!( "The function on the right of `<-` \ here takes {expected_string}.\n" ); if expected > given { if supplied_arguments == 0 { text.push_str( "The only argument that was supplied is \ the `use` callback function.\n", ) } else { text.push_str(&format!( "You supplied {supplied_arguments_string} \ and the final one is the `use` callback function.\n" )); } } else { text.push_str( "All the arguments have already been supplied, \ so it cannot take the `use` callback function as a final argument.\n", ) }; text.push_str("\nSee: https://tour.gleam.run/advanced-features/use/"); Diagnostic { title: "Incorrect arity".into(), text: wrap(&text), hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some(label), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::UseFnDoesntTakeCallback { location, actual_type: Some(actual), } => { let mut printer = Printer::new(names); let text = wrap_format!( "The function on the right hand side of `<-` \ has to take a callback function as its last argument. \ But the last argument of this function has type: {} See: https://tour.gleam.run/advanced-features/use/", printer.print_type(actual) ); Diagnostic { title: "Type mismatch".into(), text: wrap(&text), hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::UseCallbackIncorrectArity { pattern_location, call_location, expected, given, } => { let expected = match expected { 0 => "no arguments".into(), 1 => "1 argument".into(), _ => format!("{expected} arguments"), }; let specified = match given { 0 => "none were provided".into(), 1 => "1 was provided".into(), _ => format!("{given} were provided"), }; let text = wrap_format!( "This function takes a callback that expects {expected}. \ But {specified} on the left hand side of `<-`. See: https://tour.gleam.run/advanced-features/use/" ); Diagnostic { title: "Incorrect arity".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *call_location, }, path: path.clone(), src: src.clone(), extra_labels: vec![ExtraLabel { src_info: None, label: Label { text: Some(format!("Expected {expected}, got {given}")), span: *pattern_location, }, }], }), } } TypeError::BadName { location, name, kind, } => { let kind_str = kind.as_str(); let label = format!("This is not a valid {} name", kind_str.to_lowercase()); let text = match kind { Named::Type | Named::TypeAlias | Named::CustomTypeVariant => { wrap_format!( "Hint: {} names start with an uppercase \ letter and contain only lowercase letters, numbers, \ and uppercase letters. Try: {}", kind_str, to_upper_camel_case(name) ) } Named::Variable | Named::TypeVariable | Named::Argument | Named::Label | Named::Constant | Named::Function => wrap_format!( "Hint: {} names start with a lowercase letter \ and contain a-z, 0-9, or _. Try: {}", kind_str, to_snake_case(name) ), Named::Discard => wrap_format!( "Hint: {} names start with _ and contain \ a-z, 0-9, or _. Try: _{}", kind_str, to_snake_case(name) ), }; Diagnostic { title: format!("Invalid {} name", kind_str.to_lowercase()), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some(label), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::AllVariantsDeprecated { location } => { let text = String::from( "Consider deprecating the type as a whole. @deprecated(\"message\") type Wibble { Wobble1 Wobble2 } ", ); Diagnostic { title: "All variants of custom type deprecated.".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::DeprecatedVariantOnDeprecatedType { location } => { let text = wrap( "This custom type has already been deprecated, so deprecating \ one of its variants does nothing. Consider removing the deprecation attribute on the variant.", ); Diagnostic { title: "Custom type already deprecated".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } TypeError::EchoWithNoFollowingExpression { location } => Diagnostic { title: "Invalid echo use".to_string(), text: wrap("The `echo` keyword should be followed by a value to print."), hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("I was expecting a value after this".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, TypeError::StringConcatenationWithAddInt { location } => Diagnostic { title: "Type mismatch".to_string(), text: wrap( "The + operator can only be used on Ints. To join two strings together you can use the <> operator.", ), hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("Use <> instead".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, TypeError::IntOperatorOnFloats { location, operator } => Diagnostic { title: "Type mismatch".to_string(), text: wrap_format!( "The {} operator can only be used on Ints.", operator.name() ), hint: None, level: Level::Error, location: Some(Location { label: Label { text: operator .float_equivalent() .map(|operator| format!("Use {} instead", operator.name())), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, TypeError::FloatOperatorOnInts { location, operator } => Diagnostic { title: "Type mismatch".to_string(), text: wrap_format!( "The {} operator can only be used on Floats.", operator.name() ), hint: None, level: Level::Error, location: Some(Location { label: Label { text: operator .int_equivalent() .map(|operator| format!("Use {} instead", operator.name())), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, TypeError::DoubleVariableAssignmentInBitArray { location } => Diagnostic { title: "Double variable assignment".to_string(), text: wrap( "This pattern assigns to two different variables \ at once, which is not possible in bit arrays.", ), hint: Some(wrap("Remove the `as` assignment.")), level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, TypeError::NonUtf8StringAssignmentInBitArray { location } => Diagnostic { title: "Non UTF-8 string assignment".to_string(), text: wrap( "This pattern assigns a non UTF-8 string to a \ variable in a bit array. This is planned to be supported in the future, but we are \ unsure of the desired behaviour. Please go to https://github.com/gleam-lang/gleam/issues/4566 \ and explain your usecase for this pattern, and how you would expect it to behave.", ), hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, TypeError::PrivateOpaqueType { location } => Diagnostic { title: "Private opaque type".to_string(), text: wrap("Only a public type can be opaque."), hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("You can safely remove this.".to_string()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, TypeError::SrcImportingDevDependency { location, importing_module, imported_module, package, } => Diagnostic { title: "App importing dev dependency".to_string(), text: wrap_format!( "The application module `{importing_module}` is \ importing the module `{imported_module}`, but `{package}`, the package it \ belongs to, is a dev dependency. Dev dependencies are not included in production builds so application \ modules should not import them. Perhaps change `{package}` to a regular dependency." ), hint: None, level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, TypeError::ExternalTypeWithConstructors { location } => Diagnostic { title: "External type with constructors".to_string(), text: wrap_format!( "This type is annotated with the `@external` annotation, \ but it has constructors. The `@external` annotation is only for external types \ with no constructors." ), hint: Some("Remove the `@external` annotation".into()), level: Level::Error, location: Some(Location { label: Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, TypeError::LowercaseBoolPattern { location } => Diagnostic { title: "Lowercase bool pattern".to_string(), text: "See: https://tour.gleam.run/basics/bools/".into(), hint: Some("In Gleam bool literals are `True` and `False`.".into()), level: Level::Error, location: Some(Location { label: Label { text: Some("This is not a bool".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, }) .collect_vec(), Error::Parse { path, src, error } => { let location = if error.error == ParseErrorType::UnexpectedEof { crate::ast::SrcSpan { start: (src.len() - 1) as u32, end: (src.len() - 1) as u32, } } else { error.location }; let title = String::from("Syntax error"); let ParseErrorDetails { text, label_text, extra_labels, hint, } = error.error.details(); vec![Diagnostic { title, text, level: Level::Error, location: Some(Location { src: src.clone(), path: path.clone(), label: Label { text: Some(label_text.into()), span: location, }, extra_labels, }), hint, }] } Error::ImportCycle { modules } => { let first_location = &modules.first().1; let rest_locations = modules .iter() .skip(1) .map(|(_, l)| ExtraLabel { label: Label { text: Some("Imported here".into()), span: l.location, }, src_info: Some((l.src.clone(), l.path.clone())), }) .collect_vec(); let mut text = "The import statements for these modules form a cycle: " .into(); let mod_names = modules.iter().map(|m| m.0.clone()).collect_vec(); write_cycle(&mut text, &mod_names); text.push_str( "Gleam doesn't support dependency cycles like these, please break the cycle to continue.", ); vec![Diagnostic { title: "Import cycle".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: Some("Imported here".into()), span: first_location.location, }, path: first_location.path.clone(), src: first_location.src.clone(), extra_labels: rest_locations, }), }] } Error::PackageCycle { packages } => { let mut text = "The dependencies for these packages form a cycle: " .into(); write_cycle(&mut text, packages); text.push_str( "Gleam doesn't support dependency cycles like these, please break the cycle to continue.", ); vec![Diagnostic { title: "Dependency cycle".into(), text, hint: None, level: Level::Error, location: None, }] } Error::UnknownImport { import, details } => { let UnknownImportDetails { module, location, path, src, modules, } = details.as_ref(); let text = wrap(&format!( "The module `{module}` is trying to import the module `{import}`, \ but it cannot be found." )); vec![Diagnostic { title: "Unknown import".into(), text, hint: None, level: Level::Error, location: Some(Location { label: Label { text: did_you_mean(import, modules), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }] } Error::StandardIo { action, err } => { let err = match err { Some(e) => format!( "\nThe error message from the stdio library was:\n\n {}\n", std_io_error_kind_text(e) ), None => "".into(), }; vec![Diagnostic { title: "Standard IO failure".into(), text: format!( "An error occurred while trying to {}: {}", action.text(), err, ), hint: None, location: None, level: Level::Error, }] } Error::Format { problem_files } => { let files: Vec<_> = problem_files .iter() .map(|formatted| formatted.source.as_str()) .map(|p| format!(" - {p}")) .sorted() .collect(); let mut text = files.iter().join("\n"); text.push('\n'); vec![Diagnostic { title: "These files have not been formatted".into(), text, hint: None, location: None, level: Level::Error, }] } Error::ForbiddenWarnings { count } => { let word_warning = match count { 1 => "warning", _ => "warnings", }; let text = "Your project was compiled with the `--warnings-as-errors` flag. Fix the warnings and try again." .into(); vec![Diagnostic { title: format!("{count} {word_warning} generated."), text, hint: None, location: None, level: Level::Error, }] } Error::DownloadPackageError { package_name, package_version, error, } => { let text = format!( "A problem was encountered when downloading `{package_name}` {package_version}. The error from the package manager client was: {error}" ); vec![Diagnostic { title: "Failed to download package".into(), text, hint: None, location: None, level: Level::Error, }] } Error::Http(error) => { let text = format!( "A HTTP request failed. The error from the HTTP client was: {error}" ); vec![Diagnostic { title: "HTTP error".into(), text, hint: None, location: None, level: Level::Error, }] } Error::InvalidVersionFormat { input, error } => { let text = format!( "I was unable to parse the version \"{input}\". The error from the parser was: {error}" ); vec![Diagnostic { title: "Invalid version format".into(), text, hint: None, location: None, level: Level::Error, }] } Error::IncompatibleLockedVersion { error } => { let text = format!( "There is an incompatiblity between a version specified in manifest.toml and a version range specified in gleam.toml: {error}" ); vec![Diagnostic { title: "Incompatible locked version".into(), text, hint: None, location: None, level: Level::Error, }] } Error::DependencyCanonicalizationFailed(package) => { let text = format!("Local package `{package}` has no canonical path"); vec![Diagnostic { title: "Failed to create canonical path".into(), text, hint: None, location: None, level: Level::Error, }] } Error::DependencyResolutionError(error) => vec![Diagnostic { title: "Dependency resolution failed".into(), text: wrap(error), hint: None, location: None, level: Level::Error, }], Error::DependencyResolutionNoSolution { root_package_name, derivation_tree, } => { let text = wrap( &DerivationTreePrinter::new( root_package_name.clone(), derivation_tree.0.clone(), ) .print(), ); vec![Diagnostic { title: "Dependency resolution failed".into(), text, hint: None, location: None, level: Level::Error, }] } Error::WrongDependencyProvided { path, expected, found, } => { let text = format!( "Expected package `{expected}` at path `{path}` but found `{found}` instead.", ); vec![Diagnostic { title: "Wrong dependency provided".into(), text, hint: None, location: None, level: Level::Error, }] } Error::ProvidedDependencyConflict { package, source_1, source_2, } => { let text = format!( "The package `{package}` is provided as both `{source_1}` and `{source_2}`.", ); vec![Diagnostic { title: "Conflicting provided dependencies".into(), text, hint: None, location: None, level: Level::Error, }] } Error::DuplicateDependency(name) => { let text = format!( "The package `{name}` is specified in both the dependencies and dev_dependencies sections of the gleam.toml file." ); vec![Diagnostic { title: "Dependency duplicated".into(), text, hint: None, location: None, level: Level::Error, }] } Error::MissingHexPublishFields { description_missing, licence_missing, } => { let mut text = "Licence information and package description are required to publish a package to Hex.\n" .to_string(); text.push_str(if *description_missing && *licence_missing { r#"Add the licences and description fields to your gleam.toml file. description = "" licences = ["Apache-2.0"]"# } else if *description_missing { r#"Add the description field to your gleam.toml file. description = """# } else { r#"Add the licences field to your gleam.toml file. licences = ["Apache-2.0"]"# }); vec![Diagnostic { title: "Missing required package fields".into(), text, hint: None, location: None, level: Level::Error, }] } Error::PublishNonHexDependencies { package } => vec![Diagnostic { title: "Unpublished dependencies".into(), text: wrap_format!( "The package cannot be published to Hex \ because dependency `{package}` is not a Hex dependency.", ), hint: None, location: None, level: Level::Error, }], Error::UnsupportedBuildTool { package, build_tools, } => { let text = wrap_format!( "The package `{}` cannot be built as it does not use \ a build tool supported by Gleam. It uses {:?}. If you would like us to support this package please let us know by opening an \ issue in our tracker: https://github.com/gleam-lang/gleam/issues", package, build_tools ); vec![Diagnostic { title: "Unsupported build tool".into(), text, hint: None, location: None, level: Level::Error, }] } Error::FailedToOpenDocs { path, error } => { let error = format!("\nThe error message from the library was:\n\n {error}\n"); let text = format!( "An error occurred while trying to open the docs: {path} {error}", ); vec![Diagnostic { title: "Failed to open docs".into(), text, hint: None, level: Level::Error, location: None, }] } Error::IncompatibleCompilerVersion { package, required_version, gleam_version, } => { let text = format!( "The package `{package}` requires a Gleam version \ satisfying {required_version} but you are using v{gleam_version}.", ); vec![Diagnostic { title: "Incompatible Gleam version".into(), text, hint: None, location: None, level: Level::Error, }] } Error::InvalidRuntime { target, invalid_runtime, } => { let text = format!( "Invalid runtime for {target} target: {invalid_runtime}", target = target.as_presentable_str(), invalid_runtime = invalid_runtime.as_presentable_str(), ); let hint = match target { Target::JavaScript => { Some("available runtimes for JavaScript are: node, deno.".into()) } Target::Erlang => Some( "You can not set a runtime for Erlang. Did you mean to target JavaScript?" .into(), ), }; vec![Diagnostic { title: format!( "Invalid runtime for {target}", target = target.as_presentable_str(), ), text, hint, location: None, level: Level::Error, }] } Error::JavaScriptPreludeRequired => vec![Diagnostic { title: "JavaScript prelude required".into(), text: "The --javascript-prelude flag must be given when compiling to JavaScript." .into(), level: Level::Error, location: None, hint: None, }], Error::CorruptManifest => vec![Diagnostic { title: "Corrupt manifest.toml".into(), text: "The `manifest.toml` file is corrupt.".into(), level: Level::Error, location: None, hint: Some("Please run `gleam update` to fix it.".into()), }], Error::GleamModuleWouldOverwriteStandardErlangModule { name, path } => { vec![Diagnostic { title: "Erlang module name collision".into(), text: wrap_format!( "The module `{path}` compiles to an Erlang module \ named `{name}`. By default Erlang includes a module with the same name so if we were \ to compile and load your module it would overwrite the Erlang one, potentially \ causing confusing errors and crashes. " ), level: Level::Error, location: None, hint: Some("Rename this module and try again.".into()), }] } Error::HexPublishReplaceRequired { version } => vec![Diagnostic { title: "Version already published".into(), text: wrap_format!( "Version v{version} has already been published. This release has been recently published so you can replace it \ or you can publish it using a different version number" ), level: Level::Error, location: None, hint: Some( "Please add the --replace flag if you want to replace the release.".into(), ), }], Error::HexPublishAccessDenied { name, version } => vec![Diagnostic { title: "Access denied".to_string(), text: wrap_format!( "You are not one of the maintainers of the {name} package, so \ you cannot publish a new {version} version. Are you logged into the correct account? If you are trying to publish a new package then you will need to pick another, \ as this one is already in use. " ), level: Level::Error, location: None, hint: None, }], Error::CannotAddSelfAsDependency { name } => vec![Diagnostic { title: "Dependency cycle".into(), text: wrap_format!( "A package cannot depend on itself, so you cannot \ add `gleam add {name}` in this project." ), level: Level::Error, location: None, hint: None, }], } } } fn std_io_error_kind_text(kind: &std::io::ErrorKind) -> String { use std::io::ErrorKind; match kind { ErrorKind::NotFound => "Could not find the stdio stream".into(), ErrorKind::PermissionDenied => "Permission was denied".into(), ErrorKind::ConnectionRefused => "Connection was refused".into(), ErrorKind::ConnectionReset => "Connection was reset".into(), ErrorKind::ConnectionAborted => "Connection was aborted".into(), ErrorKind::NotConnected => "Was not connected".into(), ErrorKind::AddrInUse => "The stream was already in use".into(), ErrorKind::AddrNotAvailable => "The stream was not available".into(), ErrorKind::BrokenPipe => "The pipe was broken".into(), ErrorKind::AlreadyExists => "A handle to the stream already exists".into(), ErrorKind::WouldBlock => "This operation would block when it was requested not to".into(), ErrorKind::InvalidInput => "Some parameter was invalid".into(), ErrorKind::InvalidData => "The data was invalid. Check that the encoding is UTF-8".into(), ErrorKind::TimedOut => "The operation timed out".into(), ErrorKind::WriteZero => { "An attempt was made to write, but all bytes could not be written".into() } ErrorKind::Interrupted => "The operation was interrupted".into(), ErrorKind::UnexpectedEof => "The end of file was reached before it was expected".into(), ErrorKind::HostUnreachable | ErrorKind::NetworkUnreachable | ErrorKind::NetworkDown | ErrorKind::NotADirectory | ErrorKind::IsADirectory | ErrorKind::DirectoryNotEmpty | ErrorKind::ReadOnlyFilesystem | ErrorKind::StaleNetworkFileHandle | ErrorKind::StorageFull | ErrorKind::NotSeekable | ErrorKind::QuotaExceeded | ErrorKind::FileTooLarge | ErrorKind::ResourceBusy | ErrorKind::ExecutableFileBusy | ErrorKind::Deadlock | ErrorKind::CrossesDevices | ErrorKind::TooManyLinks | ErrorKind::InvalidFilename | ErrorKind::ArgumentListTooLong | ErrorKind::Unsupported | ErrorKind::OutOfMemory | ErrorKind::Other | _ => "An unknown error occurred".into(), } } fn write_cycle(buffer: &mut String, cycle: &[EcoString]) { buffer.push_str( " ┌─────┐\n", ); for (index, name) in cycle.iter().enumerate() { if index != 0 { buffer.push_str(" │ ↓\n"); } buffer.push_str(" │ "); buffer.push_str(name); buffer.push('\n'); } buffer.push_str(" └─────┘\n"); } fn hint_alternative_operator(op: &BinOp, given: &Type) -> Option { match op { BinOp::AddInt if given.is_float() => Some(hint_numeric_message("+.", "Float")), BinOp::DivInt if given.is_float() => Some(hint_numeric_message("/.", "Float")), BinOp::GtEqInt if given.is_float() => Some(hint_numeric_message(">=.", "Float")), BinOp::GtInt if given.is_float() => Some(hint_numeric_message(">.", "Float")), BinOp::LtEqInt if given.is_float() => Some(hint_numeric_message("<=.", "Float")), BinOp::LtInt if given.is_float() => Some(hint_numeric_message("<.", "Float")), BinOp::MultInt if given.is_float() => Some(hint_numeric_message("*.", "Float")), BinOp::SubInt if given.is_float() => Some(hint_numeric_message("-.", "Float")), BinOp::AddFloat if given.is_int() => Some(hint_numeric_message("+", "Int")), BinOp::DivFloat if given.is_int() => Some(hint_numeric_message("/", "Int")), BinOp::GtEqFloat if given.is_int() => Some(hint_numeric_message(">=", "Int")), BinOp::GtFloat if given.is_int() => Some(hint_numeric_message(">", "Int")), BinOp::LtEqFloat if given.is_int() => Some(hint_numeric_message("<=", "Int")), BinOp::LtFloat if given.is_int() => Some(hint_numeric_message("<", "Int")), BinOp::MultFloat if given.is_int() => Some(hint_numeric_message("*", "Int")), BinOp::SubFloat if given.is_int() => Some(hint_numeric_message("-", "Int")), BinOp::AddInt if given.is_string() => Some(hint_string_message()), BinOp::AddFloat if given.is_string() => Some(hint_string_message()), BinOp::And | BinOp::Or | BinOp::Eq | BinOp::NotEq | BinOp::LtInt | BinOp::LtEqInt | BinOp::LtFloat | BinOp::LtEqFloat | BinOp::GtEqInt | BinOp::GtInt | BinOp::GtEqFloat | BinOp::GtFloat | BinOp::AddInt | BinOp::AddFloat | BinOp::SubInt | BinOp::SubFloat | BinOp::MultInt | BinOp::MultFloat | BinOp::DivInt | BinOp::DivFloat | BinOp::RemainderInt | BinOp::Concatenate => None, } } fn hint_wrap_value_in_result(expected: &Arc, given: &Arc) -> Option { let expected = collapse_links(expected.clone()); let (expected_ok_type, expected_error_type) = expected.result_types()?; if given.same_as(expected_ok_type.as_ref()) { Some("Did you mean to wrap this in an `Ok`?".into()) } else if given.same_as(expected_error_type.as_ref()) { Some("Did you mean to wrap this in an `Error`?".into()) } else { None } } fn hint_numeric_message(alt: &str, type_: &str) -> String { format!("the {alt} operator can be used with {type_}s\n") } fn hint_string_message() -> String { wrap("Strings can be joined using the `<>` operator.") } #[derive(Debug, Clone, PartialEq, Eq)] pub struct Unformatted { pub source: Utf8PathBuf, pub destination: Utf8PathBuf, pub input: EcoString, pub output: String, } pub fn wrap(text: &str) -> String { let mut result = String::with_capacity(text.len()); for (i, line) in wrap_text(text, 75).iter().enumerate() { if i > 0 { result.push('\n'); } result.push_str(line); } result } fn wrap_text(text: &str, width: usize) -> Vec> { let mut lines: Vec> = Vec::new(); for line in text.split('\n') { // check if line needs to be broken match line.len() > width { false => lines.push(Cow::from(line)), true => { let mut new_lines = break_line(line, width); lines.append(&mut new_lines); } }; } lines } fn break_line(line: &str, width: usize) -> Vec> { let mut lines: Vec> = Vec::new(); let mut newline = String::from(""); // split line by spaces for (i, word) in line.split(' ').enumerate() { let is_new_line = i < 1 || newline.is_empty(); let can_add_word = match is_new_line { true => newline.len() + word.len() <= width, // +1 accounts for space added before word false => newline.len() + (word.len() + 1) <= width, }; if can_add_word { if !is_new_line { newline.push(' '); } newline.push_str(word); } else { // word too big, save existing line if present if !newline.is_empty() { // save current line and reset it lines.push(Cow::from(newline.to_owned())); newline.clear(); } // then save word to a new line or break it match word.len() > width { false => newline.push_str(word), true => { let (mut newlines, remainder) = break_word(word, width); lines.append(&mut newlines); newline.push_str(remainder); } } } } // save last line after loop finishes if !newline.is_empty() { lines.push(Cow::from(newline)); } lines } // breaks word into n lines based on width. Returns list of new lines and remainder fn break_word(word: &str, width: usize) -> (Vec>, &str) { let mut new_lines: Vec> = Vec::new(); let (first, mut remainder) = word.split_at(width); new_lines.push(Cow::from(first)); // split remainder until it's small enough while remainder.len() > width { let (first, second) = remainder.split_at(width); new_lines.push(Cow::from(first)); remainder = second; } (new_lines, remainder) } ================================================ FILE: compiler-core/src/exhaustiveness/missing_patterns.rs ================================================ use super::{CompileCaseResult, Decision, FallbackCheck, RuntimeCheck, Variable, printer::Printer}; use crate::type_::environment::Environment; use ecow::EcoString; use indexmap::IndexSet; use std::collections::HashMap; /// Returns a list of patterns not covered by the match expression. pub fn missing_patterns( result: &CompileCaseResult, environment: &Environment<'_>, ) -> Vec { let subjects = &result.compiled_case.subject_variables; let mut generator = MissingPatternsGenerator::new(subjects, environment); generator.add_missing_patterns(&result.compiled_case.tree); generator.missing.into_iter().collect() } #[derive(Debug, Clone)] pub struct VariantField { pub label: Option, pub variable: Variable, } /// Information about a single constructor/value (aka term) being tested, used /// to build a list of names of missing patterns. #[derive(Debug)] pub enum Term { Variant { variable: Variable, name: EcoString, module: EcoString, fields: Vec, }, Tuple { variable: Variable, elements: Vec, }, Infinite { variable: Variable, }, EmptyList { variable: Variable, }, List { variable: Variable, first: Variable, rest: Variable, }, } impl Term { pub fn variable(&self) -> &Variable { match self { Term::Variant { variable, .. } => variable, Term::Tuple { variable, .. } => variable, Term::Infinite { variable } => variable, Term::EmptyList { variable } => variable, Term::List { variable, .. } => variable, } } } struct MissingPatternsGenerator<'a, 'env> { subjects: &'a Vec, terms: Vec, missing: IndexSet, environment: &'a Environment<'env>, printer: Printer<'a>, } impl<'a, 'env> MissingPatternsGenerator<'a, 'env> { fn new(subjects: &'a Vec, environment: &'a Environment<'env>) -> Self { MissingPatternsGenerator { subjects, terms: vec![], missing: IndexSet::new(), environment, printer: Printer::new(environment.current_module.clone(), &environment.names), } } fn print_terms(&self, mapping: HashMap) -> EcoString { self.printer .print_terms(self.subjects, &self.terms, &mapping) } fn add_missing_patterns(&mut self, node: &Decision) { match node { Decision::Run { .. } => {} Decision::Guard { if_false, .. } => self.add_missing_patterns(if_false), Decision::Fail => { // At this point the terms stack looks something like this: // `[term, term + arguments, term, ...]`. To construct a pattern // name from this stack, we first map all variables to their // term indexes. This is needed because when a term defines // arguments, the terms for those arguments don't necessarily // appear in order in the term stack. // // This mapping is then used when (recursively) generating a // pattern name. // // This approach could probably be done more efficiently, so if // you're reading this and happen to know of a way, please // submit a merge request :) let mut mapping = HashMap::new(); for (index, step) in self.terms.iter().enumerate() { _ = mapping.insert(step.variable().id, index); } let pattern = self.print_terms(mapping); _ = self.missing.insert(pattern); } Decision::Switch { var, choices, fallback, fallback_check, } => { for (check, body) in choices { self.add_missing_patterns_after_check(var, check, body); } match fallback_check.as_ref() { FallbackCheck::InfiniteCatchAll => { self.add_missing_patterns(fallback); } FallbackCheck::RuntimeCheck { check } => { self.add_missing_patterns_after_check(var, check, fallback) } FallbackCheck::CatchAll { ignored_checks } => { for check in ignored_checks { self.add_missing_patterns_after_check(var, check, fallback); } } }; } } } fn add_missing_patterns_after_check( &mut self, var: &Variable, check: &RuntimeCheck, body: &Decision, ) { let term = self.check_to_term(var.clone(), check); self.terms.push(term); self.add_missing_patterns(body); _ = self.terms.pop(); } fn check_to_term(&self, variable: Variable, check: &RuntimeCheck) -> Term { match check { RuntimeCheck::Int { .. } | RuntimeCheck::Float { .. } | RuntimeCheck::String { .. } | RuntimeCheck::BitArray { .. } | RuntimeCheck::StringPrefix { .. } => Term::Infinite { variable }, RuntimeCheck::Tuple { elements, .. } => Term::Tuple { variable, elements: elements.clone(), }, RuntimeCheck::Variant { index, fields, labels, .. } => { let (module, name) = variable .type_ .named_type_name() .expect("Should be a named type"); let name = self .environment .get_constructors_for_type(&module, &name) .expect("Custom type constructor must have custom type kind") .variants .get(*index) .expect("Custom type constructor exist for type") .name .clone(); let fields = fields .iter() .enumerate() .map(|(index, variable)| VariantField { label: labels.get(&index).cloned(), variable: variable.clone(), }) .collect(); Term::Variant { variable, name, module, fields, } } RuntimeCheck::NonEmptyList { first, rest } => Term::List { variable, first: first.clone(), rest: rest.clone(), }, RuntimeCheck::EmptyList => Term::EmptyList { variable }, } } } ================================================ FILE: compiler-core/src/exhaustiveness/printer.rs ================================================ use std::collections::HashMap; use ecow::EcoString; use crate::{ ast::Publicity, type_::printer::{NameContextInformation, Names}, }; use super::{Variable, missing_patterns::Term}; #[derive(Debug)] pub struct Printer<'a> { names: &'a Names, /// This is the module that is being currently analysed. current_module: EcoString, } impl<'a> Printer<'a> { pub fn new(current_module: EcoString, names: &'a Names) -> Self { Printer { current_module, names, } } pub fn print_terms( &self, subjects: &[Variable], terms: &[Term], mapping: &HashMap, ) -> EcoString { let mut buffer = EcoString::new(); for (i, subject) in subjects.iter().enumerate() { if i != 0 { buffer.push_str(", "); } match mapping.get(&subject.id) { Some(&index) => { let term = terms.get(index).expect("Term must exist"); self.print(term, terms, mapping, &mut buffer); } None => buffer.push('_'), } } buffer } fn print( &self, term: &Term, terms: &[Term], mapping: &HashMap, buffer: &mut EcoString, ) { match term { Term::Variant { name, module, fields, variable, } => { let is_defined_in_current_module = *module == self.current_module; let is_internal = variable .type_ .named_type_publicity() .unwrap_or(Publicity::Public) .is_internal(); // We don't want to expose information about an internal type, // making it easy to rely on its internal structure. // So what we do is we just show a catch all pattern `_` for // those. // We do this only if the internal type is defined in a // different module from the one being analysed, otherwise it's // totally fair to want to match on such type. if is_internal && !is_defined_in_current_module { buffer.push('_'); return; } let (module, name) = match self.names.named_constructor(module, name) { NameContextInformation::Qualified(module, name) => (Some(module), name), NameContextInformation::Unqualified(name) => (None, name), NameContextInformation::Unimported(module, name) => { (module.split('/').next_back(), name) } }; if let Some(module) = module { buffer.push_str(module); buffer.push('.'); } buffer.push_str(name); if fields.is_empty() { return; } buffer.push('('); for (i, field) in fields.iter().enumerate() { if i != 0 { buffer.push_str(", "); } let mut has_label = false; if let Some(label) = &field.label { buffer.push_str(label); buffer.push(':'); has_label = true; } if let Some(&idx) = mapping.get(&field.variable.id) { let term = terms.get(idx).expect("Term must exist"); match term { // If it is an infinite term and this field is labelled, it is generally // more useful to print just the label using label shorthand syntax. // For example, printing `Person(name:, age:)` instead of // `Person(name: _, age: _)`. Term::Infinite { .. } if has_label => {} Term::Infinite { .. } | Term::Variant { .. } | Term::Tuple { .. } | Term::EmptyList { .. } | Term::List { .. } => { // If this field has a label, the current buffer looks like `label:`, // so we want to print a space before printing the pattern for it. // If there is no label, we don't need to print the space. if has_label { buffer.push(' '); } self.print(term, terms, mapping, buffer); } } } else if !has_label { buffer.push('_'); } } buffer.push(')'); } Term::Tuple { elements, .. } => { buffer.push_str("#("); for (i, variable) in elements.iter().enumerate() { if i != 0 { buffer.push_str(", "); } if let Some(&idx) = mapping.get(&variable.id) { self.print( terms.get(idx).expect("Term must exist"), terms, mapping, buffer, ); } else { buffer.push('_'); } } buffer.push(')'); } Term::Infinite { .. } => buffer.push('_'), Term::EmptyList { .. } => buffer.push_str("[]"), Term::List { .. } => { buffer.push('['); self.print_list(term, terms, mapping, buffer); buffer.push(']'); } } } fn print_list( &self, term: &Term, terms: &[Term], mapping: &HashMap, buffer: &mut EcoString, ) { match term { Term::Infinite { .. } | Term::Variant { .. } | Term::Tuple { .. } => buffer.push('_'), Term::EmptyList { .. } => {} Term::List { first, rest, .. } => { if let Some(&idx) = mapping.get(&first.id) { self.print( terms.get(idx).expect("Term must exist"), terms, mapping, buffer, ) } else { buffer.push('_'); } if let Some(&idx) = mapping.get(&rest.id) { let term = terms.get(idx).expect("Term must exist"); match term { Term::EmptyList { .. } => {} Term::Variant { .. } | Term::Tuple { .. } | Term::Infinite { .. } | Term::List { .. } => { buffer.push_str(", "); self.print_list(term, terms, mapping, buffer) } } } else { buffer.push_str(", .."); } } } } } #[cfg(test)] mod tests { use ecow::EcoString; use super::Printer; use std::{collections::HashMap, sync::Arc}; use crate::{ ast::SrcSpan, exhaustiveness::{ Variable, missing_patterns::{Term, VariantField}, }, type_::{Type, printer::Names}, }; /// Create a variable with a dummy type, for ease of writing tests fn make_variable(id: usize) -> Variable { Variable { id, type_: Arc::new(Type::Tuple { elements: Vec::new(), }), } } fn field(variable: Variable, label: Option<&str>) -> VariantField { VariantField { variable, label: label.map(EcoString::from), } } fn get_mapping(terms: &[Term]) -> HashMap { let mut mapping: HashMap = HashMap::new(); for (index, term) in terms.iter().enumerate() { _ = mapping.insert(term.variable().id, index); } mapping } #[test] fn test_value_in_current_module() { let current_module = EcoString::from("module"); let mut names = Names::new(); names.named_constructor_in_scope(current_module.clone(), "Wibble".into(), "Wibble".into()); let printer = Printer::new(current_module.clone(), &names); let subjects = &[make_variable(0)]; let term = Term::Variant { variable: subjects[0].clone(), name: "Wibble".into(), module: current_module, fields: Vec::new(), }; let terms = &[term]; let mapping = get_mapping(terms); assert_eq!(printer.print_terms(subjects, terms, &mapping), "Wibble"); } #[test] fn test_value_in_current_module_with_arguments() { let current_module = EcoString::from("module"); let mut names = Names::new(); names.named_constructor_in_scope(current_module.clone(), "Wibble".into(), "Wibble".into()); let printer = Printer::new(current_module.clone(), &names); let var1 = make_variable(1); let var2 = make_variable(2); let subjects = &[make_variable(0)]; let term = Term::Variant { variable: subjects[0].clone(), name: "Wibble".into(), module: current_module, fields: vec![field(var1.clone(), None), field(var2.clone(), None)], }; let terms = &[ term, Term::EmptyList { variable: var1 }, Term::Infinite { variable: var2 }, ]; let mapping = get_mapping(terms); assert_eq!( printer.print_terms(subjects, terms, &mapping), "Wibble([], _)" ); } #[test] fn test_value_in_current_module_with_labelled_arguments() { let current_module = EcoString::from("module"); let mut names = Names::new(); names.named_constructor_in_scope(current_module.clone(), "Wibble".into(), "Wibble".into()); let printer = Printer::new(current_module.clone(), &names); let var1 = make_variable(1); let var2 = make_variable(2); let subjects = &[make_variable(0)]; let term = Term::Variant { variable: subjects[0].clone(), name: "Wibble".into(), module: current_module, fields: vec![ field(var1.clone(), Some("list")), field(var2.clone(), Some("other")), ], }; let terms = &[ term, Term::EmptyList { variable: var1 }, Term::Infinite { variable: var2 }, ]; let mapping = get_mapping(terms); assert_eq!( printer.print_terms(subjects, terms, &mapping), "Wibble(list: [], other:)" ); } #[test] fn test_module_alias() { let mut names = Names::new(); assert!( names .imported_module("mod".into(), "shapes".into(), SrcSpan::new(50, 60)) .is_none() ); let printer = Printer::new("module".into(), &names); let subjects = &[make_variable(0)]; let term = Term::Variant { variable: subjects[0].clone(), name: "Rectangle".into(), module: "mod".into(), fields: Vec::new(), }; let terms = &[term]; let mapping = get_mapping(terms); assert_eq!( printer.print_terms(subjects, terms, &mapping), "shapes.Rectangle" ); } #[test] fn test_unqualified_value() { let mut names = Names::new(); names.named_constructor_in_scope("regex".into(), "Regex".into(), "Regex".into()); let printer = Printer::new("module".into(), &names); let arg = make_variable(1); let subjects = &[make_variable(0)]; let term = Term::Variant { variable: subjects[0].clone(), name: "Regex".into(), module: "regex".into(), fields: vec![field(arg.clone(), None)], }; let terms = &[term, Term::Infinite { variable: arg }]; let mapping = get_mapping(terms); assert_eq!(printer.print_terms(subjects, terms, &mapping), "Regex(_)"); } #[test] fn test_unqualified_value_with_alias() { let mut names = Names::new(); names.named_constructor_in_scope("regex".into(), "Regex".into(), "Reg".into()); names.named_constructor_in_scope("gleam".into(), "None".into(), "None".into()); let printer = Printer::new("current_module".into(), &names); let arg = make_variable(1); let subjects = &[make_variable(0)]; let term = Term::Variant { variable: subjects[0].clone(), name: "Regex".into(), module: "regex".into(), fields: vec![field(arg.clone(), None)], }; let terms = &[ term, Term::Variant { variable: arg, name: "None".into(), module: "gleam".into(), fields: vec![], }, ]; let mapping = get_mapping(terms); assert_eq!(printer.print_terms(subjects, terms, &mapping), "Reg(None)"); } #[test] fn test_list_pattern() { let mut names = Names::new(); names.named_constructor_in_scope("module".into(), "Type".into(), "Type".into()); let printer = Printer::new("module".into(), &names); let var1 = make_variable(1); let var2 = make_variable(2); let var3 = make_variable(3); let subjects = &[make_variable(0)]; let term = Term::List { variable: subjects[0].clone(), first: var1.clone(), rest: var2.clone(), }; let terms = &[ term, Term::Variant { variable: var1, name: "Type".into(), module: "module".into(), fields: Vec::new(), }, Term::List { variable: var2, first: var3.clone(), rest: make_variable(4), }, Term::Infinite { variable: var3 }, ]; let mapping = get_mapping(terms); assert_eq!( printer.print_terms(subjects, terms, &mapping), "[Type, _, ..]" ); } #[test] fn test_multi_pattern() { let mut names = Names::new(); names.named_constructor_in_scope("gleam".into(), "Ok".into(), "Ok".into()); names.named_constructor_in_scope("gleam".into(), "False".into(), "False".into()); let printer = Printer::new("module".into(), &names); let subjects = &[make_variable(0), make_variable(1), make_variable(2)]; let terms = &[ Term::Variant { variable: subjects[0].clone(), name: "Ok".into(), module: "gleam".into(), fields: vec![field(make_variable(3), None)], }, Term::Variant { variable: subjects[2].clone(), name: "False".into(), module: "gleam".into(), fields: Vec::new(), }, ]; let mapping = get_mapping(terms); assert_eq!( printer.print_terms(subjects, terms, &mapping), "Ok(_), _, False" ); } } ================================================ FILE: compiler-core/src/exhaustiveness.rs ================================================ //! An implementation of the algorithm described in: //! //! - How to compile pattern matching, Jules Jacobs. //! //! //! - Efficient manipulation of binary data using pattern matching, //! Per Gustafsson and Konstantinos Sagonas. //! //! //! - Compiling Pattern Matching to good Decision Trees, Luc Maranget. //! //! //! The first implementation of the decision tree was adapted from Yorick //! Peterse's implementation at //! . //! Thank you Yorick! //! //! > This module comment (and all the following doc comments) are a rough //! > explanation. It's great to set some expectations on what to expect from //! > the following code and why the data looks the way it does. //! > If you want a more detailed explanation, the original paper is a lot more //! > detailed! //! //! A case to be compiled looks a bit different from the case expressions we're //! used to in Gleam: instead of having a variable to match on and a series of //! branches, a `CaseToCompile` is made up of a series of branches that can each //! contain multiple pattern checks. With a psedo-Gleam syntax, this is what it //! would look like: //! //! ```txt //! case { //! a is Some, b is 1, c is _ -> todo //! a is wibble -> todo //! } //! ``` //! //! > You may wonder, why are we writing branches like this? Usually a case //! > expression matches on a single variable and each branch refers to it. For //! > example in gleam you'd write: //! > //! > ```gleam //! > case a { //! > Some(_) -> todo //! > None -> todo //! > } //! > ``` //! > //! > In out representation that would turn into: //! > //! > ```txt //! > case { //! > a is Some(_) -> todo //! > a is None -> todo //! > } //! > ``` //! > //! > This change makes it way easier to compile the pattern matching into a //! > decision tree, because now we can add multiple checks on different //! > variables in each branch. //! //! Starting from this data structure, we'll be splitting all the branches into //! a decision tree that can be used to perform exhaustiveness checking and code //! generation. //! mod missing_patterns; pub mod printer; use crate::{ ast::{ self, AssignName, BitArraySize, Endianness, IntOperator, SrcSpan, TypedBitArraySize, TypedClause, TypedPattern, TypedPatternBitArraySegment, }, parse::LiteralFloatValue, strings::{ convert_string_escape_chars, length_utf16, length_utf32, string_to_utf16_bytes, string_to_utf32_bytes, }, type_::{ Environment, Opaque, Type, TypeValueConstructor, TypeValueConstructorField, TypeVar, TypeVariantConstructors, collapse_links, error::UnreachablePatternReason, is_prelude_module, string, }, }; use bitvec::{order::Msb0, slice::BitSlice, vec::BitVec, view::BitView}; use ecow::EcoString; use id_arena::{Arena, Id}; use itertools::Itertools; use num_bigint::{BigInt, Sign}; use num_traits::ToPrimitive; use radix_trie::{Trie, TrieCommon}; use std::{ cell::RefCell, cmp::Ordering, collections::{HashMap, HashSet, VecDeque}, hash::Hash, sync::Arc, }; /// A single branch composing a `case` expression to be compiled into a decision /// tree. /// /// As shown in the module documentation, branches are a bit different from the /// usual branches we see in Gleam's case expressions. Each branch can perform /// multiple checks (each on a different variable, which appears in the check /// itself!): /// /// ```txt /// a is Some, b is 1 if condition -> todo /// ─┬─────── ─┬──── ─┬────────── ─┬── /// │ │ │ ╰── body: an arbitrary expression /// │ │ ╰── guard: an additional boolean condition /// ╰──────────┴── checks: check that a variable matches with a pattern /// ─┬──────────────────────────────────── /// ╰── branch: one of the branches making up a pattern matching expression /// ``` /// /// As shown here a branch can also optionally include a guard with a boolean /// condition and is followed by a body that is to be executed if all the checks /// match (and the guard evaluates to true). /// #[derive(Clone, Eq, PartialEq, Debug)] struct Branch { /// Each branch is identified by a numeric index, so we can nicely /// report errors once we find something's wrong with a branch. /// clause_index: usize, /// Each alternative pattern in an alternative pattern matching (e.g. /// `one | two | three -> todo`) gets turned into its own branch in this /// internal representation. So we also keep track of the index of the /// alternative this comes from (0 being the first one and so on...) /// alternative_index: usize, checks: Vec, guard: Option, body: Body, } impl Branch { fn new( clause_index: usize, alternative_index: usize, checks: Vec, has_guard: bool, ) -> Self { Self { clause_index, alternative_index, checks, guard: if has_guard { Some(clause_index) } else { None }, body: Body::new(clause_index), } } /// Removes and returns a `PatternCheck` on the given variable from this /// branch. /// fn pop_check_on_var(&mut self, var: &Variable) -> Option { let index = self.checks.iter().position(|check| check.var == *var)?; Some(self.checks.remove(index)) } fn add_check(&mut self, check: PatternCheck) { self.checks.push(check); } /// To simplify compiling the pattern we can get rid of all catch-all /// patterns that are guaranteed to match by turning those into assignments. /// /// What does this look like in practice? Let's go over an example. /// Let's say we have this case to compile: /// /// ```gleam /// case a { /// Some(1) -> Some(2) /// otherwise -> otherwise /// } /// ``` /// /// In our internal representation this would become: /// /// ```txt /// case { /// a is Some(1) -> Some(2) /// a is otherwise -> otherwise /// ─┬──────────── /// ╰── `a` will always match with this "catch all" variable pattern /// } /// ``` /// /// Focusing on the last branch, we can remove that check that always matches /// by keeping track in its body of the correspondence. So it would end up /// looking like this: /// /// ```txt /// case { /// a is Some(1) -> Some(2) /// ∅ -> { /// ┬ /// ╰── This represents the fact that there's no checks left for this branch! /// So we can make another observation: if there's no checks left in a /// branch we know it will always match and we can produce a leaf in the /// decision tree (there's an exception when we have guards, but we'll /// get to it later)! /// /// let otherwise = a /// ─┬─────────────── /// ╰── And now we can understand what those `bindings` at the start of /// a body are: as we remove variable patterns, we will rewrite those /// as assignments at the top of the body of the corresponding branch. /// /// otherwise /// } /// } /// ``` /// fn move_unconditional_patterns(&mut self, compiler: &mut Compiler<'_>) { self.checks.retain_mut(|check| { loop { match compiler.pattern(check.pattern) { // Variable patterns always match, so we move those to the body // and remove them from the branch's checks. Pattern::Variable { name } => { self.body.assign(name.clone(), check.var.clone()); return false; } // A discard pattern always matches, but since the value is not // used we can just remove it without even adding an assignment // to the body! Pattern::Discard => return false, // Assigns are kind of special: they get turned into assignments // (shocking) but then we can't discard the pattern they wrap. // So we replace the assignment pattern with the one it's wrapping // and try again. Pattern::Assign { name, pattern } => { self.body.assign(name.clone(), check.var.clone()); check.pattern = *pattern; } // There's a special case of assignments when it comes to string // prefix patterns. We can give a name to a literal prefix like this: // `"0" as digit <> rest`. // We also want to move this special case of an assignment to the // branch body! Pattern::StringPrefix { prefix, prefix_name, rest: _, } => { if let Some(variable) = std::mem::take(prefix_name) { self.body .assign_literal_string(variable.clone(), prefix.clone()); } return true; } // There's a special case of assignments when it comes to bit // array patterns. We can give a name to one slice of the array and // bind it to a variable to be used by later steps of the pattern // like this: `<>` (here we're binding // two variables! `len` and `payload`). // // This kind of slicing will always match if it's not guarded by // any size test, so if we find a `ReadAction` that is the first // test to perform in a bit array pattern we know it's always // going to match and can be safely moved into the branch's body. Pattern::BitArray { tests } => match tests.front_mut() { Some(BitArrayTest::Match(MatchTest { value: BitArrayMatchedValue::Variable(name), read_action, })) => { let bit_array = check.var.clone(); self.body.assign_bit_array_slice( name.clone(), bit_array, read_action.clone(), ); let _ = tests.pop_front(); } Some(test) => match test { // If we have `_ as a` we treat that as a regular variable // assignment. BitArrayTest::Match(MatchTest { value: BitArrayMatchedValue::Assign { name, value }, read_action, }) if value.is_discard() => { *test = BitArrayTest::Match(MatchTest { value: BitArrayMatchedValue::Variable(name.clone()), read_action: read_action.clone(), }); } // Just like regular assigns, those patterns are unrefutable // and will become assignments in the branch's body. BitArrayTest::Match(MatchTest { value: BitArrayMatchedValue::Assign { name, value }, read_action, }) => { self.body .assign_segment_constant_value(name.clone(), value.as_ref()); // We will still need to check the aliased value! *test = BitArrayTest::Match(MatchTest { value: value.as_ref().clone(), read_action: read_action.clone(), }); } // Discards are removed directly without even binding them // in the branch's body. _ if test.is_discard() => { let _ = tests.pop_front(); } // Otherwise there's no unconditional test to pop, we // keep the pattern without changing it. BitArrayTest::Size(_) | BitArrayTest::Match(_) | BitArrayTest::CatchAllIsBytes { .. } | BitArrayTest::ReadSizeIsNotNegative { .. } | BitArrayTest::SegmentIsFiniteFloat { .. } => return true, }, // If a bit array pattern has no tests then it's always // going to match, no matter what. We just remove it. None => return false, }, // All other patterns are not unconditional, so we just keep them. Pattern::Int { .. } | Pattern::Float { .. } | Pattern::String { .. } | Pattern::Tuple { .. } | Pattern::Variant { .. } | Pattern::NonEmptyList { .. } | Pattern::EmptyList => return true, } } }); } } /// The body of a branch. It always starts with a series of variable assignments /// in the form: `let a = b`. As explained in `move_unconditional_patterns`' doc, /// each body starts with a series of assignments we keep track of as we're /// compiling each branch. /// #[derive(Clone, Eq, PartialEq, Debug, serde::Serialize, serde::Deserialize)] pub struct Body { /// Any variables to bind before running the code. /// /// The tuples are in the form `(name, value)`, so `(wibble, var)` /// corresponds to `let wibble = var`. /// pub bindings: Vec<(EcoString, BoundValue)>, /// The index of the clause in the case expression that should be run. /// pub clause_index: usize, } /// A value that can appear on the right hand side of one of the assignments we /// find at the top of a body. /// #[derive(Clone, Eq, PartialEq, Debug, serde::Serialize, serde::Deserialize)] pub enum BoundValue { /// `let a = variable` /// Variable(Variable), /// `let a = "a literal string"` /// LiteralString(EcoString), /// `let a = 123` /// LiteralInt(BigInt), /// `let a = 12.2` /// LiteralFloat(EcoString), /// `let a = sliceAsInt(bit_array, 0, 16, ...)` /// BitArraySlice { bit_array: Variable, read_action: ReadAction, }, } impl Body { pub fn new(clause_index: usize) -> Self { Self { bindings: vec![], clause_index, } } /// Adds a new assignment to the body, binding `let variable = value` /// pub fn assign(&mut self, variable: EcoString, value: Variable) { self.bindings.push((variable, BoundValue::Variable(value))); } fn assign_literal_string(&mut self, variable: EcoString, value: EcoString) { self.bindings .push((variable, BoundValue::LiteralString(value))); } fn assign_bit_array_slice( &mut self, segment_name: EcoString, bit_array: Variable, value: ReadAction, ) { self.bindings.push(( segment_name, BoundValue::BitArraySlice { bit_array, read_action: value, }, )) } fn assign_segment_constant_value(&mut self, name: EcoString, value: &BitArrayMatchedValue) { let value = match value { BitArrayMatchedValue::LiteralFloat(value) => BoundValue::LiteralFloat(value.clone()), BitArrayMatchedValue::LiteralInt { value, .. } => BoundValue::LiteralInt(value.clone()), BitArrayMatchedValue::LiteralString { value, .. } => { BoundValue::LiteralString(value.clone()) } BitArrayMatchedValue::Variable(_) | BitArrayMatchedValue::Discard(_) | BitArrayMatchedValue::Assign { .. } => { panic!("aliased non constant value: {value:#?}") } }; self.bindings.push((name, value)) } } /// A user defined pattern such as `Some((x, 10))`. /// This is a bit simpler than the full fledged `TypedPattern` used for code analysis /// and only focuses on the relevant bits needed to perform exhaustiveness checking /// and code generation. /// /// Using this simplified version of a pattern for the case compiler makes it a /// whole lot simpler and more efficient (patterns will have to be cloned, so /// we use an arena to allocate those and only store ids to make this operation /// extra cheap). /// #[derive(Clone, Eq, PartialEq, Debug)] pub enum Pattern { Discard, Int { int_value: BigInt, }, Float { float_value: LiteralFloatValue, }, String { value: EcoString, }, StringPrefix { prefix: EcoString, prefix_name: Option, rest: Id, }, Assign { name: EcoString, pattern: Id, }, Variable { name: EcoString, }, Tuple { elements: Vec>, }, Variant { index: usize, name: EcoString, module: Option, fields: Vec>, }, NonEmptyList { first: Id, rest: Id, }, EmptyList, BitArray { tests: VecDeque, }, } impl Pattern { /// Each pattern (with a couple exceptions) can be turned into a /// simpler `RuntimeCheck`: that is a check that can be performed at runtime /// to make sure a `PatternCheck` can succeed on a specific value. /// fn to_runtime_check_kind(&self) -> Option { let kind = match self { // These patterns are unconditional: they will always match and be moved // out of a branch's checks. So there's no corresponding runtime check // we can perform for them. Pattern::Discard | Pattern::Variable { .. } | Pattern::Assign { .. } => return None, Pattern::Int { int_value, .. } => RuntimeCheckKind::Int { int_value: int_value.clone(), }, Pattern::Float { float_value, .. } => RuntimeCheckKind::Float { float_value: *float_value, }, Pattern::String { value } => RuntimeCheckKind::String { value: value.clone(), }, Pattern::StringPrefix { prefix, .. } => RuntimeCheckKind::StringPrefix { prefix: prefix.clone(), }, Pattern::Tuple { elements } => RuntimeCheckKind::Tuple { size: elements.len(), }, Pattern::Variant { index, .. } => RuntimeCheckKind::Variant { index: *index }, Pattern::NonEmptyList { .. } => RuntimeCheckKind::NonEmptyList, Pattern::EmptyList => RuntimeCheckKind::EmptyList, // Bit arrays have no corresponding kind as they're dealt with in a // completely different way. Pattern::BitArray { .. } => return None, }; Some(kind) } fn is_matching_on_unreachable_variant(&self, branch_mode: &BranchMode) -> bool { match (self, branch_mode) { ( Self::Variant { index, .. }, BranchMode::NamedType { inferred_variant: Some(variant), .. }, ) if index != variant => true, _ => false, } } fn is_matching_on_impossible_segment(&self) -> Option> { match self { Self::BitArray { tests } => { let impossible_segments = tests .iter() .filter_map(|test| match test { BitArrayTest::Size(_) | BitArrayTest::CatchAllIsBytes { .. } | BitArrayTest::ReadSizeIsNotNegative { .. } | BitArrayTest::SegmentIsFiniteFloat { .. } => None, BitArrayTest::Match(MatchTest { value, read_action }) => { value.is_impossible_segment(read_action) } }) .collect_vec(); if impossible_segments.is_empty() { None } else { Some(impossible_segments) } } Self::Discard | Self::Int { .. } | Self::Float { .. } | Self::String { .. } | Self::StringPrefix { .. } | Self::Assign { .. } | Self::Variable { .. } | Self::Tuple { .. } | Self::Variant { .. } | Self::NonEmptyList { .. } | Self::EmptyList => None, } } } /// A single check making up a branch, checking that a variable matches with a /// given pattern. For example, the following branch has 2 checks: /// /// ```txt /// a is Some, b is 1 -> todo /// ┬ ─┬── /// │ ╰── This is the pattern being checked /// ╰── This is the variable being pattern matched on /// ─┬─────── ─┬──── /// ╰─────────┴── Two `PatternCheck`s /// ``` /// #[derive(Clone, Eq, PartialEq, Debug)] struct PatternCheck { var: Variable, pattern: Id, } /// This is one of the checks we can take at runtime to decide how to move /// forward in the decision tree. /// /// After performing a successful check on a value we will discover something /// about its shape: it might be an int, an variant of a custom type, ... /// Some values (like variants and lists) might hold onto additional data we /// will have to pattern match on: in order to do that we need a name to refer /// to those new variables we've discovered after performing a check. That's /// what `args` is for. /// /// Let's have a look at an example. Imagine we have a pattern like this one: /// `a is Wibble(1, _, [])`; after performing a runtime check to make sure `a` /// is indeed a `Wibble`, we'll need to perform additional checks on it's /// arguments: that pattern will be replaced by three new ones `a0 is 1`, /// `a1 is _` and `a2 is []`. Those new variables are the `args`. /// #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum RuntimeCheck { Int { int_value: BigInt, }, Float { float_value: LiteralFloatValue, }, String { value: EcoString, }, StringPrefix { prefix: EcoString, rest: Variable, }, Tuple { size: usize, elements: Vec, }, BitArray { test: BitArrayTest, }, Variant { match_: VariantMatch, index: usize, labels: HashMap, fields: Vec, }, NonEmptyList { first: Variable, rest: Variable, }, EmptyList, } impl RuntimeCheck { fn kind(&self) -> Option { let kind = match self { RuntimeCheck::Int { int_value, .. } => RuntimeCheckKind::Int { int_value: int_value.clone(), }, RuntimeCheck::Float { float_value, .. } => RuntimeCheckKind::Float { float_value: *float_value, }, RuntimeCheck::String { value } => RuntimeCheckKind::String { value: value.clone(), }, RuntimeCheck::StringPrefix { prefix, rest: _ } => RuntimeCheckKind::StringPrefix { prefix: prefix.clone(), }, RuntimeCheck::Tuple { size, elements: _ } => RuntimeCheckKind::Tuple { size: *size }, RuntimeCheck::Variant { index, .. } => RuntimeCheckKind::Variant { index: *index }, RuntimeCheck::EmptyList => RuntimeCheckKind::EmptyList, RuntimeCheck::NonEmptyList { first: _, rest: _ } => RuntimeCheckKind::NonEmptyList, RuntimeCheck::BitArray { .. } => return None, }; Some(kind) } pub(crate) fn is_ignored(&self) -> bool { match self { RuntimeCheck::Variant { match_: VariantMatch::NeverExplicitlyMatchedOn { .. }, .. } => true, RuntimeCheck::Int { .. } | RuntimeCheck::Float { .. } | RuntimeCheck::String { .. } | RuntimeCheck::StringPrefix { .. } | RuntimeCheck::Tuple { .. } | RuntimeCheck::BitArray { .. } | RuntimeCheck::NonEmptyList { .. } | RuntimeCheck::Variant { .. } | RuntimeCheck::EmptyList => false, } } /// Returns all the bit array segments referenced in this check. /// For each segment it returns its name and the read action used to access /// such segment. /// pub(crate) fn referenced_segment_patterns(&self) -> Vec<(&EcoString, &ReadAction)> { match self { RuntimeCheck::BitArray { test } => test.referenced_segment_patterns(), RuntimeCheck::Int { .. } | RuntimeCheck::Float { .. } | RuntimeCheck::String { .. } | RuntimeCheck::StringPrefix { .. } | RuntimeCheck::Tuple { .. } | RuntimeCheck::Variant { .. } | RuntimeCheck::NonEmptyList { .. } | RuntimeCheck::EmptyList => vec![], } } } #[derive(Eq, PartialEq, Clone, Hash, Debug)] pub enum RuntimeCheckKind { Int { int_value: BigInt }, Float { float_value: LiteralFloatValue }, String { value: EcoString }, StringPrefix { prefix: EcoString }, Tuple { size: usize }, Variant { index: usize }, EmptyList, NonEmptyList, } /// All possible variant checks are automatically generated beforehand once we /// know we are matching on a value with a custom type. /// Then if the compiled case is explicitly matching on one of those, we update /// it to store additional information: for example how the variant is used /// (if qualified or unqualified and if it is aliased). /// /// This way when we get to code generation we can clump all variants that were /// never explicitly matched on in a single `else` block without blowing up code /// size! /// #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum VariantMatch { ExplicitlyMatchedOn { name: EcoString, module: Option, }, NeverExplicitlyMatchedOn { name: EcoString, }, } impl VariantMatch { pub(crate) fn name(&self) -> EcoString { match self { VariantMatch::ExplicitlyMatchedOn { name, module: _ } => name.clone(), VariantMatch::NeverExplicitlyMatchedOn { name } => name.clone(), } } pub(crate) fn module(&self) -> Option { match self { VariantMatch::ExplicitlyMatchedOn { name: _, module } => module.clone(), VariantMatch::NeverExplicitlyMatchedOn { name: _ } => None, } } } /// A variable that can be matched on in a branch. /// #[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct Variable { pub id: usize, pub type_: Arc, } impl Variable { fn new(id: usize, type_: Arc) -> Self { Self { id, type_ } } /// Builds a `PatternCheck` that checks this variable matches the given pattern. /// So we can build pattern checks the same way we informally describe them: /// ```txt /// var is pattern /// ``` /// With this builder method would become: /// ```rs /// var.is(pattern) /// ``` /// fn is(&self, pattern: Id) -> PatternCheck { PatternCheck { var: self.clone(), pattern, } } } #[derive(Debug)] /// Different types need to be handled differently when compiling a case expression /// into a decision tree. There's some types that have infinite matching patterns /// (like ints, strings, ...) and thus will always need a fallback option. /// /// Other types, like custom types, only have a well defined and finite number /// of patterns that could match: when matching on a `Result` we know that we can /// only have an `Ok(_)` and an `Error(_)`, anything else would end up being a /// type error! /// /// So this enum is used to pick the correct strategy to compile a case that's /// performing a `PatternCheck` on a variable with a specific type. /// enum BranchMode { /// This covers numbers, functions, variables, strings, and bitarrays. Infinite, Tuple { elements: Vec>, }, List { inner_type: Arc, }, NamedType { constructors: Vec, inferred_variant: Option, }, } impl BranchMode { /// Returns a heuristic estimate of the branching factor. /// /// This value is used by the pivot-selection to prefer splits /// with fewer branches, which tends to produce smaller and shallower /// decision trees. fn branching_factor(&self) -> usize { match self { BranchMode::Infinite => usize::MAX, BranchMode::Tuple { elements } => elements.len(), BranchMode::List { .. } => 2, BranchMode::NamedType { constructors, .. } => constructors.len(), } } fn is_infinite(&self) -> bool { match self { BranchMode::Infinite => true, BranchMode::Tuple { .. } | BranchMode::List { .. } | BranchMode::NamedType { .. } => { false } } } } impl Variable { fn branch_mode(&self, env: &Environment<'_>) -> BranchMode { match collapse_links(self.type_.clone()).as_ref() { Type::Fn { .. } | Type::Var { .. } => BranchMode::Infinite, Type::Named { module, name, .. } if is_prelude_module(module) && (name == "Int" || name == "Float" || name == "BitArray" || name == "String") => { BranchMode::Infinite } Type::Named { module, name, arguments, .. } if is_prelude_module(module) && name == "List" => BranchMode::List { inner_type: arguments.first().expect("list has a type argument").clone(), }, Type::Tuple { elements } => BranchMode::Tuple { elements: elements.clone(), }, Type::Named { module, name, arguments, inferred_variant, .. } => { let constructors = ConstructorSpecialiser::specialise_constructors( env.get_constructors_for_type(module, name) .expect("Custom type variants must exist"), arguments.as_slice(), &env.current_module, module, ); let inferred_variant = inferred_variant.map(|i| i as usize); BranchMode::NamedType { constructors, inferred_variant, } } } } } /// When compiling a bit array pattern each segment is turned into a series of /// tests that all need to match in order for the pattern to succeed. /// Each segment might add some requirements on the total size of a bit array /// and/or on the value of some of its specific parts. /// /// Let's look at a simple example to get started: /// /// ```txt /// <<0:size(12), 1, rest:bits>> /// ─┬──────── /// ╰── This first segment requires that the bit array must have at least /// 12 bits and their value must be the Int `0`. So this first /// segment would be turned into the following series of tests: /// `Size(>=, 12)` and `Match(0, ReadAction(0, 12, int))` /// ``` /// /// However, the various sizes and offsets of the different segments might not /// always be known at compile time and depend on previous sections of the /// pattern. This is no big deal: as you'll discover in more detail in the /// `SizeExpression`'s doc we can also represent those variable sizes: /// /// ```txt /// <> /// ─┬─ ─┬─────────────── /// │ ╰── For this segment to match the bit array must have enough bits /// │ for the previous segment (8 bits) and for this one (`len` bits), /// │ so it will turn into the following series of tests: /// │ `Size(>=, 8 + len)` and `Match(len, ReadAction(8, len, int))` /// │ /// ╰── This first segment is 8 bits and an int (it's the default Gleam picks /// if no options are supplied) /// ``` /// #[derive(Clone, Eq, PartialEq, Debug, serde::Serialize, serde::Deserialize)] pub enum BitArrayTest { Size(SizeTest), Match(MatchTest), /// This is a special test to check that the remaining part of a bit array /// has a whole number of bytes when using the `:bytes` option. /// CatchAllIsBytes { size_so_far: Offset, }, /// This test is made to ensure a given variable is positive: a segment /// pattern where the size is a variable with a negative value will never /// match. So we check this to make sure the test will fail. /// ReadSizeIsNotNegative { size: ReadSize, }, /// This test checks that the segment read by the given read action is a /// finite Float and not a `NaN` or `Infinity`. /// /// We need this check as `NaN` and `Infinity` will not match with float /// segments (like `<<_:32-float>>`) on the Erlang target and we must /// replicate the same behaviours on the JavaScript target as well. /// SegmentIsFiniteFloat { read_action: ReadAction, }, } impl BitArrayTest { fn is_discard(&self) -> bool { match self { BitArrayTest::Match(MatchTest { value: BitArrayMatchedValue::Discard(_), .. }) => true, BitArrayTest::Match(_) | BitArrayTest::Size(_) | BitArrayTest::CatchAllIsBytes { .. } | BitArrayTest::ReadSizeIsNotNegative { .. } | BitArrayTest::SegmentIsFiniteFloat { .. } => false, } } pub(crate) fn referenced_segment_patterns(&self) -> Vec<(&EcoString, &ReadAction)> { match self { BitArrayTest::ReadSizeIsNotNegative { size } => { let mut references = Vec::new(); size.referenced_segment_patterns(&mut references); references } BitArrayTest::Size(SizeTest { operator: _, size }) | BitArrayTest::CatchAllIsBytes { size_so_far: size } => { size.referenced_segment_patterns() } BitArrayTest::SegmentIsFiniteFloat { read_action: ReadAction { from, size, .. }, } | BitArrayTest::Match(MatchTest { read_action: ReadAction { from, size, .. }, .. }) => { let mut references = vec![]; references.append(&mut from.referenced_segment_patterns()); size.referenced_segment_patterns(&mut references); references } } } } /// Test to make sure the bit array has a specific number of bits. /// #[derive(Clone, Eq, PartialEq, Debug, serde::Serialize, serde::Deserialize)] pub struct SizeTest { pub operator: SizeOperator, pub size: Offset, } impl SizeTest { /// Tells us if this test is guaranteed to succeed given another test that /// we know has already succeeded. /// /// For example, ">= 5" is certainly going to succeed if ">= 6" has already /// succeeded. /// fn succeeds_if_succeeding(&self, succeeding: &SizeTest) -> Confidence { match (succeeding.operator, self.operator) { (SizeOperator::Equal, SizeOperator::Equal) if succeeding.size == self.size => { Confidence::Certain } (_, SizeOperator::GreaterEqual) => succeeding.size.greater_equal(&self.size), _ => Confidence::Uncertain, } } /// Tells us if this test is guaranteed to fail given another test that /// we know has already failed. /// /// For example, ">= 5" is certainly going to fail if ">= 4" has already /// failed. /// fn fails_if_failing(&self, failing: &SizeTest) -> Confidence { match (failing.operator, self.operator) { (SizeOperator::GreaterEqual, _) => self.size.greater_equal(&failing.size), (_, _) if self == failing => Confidence::Certain, _ => Confidence::Uncertain, } } /// Tells us if this test is guaranteed to fail given another test that /// we know has already succeeded. /// /// For example, "= 1" is certainly going to fail if "= 2" has already /// succeeded. /// fn fails_if_succeeding(&self, succeeding: &SizeTest) -> Confidence { match (succeeding.operator, self.operator) { (SizeOperator::GreaterEqual, SizeOperator::Equal) => { succeeding.size.greater(&self.size) } (SizeOperator::Equal, SizeOperator::GreaterEqual) => { self.size.greater(&succeeding.size) } (SizeOperator::Equal, SizeOperator::Equal) => succeeding.size.different(&self.size), _ => Confidence::Uncertain, } } } /// Test to make sure the segment read by the specified `read_action` matches /// a given value. /// #[derive(Clone, Eq, PartialEq, Debug, serde::Serialize, serde::Deserialize)] pub struct MatchTest { pub value: BitArrayMatchedValue, pub read_action: ReadAction, } #[derive(Debug)] struct Interference { interfering_bits_are_equal: bool, first_encloses_second: bool, second_encloses_first: bool, } impl MatchTest { /// If this match test interferes with the other one, this will return /// information about how they interfere. /// /// /// # What is interference used for? /// /// Interference analysis is an optimization that we can apply to segments /// matching on a literal value with a known offset and size. /// It allows discarding many overlapping checks that we can for sure tell /// will never (or will always) match. /// /// This optimization is particularly important for network protocol /// applications where it is typical to match on some fixed patterns at the /// start of the bitarray: /// /// ```gleam /// case packet { /// <<"CONTENT_LENGTH", 0, rest:bytes>> -> todo /// <<"QUERY_STRING", 0, rest:bytes>> -> todo /// // ... /// _ -> todo /// } /// ``` /// /// /// # What is interference? /// /// We say two read actions interfere with each other if: /// - They're both matching against a statically known value, we will call /// them `v1` and `v2` /// - They both start at a statically known offset, `o1` and `o2` /// - They both have a statically known size, `s1` and `s2` /// - `o1 <= o2 && o1 + s1 > o2` /// /// This is a lot of letters to say something actually quite simple, having /// a look at a graphical example will make this easier to grasp. Let's /// visualize each match action as a segment; each read action will match /// against a portion of the bit array, starting at the given offset and /// with the given number of bits (in the example I'm also showing the bits /// that the read action matches against): /// /// ```txt /// o1 (o1+s1) /// ┄├0001010110┤┄ /// ┄├1101000000111011┤┄ /// o2 (o2+s2) /// ``` /// /// They interfere if the first one comes first (`o1 <= o2`) and the start /// of the second one falls inside the range covered by the first one /// (`o1 + s1 > p2`). /// So the example above showcases two interfering read actions, here's an /// example of two read actions that are not interfering with each other: /// /// ```txt /// o1 (o1+s1) /// ┄├0110101┤┄ /// ┄├0101110101┤┄ /// o2 (o2+s2) /// ``` /// /// /// # How is interference useful /// /// Knowing that two read actions interfere is very useful because, knowing /// if the first one matches or not can allow us to tell for certain if an /// interfering match has no change of succeeding as well. /// Let's look at the three cases: /// /// 1. We know the first action succeeded, so the binary has the expected /// bits in the matched segment /// ```txt /// o1 (o1+s1) /// ┄├0110101111┤┄ ← This check succeded! /// ┄├0001110101┤┄ /// o2 (o2+s2) /// ``` /// Can the second check ever succeed? No! Since the first check /// succeeded we know for certain that the first three bits the second /// check would match against are going to be `111`, so they surely won't /// match with `000`. /// /// 2. We know the first action succeeded, and the second action is fully /// contained inside it: /// ```txt /// o1 (o1+s1) /// ┄├011010000001111┤┄ ← This check succeded! /// ┄├0100000┤┄ /// o2 (o2+s2) /// ``` /// In that case we know for certain that the second check will succeed /// as well (and so can be skipped) if the overlapping bits being checked /// are exactly the same. /// /// 3. We know that the first action failed, and the second one is /// fully enclosing it: /// ```txt /// o1 (o1+s1) /// ┄├010┤┄ ← This check failed! /// ┄├01000001010000001111┤┄ /// o2 (o2+s2) /// ``` /// Can the second check ever succeed? No! Since the first check failed we /// know for certain that the first bits are not `010`, so there's no /// chance for the second match to succeed as it requires those three bits /// to be `010` as well. /// /// /// ## What information is returned by this function /// /// If the two actions are not interfering with each other this will return /// `None`. Otherwise, it will return all the information needed by the /// three examples above (check usages of this function to see how this is /// used, hopefully it should be pretty straightforward!): /// - whether the bits in the overlapping section are the same /// - whether the first action fully encloses the second one /// - whether the second action fully encloses the first one /// fn interfering_bits(&self, other: &Self) -> Option { // After reading the doc comment this should be pretty easy to follow: // The first requirement for interference is: `o1 <= o2` let offset_one = self.read_action.from.constant_bits()?.to_usize()?; let offset_other = other.read_action.from.constant_bits()?.to_usize()?; if offset_one > offset_other { return None; }; // The second requirement is that: `o1 + s1 > o2` let size_one = self.read_action.size.constant_bits()?.to_usize()?; let size_other = other.read_action.size.constant_bits()?.to_usize()?; if offset_one + size_one <= offset_other { return None; }; // At this point we know that both are interfering, so we compare the // overlapping slice of bits they're matching against. // A little implementation note: we're storing the matched _bytes_, not // bits, so we will be using the `view_bits` function to perform a // comparison of slices of bits. let bits_one = self.value.constant_bits()?; let bits_other = other.value.constant_bits()?; let end = (offset_other + size_other).min(offset_one + size_one); let range_one = offset_one..=(offset_one + size_one); let range_other = offset_other..=(offset_other + size_other); Some(Interference { interfering_bits_are_equal: bits_one[offset_other - offset_one..end - offset_one] == bits_other[0..end - offset_other], first_encloses_second: range_contains(&range_one, &range_other), second_encloses_first: range_contains(&range_other, &range_one), }) } } /// Returns true if the first range fully contains the other one. fn range_contains( one: &std::ops::RangeInclusive, other: &std::ops::RangeInclusive, ) -> bool { one.contains(other.start()) && one.contains(other.end()) } /// A value that can be matched in a bit array pattern's segment. We do not use /// a `Pattern` directly since the allowed values are actually a subset of all /// the possible patterns: it can only contain literal floats, ints, strings, /// a variable name, and a discard. /// #[derive(Clone, Eq, PartialEq, Debug, serde::Serialize, serde::Deserialize)] pub enum BitArrayMatchedValue { LiteralFloat(EcoString), LiteralInt { value: BigInt, /// This is carried around for better error reporting in case a segment /// is deemed unreachable: it is the location this literal value comes /// from in the whole pattern. location: SrcSpan, /// The bits representing the given literal int, with the correct /// signed- and endianness as specified in the bit array segment. bits: Result, IntToBitsError>, }, LiteralString { value: EcoString, encoding: StringEncoding, /// The bytes representing the given literal string, with the correct /// encoding and endianness specified in the bit array segment. bytes: Vec, }, Variable(EcoString), Discard(EcoString), Assign { name: EcoString, value: Box, }, } impl BitArrayMatchedValue { /// This is an arbitrary limit beyond which interference _may_ no longer be done. /// This is necessary because people may write very silly segments like /// `<<0:9_007_199_254_740_992>>`, which would allocate around a wee petabyte of memory. /// Literal strings are already in memory, and thus ignore this limit. const MAX_BITS_INTERFERENCE: u32 = u16::MAX as u32; pub(crate) fn is_literal(&self) -> bool { match self { BitArrayMatchedValue::LiteralFloat(_) | BitArrayMatchedValue::LiteralInt { .. } | BitArrayMatchedValue::LiteralString { .. } => true, BitArrayMatchedValue::Variable(..) | BitArrayMatchedValue::Discard(..) => false, BitArrayMatchedValue::Assign { value, .. } => value.is_literal(), } } /// If the matched value is a literal value for which we implement /// interference pruning, this returns the bits representing it to be used /// for interference. /// fn constant_bits(&self) -> Option<&BitSlice> { match self { BitArrayMatchedValue::LiteralString { bytes, .. } => Some(bytes.view_bits::()), BitArrayMatchedValue::Assign { value, .. } => value.constant_bits(), BitArrayMatchedValue::LiteralInt { bits, .. } => bits.as_deref().ok(), // TODO: We could also implement the interfering optimisation for // literal floats as well, but the usefulness is questionable BitArrayMatchedValue::LiteralFloat(_) | BitArrayMatchedValue::Variable(_) | BitArrayMatchedValue::Discard(_) => None, } } /// If we can statically tell the segment will never match, this will return /// the reason why it can't match. Otherwise it returns `None`. /// fn is_impossible_segment( &self, read_action: &ReadAction, ) -> Option { match self { BitArrayMatchedValue::Assign { value, .. } => value.is_impossible_segment(read_action), BitArrayMatchedValue::LiteralInt { value, location, bits, } => match bits { Err(IntToBitsError::Unrepresentable { size }) => { Some(ImpossibleBitArraySegmentPattern::UnrepresentableInteger { value: value.clone(), size: *size, location: *location, signed: read_action.signed, }) } _ => None, }, BitArrayMatchedValue::LiteralFloat(_) | BitArrayMatchedValue::LiteralString { .. } | BitArrayMatchedValue::Variable(_) | BitArrayMatchedValue::Discard(_) => None, } } /// Returns true if the two `MatchedValue`s are matching for the exact same /// thing. Note how this doesn't automatically mean they will be matching /// for the exact same thing in a whole bit array pattern as they could be /// matching at different positions! /// /// ```gleam /// <<1, _>> /// <<_, 1>> /// // Both are matching on the same value but at different positions! /// ``` /// fn checking_same_value(&self, other: &Self) -> bool { match (self, other) { // Ints need special care: they're carrying around information about // where they come from. But as for equality is concerned for the // pattern match compilation, they are considered equal as long as // the two values are equal, no matter where the values come from. (Self::LiteralInt { value: one, .. }, Self::LiteralInt { value: other, .. }) => { one == other } (Self::LiteralInt { .. }, _) => false, // All the other cases follow the standard equality implementation. (Self::LiteralFloat(one), Self::LiteralFloat(other)) => one == other, (Self::LiteralFloat(_), _) => false, // Two literal string matches are the same if they match for the // same bytes. No matter the original starting string and encoding. (Self::LiteralString { bytes: one, .. }, Self::LiteralString { bytes: other, .. }) => { one == other } (Self::LiteralString { .. }, _) => false, (Self::Variable(one), Self::Variable(other)) => one == other, (Self::Variable(_), _) => false, (Self::Discard(_), Self::Discard(_)) => true, (Self::Discard(_), _) => false, ( Self::Assign { name: _, value: one, }, Self::Assign { name: _, value: other, }, ) => one.checking_same_value(other), (Self::Assign { .. }, _) => false, } } } #[derive(Clone, Copy, Eq, PartialEq, Debug, serde::Serialize, serde::Deserialize)] pub enum StringEncoding { Utf8, Utf16, Utf32, } impl BitArrayMatchedValue { pub fn is_discard(&self) -> bool { match self { BitArrayMatchedValue::Discard(_) => true, BitArrayMatchedValue::LiteralFloat(_) | BitArrayMatchedValue::LiteralInt { .. } | BitArrayMatchedValue::LiteralString { .. } | BitArrayMatchedValue::Variable(_) | BitArrayMatchedValue::Assign { .. } => false, } } } impl BitArrayTest { /// Wether two bit array tests are equivalent, that is they are checking /// for the same thing. /// fn equivalent_to(&self, other: &Self) -> bool { match (self, other) { (BitArrayTest::Size(one), BitArrayTest::Size(other)) => one == other, (BitArrayTest::Size(_), _) => false, ( BitArrayTest::Match(MatchTest { value, read_action }), BitArrayTest::Match(MatchTest { value: other_value, read_action: other_read_action, }), ) => value.checking_same_value(other_value) && read_action == other_read_action, (BitArrayTest::Match(_), _) => false, ( BitArrayTest::CatchAllIsBytes { size_so_far: one }, BitArrayTest::CatchAllIsBytes { size_so_far: other }, ) => one == other, (BitArrayTest::CatchAllIsBytes { .. }, ..) => false, ( BitArrayTest::ReadSizeIsNotNegative { size: one }, BitArrayTest::ReadSizeIsNotNegative { size: other }, ) => one == other, (BitArrayTest::ReadSizeIsNotNegative { .. }, _) => false, ( BitArrayTest::SegmentIsFiniteFloat { read_action: one }, BitArrayTest::SegmentIsFiniteFloat { read_action: other }, ) => one == other, (BitArrayTest::SegmentIsFiniteFloat { .. }, _) => false, } } /// Tells us if this test is guaranteed to succeed given another test that /// we know has already succeeded. /// /// For example, "size >= 5" is certainly going to succeed if "size >= 6" /// has already succeeded. /// #[must_use] fn succeeds_if_succeeding(&self, succeeding: &BitArrayTest) -> Confidence { match (succeeding, self) { (one, other) if one.equivalent_to(other) => Confidence::Certain, (BitArrayTest::Size(succeeding), BitArrayTest::Size(test)) => { test.succeeds_if_succeeding(succeeding) } (BitArrayTest::Match(succeeding), BitArrayTest::Match(test)) => { // This is example 2. in the doc comment of `interfering_bits`: // if the bits are the same and the second one is fully enclosed // in the first one, then the second one will succeed as well! if let Some(Interference { interfering_bits_are_equal: true, first_encloses_second: true, .. }) = succeeding.interfering_bits(test) { Confidence::Certain } else { Confidence::Uncertain } } // The tests are not comparable, we can't deduce any new information. _ => Confidence::Uncertain, } } /// Tells us if this test is guaranteed to fail given another test that /// we know has already failed. /// /// For example, "size >= 5" is certainly going to fail if "size >= 4" /// has already failed. /// #[must_use] fn fails_if_failing(&self, failing: &BitArrayTest) -> Confidence { match (failing, self) { (one, other) if one.equivalent_to(other) => Confidence::Certain, (BitArrayTest::Size(failing), BitArrayTest::Size(test)) => { test.fails_if_failing(failing) } (BitArrayTest::Match(succeeding), BitArrayTest::Match(test)) => { // This is example 3. in the doc comment of `interfering_bits`: // if the bits are the same and the second one is fully // enclosing the first one, then the second one will fail as // well! if let Some(Interference { interfering_bits_are_equal: true, second_encloses_first: true, .. }) = succeeding.interfering_bits(test) { Confidence::Certain } else { Confidence::Uncertain } } // The tests are not comparable, we can't deduce any new information. _ => Confidence::Uncertain, } } /// Tells us if this test is guaranteed to fail given another test that /// we know has already succeeded. /// /// For example, "size = 1" is certainly going to fail if "size = 2" has already /// succeeded. /// #[must_use] fn fails_if_succeeding(&self, succeeding: &BitArrayTest) -> Confidence { match (succeeding, self) { // This is an implementation of Table 6 (a) of the bit array pattern // matching paper linked at the top of this module's documentation! (BitArrayTest::Size(succeeding), BitArrayTest::Size(test)) => { test.fails_if_succeeding(succeeding) } (BitArrayTest::Match(succeeding), BitArrayTest::Match(test)) => { // This is example 1. in the doc comment of `interfering_bits`: // if the bits are the different and the first one is succeeding // then we know the second one will fail. if let Some(Interference { interfering_bits_are_equal: false, .. }) = succeeding.interfering_bits(test) { Confidence::Certain } else { Confidence::Uncertain } } // The tests are not comparable, we can't deduce any new information. _ => Confidence::Uncertain, } } } /// When performing a size test it could require that a size is exactly some /// specific number of bits (for example if we're matching the last segment /// of a bit array) or that it has at least some number of bits. For example: /// /// ```txt /// <<0:size(12), 1:size(8)>> /// ─┬──────── ─┬─────── /// │ ╰── For this segment to successfully match the bit array must /// │ have exactly 20 bits: because it will need to read 8 bits /// │ from bit 13 where the previous one ends: `Size(=, 20)` /// │ /// ╰── For this segment to successfully match, the bit array must have at /// least 12 bits: `Size(>, 12)` /// ``` /// #[derive(Clone, Eq, PartialEq, Debug, Copy, serde::Serialize, serde::Deserialize)] pub enum SizeOperator { GreaterEqual, Equal, } /// Represents the action of reading a certain number of bits from a bit array /// at a given position, returning a value with the given type. /// /// Notice how the starting position and number of bits to read might not be /// known at compile time but also include variables! For example here: /// /// ```txt // <> /// ─┬─ ─┬─────────────── /// │ ╰── Here payload has a variable size, given by the `len` variable /// │ /// ╰── While this segment has a constant size of 8 bits /// ``` /// #[derive(Clone, Eq, PartialEq, Debug, Hash, serde::Serialize, serde::Deserialize)] pub struct ReadAction { /// The offset to start reading the bits from. /// pub from: Offset, /// Number of _bits_ to read. /// pub size: ReadSize, /// The type of the read value. /// pub type_: ReadType, /// Endianness of the read value. /// pub endianness: Endianness, /// Signedness of the read value. /// pub signed: bool, } /// Only a subset of all the possible Gleam types can be used for a pattern /// segment. We enumerate those out explicitly here. /// #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] pub enum ReadType { Float, Int, String, BitArray, UtfCodepoint, } impl ReadType { pub(crate) fn is_int(&self) -> bool { match self { ReadType::Int => true, ReadType::Float | ReadType::String | ReadType::BitArray | ReadType::UtfCodepoint => { false } } } pub(crate) fn is_float(&self) -> bool { match self { ReadType::Float => true, ReadType::Int | ReadType::String | ReadType::BitArray | ReadType::UtfCodepoint => false, } } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Confidence { Certain, Uncertain, } impl Confidence { fn is_certain(&self) -> bool { match self { Confidence::Certain => true, Confidence::Uncertain => false, } } fn or_else(&self, fun: impl Fn() -> Confidence) -> Confidence { match self { Confidence::Certain => Confidence::Certain, Confidence::Uncertain => fun(), } } } /// An offset, in bits, into a bit array. An offset contains three parts. For /// example, in the following pattern: /// ```txt /// <> /// ``` /// /// The start of the `payload` segment is the sum of the length of `a` (which we /// know is 1 byte), plus the size of `b`, which is the value of `a`, plus the /// size of `c`, which is 1 less than the value of `b`. /// /// Here, `constant` would be `8`; `variables` would map `a` to `1`, because it /// is referenced once, in a segment with unit `1`; `calculations` would contain /// `b - 1`. /// #[derive(Clone, Eq, PartialEq, Debug, Hash, serde::Serialize, serde::Deserialize)] pub struct Offset { pub constant: BigInt, pub variables: im::HashMap, pub calculations: im::Vector, } #[derive(Clone, Eq, PartialEq, Debug, Hash, serde::Serialize, serde::Deserialize)] pub struct OffsetCalculation { pub left: Offset, pub right: Offset, pub operator: IntOperator, } impl Offset { pub fn constant(value: impl Into) -> Self { Self { constant: value.into(), variables: im::HashMap::new(), calculations: im::Vector::new(), } } fn from_size(size: &ReadSize) -> Self { Self::constant(0).add_size(size) } fn add_size(mut self, size: &ReadSize) -> Offset { match size { ReadSize::ConstantBits(value) => Self { constant: self.constant + value, variables: self.variables, calculations: self.calculations, }, ReadSize::VariableBits { variable, unit } => Self { constant: self.constant, variables: self.variables.alter( |value| match value { Some(value) => Some(value + (*unit as usize)), None => Some(*unit as usize), }, variable.as_ref().clone(), ), calculations: self.calculations, }, ReadSize::RemainingBits | ReadSize::RemainingBytes => self, ReadSize::BinaryOperator { left, right, operator, } => match operator { IntOperator::Add => self.add_size(left).add_size(right), IntOperator::Subtract | IntOperator::Multiply | IntOperator::Divide | IntOperator::Remainder => { self.calculations.push_back(OffsetCalculation { left: Self::from_size(left), right: Self::from_size(right), operator: *operator, }); self } }, } } pub fn add_constant(&self, constant: impl Into) -> Offset { Offset { constant: self.constant.clone() + constant.into(), variables: self.variables.clone(), calculations: self.calculations.clone(), } } /// Returns `true` if we can tell for certain this offset is greater than /// another one. /// fn greater(&self, other: &Offset) -> Confidence { // We can't easily tell if one calculation is greater than another, so // we can only be certain about one being greater if there are no calculations if self.constant > other.constant && self.calculations.is_empty() && other.calculations.is_empty() && superset(&self.variables, &other.variables) { Confidence::Certain } else { Confidence::Uncertain } } /// Returns `true` if we can tell for certain this offset is greater or equal /// to another one. /// fn greater_equal(&self, other: &Offset) -> Confidence { // Like `greater`, we can only be certain if there are no calculations if self == other || (self.constant >= other.constant && self.calculations.is_empty() && other.calculations.is_empty() && superset(&self.variables, &other.variables)) { Confidence::Certain } else { Confidence::Uncertain } } /// Returns `true` if we can tell for certain this offset is different from /// another one. /// fn different(&self, other: &Offset) -> Confidence { self.greater(other).or_else(|| other.greater(self)) } pub(crate) fn is_zero(&self) -> bool { self.constant == BigInt::ZERO && self.variables.is_empty() && self.calculations.is_empty() } /// If this offset has a constant size, returns its size in bits. /// pub(crate) fn constant_bits(&self) -> Option { if self.variables.is_empty() && self.calculations.is_empty() { Some(self.constant.clone()) } else { None } } /// If this offset has a constant size that's a whole number of bytes, /// returns its size in bytes. /// pub(crate) fn constant_bytes(&self) -> Option { let bits = self.constant_bits()?; if bits.clone() % 8 == BigInt::ZERO { Some(bits / 8) } else { None } } fn referenced_segment_patterns(&self) -> Vec<(&EcoString, &ReadAction)> { let mut references = Vec::new(); for variable_usage in self.variables.keys() { match variable_usage { VariableUsage::PatternSegment(segment_name, read_action) => { references.push((segment_name, read_action)); } VariableUsage::OutsideVariable(..) => {} } } for calculation in self.calculations.iter() { references.extend(calculation.left.referenced_segment_patterns()); references.extend(calculation.right.referenced_segment_patterns()); } references } } /// The number of bits to read in a read action when reading a segment. /// #[derive(Clone, Eq, PartialEq, Debug, Hash, serde::Serialize, serde::Deserialize)] pub enum ReadSize { /// Read a constant, compile-time-known number of bits. /// /// ```txt /// <> /// ─┬───────── /// ╰─ Here we know that we have to read exactly 8 bits /// ``` /// ConstantBits(BigInt), /// Read a variable number of bits. /// /// ```txt /// <> /// ─┬─────────────── /// ╰─ Here we will know how many bits to read only at runtime since /// the length is a variable /// ``` /// VariableBits { variable: Box, unit: u8, }, /// A maths expression calculating the read size from one or more variables. /// /// ```txt /// <> /// ─┬─────────────────── /// ╰─ Here we have to calculate the length by performing the /// multiplication at runtime /// ``` BinaryOperator { left: Box, right: Box, operator: IntOperator, }, /// Read all the remaining bits in the bit array when using a catch all /// pattern. /// /// ```txt /// <<_, rest:bits>> /// ─┬─────── /// ╰─ We take all the remaining bits from the bit array /// ``` /// RemainingBits, RemainingBytes, } impl ReadSize { /// If this is a constant number of bits returns it wrapped in `Some`. pub(crate) fn constant_bits(&self) -> Option { match self { ReadSize::ConstantBits(value) => Some(value.clone()), ReadSize::VariableBits { .. } | ReadSize::BinaryOperator { .. } | ReadSize::RemainingBits | ReadSize::RemainingBytes => None, } } /// If this is a constant number of bytes returns it wrapped in `Some`. pub(crate) fn constant_bytes(&self) -> Option { let bits = self.constant_bits()?; if bits.clone() % 8 == BigInt::ZERO { Some(bits / 8) } else { None } } fn referenced_segment_patterns<'a>( &'a self, references: &mut Vec<(&'a EcoString, &'a ReadAction)>, ) { match self { ReadSize::VariableBits { variable, unit: _ } => match variable.as_ref() { VariableUsage::PatternSegment(segment_value, read_action) => { references.push((segment_value, read_action)); } VariableUsage::OutsideVariable(..) => (), }, ReadSize::BinaryOperator { left, right, .. } => { left.referenced_segment_patterns(references); right.referenced_segment_patterns(references); } ReadSize::ConstantBits(..) | ReadSize::RemainingBits | ReadSize::RemainingBytes => (), }; } fn can_be_negative(&self) -> bool { match self { ReadSize::ConstantBits(value) => *value < BigInt::ZERO, ReadSize::VariableBits { variable, .. } => match variable.as_ref() { VariableUsage::PatternSegment(_, read_action) => read_action.signed, VariableUsage::OutsideVariable(_) => true, }, ReadSize::BinaryOperator { left, right, operator, } => { *operator == IntOperator::Subtract || left.can_be_negative() || right.can_be_negative() } ReadSize::RemainingBits | ReadSize::RemainingBytes => false, } } } #[derive(Clone, Eq, PartialEq, Debug, Hash, serde::Serialize, serde::Deserialize)] pub enum VariableUsage { /// A bit array named segment that was brought into scope in the bit array /// pattern itself and might be referenced by later segments. PatternSegment(EcoString, ReadAction), /// A variable defined somewhere else OutsideVariable(EcoString), } impl VariableUsage { pub fn name(&self) -> &EcoString { match self { VariableUsage::PatternSegment(name, _) | VariableUsage::OutsideVariable(name) => name, } } } /// This is the decision tree that a pattern matching expression gets turned /// into: it's a tree-like structure where each path to a root node contains a /// series of checks to perform at runtime to understand if a value matches with /// a given pattern. /// #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum Decision { /// This is the final node of the tree, once we get to this one we know we /// have a body to run because a given pattern matched. /// Run { body: Body }, /// We have to make this decision when we run into a branch that also has a /// guard: if it is true we can finally run the body of the branch, stored in /// `if_true`. /// If it is false we might still have to take other decisions and so we might /// have another `DecisionTree` to traverse, stored in `if_false`. /// Guard { guard: usize, if_true: Body, if_false: Box, }, /// When reaching this node we'll have to see if any of the possible checks /// in `choices` will succeed on `var`. If it does, we know that's the path /// we have to go down to. If none of the checks matches, then we'll have to /// go down the `fallback` branch. /// /// The type system guarantees that all switches will always have at least /// one last choice that acts as a fallback to pick if none of the others /// matches; no matter the value we're matching on. /// /// In case we're dealing with exhaustive cases (for example on lists/custom /// types) we also keep track of the final check associated with the final /// branch: keep in mind, we don't actually have to run that check because /// we know that the type system guarantees it will always match; but it /// can hold useful informations for type generation so we keep it around. /// You can read the doc for `FallbackCheck` to find out a more in depth /// explanation and some examples! /// Switch { var: Variable, choices: Vec<(RuntimeCheck, Decision)>, fallback: Box, fallback_check: Box, }, /// This is a special node: it represents a missing pattern. If a tree /// contains such a node, then we know that the patterns it was compiled /// from are not exhaustive and the path leading to this node will describe /// what kind of pattern doesn't match! /// Fail, } /// When we have a swith in the decision tree we know there's always going to be /// at least one choice that comes last and we know is going to match no matter /// what. This might fall under three different categories. /// #[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)] pub enum FallbackCheck { /// This corresponds to the catch all added at the end of a case expression /// matching on an infinite type. /// /// ```txt /// case todo { /// 1 -> todo /// _ -> todo /// ─┬─────── // ╰── when matching on an infinite type there's going to be a catch all /// } /// InfiniteCatchAll, /// This happens when we're matching on a variant whose checks are all /// explicitly written down: /// /// ```txt /// case todo { /// Ok(_) -> todo /// Error(Nil) -> todo /// ─┬──────────────── // ╰── when matching on a custom type (or list!) we'll have to write all // variants down, so there's always going to be a last one. /// } /// ``` /// /// The type system will make sure that this last check will always match, /// no matter what, if none of the other checks matches. We still keep the /// corresponding runtime check around because it's useful for code generation! /// RuntimeCheck { check: RuntimeCheck }, /// This is a special case for a catch all! It happens when we're matching /// on a variant and use a catch all pattern: /// /// ```txt /// case todo { /// Ok(_) -> todo /// _ -> todo /// ─┬─────── /// ╰── here we know we're just skipping over the `Error` variant with /// this catch all. /// } /// ``` /// /// We know exactly which variants we're skipping, so we keep track of /// those skipped checks (they will end up being useful for reporting /// missing patterns!) /// CatchAll { ignored_checks: Vec }, } impl Decision { pub fn run(body: Body) -> Self { Decision::Run { body } } pub fn guard(guard: usize, if_true: Body, if_false: Self) -> Self { Decision::Guard { guard, if_true, if_false: Box::new(if_false), } } } /// The `case` compiler itself (shocking, I know). /// #[derive(Debug)] struct Compiler<'a> { environment: &'a Environment<'a>, patterns: Arena, variable_id: usize, diagnostics: Diagnostics, } /// The result of compiling a pattern match expression. /// #[derive(Debug)] pub struct CompileCaseResult { pub compiled_case: CompiledCase, pub diagnostics: Diagnostics, } impl CompileCaseResult { pub fn is_reachable(&self, clause: usize, pattern_index: usize) -> Reachability { if self .diagnostics .reachable .contains(&(clause, pattern_index)) { Reachability::Reachable } else if self .diagnostics .match_impossible_variants .contains(&(clause, pattern_index)) { Reachability::Unreachable(UnreachablePatternReason::ImpossibleVariant) } else if let Some(segments) = self .diagnostics .match_impossible_segments .get(&(clause, pattern_index)) { Reachability::Unreachable(UnreachablePatternReason::ImpossibleSegments( segments.clone(), )) } else { Reachability::Unreachable(UnreachablePatternReason::DuplicatePattern) } } pub fn missing_patterns(&self, environment: &Environment<'_>) -> Vec { missing_patterns::missing_patterns(self, environment) } } #[derive(Debug, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)] pub struct CompiledCase { pub tree: Decision, pub subject_variables: Vec, } impl CompiledCase { pub fn failure() -> Self { Self { tree: Decision::Fail, subject_variables: vec![], } } /// The decision tree for simple variable assignment, such as in the following /// assignment: /// ```gleam /// let x = 10 /// ``` pub fn simple_variable_assignment(name: EcoString, type_: Arc) -> Self { let variable = Variable::new(0, type_); let mut body = Body::new(0); body.assign(name, variable.clone()); Self { tree: Decision::Run { body }, subject_variables: vec![variable], } } } /// Whether a pattern is reachable, or why it is unreachable. /// #[derive(Debug, Clone, PartialEq, Eq)] pub enum Reachability { Reachable, Unreachable(UnreachablePatternReason), } /// A type for storing diagnostics produced by the decision tree compiler. /// #[derive(Debug)] pub struct Diagnostics { /// A flag indicating the match is missing one or more pattern. pub missing: bool, /// The patterns that are reachable. If a pattern isn't in this list it /// means it is redundant. /// Each entry is a pair of indices: The index of the clause the pattern /// belongs to, followed by the index of the pattern itself within the /// clause. For example, in this case expression: /// ```gleam /// case x { /// 1 -> todo /// 2 | 3 -> todo /// _ -> todo /// } /// ``` /// /// The `3` pattern would be `(1, 1)`, because it is the second pattern of /// the second clause, and both are zero-indexed. /// pub reachable: HashSet<(usize, usize)>, /// Patterns which match on variants of a type which the compiler /// can tell will never be present, due to variant inference. /// /// See `reachable` for an explanation of its structure. /// pub match_impossible_variants: HashSet<(usize, usize)>, /// Patterns that are matching on bit array segments the compiler can tell /// for sure will never match. /// pub match_impossible_segments: HashMap<(usize, usize), Vec>, } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum ImpossibleBitArraySegmentPattern { UnrepresentableInteger { value: BigInt, size: u32, location: SrcSpan, signed: bool, }, } impl ImpossibleBitArraySegmentPattern { pub fn location(&self) -> SrcSpan { match self { ImpossibleBitArraySegmentPattern::UnrepresentableInteger { location, .. } => *location, } } } impl<'a> Compiler<'a> { fn new(environment: &'a Environment<'a>, variable_id: usize, patterns: Arena) -> Self { Self { environment, patterns, variable_id, diagnostics: Diagnostics { missing: false, reachable: HashSet::new(), match_impossible_variants: HashSet::new(), match_impossible_segments: HashMap::new(), }, } } fn pattern(&mut self, pattern_id: Id) -> &mut Pattern { self.patterns .get_mut(pattern_id) .expect("unknown pattern id") } /// Returns a new fresh variable (i.e. guaranteed to have a unique `variable_id`) /// with the given type. /// fn fresh_variable(&mut self, type_: Arc) -> Variable { let var = Variable::new(self.variable_id, type_); self.variable_id += 1; var } fn mark_as_reached(&mut self, branch: &Branch) { let _ = self .diagnostics .reachable .insert((branch.clause_index, branch.alternative_index)); } fn mark_as_matching_impossible_variant(&mut self, branch: &Branch) { let _ = self .diagnostics .reachable .remove(&(branch.clause_index, branch.alternative_index)); let _ = self .diagnostics .match_impossible_variants .insert((branch.clause_index, branch.alternative_index)); } fn mark_as_matching_impossible_segment( &mut self, branch: &Branch, segments: Vec, ) { let _ = self .diagnostics .reachable .remove(&(branch.clause_index, branch.alternative_index)); let _ = self .diagnostics .match_impossible_segments .insert((branch.clause_index, branch.alternative_index), segments); } fn compile(&mut self, mut branches: VecDeque) -> Decision { branches .iter_mut() .for_each(|branch| branch.move_unconditional_patterns(self)); let Some(first_branch) = branches.front() else { // If there's no branches, that means we have a pattern that is not // exhaustive as there's nothing that could match! self.diagnostics.missing = true; return Decision::Fail; }; self.mark_as_reached(first_branch); // In order to compile the branches, we need to pick a `PatternCheck` from // the first branch, and use the variable it's pattern matching on to create // a new node in the tree. All the branches will be split into different // possible paths of this tree. match find_pivot_check(first_branch, &branches, self.environment) { Some(PatternCheck { var, .. }) => self.split_and_compile_with_pivot_var(var, branches), // If the branch has no remaining checks, it means that we've moved all // its variable patterns as assignments into the body and there's no // additional checks remaining. So the only thing left that could result // in the match failing is the additional guard. None => match first_branch.guard { // If there's no guard we're in the following situation: // `∅ -> body`. It means that this branch will always match no // matter what, all the remaining branches are just discarded and // we can produce a terminating node to run the body // unconditionally. None => Decision::run(first_branch.body.clone()), // If we have a guard we're in this scenario: // `∅ if condition -> body`. We can produce a `Guard` node: // if the condition evaluates to `True` we can run its body. // Otherwise, we'll have to keep looking at the remaining branches // to know what to do if this branch doesn't match. Some(guard) => { let if_true = first_branch.body.clone(); // All the remaining branches will be compiled and end up // in the path of the tree to choose if the guard is false. let _ = branches.pop_front(); let if_false = self.compile(branches); Decision::guard(guard, if_true, if_false) } }, } } fn split_and_compile_with_pivot_var( &mut self, pivot_var: Variable, branches: VecDeque, ) -> Decision { // We first try and come up with a list of all the runtime checks we might // have to perform on the variable at runtime. In most cases it's a limited // number of checks that we know before hand (for example, when matching // on a list, or on a custom type). let branch_mode = pivot_var.branch_mode(self.environment); let known_checks = match &branch_mode { // If the type being matched on is infinite there's no known runtime // check we could come up with in advance. So we'll build them as // we go. BranchMode::Infinite => vec![], // If the type is a tuple there's only one runtime check we could // perform that actually makes sense. BranchMode::Tuple { elements } => vec![self.is_tuple_check(elements)], // If the type being matched on is a list we know the resulting // decision tree node is only ever going to have two different paths: // one to follow if the list is empty, and one to follow if it's not. BranchMode::List { inner_type } => { vec![ RuntimeCheck::EmptyList, self.is_list_check(inner_type.clone()), ] } // If we know that a specific variant is inferred we will require just // that one and not all the other ones we know for sure are not going to // be there. BranchMode::NamedType { constructors, inferred_variant: Some(index), } => { let constructor = constructors .get(*index) .expect("wrong index for inferred variant"); vec![self.is_variant_check(*index, constructor)] } // Otherwise we know we'll need a check for each of its possible variants. BranchMode::NamedType { constructors, .. } => constructors .iter() .enumerate() .map(|(index, constructor)| self.is_variant_check(index, constructor)) .collect_vec(), }; // We then split all the branches using these checks and compile the // choices they've been split up into. let mut splitter = BranchSplitter::from_checks(known_checks); self.split_branches(&mut splitter, branches, pivot_var.clone(), &branch_mode); if branch_mode.is_infinite() { // If the branching is infinite, that means we always need to also have // a fallback (imagine you're pattern matching on an `Int` and put no // `_` at the end of the case expression). self.splitter_to_switch(pivot_var, splitter) } else if splitter.choices.is_empty() { // If the branching doesn't need any fallback but we ended up with no // checks it means we're trying to pattern match on an external type // but haven't provided a catch-all case. // That's never going to match, so we produce a failure node. self.diagnostics.missing = true; Decision::Fail } else { // Otherwise we know that one of the possible runtime checks is always // going to succeed and there's no need to also have a fallback branch. self.splitter_to_exhaustive_switch(pivot_var, splitter) } } fn split_branches( &mut self, splitter: &mut BranchSplitter, branches: VecDeque, pivot_var: Variable, branch_mode: &BranchMode, ) { for mut branch in branches { let Some(pattern_check) = branch.pop_check_on_var(&pivot_var) else { // If the branch doesn't perform any check on the pivot variable, it means // it could still match no matter what shape `pivot_var` has. So we must // add it as a fallback branch, that is a branch that is still relevant // for all possible paths in the decision tree. splitter.add_fallback_branch(branch); continue; }; let checked_pattern = self.pattern(pattern_check.pattern); if checked_pattern.is_matching_on_unreachable_variant(branch_mode) { self.mark_as_matching_impossible_variant(&branch); continue; } else if let Some(unreachable_segments) = checked_pattern.is_matching_on_impossible_segment() { self.mark_as_matching_impossible_segment(&branch, unreachable_segments); continue; } splitter.add_checked_branch(pattern_check, branch, branch_mode, self); } } /// Compiles the branches in a splitter down to a switch matching on a type /// with infinite variants (like ints, floats and strings). With a fallaback /// branch. /// fn splitter_to_switch(&mut self, var: Variable, splitter: BranchSplitter) -> Decision { let choices = self.compile_all_choices(splitter.choices); let last_choice = self.compile(splitter.fallback); Decision::Switch { var, choices, fallback: Box::new(last_choice), fallback_check: Box::new(FallbackCheck::InfiniteCatchAll), } } /// Compiles the branches in a splitter down to a switch matching on a type /// with a finite known number of variants. /// fn splitter_to_exhaustive_switch( &mut self, var: Variable, splitter: BranchSplitter, ) -> Decision { let mut choices = splitter.choices; let (choices, fallback, fallback_check) = if choices.iter().any(|(check, _)| check.is_ignored()) { // If there's any check that is ignored and not explicitly being // matched on then we want to ignore all of those and clump them // into a single "fallback" branch to avoid bloating the generated // tree. let mut ignored_checks = vec![]; let mut remaining_choices = vec![]; for choice in choices.into_iter() { if choice.0.is_ignored() { ignored_checks.push(choice.0) } else { remaining_choices.push(choice) } } let fallback_check = FallbackCheck::CatchAll { ignored_checks }; (remaining_choices, splitter.fallback, fallback_check) } else { // Otherwise we just use the last check as the final one that // can be outright skipped. let (last_check, last_choice) = choices.pop().expect("at least one choice"); let fallback_check = FallbackCheck::RuntimeCheck { check: last_check }; (choices, last_choice, fallback_check) }; let choices = self.compile_all_choices(choices); let fallback = Box::new(self.compile(fallback)); Decision::Switch { var, choices, fallback, fallback_check: Box::new(fallback_check), } } fn compile_all_choices( &mut self, choices: Vec<(RuntimeCheck, VecDeque)>, ) -> Vec<(RuntimeCheck, Decision)> { choices .into_iter() .map(|(check, branches)| (check, self.compile(branches))) .collect_vec() } /// Turns a `RuntimeCheckKind` into a new `RuntimeCheck` by coming up with /// the needed new fresh variables. /// All the type information needed to create these variables is in the /// `branch_mode` arg. /// fn fresh_runtime_check( &mut self, kind: RuntimeCheckKind, branch_mode: &BranchMode, ) -> RuntimeCheck { match (kind, branch_mode) { (RuntimeCheckKind::Int { int_value }, _) => RuntimeCheck::Int { int_value: int_value.clone(), }, (RuntimeCheckKind::Float { float_value }, _) => RuntimeCheck::Float { float_value }, (RuntimeCheckKind::String { value }, _) => RuntimeCheck::String { value: value.clone(), }, (RuntimeCheckKind::StringPrefix { prefix }, _) => RuntimeCheck::StringPrefix { prefix: prefix.clone(), rest: self.fresh_variable(string()), }, (RuntimeCheckKind::Tuple { .. }, BranchMode::Tuple { elements }) => { self.is_tuple_check(elements) } (RuntimeCheckKind::Variant { index }, BranchMode::NamedType { constructors, .. }) => { self.is_variant_check( index, constructors.get(index).expect("unknown variant index"), ) } (RuntimeCheckKind::EmptyList, _) => RuntimeCheck::EmptyList, (RuntimeCheckKind::NonEmptyList, BranchMode::List { inner_type }) => { self.is_list_check(inner_type.clone()) } (_, _) => unreachable!("type checking should make this impossible"), } } /// Comes up with new pattern cecks that have to match in case a given /// runtime check succeeds for the given pattern. /// /// Let's make an example: when we have a pattern - say `a is Wibble(1, [])` - /// we come up with a runtime check to perform on it. For our example the /// runtime check is to make sure that `a` is indeed a `Wibble` variant. /// However, after successfully performing that check we're left with much to /// do! We know that `a` is `Wibble` but now we'll have to make sure that its /// inner arguments also match the given patterns. So the new additional checks /// we have to add are `a0 is 1, a1 is []` (where `a0` and `a1` are the fresh /// variable names we use to refer to the constructor's arguments). /// fn new_checks( &mut self, for_pattern: &Pattern, after_succeding_check: &RuntimeCheck, ) -> Vec { match (for_pattern, after_succeding_check) { // These patterns never result in adding new checks. After a runtime // check matches on them there's nothing else to discover. ( Pattern::Discard | Pattern::Assign { .. } | Pattern::Variable { .. } | Pattern::Int { .. } | Pattern::Float { .. } | Pattern::BitArray { .. } | Pattern::EmptyList, _, ) | (Pattern::String { .. }, RuntimeCheck::String { .. }) => vec![], // After making sure a value is not an empty list we'll have to perform // additional checks on its first item and on the tail. ( Pattern::NonEmptyList { first: first_pattern, rest: rest_pattern, }, RuntimeCheck::NonEmptyList { first: first_variable, rest: rest_variable, }, ) => vec![ first_variable.is(*first_pattern), rest_variable.is(*rest_pattern), ], // After making sure a value is a specific variant we'll have to check each // of its arguments respects the given patterns (as shown in the doc example for // this function!) ( Pattern::Variant { fields: patterns, .. }, RuntimeCheck::Variant { fields: variables, .. }, ) => (variables.iter().zip(patterns)) .map(|(field, pattern)| field.is(*pattern)) .collect_vec(), // Tuples are exactly the same as variants: after making sure we're dealing with // a tuple, we will have to check that each of its elements matches the given // pattern: `a is #(1, _)` will result in the following checks // `a0 is 1, a1 is _` (where `a0` and `a1` are fresh variable names we use to // refer to each of the tuple's elements). ( Pattern::Tuple { elements: patterns }, RuntimeCheck::Tuple { elements: variables, .. }, ) => (variables.iter().zip(patterns)) .map(|(element, pattern)| element.is(*pattern)) .collect_vec(), // Strings are quite fun: if we've checked at runtime a string starts with a given // prefix and we want to check that it's some overlapping literal value we'll still // have some amount of work to perform. // // Let's have a look at an example: the pattern we care about is `a is "wibble"` // and we've just successfully ran the runtime check for `a is "wib" <> rest`. // So we know the string already starts with `"wib"` what we have to check now // is that the remaining part `rest` is `"ble"`. (Pattern::String { value }, RuntimeCheck::StringPrefix { prefix, rest, .. }) => { let remaining = value.strip_prefix(prefix.as_str()).unwrap_or(value); vec![rest.is(self.string_pattern(remaining))] } // String prefixes are similar to strings, but a bit more involved. Let's say we're // checking the pattern: // // ```txt // "wibblest" <> rest1 // ─┬──────── // ╰── We will refer to this as `prefix1` // ``` // // And we know that the following overlapping runtime check has already succeeded: // // ```txt // "wibble" <> rest0 // ─┬────── // ╰── We will refer to this as `prefix0` // ``` // // We're lucky because we now know quite a bit about the shape of the string. Since // we know it already starts with `"wibble"` we can just check that the remaining // part after that starts with the missing part of the prefix: // `prefix0 is "st" <> rest1`. ( Pattern::StringPrefix { prefix: prefix1, prefix_name: _, rest: rest1, }, RuntimeCheck::StringPrefix { prefix: prefix0, rest: rest0, }, ) => { let remaining = prefix1.strip_prefix(prefix0.as_str()).unwrap_or(prefix1); // If the prefixes are exactly the same then the only remaining check // is for the two remaining bits to be the same. if remaining.is_empty() { vec![rest0.is(*rest1)] } else { vec![rest0.is(self.string_prefix_pattern(remaining, *rest1))] } } (_, _) => unreachable!("invalid pattern overlapping"), } } /// Builds an `IsVariant` runtime check, coming up with new fresh variable names /// for its arguments. /// fn is_variant_check( &mut self, index: usize, constructor: &TypeValueConstructor, ) -> RuntimeCheck { RuntimeCheck::Variant { index, match_: VariantMatch::NeverExplicitlyMatchedOn { name: constructor.name.clone(), }, labels: constructor .parameters .iter() .enumerate() .filter_map(|(i, parameter)| Some((i, parameter.label.clone()?))) .collect(), fields: constructor .parameters .iter() .map(|parameter| parameter.type_.clone()) .map(|type_| self.fresh_variable(type_)) .collect_vec(), } } /// Builds an `IsNonEmptyList` runtime check, coming up with fresh variable /// names for its arguments. /// fn is_list_check(&mut self, inner_type: Arc) -> RuntimeCheck { RuntimeCheck::NonEmptyList { first: self.fresh_variable(inner_type.clone()), rest: self.fresh_variable(Arc::new(Type::list(inner_type))), } } /// Builds an `IsTuple` runtime check, coming up with fresh variable /// names for its arguments. /// fn is_tuple_check(&mut self, elements: &[Arc]) -> RuntimeCheck { RuntimeCheck::Tuple { size: elements.len(), elements: elements .iter() .map(|type_| self.fresh_variable(type_.clone())) .collect_vec(), } } /// Allocates a new `StringPattern` with the given value. /// fn string_pattern(&mut self, value: &str) -> Id { self.patterns.alloc(Pattern::String { value: EcoString::from(value), }) } fn bit_array_pattern(&mut self, tests: VecDeque) -> Id { self.patterns.alloc(Pattern::BitArray { tests }) } /// Allocates a new `StringPrefix` pattern with the given prefix and pattern /// for the rest of the string. /// fn string_prefix_pattern(&mut self, prefix: &str, rest: Id) -> Id { self.patterns.alloc(Pattern::StringPrefix { prefix: EcoString::from(prefix), prefix_name: None, rest, }) } } /// Returns a pattern check from `first_branch` to be used as a pivot to split all /// the `branches`. /// fn find_pivot_check( first_branch: &Branch, branches: &VecDeque, env: &Environment<'_>, ) -> Option { // To try and minimise code duplication, we use the following heuristic: we // choose the check matching on the variable that is referenced the most // across all checks in all branches. let mut var_references = HashMap::new(); for branch in branches { for check in &branch.checks { let _ = var_references .entry(check.var.id) .and_modify(|references| *references += 1) .or_insert(0); } } // We want to picke the variable that has the smallest branching factor // possible, this tends to yield smaller, shallower decision trees. // So, for example, we will pick a list (branching factor of 2 `[]` and // `[_, ..]`) over an integer (infinitely many choices). // // In case two variables are tied we break the tie by choosing the subject // that appears in the most clauses (i.e. a test that will pay off for more // rows). // // Both parts mirror standard guidance for good match trees (small branching // factor; prioritize columns that are widely useful): // https://www.cs.tufts.edu/~nr/cs257/archive/luc-maranget/jun08.pdf first_branch .checks .iter() .min_by_key(|check| { let branching_factor = check.var.branch_mode(env).branching_factor(); let references = var_references.get(&check.var.id).cloned().unwrap_or(0); // Notice how we're using `-references`: we want to favour the one // that has the most references, so the one were `-references` is // the smallest. (branching_factor, -references) }) .cloned() } /// A handy data structure we use to split branches in different possible paths /// based on a check. /// struct BranchSplitter { pub choices: Vec<(RuntimeCheck, VecDeque)>, pub fallback: VecDeque, /// This is used to allow quickly looking up a choice in the `choices` /// vector, without loosing track of the checks' order. indices: HashMap, /// This is used to store the indices of just the prefix checks as they have /// different rules from all the other `RuntimeCheckKinds` whose indices are /// instead stored in the `indices` field. /// /// We discuss this in more detail in the `index_of_overlapping_runtime_check` /// function! prefix_indices: Trie, } impl BranchSplitter { /// Creates a new splitter with the given starting checks. /// fn from_checks(checks: Vec) -> Self { let mut choices = Vec::with_capacity(checks.len()); let mut indices = HashMap::new(); for (index, runtime_check) in checks.into_iter().enumerate() { let Some(kind) = runtime_check.kind() else { continue; }; let _ = indices.insert(kind, index); choices.push((runtime_check, VecDeque::new())); } Self { fallback: VecDeque::new(), choices, indices, prefix_indices: Trie::new(), } } /// Add a fallback branch: this is a branch that is relevant to all possible /// paths as it could still run, no matter the result of any of the `Check`s /// we've stored! /// fn add_fallback_branch(&mut self, branch: Branch) { self.choices .iter_mut() .for_each(|(_, branches)| branches.push_back(branch.clone())); self.fallback.push_back(branch); } /// Given a branch and the pattern its using to check on the pivot variable, /// adds it to the paths where it's relevant, that is where we know from /// previous checks that this pattern has a chance of matching. /// fn add_checked_branch( &mut self, pattern_check: PatternCheck, mut branch: Branch, branch_mode: &BranchMode, compiler: &mut Compiler<'_>, ) { let pattern = compiler.pattern(pattern_check.pattern).clone(); // Bit array patterns are split in a different way that requires special // handling. Instead of reasoning on overlapping checks what we do it we // always split the decision tree in two distinct paths based on one of // the bit array pattern's tests. if let Pattern::BitArray { tests } = pattern { self.add_checked_bit_array_branch(pattern_check, tests, branch, compiler); return; }; let kind = pattern .to_runtime_check_kind() .expect("no unconditional patterns left"); let indices_of_overlapping_checks = self.indices_of_overlapping_checks(&kind); if indices_of_overlapping_checks.is_empty() { // This is a new choice we haven't yet discovered as it is not overlapping // with any of the existing ones. So we add it as a possible new path // we might have to go down to in the decision tree. self.save_index_of_new_choice(kind.clone()); let check = compiler.fresh_runtime_check(kind, branch_mode); for new_check in compiler.new_checks(&pattern, &check) { branch.add_check(new_check); } let mut branches = self.fallback.clone(); branches.push_back(branch); self.choices.push((check, branches)); } else { // Otherwise, we know that the check for this branch overlaps with // (possibly more than one) existing checks and so is relevant only // as part of those existing paths. // We'll add the branch with its newly discovered checks only to those // paths. for index in indices_of_overlapping_checks.iter() { let (overlapping_check, branches) = self .choices .get_mut(*index) .expect("check to already be a choice"); let mut branch = branch.clone(); for new_check in compiler.new_checks(&pattern, overlapping_check) { branch.add_check(new_check); } branches.push_back(branch); } } // Then we have to update all variant checks with any new name/module // we might have discovered from the pattern to make sure we're using // the correct qualification to refer to each constructor. if let Pattern::Variant { name, module, .. } = pattern { for index in indices_of_overlapping_checks { let (check, _) = self .choices .get_mut(index) .expect("check to already be a choice"); if let RuntimeCheck::Variant { match_, .. } = check { *match_ = VariantMatch::ExplicitlyMatchedOn { module: module.clone(), name: name.clone(), } } } } } /// When we work with bit array patterns the splitter follows a different /// strategy to split branches: instead of trying to create a multiway /// decision tree with possibly many branches, we create only two branches /// based on the first segment test we run into. /// One path is going to be for the branches that can match if the test is /// successful, and the other one is going to be the usual fallback to go /// down to if it's not successful. /// /// > "And now for the tricky bit..." /// > /// fn add_checked_bit_array_branch( &mut self, pattern_check: PatternCheck, tests: VecDeque, mut branch: Branch, compiler: &mut Compiler<'_>, ) { // If we haven't found a test yet we just use the first one we find // as the pivot to split all the branches. let pivot_test = match self.choices.as_slice() { [] => { let test = tests.front().expect("empty bit array test").clone(); self.choices.push(( RuntimeCheck::BitArray { test: test.clone() }, VecDeque::new(), )); test } [(RuntimeCheck::BitArray { test }, _)] => test.clone(), _ => unreachable!("non bit array check when splitting bit array patterns"), }; let pattern_fails_if_test_succeeds = tests .iter() .any(|test| test.fails_if_succeeding(&pivot_test).is_certain()); let pattern_fails_if_test_fails = tests .iter() .any(|test| test.fails_if_failing(&pivot_test).is_certain()); // The branch is still relevant for the if_true path if it still has a // chance of matching knowing that the pivot test has matched. // So if we know for certain that the pattern would fail, we don't even // bother adding it to the if_true path. if !pattern_fails_if_test_succeeds { // If the branch is relevant we can further simplify it by removing // all those tests that are guaranteed to succeed. For example, // if the succeeding pivot test is `size >= 20` there's no point // in checking that `size >= 10`, we know that's always true in this // path of the decision tree! let tests = tests .into_iter() .filter(|test| test.succeeds_if_succeeding(&pivot_test) == Confidence::Uncertain) .collect::>(); let mut branch = branch.clone(); let variable = &pattern_check.var; branch.add_check(variable.is(compiler.bit_array_pattern(tests))); // We know that there's always going to be a single choice for the // successful check, so we get that and add the branch to it. let (_, if_true_branches) = self .choices .get_mut(0) .expect("bit array compilation with no choice"); if_true_branches.push_back(branch); } // Same goes for the if_false branch: knowing the pivot test has failed // we only want to keep those branches with a pattern that still have a // chance to match. if !pattern_fails_if_test_fails { // The main difference with the if true case is that we have no way // of pruning the number of needed tests, so we add this check back // exactly as it is. branch.add_check(pattern_check); self.fallback.push_back(branch); } } fn save_index_of_new_choice(&mut self, kind: RuntimeCheckKind) { let _ = match kind { RuntimeCheckKind::Int { .. } | RuntimeCheckKind::Float { .. } | RuntimeCheckKind::String { .. } | RuntimeCheckKind::Tuple { .. } | RuntimeCheckKind::Variant { .. } | RuntimeCheckKind::EmptyList | RuntimeCheckKind::NonEmptyList => self.indices.insert(kind, self.choices.len()), RuntimeCheckKind::StringPrefix { prefix } => self .prefix_indices .insert(prefix.to_string(), self.choices.len()), }; } fn indices_of_overlapping_checks(&self, kind: &RuntimeCheckKind) -> Vec { match kind { // All these checks will only overlap with a check that is exactly the // same, so we just look up their index in the `indices` map using the // kind as the lookup. RuntimeCheckKind::Int { .. } | RuntimeCheckKind::Float { .. } | RuntimeCheckKind::Tuple { .. } | RuntimeCheckKind::Variant { .. } | RuntimeCheckKind::EmptyList | RuntimeCheckKind::NonEmptyList => { self.indices.get(kind).cloned().into_iter().collect_vec() } // String patterns are a bit more tricky as they might end up overlapping // even if they're not exactly the same kind of check! Let's have a look // at an example. Say we're compiling these branches: // // ``` // a is "wibble" <> rest -> todo // a is "wibbler" <> rest -> todo // ``` // // We use the first (and only) check in the first branch as the pivot and // now we have to decide where to put the next branch. Is it matching with // the first one or completely unrelated? // Since `"wibbler"` starts with `"wibble"` we know it's overlapping and // it cannot possibly match if the previous one doesn't! // // So when we find a `String`/`StringPrefix` pattern we look for a prefix // among the ones we have discovered so far that could match with it. // That is, we look for a prefix of the pattern we're checking in the prefix // trie. RuntimeCheckKind::StringPrefix { prefix: value } => { ancestors_values(&self.prefix_indices, value).collect_vec() } // Strings are almost exactly the same, except they could also have an exact // match with other string patterns. So a string pattern could overlap with // another string pattern (if they're matching on the same value), or with // one or more string prefix patterns with a matching prefix. RuntimeCheckKind::String { value } => { let first_index = self.indices.get(kind).cloned(); first_index .into_iter() .chain(ancestors_values(&self.prefix_indices, value)) .collect_vec() } } } } fn ancestors_values(trie: &Trie, key: &str) -> impl Iterator { trie.get_ancestor(key) .into_iter() .flat_map(|ancestor| ancestor.values().copied()) } #[derive(Debug)] pub struct ConstructorSpecialiser { specialised_types: HashMap>, } impl ConstructorSpecialiser { fn specialise_constructors( constructors: &TypeVariantConstructors, type_arguments: &[Arc], current_module: &EcoString, type_module: &EcoString, ) -> Vec { match constructors.opaque { // If the type is opaque and we are not in the definition module of // that type, we don't have access to any constructors so we treat // it the same as an external type. Opaque::Opaque if current_module != type_module => return Vec::new(), Opaque::Opaque | Opaque::NotOpaque => {} }; let specialiser = Self::new(constructors.type_parameters_ids.as_slice(), type_arguments); constructors .variants .iter() .map(|v| specialiser.specialise_type_value_constructor(v)) .collect_vec() } fn new(parameters: &[u64], type_arguments: &[Arc]) -> Self { let specialised_types = parameters .iter() .copied() .zip(type_arguments.iter().cloned()) .collect(); Self { specialised_types } } fn specialise_type_value_constructor(&self, v: &TypeValueConstructor) -> TypeValueConstructor { let TypeValueConstructor { name, parameters, documentation, } = v; let parameters = parameters .iter() .map(|p| TypeValueConstructorField { type_: self.specialise_type(p.type_.as_ref()), label: p.label.clone(), documentation: p.documentation.clone(), }) .collect_vec(); TypeValueConstructor { name: name.clone(), parameters, documentation: documentation.clone(), } } fn specialise_type(&self, type_: &Type) -> Arc { Arc::new(match type_ { Type::Named { publicity, package, module, name, arguments, inferred_variant, } => Type::Named { publicity: *publicity, package: package.clone(), module: module.clone(), name: name.clone(), arguments: arguments .iter() .map(|argument| self.specialise_type(argument)) .collect(), inferred_variant: *inferred_variant, }, Type::Fn { arguments, return_ } => Type::Fn { arguments: arguments .iter() .map(|argument| self.specialise_type(argument)) .collect(), return_: return_.clone(), }, Type::Var { type_ } => Type::Var { type_: Arc::new(RefCell::new(self.specialise_var(type_))), }, Type::Tuple { elements } => Type::Tuple { elements: elements .iter() .map(|element| self.specialise_type(element)) .collect(), }, }) } fn specialise_var(&self, type_: &RefCell) -> TypeVar { match &*type_.borrow() { TypeVar::Unbound { id } => TypeVar::Unbound { id: *id }, TypeVar::Link { type_ } => TypeVar::Link { type_: self.specialise_type(type_.as_ref()), }, TypeVar::Generic { id } => match self.specialised_types.get(id) { Some(type_) => TypeVar::Link { type_: type_.clone(), }, None => TypeVar::Generic { id: *id }, }, } } } /// Intermiate data structure that's used to set up everything that's needed by /// the pattern matching compiler and get a case expression ready to be compiled, /// while hiding the intricacies of handling an arena to record different patterns. /// #[derive(Debug)] pub struct CaseToCompile { patterns: Arena, branches: Vec, subject_variables: Vec, /// The number of clauses in this case to compile. number_of_clauses: usize, variable_id: usize, } impl CaseToCompile { pub fn new(subject_types: &[Arc]) -> Self { let mut variable_id = 0; let subject_variables = subject_types .iter() .map(|type_| { let id = variable_id; variable_id += 1; Variable::new(id, type_.clone()) }) .collect_vec(); Self { patterns: Arena::new(), branches: vec![], number_of_clauses: 0, subject_variables, variable_id, } } /// Registers a `TypedClause` as one of the branches to be compiled. /// /// If you don't have a clause and just have a simple `TypedPattern` you want /// to generate a decision tree for you can use `add_pattern`. /// pub fn add_clause(&mut self, branch: &TypedClause) { let all_patterns = std::iter::once(&branch.pattern).chain(branch.alternative_patterns.iter()); for (alternative_index, patterns) in all_patterns.enumerate() { let mut checks = Vec::with_capacity(patterns.len()); // We're doing the zipping ourselves instead of using iters.zip as the // borrow checker would complain and the only workaround would be to // allocate an entire new vector each time. for i in 0..patterns.len() { let pattern = self.register(patterns.get(i).expect("pattern index")); let var = self .subject_variables .get(i) .expect("wrong number of subjects"); checks.push(var.is(pattern)) } let has_guard = branch.guard.is_some(); let branch = Branch::new(self.number_of_clauses, alternative_index, checks, has_guard); self.branches.push(branch); } self.number_of_clauses += 1; } /// Add a single pattern as a branch to be compiled. /// /// This is useful in case one wants to check exhaustiveness of a single /// pattern without having a fully fledged `TypedClause` to pass to the `add_clause` /// method. For example, in `let` destructurings. /// pub fn add_pattern(&mut self, pattern: &TypedPattern) { let pattern = self.register(pattern); let var = self .subject_variables .first() .expect("wrong number of subject variables for pattern"); let branch = Branch::new(self.number_of_clauses, 0, vec![var.is(pattern)], false); self.number_of_clauses += 1; self.branches.push(branch); } pub fn compile(self, env: &Environment<'_>) -> CompileCaseResult { let mut compiler = Compiler::new(env, self.variable_id, self.patterns); let decision = if self.branches.is_empty() { let var = self .subject_variables .first() .expect("case with no subjects") .clone(); compiler.split_and_compile_with_pivot_var(var, VecDeque::new()) } else { compiler.compile(self.branches.into()) }; CompileCaseResult { diagnostics: compiler.diagnostics, compiled_case: CompiledCase { tree: decision, subject_variables: self.subject_variables, }, } } /// Registers a typed pattern (and all its sub-patterns) into this /// `CaseToCompile`'s pattern arena, returning an id to get the pattern back. /// fn register(&mut self, pattern: &TypedPattern) -> Id { match pattern { TypedPattern::Invalid { .. } => self.insert(Pattern::Discard), TypedPattern::Discard { .. } => self.insert(Pattern::Discard), TypedPattern::Int { int_value, .. } => { let int_value = int_value.clone(); self.insert(Pattern::Int { int_value }) } TypedPattern::Float { float_value, .. } => { let float_value = *float_value; self.insert(Pattern::Float { float_value }) } TypedPattern::String { value, .. } => { let value = value.clone(); self.insert(Pattern::String { value }) } TypedPattern::Variable { name, .. } => { let name = name.clone(); self.insert(Pattern::Variable { name }) } TypedPattern::Assign { name, pattern, .. } => { let name = name.clone(); let pattern = self.register(pattern); self.insert(Pattern::Assign { name, pattern }) } TypedPattern::Tuple { elements, .. } => { let elements = elements .iter() .map(|element| self.register(element)) .collect_vec(); self.insert(Pattern::Tuple { elements }) } TypedPattern::List { elements, tail, .. } => { let mut list = match tail { Some(tail) => self.register(&tail.pattern), None => self.insert(Pattern::EmptyList), }; for element in elements.iter().rev() { let first = self.register(element); list = self.insert(Pattern::NonEmptyList { first, rest: list }); } list } TypedPattern::Constructor { arguments, constructor, name, module, .. } => { let index = constructor.expect_ref("must be inferred").constructor_index as usize; let module = module.as_ref().map(|(module, _)| module.clone()); let fields = arguments .iter() .map(|argument| self.register(&argument.value)) .collect_vec(); self.insert(Pattern::Variant { name: name.clone(), module, index, fields, }) } TypedPattern::BitArray { segments, .. } => { let tests = self.bit_array_to_tests(segments); self.insert(Pattern::BitArray { tests }) } TypedPattern::StringPrefix { left_side_string, left_side_assignment, right_side_assignment, .. } => { let prefix = left_side_string.clone(); let prefix_name = left_side_assignment .as_ref() .map(|(label, _)| label.clone()); let rest_pattern = match right_side_assignment { AssignName::Variable(name) => Pattern::Variable { name: name.clone() }, AssignName::Discard(_) => Pattern::Discard, }; let rest = self.insert(rest_pattern); self.insert(Pattern::StringPrefix { prefix, prefix_name, rest, }) } TypedPattern::BitArraySize { .. } => { unreachable!("Cannot convert BitArraySize to exhaustiveness pattern") } } } fn insert(&mut self, pattern: Pattern) -> Id { self.patterns.alloc(pattern) } fn bit_array_to_tests( &mut self, segments: &[TypedPatternBitArraySegment], ) -> VecDeque { // If there's no segments then we just add a single check to make sure // the bit array is empty. if segments.is_empty() { let mut test = VecDeque::new(); test.push_front(BitArrayTest::Size(SizeTest { operator: SizeOperator::Equal, size: Offset::constant(0), })); return test; } let mut previous_end = Offset::constant(0); let mut tests = VecDeque::with_capacity(segments.len() * 2); let mut pattern_variables = HashMap::new(); let segments_count = segments.len(); for (i, segment) in segments.iter().enumerate() { let segment_size = segment_size(segment, &pattern_variables, None); // If we're reading a variable number of bits we need to make sure // that that variable is not negative! if segment_size.can_be_negative() { tests.push_back(BitArrayTest::ReadSizeIsNotNegative { size: segment_size.clone(), }); } // All segments but the last will require the original bit array to // have a minimum number of bits for the pattern to succeed. The // final segment is special as it could require a specific size, or // be a catch all that matches with any number of remaining bits. let is_last_segment = i + 1 == segments_count; match &segment_size { ReadSize::RemainingBits => (), ReadSize::RemainingBytes => tests.push_back(BitArrayTest::CatchAllIsBytes { size_so_far: previous_end.clone(), }), ReadSize::ConstantBits(_) | ReadSize::VariableBits { .. } | ReadSize::BinaryOperator { .. } => { let size = previous_end.clone().add_size(&segment_size); let operator = if is_last_segment { SizeOperator::Equal } else { SizeOperator::GreaterEqual }; tests.push_back(BitArrayTest::Size(SizeTest { operator, size })); } }; let type_ = match &segment.type_ { type_ if type_.is_int() => ReadType::Int, type_ if type_.is_float() => ReadType::Float, type_ if type_.is_string() => ReadType::String, type_ if type_.is_bit_array() => ReadType::BitArray, type_ if type_.is_utf_codepoint() => ReadType::UtfCodepoint, x => panic!("invalid segment type in exhaustiveness {x:?}"), }; let read_action = ReadAction { size: segment_size.clone(), from: previous_end.clone(), type_, endianness: segment.endianness(), signed: segment.signed(), }; // Each segment is also turned into a match test, checking the // selected bits match with the pattern's value. let value = segment_matched_value(segment, None, &read_action); // Then if the matched value is a variable that is in scope for the // rest of the pattern we keep track of it, so it can be used in the // following read actions as a valid size. match &value { BitArrayMatchedValue::LiteralFloat(_) | BitArrayMatchedValue::LiteralInt { .. } | BitArrayMatchedValue::LiteralString { .. } | BitArrayMatchedValue::Discard(_) => {} BitArrayMatchedValue::Variable(name) | BitArrayMatchedValue::Assign { name, .. } => { let _ = pattern_variables.insert(name.clone(), read_action.clone()); } } // If we are matching on a float segment that is not a literal we want // to add an additional check to make sure that we won't match with // `NaN` and `Infinity`! if type_.is_float() && !value.is_literal() { tests.push_back(BitArrayTest::SegmentIsFiniteFloat { read_action: read_action.clone(), }); } tests.push_back(BitArrayTest::Match(MatchTest { value, read_action })); previous_end = previous_end.add_size(&segment_size); } tests } } fn segment_matched_value( segment: &TypedPatternBitArraySegment, // If we are compiling an assignment pattern, we still need access to the // `type_` and `options` fields of the `segment`, so we must still pass that // in above. However, we need to check the correct sub-pattern of the original // pattern, so if they are different we set this argument to `Some`. pattern: Option<&TypedPattern>, read_action: &ReadAction, ) -> BitArrayMatchedValue { let pattern = pattern.unwrap_or(&segment.value); match pattern { ast::Pattern::Int { int_value, location, .. } => BitArrayMatchedValue::LiteralInt { value: int_value.clone(), location: *location, bits: int_to_bits( int_value, &read_action.size, read_action.endianness, read_action.signed, ), }, ast::Pattern::Float { value, .. } => BitArrayMatchedValue::LiteralFloat(value.clone()), ast::Pattern::String { value, .. } if segment.has_utf16_option() => { BitArrayMatchedValue::LiteralString { value: value.clone(), encoding: StringEncoding::Utf16, bytes: string_to_utf16_bytes( &convert_string_escape_chars(value), read_action.endianness, ), } } ast::Pattern::String { value, .. } if segment.has_utf32_option() => { BitArrayMatchedValue::LiteralString { value: value.clone(), encoding: StringEncoding::Utf32, bytes: string_to_utf32_bytes( &convert_string_escape_chars(value), read_action.endianness, ), } } ast::Pattern::String { value, .. } => BitArrayMatchedValue::LiteralString { value: value.clone(), encoding: StringEncoding::Utf8, bytes: convert_string_escape_chars(value).as_bytes().into(), }, ast::Pattern::Variable { name, .. } => BitArrayMatchedValue::Variable(name.clone()), ast::Pattern::Discard { name, .. } => BitArrayMatchedValue::Discard(name.clone()), ast::Pattern::Assign { name, pattern, .. } => BitArrayMatchedValue::Assign { name: name.clone(), value: Box::new(segment_matched_value(segment, Some(pattern), read_action)), }, ast::Pattern::BitArraySize(_) | ast::Pattern::List { .. } | ast::Pattern::Constructor { .. } | ast::Pattern::Tuple { .. } | ast::Pattern::BitArray { .. } | ast::Pattern::StringPrefix { .. } | ast::Pattern::Invalid { .. } => panic!("unexpected segment value pattern {pattern:?}"), } } fn int_to_bits( value: &BigInt, read_size: &ReadSize, endianness: Endianness, signed: bool, ) -> Result, IntToBitsError> { let size = read_size .constant_bits() .ok_or(IntToBitsError::NonConstantSize)? .to_u32() .ok_or(IntToBitsError::ExceedsMaximumSize)?; if !representable_with_bits(value, size, signed) { return Err(IntToBitsError::Unrepresentable { size }); } else if size > BitArrayMatchedValue::MAX_BITS_INTERFERENCE { return Err(IntToBitsError::ExceedsMaximumSize); } // Pad negative numbers with 1s (true) and non-negative numbers with 0s (false) let pad_digit = value.sign() == Sign::Minus; let size = size as usize; let mut bytes = int_to_bytes(value, endianness, signed); let bytes_size = bytes.len() * 8; let bits = match (endianness, bytes_size.cmp(&size)) { (_, Ordering::Equal) => BitVec::from_vec(bytes), // Even though we return an error if `value` cannot be represented in `size` bits, // we may need to slice off up a couple bits if the read size is not a multiple of 8. // <3:4> yields the bytes [0b0000_0011]. We slice off 4 bits and get 0011 (Endianness::Big, Ordering::Greater) => { BitVec::from_bitslice(&bytes.view_bits()[bytes_size - size..]) } // If the number needs fewer bits than the read size, we pad it. // <3:9> yields the bytes [0b0000_0011]. We add one digit and get 0_0000_0011 (Endianness::Big, Ordering::Less) => { let mut bits = BitVec::repeat(pad_digit, size - bytes_size); bits.extend_from_raw_slice(&bytes); bits } (Endianness::Little, Ordering::Greater) => { // If the difference is greater than a byte, we returned an Error earlier let remainder = size % 8; if remainder == 0 { BitVec::from_vec(bytes) } else { // If the size is not a multiple of 8, we need to truncate the most significant bits. // As they are in the last byte, we leftshift by the appropriate amount and // truncate the final bits after conversion let last_byte = bytes.last_mut().expect("bytes must not be empty"); *last_byte <<= 8 - remainder; let mut bits = BitVec::from_vec(bytes); bits.truncate(size); bits } } (Endianness::Little, Ordering::Less) => { let mut bits = BitVec::from_vec(bytes); let padding: BitVec = BitVec::repeat(pad_digit, size - bytes_size); bits.extend_from_bitslice(padding.as_bitslice()); bits } }; Ok(bits) } fn int_to_bytes(value: &BigInt, endianness: Endianness, signed: bool) -> Vec { match (endianness, signed) { (Endianness::Big, false) => value.to_bytes_be().1, (Endianness::Big, true) => value.to_signed_bytes_be(), (Endianness::Little, false) => value.to_bytes_le().1, (Endianness::Little, true) => value.to_signed_bytes_le(), } } #[derive(Clone, Copy, Eq, PartialEq, Debug, serde::Serialize, serde::Deserialize)] pub enum IntToBitsError { Unrepresentable { size: u32 }, ExceedsMaximumSize, NonConstantSize, } fn segment_size( segment: &TypedPatternBitArraySegment, pattern_variables: &HashMap, // If we are compiling an assignment pattern, we still need access to the // `type_` and `options` fields of the `segment`, so we must still pass that // in above. However, we need to check the correct sub-pattern of the original // pattern, so if they are different we set this argument to `Some`. pattern: Option<&TypedPattern>, ) -> ReadSize { let pattern = pattern.unwrap_or(&segment.value); match segment.size() { // The size of a segment must be a `BitArraySize` pattern. Some(ast::Pattern::BitArraySize(size)) => { bit_array_size(segment.unit(), pattern_variables, size) } Some(x) => panic!("invalid pattern size made it to code generation {x:?}"), // If a segment has the `bits`/`bytes` option and has no size, that // means it's the final catch all segment: we'll have to read any number // of bits. _ if segment.has_bits_option() => ReadSize::RemainingBits, _ if segment.has_bytes_option() => ReadSize::RemainingBytes, // If there's no size option we go for a default: 8 bits for int // segments, and 64 for anything else. None if segment.type_.is_int() => ReadSize::ConstantBits(8.into()), None => match pattern { ast::Pattern::Assign { pattern, .. } => { segment_size(segment, pattern_variables, Some(pattern)) } ast::Pattern::String { value, .. } if segment.has_utf16_option() => { ReadSize::ConstantBits( // Each utf16 code unit is 16 bits length_utf16(&convert_string_escape_chars(value)) * BigInt::from(16), ) } ast::Pattern::String { value, .. } if segment.has_utf32_option() => { // Each utf32 code unit is 32 bits ReadSize::ConstantBits( length_utf32(&convert_string_escape_chars(value)) * BigInt::from(32), ) } // If the segment is a literal string then it has an automatic size // given by its number of bytes. ast::Pattern::String { value, .. } => { ReadSize::ConstantBits(convert_string_escape_chars(value).len() * BigInt::from(8)) } // In all other cases the segment is considered to be 64 bits ast::Pattern::Int { .. } | ast::Pattern::Float { .. } | ast::Pattern::Variable { .. } | ast::Pattern::BitArraySize(_) | ast::Pattern::Discard { .. } | ast::Pattern::List { .. } | ast::Pattern::Constructor { .. } | ast::Pattern::Tuple { .. } | ast::Pattern::BitArray { .. } | ast::Pattern::StringPrefix { .. } | ast::Pattern::Invalid { .. } => ReadSize::ConstantBits(64.into()), }, } } fn bit_array_size( unit: u8, pattern_variables: &HashMap, size: &TypedBitArraySize, ) -> ReadSize { match size { BitArraySize::Int { int_value, .. } => ReadSize::ConstantBits(int_value * unit), BitArraySize::Variable { name, .. } => { let variable = match pattern_variables.get(name) { Some(read_action) => { VariableUsage::PatternSegment(name.clone(), read_action.clone()) } None => VariableUsage::OutsideVariable(name.clone()), }; ReadSize::VariableBits { variable: Box::new(variable), unit, } } BitArraySize::BinaryOperator { operator, left, right, .. } => { let size = ReadSize::BinaryOperator { left: Box::new(bit_array_size(1, pattern_variables, left)), right: Box::new(bit_array_size(1, pattern_variables, right)), operator: *operator, }; if unit == 1 { size } else { ReadSize::BinaryOperator { left: Box::new(size), right: Box::new(ReadSize::ConstantBits(unit.into())), operator: IntOperator::Multiply, } } } BitArraySize::Block { inner, .. } => bit_array_size(unit, pattern_variables, inner), } } /// Returns `true` if one bag is a superset of the other: that is it contains /// all the keys of `other` in a quantity that's greater or equal. /// #[must_use] fn superset( one: &im::HashMap, other: &im::HashMap, ) -> bool { other .iter() .all(|(key, other_occurrences)| match one.get(key) { Some(occurrences) => occurrences >= other_occurrences, None => false, }) } #[must_use] fn representable_with_bits(value: &BigInt, bits: u32, signed: bool) -> bool { // No number is representable in 0 bits. if bits == 0 { return false; }; let required_bits = match (value.sign(), signed) { // Zero always needs one bit. (Sign::NoSign, _) => 1, // `BigInt::bits` does not consider the sign! (Sign::Plus, false) => value.bits(), // Therefore we need to add the sign bit here: `10` -> `010` (Sign::Plus, true) => value.bits() + 1, // A negative number must be signed (Sign::Minus, false) => return false, (Sign::Minus, true) => { let bits_unsigned = value.bits(); let trailing_zeros = value .trailing_zeros() .expect("trailing_zeros to return a value for non-zero numbers"); let is_power_of_2 = trailing_zeros == bits_unsigned - 1; // Negative powers of two don't need an extra sign bit. E.g. `-2 == 0b10` if is_power_of_2 { bits_unsigned } else { bits_unsigned + 1 } } }; match required_bits.to_u32() { Some(required_bits) => bits >= required_bits, None => false, } } #[cfg(test)] mod representable_with_bits_test { use crate::exhaustiveness::representable_with_bits; use num_bigint::BigInt; #[test] fn positive_number_representable_with_bits_test() { // 9 can be represented as a >=4 bits unsigned number. assert!(representable_with_bits(&9.into(), 4, false)); assert!(representable_with_bits(&9.into(), 5, false)); // But not in <=3 bits as an unsigned number. assert!(!representable_with_bits(&9.into(), 3, false)); assert!(!representable_with_bits(&9.into(), 2, false)); // It can be represented as a >=5 bit signed number. assert!(representable_with_bits(&9.into(), 5, true)); assert!(representable_with_bits(&9.into(), 6, true)); // But not in <= 4 bits as a signed number. assert!(!representable_with_bits(&9.into(), 4, true)); assert!(!representable_with_bits(&9.into(), 3, true)); } #[test] fn negative_number_representable_with_bits_test() { // A negative number will never be representable as an unsigned number, // no matter the number of bits! assert!(!representable_with_bits(&BigInt::from(-9), 1, false)); assert!(!representable_with_bits(&BigInt::from(-9), 500, false)); // -9 can be represented in >=5 bits as a signed number. assert!(representable_with_bits(&BigInt::from(-9), 5, true)); assert!(representable_with_bits(&BigInt::from(-9), 6, true)); // But not in <= 4 bits as a signed number. assert!(!representable_with_bits(&BigInt::from(-9), 4, true)); assert!(!representable_with_bits(&BigInt::from(-9), 3, true)); } #[test] fn zero_representable_with_bits_test() { for i in 0..12 { println!("{i}: {}", BigInt::from(i).bits()); } assert!(!representable_with_bits(&BigInt::ZERO, 0, false)); assert!(!representable_with_bits(&BigInt::ZERO, 0, true)); assert!(representable_with_bits(&BigInt::ZERO, 1, false)); assert!(representable_with_bits(&BigInt::ZERO, 1, true)); } #[test] fn number_representable_with_sign_test() { // Sign needs one additional bit assert!(representable_with_bits(&8.into(), 4, false)); assert!(!representable_with_bits(&8.into(), 4, true)); assert!(representable_with_bits(&8.into(), 5, true)); // Negative number must be signed assert!(!representable_with_bits(&BigInt::from(-8), 100, false)); // Negative powers of two don't need an extra sign bit assert!(!representable_with_bits(&BigInt::from(-8), 3, true)); assert!(representable_with_bits(&BigInt::from(-8), 4, true)); assert!(!representable_with_bits(&BigInt::from(-9), 4, true)); assert!(representable_with_bits(&BigInt::from(-9), 5, true)); } } #[cfg(test)] mod int_to_bits_test { use std::assert_eq; use crate::{ ast::Endianness, exhaustiveness::{BitArrayMatchedValue, IntToBitsError, ReadSize, int_to_bits}, }; use bitvec::{bitvec, order::Msb0, vec::BitVec}; use num_bigint::BigInt; fn read_size(size: u32) -> ReadSize { ReadSize::ConstantBits(BigInt::from(size)) } #[test] fn int_to_bits_size_too_big() { assert_eq!( int_to_bits( &BigInt::ZERO, &read_size(BitArrayMatchedValue::MAX_BITS_INTERFERENCE + 1), Endianness::Big, true, ), Err(IntToBitsError::ExceedsMaximumSize), ); } #[test] fn int_to_bits_zero() { let expect = Ok(bitvec![u8, Msb0; 0; 3]); assert_eq!( int_to_bits(&BigInt::ZERO, &read_size(3), Endianness::Big, false), expect ); assert_eq!( int_to_bits(&BigInt::ZERO, &read_size(3), Endianness::Little, false), expect ); let expect = Ok(bitvec![u8, Msb0; 0; 10]); assert_eq!( int_to_bits(&BigInt::ZERO, &read_size(10), Endianness::Big, false), expect ); assert_eq!( int_to_bits(&BigInt::ZERO, &read_size(10), Endianness::Little, false), expect ); } #[test] fn int_to_bits_positive() { // Exact match assert_eq!( int_to_bits( &BigInt::from(0xff00), &read_size(16), Endianness::Big, false ), Ok(BitVec::::from_vec(vec![0xff, 0x00])), ); assert_eq!( int_to_bits( &BigInt::from(0xff00), &read_size(16), Endianness::Little, false ), Ok(BitVec::::from_vec(vec![0x00, 0xff])), ); assert_eq!( int_to_bits( &BigInt::from(0b11_1111_0000), &read_size(10), Endianness::Big, false ), Ok(bitvec![u8, Msb0; 1, 1, 1, 1, 1, 1, 0, 0, 0, 0]), ); assert_eq!( int_to_bits( &BigInt::from(0b11_1111_0000), &read_size(10), Endianness::Little, false ), Ok(bitvec![u8, Msb0; 1, 1, 1, 1, 0, 0, 0, 0, 1, 1]), ); // Too few bits in int assert_eq!( int_to_bits(&BigInt::from(0xff), &read_size(12), Endianness::Big, false), Ok(bitvec![u8, Msb0; 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1]), ); assert_eq!( int_to_bits( &BigInt::from(0xff), &read_size(12), Endianness::Little, false ), Ok(bitvec![u8, Msb0; 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0]), ); } #[test] fn int_to_bits_signed() { assert_eq!( int_to_bits(&BigInt::from(-128), &read_size(12), Endianness::Big, true), Ok(bitvec![u8, Msb0; 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]), ); assert_eq!( int_to_bits( &BigInt::from(-128), &read_size(12), Endianness::Little, true ), Ok(bitvec![u8, Msb0; 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1]), ); } } ================================================ FILE: compiler-core/src/fix.rs ================================================ use crate::{ Error, Result, format::{Formatter, Intermediate}, warning::WarningEmitter, }; use camino::Utf8Path; use ecow::EcoString; pub fn parse_fix_and_format(src: &EcoString, path: &Utf8Path) -> Result { // Parse let parsed = crate::parse::parse_module(path.to_owned(), src, &WarningEmitter::null()) .map_err(|error| Error::Parse { path: path.to_path_buf(), src: src.clone(), error: Box::new(error), })?; let intermediate = Intermediate::from_extra(&parsed.extra, src); let module = parsed.module; // Fix // let module = some_fixer_module::Fixer::fix(module); // Format let mut buffer = String::new(); Formatter::with_comments(&intermediate) .module(&module) .pretty_print(80, &mut buffer)?; Ok(buffer) } ================================================ FILE: compiler-core/src/format/tests/asignments.rs ================================================ use crate::assert_format; // https://github.com/gleam-lang/gleam/issues/2095 #[test] fn comment() { assert_format!( r#"pub fn main() { // Hello let x = 1 x } "# ); } // https://github.com/gleam-lang/gleam/issues/2095 #[test] fn assert_comment() { assert_format!( r#"pub fn main() { // Hello let assert x = 1 x } "# ); } ================================================ FILE: compiler-core/src/format/tests/binary_operators.rs ================================================ use crate::assert_format; // https://github.com/gleam-lang/gleam/issues/835 #[test] pub fn long_binary_operation_sequence() { assert_format!( r#"pub fn main() { int.to_string(color.red) <> ", " <> int.to_string(color.green) <> ", " <> int.to_string(color.blue) <> ", " <> float.to_string(color.alpha) } "# ); } #[test] pub fn long_comparison_chain() { assert_format!( r#"pub fn main() { trying_a_comparison(this, is, a, function) > with_ints && trying_other_comparisons < with_ints || trying_other_comparisons <= with_ints && trying_other_comparisons >= with_ints || and_now_an_equality_check == with_a_function(wibble, wobble) && trying_other_comparisons >. with_floats || trying_other_comparisons <. with_floats(wobble) && trying_other_comparisons <=. with_floats || trying_other_comparisons(wibble, wobble) >=. with_floats && wibble <> wobble } "# ); } #[test] pub fn long_chain_mixing_operators() { assert_format!( r#"pub fn main() { variable + variable - variable * variable / variable == variable * variable / variable - variable + variable || wibble * wobble > 11 } "# ); assert_format!( r#"pub fn main() { variable +. variable -. variable *. variable /. variable == variable *. variable /. variable -. variable +. variable || wibble *. wobble >=. 11 } "# ); } // Thanks Hayleigh for pointing this out! #[test] fn case_branch_is_not_broken_if_can_fit_on_line() { assert_format!( r#"pub fn main() { case remainder { _ if remainder >=. 0.5 && x >=. 0.0 -> float_sign(x) *. truncate_float(xabs +. 1.0) /. p _ -> float_sign(x) *. xabs_truncated /. p } } "# ); } // https://discord.com/channels/768594524158427167/1187508793945378847/1187508793945378847 #[test] fn binary_operation_in_assignment_that_is_almost_80_chars() { assert_format!( r#"pub fn main() { let is_vr_implicit = dicom_read_context.transfer_syntax == transfer_syntax.ImplicitVrLittleEndian } "# ); } // https://github.com/gleam-lang/gleam/issues/2480 #[test] fn labelled_field_with_binary_operators_are_not_broken_if_they_can_fit() { assert_format!( r#"pub fn main() { Ok(Lesson( name: names.name, text: text, code: code, path: chapter_path <> "/", previous: None, next: None, )) } "# ); assert_format!( r#"pub fn main() { Ok(Lesson( name: names.name, text:, code:, path: chapter_path <> "/" <> this_one_doesnt_fit <> "and ends up on multiple lines", previous: None, next: None, )) } "# ); assert_format!( r#"pub fn main() { Ok(wibble( name: names.name, text:, code:, path: chapter_path <> "/", previous: None, next: None, )) } "# ); assert_format!( r#"pub fn main() { Ok(wibble( name: names.name, text:, code:, path: chapter_path <> "/" <> this_one_doesnt_fit <> "and ends up on multiple lines", previous: None, next: None, )) } "# ); } #[test] fn math_binops_kept_on_a_single_line_in_pipes() { assert_format!( r#"pub fn main() { 1 + 2 * 3 / 4 - 5 |> wibble |> wobble } "# ); assert_format!( r#"pub fn main() { 1 +. 2 *. 3 /. 4 -. 5 |> wibble |> wobble } "# ); } #[test] fn binop_used_as_function_arguments_gets_nested() { assert_format!( r#"pub fn main() { wibble( a_variable_with_a_long_name <> another_variable_with_a_long_name <> yet_another_variable_with_a_long_name, wobble, ) } "# ); } #[test] fn binop_is_not_nested_if_the_only_argument() { assert_format!( r#"pub fn main() { wibble( a_variable_with_a_long_name <> another_variable_with_a_long_name <> yet_another_variable_with_a_long_name, ) } "# ); } #[test] fn binop_inside_list_gets_nested() { assert_format!( r#"pub fn main() { [ wibble, a_variable_with_a_long_name <> another_variable_with_a_long_name <> yet_another_variable_with_a_long_name, ] } "# ); } #[test] fn binop_inside_list_is_not_nested_if_only_item() { assert_format!( r#"pub fn main() { [ a_variable_with_a_long_name <> another_variable_with_a_long_name <> yet_another_variable_with_a_long_name, ] } "# ); } #[test] fn binop_inside_tuple_gets_nested() { assert_format!( r#"pub fn main() { #( wibble, a_variable_with_a_long_name <> another_variable_with_a_long_name <> yet_another_variable_with_a_long_name, ) } "# ); } #[test] fn binop_inside_tuple_is_not_nested_if_only_item() { assert_format!( r#"pub fn main() { #( a_variable_with_a_long_name <> another_variable_with_a_long_name <> yet_another_variable_with_a_long_name, ) } "# ); } // https://github.com/gleam-lang/gleam/issues/2624 #[test] fn binop_as_argument_in_variant_with_spread_gets_nested() { assert_format!( r#"pub fn main() { Wibble( ..wibble, label: string <> "a long string that is making things go on multiple lines" <> "another string", ) } "# ); } ================================================ FILE: compiler-core/src/format/tests/bit_array.rs ================================================ use crate::{assert_format, assert_format_rewrite}; #[test] fn construction() { assert_format!( "fn main() { let a = 1 let x = <<1, a, 2:bytes>> let size = <<3:2, 4:size(3), 5:bytes-size(4), 6:size(a)>> let unit = <<7:unit(1), 8:bytes-unit(2)>> x } ", ); } #[test] fn pattern() { assert_format!( "fn main() { let a = 1 let <> = <<1, a, 2:bytes>> b } ", ); } #[test] fn long() { assert_format!( "fn main() { let some_really_long_variable_name_to_force_wrapping = 1 let bits = << some_really_long_variable_name_to_force_wrapping, some_really_long_variable_name_to_force_wrapping, >> bits } ", ); } // https://github.com/gleam-lang/gleam/issues/2932 #[test] fn tight_empty() { assert_format!( "fn main() { let some_really_really_really_really_really_really_really_long_variable_name_to_force_wrapping = <<>> some_really_really_really_really_really_really_really_long_variable_name_to_force_wrapping } " ); } #[test] fn comments_are_not_moved_out_of_empty_bit_array() { assert_format!( r#"pub fn main() { // This is an empty bit array! << // Nothing here... >> } "# ); } #[test] fn empty_bit_arrays_with_comment_inside_are_indented_properly() { assert_format!( r#"pub fn main() { fun( << // Nothing here... >>, wibble_wobble_wibble_wobble_wibble_wobble_wibble_wobble, << // Nothing here as well! >>, ) } "# ); } #[test] fn comments_inside_non_empty_bit_arrays_are_not_moved() { assert_format!( r#"pub fn main() { fun( << // One is below me. 1, 2, // Three is below me. 3, >>, wibble_wobble_wibble_wobble_wibble_wobble_wibble_wobble, << // Three is below me. 3, >>, ) } "# ); } #[test] fn concise_wrapping_of_simple_bit_arrays() { assert_format!( "pub fn main() { << 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900, 2000, >> } " ); } #[test] fn concise_wrapping_of_simple_bit_arrays1() { assert_format!( "pub fn main() { << 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 1.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 2.0, >> } " ); } #[test] fn concise_wrapping_of_simple_bit_arrays2() { assert_format!( r#"pub fn main() { << "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", >> } "# ); } #[test] fn concise_wrapping_of_simple_bit_arrays3() { assert_format!( "const values = << 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900, 2000, >> " ); } #[test] fn concise_wrapping_of_simple_bit_arrays4() { assert_format!( "const values = << 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 1.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 2.0, >> " ); } #[test] fn concise_wrapping_of_simple_bit_arrays5() { assert_format!( r#"const values = << "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", >> "# ); } #[test] fn binop_value() { assert_format!( r#"pub fn main() { <<{ 1 + 1 }>> } "# ); } #[test] fn block_value() { assert_format!( r#"pub fn main() { << { io.println("hi") 1 }, >> } "# ); } #[test] fn operator_in_pattern_size() { assert_format!( "pub fn main() { let assert <> = <<>> } " ); } #[test] // https://github.com/gleam-lang/gleam/issues/4792#issuecomment-3096177213 fn bit_array_segments_are_kept_one_per_line() { assert_format!( "pub fn main() { << 1:1, 1:1, 0:2, opcode:4, masked:1, length_section:bits, mask_key:bits, data:bits, >> |> bytes_tree.from_bit_array } " ); } #[test] fn bit_array_with_trailing_comma_is_broken() { assert_format_rewrite!( "pub fn main() { <<1, 2, a,>> }", r#"pub fn main() { << 1, 2, a, >> } "# ); } #[test] fn constant_bit_array_with_trailing_comma_is_broken() { assert_format_rewrite!( "const bit_array = <<1, 2, a,>>", r#"const bit_array = << 1, 2, a, >> "# ); } #[test] fn bit_array_with_trailing_comma_is_kept_broken() { assert_format!( r#"pub fn main() { << 1, 2, a, >> } "# ); } #[test] fn constant_bit_array_with_trailing_comma_is_kept_broken() { assert_format!( r#"const bit_array = << 1, 2, a, >> "# ); } #[test] fn bit_array_with_no_trailing_comma_is_packed_on_a_single_line() { assert_format_rewrite!( r#"pub fn main() { << 1, 2, a >> } "#, r#"pub fn main() { <<1, 2, a>> } "# ); } #[test] fn constant_bit_array_with_no_trailing_comma_is_packed_on_a_single_line() { assert_format_rewrite!( r#"const bit_array = << 1, 2, a >>"#, "const bit_array = <<1, 2, a>>\n" ); } #[test] fn bit_array_with_no_comma_is_packed_on_a_single_line_or_split_one_item_per_line() { assert_format_rewrite!( "pub fn main() { << 1, a, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, b, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312 >> } ", "pub fn main() { << 1, a, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, b, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, >> } " ); } #[test] fn constant_bit_array_with_no_comma_is_packed_on_a_single_line_or_split_one_item_per_line() { assert_format_rewrite!( "const bit_array = << 1, a, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, b, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312 >> ", "const bit_array = << 1, a, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, b, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, >> " ); } #[test] fn simple_bit_array_with_no_comma_is_packed_on_a_single_line_or_split_one_item_per_line() { assert_format_rewrite!( r#"pub fn main() { << "hello", "wibble wobble", "these are all simple strings", "but the bitarray won't be packed", "the formatter will keep", "one item", "per line", "since there's no trailing comma here ->" >> } "#, r#"pub fn main() { << "hello", "wibble wobble", "these are all simple strings", "but the bitarray won't be packed", "the formatter will keep", "one item", "per line", "since there's no trailing comma here ->", >> } "# ); } #[test] fn simple_bit_array_with_trailing_comma_and_multiple_items_per_line_is_packed() { assert_format_rewrite!( r#"pub fn main() { << "hello", "wibble wobble", "these are all simple strings", "and the bit array will be packed since the following strings are", "on the same", "line", "and there's a trailing comma ->", >> } "#, r#"pub fn main() { << "hello", "wibble wobble", "these are all simple strings", "and the bit array will be packed since the following strings are", "on the same", "line", "and there's a trailing comma ->", >> } "# ); } #[test] fn simple_constant_bit_array_with_trailing_comma_and_multiple_items_per_line_is_packed() { assert_format_rewrite!( r#"pub const bit_array = << "hello", "wibble wobble", "these are all simple strings", "and the bit array will be packed since the following strings are", "on the same", "line", "and there's a trailing comma ->", >> "#, r#"pub const bit_array = << "hello", "wibble wobble", "these are all simple strings", "and the bit array will be packed since the following strings are", "on the same", "line", "and there's a trailing comma ->", >> "# ); } #[test] fn simple_packed_bit_array_with_trailing_comma_is_kept_with_multiple_items_per_line() { assert_format!( r#"pub fn main() { << "hello", "wibble wobble", "these are all simple strings", "and the bit array will be kept packed since it ends with a trailing comma", "right here! ->", >> } "# ); } #[test] fn simple_single_line_bit_array_with_trailing_comma_is_split_one_item_per_line() { assert_format_rewrite!( r#"pub fn main() { <<"these are all simple strings", "but the bit array won't be packed", "since it ends with a trailing comma ->",>> } "#, r#"pub fn main() { << "these are all simple strings", "but the bit array won't be packed", "since it ends with a trailing comma ->", >> } "# ); } #[test] fn simple_single_line_bit_array_with_no_trailing_comma_is_split_one_item_per_line() { assert_format_rewrite!( r#"pub fn main() { <<"these are all simple strings", "but the bit array won't be packed", "even if it doesn't end with a trailing comma!">> } "#, r#"pub fn main() { << "these are all simple strings", "but the bit array won't be packed", "even if it doesn't end with a trailing comma!", >> } "# ); } ================================================ FILE: compiler-core/src/format/tests/blocks.rs ================================================ use crate::assert_format; // https://github.com/gleam-lang/gleam/issues/2119 #[test] fn assignment() { assert_format!( r#"fn main() { let greeting = { "Hello" } greeting } "# ); } // https://github.com/gleam-lang/gleam/issues/2131 #[test] fn comment() { assert_format!( r#"fn main() { wibble( // Hello { Nil }, ) } "# ); } #[test] fn block_comment() { assert_format!( r#"fn main() { testbldr.demonstrate( named: "Hello, this argument is longer to make it all wrap", with: { // Comment! Nil Nil }, ) } "# ); } #[test] fn last_comments_are_not_moved_out_of_blocks() { assert_format!( r#"fn main() { { hello // Hope I'm not yeeted out of this block! } } "# ); assert_format!( r#"fn main() { { hello { { hi } // Some nested comments } // At the end of multiple blocks } } "# ); assert_format!( r#"fn main() { case wibble { wobble -> { 1 // Hope I can stay inside this clause } } } "# ); } ================================================ FILE: compiler-core/src/format/tests/cases.rs ================================================ use crate::assert_format; #[test] fn case_with_two_long_subjects() { assert_format!( r#"pub fn main() { case wibble(one_long_argument, something_else), wobble(another_argument, this_is_long) { _ -> todo } } "# ); } #[test] fn multiple_patterns_get_split_one_on_each_line() { assert_format!( r#"pub fn main() { case wibble, wobble, wubble { Wibble(one_thing, something_else, wibble), Wobble( this_will_go_over_the_line_limit, and_the_arguments_get_broken_as_well, wobble, ), Wubble(this_will_go_over_the_line_limit, wubble) -> todo } } "# ); } #[test] fn multiple_patterns_with_guard_get_split_one_on_each_line() { assert_format!( r#"pub fn main() { case wibble, wobble, wubble { Wibble(one_thing, something_else, wibble), Wobble(this_will_go_over_the_line_limit, wobble), Wubble(this_will_go_over_the_line_limit, wubble) if wibble && wobble || wubble -> todo } } "# ); } #[test] fn multiple_patterns_with_long_guard_get_split_one_on_each_line() { assert_format!( r#"pub fn main() { case wibble, wobble, wubble { Wibble(one_thing, something_else, wibble), Wobble(this_will_go_over_the_line_limit, wobble), Wubble(this_will_go_over_the_line_limit, wubble) if { wibble || wobble } && { wibble || wobble && wibble > 10 } || wobble < 10_000_000 -> todo } } "# ); } #[test] fn multiple_patterns_and_alternative_patterns_mixed_together() { assert_format!( r#"pub fn main() { case wibble, wobble, wubble { Wibble(one_thing, something_else, wibble), Wobble(this_will_go_over_the_line_limit, wobble), Wubble(this_will_go_over_the_line_limit, wubble) | Wibble(a), Wobble(b), Wubble(c) | Wibble(one_thing, something_else, wibble), Wobble(this_will_go_over_the_line_limit, wobble), Wubble(this_will_go_over_the_line_limit, wubble) if { wibble || wobble } && { wibble || wobble && wibble > 10 } || wobble < 10_000_000 -> todo } } "# ); } #[test] fn case_pattern_split_on_multiple_lines_is_not_needlessly_nested() { assert_format!( r#"pub fn main() { case thing { CannotSaveNewSnapshot( reason: reason, title: title, destination: destination, ) -> todo } } "# ); } // https://github.com/gleam-lang/gleam/issues/3140 #[test] fn long_comment_before_case_with_multiple_subjects_doesnt_force_a_break() { assert_format!( r#"fn main() { case a, b { // a very long comment a very long comment a very long comment a very long comment _, _ -> True } } "# ); } #[test] fn alternatives_are_not_split_if_not_necessary() { assert_format!( r#"fn main() { case thing { Wibble | Wobble -> { todo todo } } } "# ); } #[test] fn alternatives_are_not_split_if_not_necessary_2() { assert_format!( r#"fn main() { case thing { Wibble | Wobble | Wabble -> loooooooong_function_call_that_barely_goes_over_the_limit() } } "# ); } #[test] fn subjects_are_not_split_if_not_necessary() { assert_format!( r#"fn main() { case is_all_uppercase(remark), string.ends_with(remark, "?"), string.trim(remark) == "" { _, _, _ -> todo } } "# ); } #[test] fn case_in_call_gets_broken_if_it_goes_over_the_limit_with_subject() { assert_format!( r#"fn main() { do_diff_attributes( dict.delete(prev, attr.name), rest, case attr.value == old.value { True -> added False -> [attr, ..added] }, ) } "# ); } #[test] fn case_in_call_is_not_broken_if_it_goes_over_the_limit_with_branches() { assert_format!( r#"fn main() { do_diff_attributes(rest, case attr.value == old.value { True -> added False -> [attr, ..added] }) } "# ); } ================================================ FILE: compiler-core/src/format/tests/conditional_compilation.rs ================================================ use crate::{assert_format, assert_format_rewrite}; #[test] fn multiple() { assert_format!( "type X @target(erlang) type Y { Y } @target(javascript) type Z { Z } " ); } #[test] fn formatter_removes_target_shorthand_erlang() { assert_format_rewrite!( "@target(erl) fn wibble() { todo }", "@target(erlang) fn wibble() { todo } " ); } #[test] fn formatter_removes_target_shorthand_javascript() { assert_format_rewrite!( "@target(js) fn wibble() { todo }", "@target(javascript) fn wibble() { todo } " ); } ================================================ FILE: compiler-core/src/format/tests/constant.rs ================================================ use crate::assert_format; // https://github.com/gleam-lang/gleam/issues/5143 #[test] pub fn constant_with_deprecated_attribute() { assert_format!( r#"@deprecated("Use tau instead") pub const pi = 3.14 "# ); } #[test] fn const_record_update_simple() { assert_format!( r#"pub type Counter { Counter(a: Int, b: Int) } pub const c = Counter(0, 0) pub const c2 = Counter(..c, a: 1, b: 2) "# ); } #[test] fn const_record_update_long() { assert_format!( r#"pub type Counter { Counter(loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong: Int) } pub const c = Counter(0) pub const c2 = Counter( ..c, loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong: 1, ) "# ); } #[test] fn const_record_update_with_module() { assert_format!( r#"pub type Counter { Counter(a: Int, b: Int) } pub const c = Counter(0, 0) pub const c2 = mod.Counter(..c, a: 1) "# ); } #[test] fn const_list_prepend() { assert_format!( "pub const wibble = [2, 3, 4] pub const wobble = [0, 1, ..wibble] " ); } #[test] fn const_list_prepend_multiline() { assert_format!( r#"pub const wibble = ["Hello", "world"] pub const wobble = [ "Some really long message that causes the line to wrap", "Something else", ..wibble ] "# ); } #[test] fn const_list_prepend_multiline_with_comments() { assert_format!( r#"pub const wibble = ["Hello", "world"] pub const wobble = [ "Some really long message that causes the line to wrap", // Some comment "Something else", // Prepend the values to `wibble` ..wibble // End of list ] "# ); } ================================================ FILE: compiler-core/src/format/tests/custom_type.rs ================================================ use crate::assert_format; #[test] fn custom_type_0() { assert_format!( "type WowThisTypeHasJustTheLongestName( some_long_type_variable, and_another, and_another_again, ) { Make } " ); } #[test] fn custom_type_1() { assert_format!( "type Result(a, e) { Ok(a) Error(e) } " ); } #[test] fn custom_type_2() { assert_format!( "type Result(a, e) { Ok(value: a) Error(error: e) } " ); } #[test] fn custom_type_3() { assert_format!( "type SillyResult(a, e) { Ok( first_value_with_really_long_name: a, second_value_with_really_long_name: a, ) Error(error: e) } " ); } #[test] fn custom_type_4() { assert_format!( "type SillyResult(a, e) { Ok( first_value_with_really_long_name: a, second_value_with_really_long_name: List( #(Int, fn(a, a, a, a, a, a, a) -> List(a)), ), ) Error(error: e) } " ); } #[test] fn custom_type_5() { assert_format!( "type X { X( start: fn() -> a_reall_really_long_name_goes_here, stop: fn() -> a_reall_really_long_name_goes_here, ) } " ); } #[test] fn custom_type_6() { assert_format!( "pub opaque type X { X } " ); } #[test] fn custom_type_7() { assert_format!( "/// pub type Option(a) { None } " ); } // https://github.com/gleam-lang/gleam/issues/1757 #[test] fn multiple_line_custom_type_constructor_field_doc_comments() { assert_format!( r#"pub type Thingy { Thingy( /// One? /// One! one: One, /// Two? /// Two! two: Two, ) } "# ); } #[test] fn deprecated_custom_type() { assert_format!( r#"@deprecated("Deprecated type") pub type One { One } "# ); } #[test] fn doc_comments_7_test() { assert_format!( r#"import one /// one ///two type Whatever { Whatever } "# ); } #[test] fn comments1() { assert_format!( r#"import one // one //two type Whatever { Whatever } "# ); } #[test] fn comments2() { assert_format!( r#"import one // one //two /// three type Whatever { Whatever } "# ); } #[test] fn comments6() { assert_format!( r#"// one //two type Thingy "# ); } #[test] fn comments7() { assert_format!( r#"// one //two type Thingy "# ); } #[test] fn comments8() { assert_format!( r#"// one //two type Whatever { Whatever } "# ); } #[test] fn comments10() { assert_format!( r#"// zero import one // one //two type Whatever { Whatever } "# ); } #[test] fn comments11() { assert_format!( "fn main() { // Hello \"world\" } " ); } #[test] fn comment21() { assert_format!( "pub type Spec { Spec( // Hello hello: Int, // World world: Int, ) } " ); } #[test] fn commented_constructors() { assert_format!( "pub type Number { // 1 One // 2 Two // 3 Three // ??? More } " ); } #[test] fn commented_constructors1() { assert_format!( "pub type Number { /// 1 One /// 2 Two /// 3 Three /// ??? More } " ); } #[test] fn commented_constructors2() { assert_format!( "pub type Number { // a /// 1 One // b /// 2 Two // c /// 3 Three // defg /// ??? More } " ); } #[test] fn commented_constructors3() { assert_format!( "pub type Number { /// 1 One(value: Int) /// > 1 Many(value: Int) } " ); } #[test] fn deprecated_variant_1() { assert_format!( r#"pub type One { @deprecated("Deprecated type") One } "# ); } #[test] fn deprecated_variant_2() { assert_format!( r#"pub type One { @deprecated("Deprecated type") One(Int, Int, Int, Int, Int, Int, Int) } "# ); } #[test] fn deprecated_variant_3() { assert_format!( r#"pub type One { @deprecated("Deprecated type with a very long message") One(Int, Int, Int, Int, Int, Int, Int) } "# ); } #[test] fn deprecated_variant_4() { assert_format!( r#"pub type One { @deprecated("Deprecated type with a very long message It even has multiple lines! ") One(Int, Int, Int, Int, Int, Int, Int) } "# ); } #[test] fn external_custom_type() { assert_format!( r#"@external(erlang, "erlang", "map") @external(javascript, "../dict.d.mts", "Dict") pub type Dict(key, value) "# ); } ================================================ FILE: compiler-core/src/format/tests/echo.rs ================================================ use crate::{assert_format, assert_format_rewrite}; #[test] fn echo() { assert_format!( "fn main() { echo } " ); } #[test] fn echo_with_value() { assert_format!( r#"fn main() { echo value } "# ); } #[test] fn echo_with_big_value_that_needs_to_be_split() { assert_format!( r#"fn main() { echo [ this_is_a_long_list_and_requires_splitting, wibble_wobble_woo, multiple_lines, ] } "# ); } #[test] fn echo_inside_a_pipeline() { assert_format!( r#"fn main() { wibble |> echo |> wobble } "# ); } #[test] fn echo_inside_a_single_line_pipeline() { assert_format!( r#"fn main() { wibble |> echo |> wobble } "# ); } #[test] fn echo_as_last_item_of_pipeline() { assert_format!( r#"fn main() { wibble |> wobble |> echo } "# ); } #[test] fn echo_as_last_item_of_multiline_pipeline() { assert_format!( r#"fn main() { wibble |> wobble |> echo } "# ); } #[test] fn echo_with_related_expression_on_following_line() { assert_format_rewrite!( r#"fn main() { panic as echo "wibble" } "#, r#"fn main() { panic as echo "wibble" } "# ); } #[test] fn echo_with_following_value_in_a_pipeline() { assert_format_rewrite!( r#"fn main() { [] |> echo wibble |> wobble } "#, r#"fn main() { [] |> echo wibble |> wobble } "# ); } #[test] fn echo_printing_multiline_pipeline() { assert_format_rewrite!( r#"fn main() { echo first |> wibble |> wobble } "#, r#"fn main() { echo first |> wibble |> wobble } "# ); } #[test] fn echo_printing_one_line_pipeline() { assert_format!( r#"fn main() { echo first |> wibble |> wobble } "# ); } #[test] fn echo_as() { assert_format!( "fn main() { echo as hello } " ); } #[test] fn echo_as_with_value() { assert_format!( r#"fn main() { echo value as message } "# ); } #[test] fn echo_as_with_big_value_that_needs_to_be_split() { assert_format!( r#"fn main() { echo call([ this_is_a_long_list_and_requires_splitting, wibble_wobble_woo, multiple_lines, ]) as "tag!" } "# ); } #[test] fn echo_as_inside_a_pipeline() { assert_format!( r#"fn main() { wibble |> echo as "echooo o o" |> wobble } "# ); } #[test] fn echo_as_inside_a_single_line_pipeline() { assert_format!( r#"fn main() { wibble |> echo as string |> wobble } "# ); } #[test] fn echo_as_as_last_item_of_pipeline() { assert_format!( r#"fn main() { wibble |> wobble |> echo as end } "# ); } #[test] fn echo_as_as_last_item_of_multiline_pipeline() { assert_format!( r#"fn main() { wibble |> wobble |> echo as message } "# ); } #[test] fn echo_as_with_related_expression_on_following_line() { assert_format_rewrite!( r#"fn main() { panic as echo "wibble" as wobble } "#, r#"fn main() { panic as echo "wibble" as wobble } "# ); } #[test] fn echo_as_with_following_value_in_a_pipeline() { assert_format_rewrite!( r#"fn main() { [] |> echo as wibble wibble |> wobble } "#, r#"fn main() { [] |> echo as wibble wibble |> wobble } "# ); } #[test] fn echo_as_printing_multiline_pipeline() { assert_format_rewrite!( r#"fn main() { echo first |> wibble |> wobble as "pipeline" } "#, r#"fn main() { echo first |> wibble |> wobble as "pipeline" } "# ); } #[test] fn echo_as_printing_one_line_pipeline() { assert_format!( r#"fn main() { echo first |> wibble |> wobble as "pipeline" } "# ); } #[test] fn echo_as_with_multiline_message() { assert_format!( r#"fn main() { echo [wibble, wobble] as { // Force this block to split "wibble" <> wobble() } } "# ); } ================================================ FILE: compiler-core/src/format/tests/external_fn.rs ================================================ use crate::assert_format; #[test] fn no_body_erlang() { assert_format!( r#"@external(erlang, "one", "one") fn one(x: Int) -> Int "# ); } #[test] fn no_body_javascript() { assert_format!( r#"@external(javascript, "one", "one") fn one(x: Int) -> Int "# ); } #[test] fn no_body_body() { assert_format!( r#"@external(erlang, "two", "two") @external(javascript, "one", "one") fn one(x: Int) -> Int "# ); } #[test] fn erlang() { assert_format!( r#"@external(erlang, "one", "one") fn one(x: Int) -> Int { todo } "# ); } #[test] fn javascript() { assert_format!( r#"@external(javascript, "one", "one") fn one(x: Int) -> Int { todo } "# ); } #[test] fn body() { assert_format!( r#"@external(erlang, "two", "two") @external(javascript, "one", "one") fn one(x: Int) -> Int { todo } "# ); } // https://github.com/gleam-lang/gleam/issues/2259 #[test] fn break_external_fn_arguments() { assert_format!( r#"@external(erlang, "ffi", "improper_list_append") fn improper_list_append( a: item_a, b: item_b, c: improper_tail, ) -> List(anything) "# ); } // Bug found by Hayleigh #[test] fn long_long_external() { assert_format!( r#"@external(javascript, "./client-component.ffi.mjs", "register") pub fn register( _app: App(WebComponent, Nil, model, msg), _name: String, ) -> Result(Nil, Error) { Error(NotABrowser) } "# ); } ================================================ FILE: compiler-core/src/format/tests/external_types.rs ================================================ use crate::assert_format; #[test] fn example1() { assert_format!("type Private\n"); } #[test] fn example2() { assert_format!("type Box(a)\n"); } #[test] fn example3() { assert_format!("type Box(a, b, zero)\n"); } #[test] fn example4() { assert_format!("pub type Private\n"); } #[test] fn example5() { assert_format!("pub type Box(a)\n"); } #[test] fn example6() { assert_format!("pub type Box(a, b, zero)\n"); } ================================================ FILE: compiler-core/src/format/tests/function.rs ================================================ use crate::{assert_format, assert_format_rewrite}; #[test] fn capture_with_single_argument() { assert_format_rewrite!( "pub fn main() -> Nil { wibble([], wobble(_)) } ", "pub fn main() -> Nil { wibble([], wobble) } " ); } #[test] fn deprecated() { assert_format!( r#"@deprecated("use something else instead") pub fn main() -> Nil { Nil } "# ); } #[test] fn deprecated_external() { assert_format!( r#"@deprecated("use something else instead") @external(erlang, "thing", "main") pub fn main() -> Nil "# ); } #[test] fn anonymous_function_as_final_function_argument() { assert_format!( r#"pub fn main() { some_function(123, 456, fn(x) { let y = x + 1 y }) } "# ); } #[test] fn anonymous_function_with_single_line_body_as_final_function_argument() { assert_format!( r#"pub fn main() { some_function(123, 456, fn(x) { x }) } "# ); } #[test] fn anonymous_function_with_multi_line_unbreakable_body_as_final_function_argument() { assert_format!( r#"pub fn main() { some_function(123, 456, fn(x) { call_to_other_function(a, b, c, d, e, f, g, h) }) } "# ); } #[test] fn anonymous_function_with_multi_line_breakable_body_as_final_function_argument() { assert_format!( r#"pub fn main() { some_function(123, 456, fn(x) { call_to_other_function(a, b, c, d, e, f, g, { x + x }) }) } "# ); } #[test] fn anonymous_function_with_multi_line_long_breakable_body_as_final_function_argument() { assert_format!( r#"pub fn main() { some_function(123, 456, fn(x) { call_to_other_function(a, b, c, d, e, f, g, case wibble { Wibble -> 1 Wobble -> 2 }) }) } "# ); } #[test] fn function_call_as_final_function_argument_goes_on_its_own_line() { assert_format!( r#"pub fn main() { some_function_with_a_long_name( 123, 456, another_function_being_called(123, 456), ) } "# ); } #[test] fn tuple_as_final_function_argument() { assert_format!( r#"pub fn main() { some_function(123, 456, #( "Here is a very long string which causes the formatter to wrap it", )) } "# ); } #[test] fn list_as_final_function_argument() { assert_format!( r#"pub fn main() { some_function(123, 456, [ "Here is a very long string which causes the formatter to wrap it", ]) } "# ); } #[test] fn case_expression_as_final_function_argument() { assert_format!( r#"pub fn main() { some_function(123, 456, case my_var { True -> True False -> False }) } "# ); } #[test] fn block_as_final_function_argument() { assert_format!( r#"pub fn main() { some_function(123, 456, { let days = 7 days * 24 * 60 * 60 }) } "# ); } #[test] fn when_all_arguments_are_too_long_each_one_is_on_its_own_line() { assert_format!( r#"pub fn main() { some_function( variable_with_really_long_name, whoah_this_is_getting_out_of_hand, ["Here is a very long string which causes the formatter to wrap it"], ) } "# ); } #[test] fn nested_breakable_lists_in_function_calls() { assert_format!( r#"pub fn main() { html([attribute("lang", "en")], [ head([attribute("wibble", "wobble")], [ title([], [text("Hello this is some HTML")]), ]), body([], [h1([], [text("Hello, world!")])]), ]) } "# ); } #[test] fn nested_breakable_tuples_in_function_calls() { assert_format!( r#"pub fn main() { html(#(attribute("lang", "en")), #( head(#(attribute("wibble", "wobble")), #( title(#(), #(text("Hello this is some HTML"))), body(#(), #(text("Hello this is some HTML"))), )), body(#(), #(h1(#(), #(text("Hello, lisp!"))))), )) } "# ); } // https://github.com/gleam-lang/gleam/issues/2435 #[test] fn only_last_argument_can_be_broken() { assert_format!( r#"pub fn main() { tbd.workbook(for: "my_project") |> task( doc: "Run the project tests", tags: list.concat([["test", "ci"], gleam, typescript]), action: fn(_, _) { tbd.command(run: "gleam", with: ["test"]) }, ) |> run } "# ); assert_format!( r#"pub fn main() { Theme( flag: styler([32]), heading: styler([1, 95]), highlight: styler([1, 36]), parameter: styler([34]), tag: styler([33]), given_tag: styler([3]), first_tag: styler([1]), tab: " ", ) } "# ); } #[test] fn function_that_is_a_little_over_the_limit() { assert_format!( r#"pub fn handle_request( handler: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, ) -> Nil { todo } "# ); } // https://github.com/gleam-lang/gleam/issues/2571 #[test] fn expr_function_as_last_argument() { assert_format!( r#"pub fn main() { Builder( accumulator: "", update: fn(accum, val) { accum <> val }, final: fn(accum) { accum }, ) } "# ); // We want to make sure that, if it goes over the limit NOT with its // arguments' list the body is still the first thing that gets split. assert_format!( r#"pub fn main() { Builder(accumulator: "", update: fn(accum, val) { accum }, final: fn(accum) { accum }) } "# ); } #[test] fn comment_at_start_of_inline_function_body() { assert_format!( r#"pub fn main() { let add = fn(x: Int, y: Int) { // This is a comment x + y } } "# ); } #[test] fn comment_at_start_of_top_level_function_body() { assert_format!( r#"pub fn add(x: Int, y: Int) { // This is a comment x + y } "# ); } #[test] fn comment_at_end_of_inline_function_args() { assert_format!( r#"pub fn main() { let add = fn( x: Int, y: Int, // This is a comment ) { x + y } } "# ); } #[test] fn comment_middle_of_inline_function_body() { assert_format!( r#"pub fn main() { let add = fn(x: Int, y: Int, z: Int) { let a = x + y // This is a comment a + z } } "# ); } // https://github.com/gleam-lang/gleam/issues/5004 #[test] fn comment_in_tuple_return_type() { assert_format_rewrite!( r#"pub fn main() -> #( // This is a string String, // This is an awesome string ) { todo } "#, r#"pub fn main() -> #( String, // This is a string // This is an awesome string ) { todo } "# ); } ================================================ FILE: compiler-core/src/format/tests/guards.rs ================================================ use crate::assert_format; #[test] fn field_access() { assert_format!( r#"pub fn main() { case x { _ if a.b -> 1 _ -> 0 } } "# ); } #[test] fn nested_field_access() { assert_format!( r#"pub fn main() { case x { _ if a.b.c.d -> 1 _ -> 0 } } "# ); } #[test] fn operators_in_guard() { assert_format!( r#"pub fn main() { case list.map(codepoints, string.utf_codepoint_to_int) { [drive, colon, slash] if { slash == 47 || slash == 92 } && colon == 58 && drive >= 65 && drive <= 90 || drive >= 97 && drive <= 122 -> { 1 |> 2 } } } "# ); } #[test] fn a_comment_before_a_guard_doesnt_force_it_to_break() { assert_format!( r#"pub fn main() { case wibble { // Apparently this comment breaks everything _ if wobble -> Ok(state.newest) } } "# ); } #[test] fn long_guard_with_alternative_patterns() { assert_format!( r#"pub fn main() { case wibble { Wibble(first_one) | Wibble(another_one) | Wibble( this_is_extra_long_to_go_over_the_line_limit, this_gets_broken_as_well, ) if True -> Ok(wibble) } } "# ); } #[test] fn guard_block_is_not_removed_even_if_redundant() { assert_format!( r#"pub fn main() { case todo { _ if { True && True } && True -> 1 _ -> 2 } } "# ); } #[test] fn nested_guard_block_is_not_removed_even_if_redundant() { assert_format!( r#"pub fn main() { case todo { _ if { True && { True && False } } && True -> 1 _ -> 2 } } "# ); } ================================================ FILE: compiler-core/src/format/tests/imports.rs ================================================ use crate::{assert_format, assert_format_rewrite}; #[test] fn types_and_values() { assert_format!( "import one/two.{type Abc, type Bcd, Abc, Bcd, abc, bcd} " ); } #[test] fn discarded_import() { assert_format!( "import one/two as _three " ); } #[test] fn discarded_import_with_unqualified() { assert_format!( "import one/two.{type Abc, Bcd, abc} as _three " ); } #[test] fn redundant_as_name_is_removed() { assert_format_rewrite!("import wibble/wobble as wobble", "import wibble/wobble\n"); assert_format_rewrite!("import wibble as wibble", "import wibble\n"); } #[test] fn imports_are_sorted_alphabetically() { assert_format_rewrite!( "import c import a/a import a/c import b import a/ab import a", "import a import a/a import a/ab import a/c import b import c " ); } #[test] fn import_groups_are_respected() { assert_format_rewrite!( "import group_one/a import group_one/b // another group import group_two/wobble import group_two/wibble // yet another group import group_three/b import group_three/c import group_three/a ", "import group_one/a import group_one/b // another group import group_two/wibble import group_two/wobble // yet another group import group_three/a import group_three/b import group_three/c " ); } #[test] fn empty_lines_define_different_groups() { assert_format_rewrite!( "import c @target(javascript) import b import a import gleam/string import gleam/list", "@target(javascript) import b import c import a import gleam/list import gleam/string " ); } #[test] fn import_groups_with_empty_lines_and_comments() { assert_format_rewrite!( "import c @target(javascript) import b import a // third group import gleam/string import gleam/list import wobble import wibble ", "@target(javascript) import b import c import a // third group import gleam/list import gleam/string import wibble import wobble " ); } #[test] fn type_definition_in_between_imports() { assert_format!( r#"import a import b pub type Wibble(a) { Wobble } import c import d import e pub type Wabble import f "# ); } #[test] fn function_definition_in_between_imports() { assert_format!( r#"import a import b pub fn wibble() { todo } import c import d import e pub fn wobble() -> Int { todo } import f "# ); } #[test] fn constant_definition_in_between_imports() { assert_format!( r#"import a import b pub const wibble = Wibble import c import d import e const wobble = 1 import f "# ); } // https://github.com/gleam-lang/gleam/issues/2915 #[test] fn white_lines_between_comments_in_import_groups_are_preserved() { assert_format!( r#"import a // comment import b "# ); } // https://github.com/gleam-lang/gleam/issues/2915 #[test] fn import_sorting_doesnt_add_spurious_white_line() { assert_format!( r#"// comment import filepath import gleam/dynamic.{type Dynamic} import gleam/io import gleam/list "# ); } ================================================ FILE: compiler-core/src/format/tests/lists.rs ================================================ use crate::{assert_format, assert_format_rewrite}; #[test] fn list_with_trailing_comma_is_broken() { assert_format_rewrite!( "pub fn main() { [ 1, 2, a, ] }", r#"pub fn main() { [ 1, 2, a, ] } "# ); } #[test] fn constant_list_with_trailing_comma_is_broken() { assert_format_rewrite!( "const list = [ 1, 2, a, ]", r#"const list = [ 1, 2, a, ] "# ); } #[test] fn list_with_trailing_comma_is_kept_broken() { assert_format!( r#"pub fn main() { [ 1, 2, a, ] } "# ); } #[test] fn constant_list_with_trailing_comma_is_kept_broken() { assert_format!( r#"const list = [ 1, 2, a, ] "# ); } #[test] fn list_with_no_trailing_comma_is_packed_on_a_single_line() { assert_format_rewrite!( r#"pub fn main() { [ 1, 2, a ] } "#, r#"pub fn main() { [1, 2, a] } "# ); } #[test] fn constant_list_with_no_trailing_comma_is_packed_on_a_single_line() { assert_format_rewrite!( r#"const list = [ 1, 2, a ]"#, "const list = [1, 2, a]\n" ); } #[test] fn list_with_no_comma_is_packed_on_a_single_line_or_split_one_item_per_line() { assert_format_rewrite!( "pub fn main() { [ 1, a, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, b, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312 ] } ", "pub fn main() { [ 1, a, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, b, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, ] } " ); } #[test] fn constant_list_with_no_comma_is_packed_on_a_single_line_or_split_one_item_per_line() { assert_format_rewrite!( "const list = [ 1, a, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, b, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312 ] ", "const list = [ 1, a, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, b, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, 12_312_312_312_312_312_312_312, ] " ); } #[test] fn simple_list_with_no_comma_is_packed_on_a_single_line_or_split_one_item_per_line() { assert_format_rewrite!( r#"pub fn main() { [ "hello", "wibble wobble", "these are all simple strings", "but the list won't be packed", "the formatter will keep", "one item", "per line", "since there's no trailing comma here ->" ] } "#, r#"pub fn main() { [ "hello", "wibble wobble", "these are all simple strings", "but the list won't be packed", "the formatter will keep", "one item", "per line", "since there's no trailing comma here ->", ] } "# ); } #[test] fn simple_list_with_trailing_comma_and_multiple_items_per_line_is_packed() { assert_format_rewrite!( r#"pub fn main() { [ "hello", "wibble wobble", "these are all simple strings", "and the list will be packed since the following strings are", "on the same", "line", "and there's a trailing comma ->", ] } "#, r#"pub fn main() { [ "hello", "wibble wobble", "these are all simple strings", "and the list will be packed since the following strings are", "on the same", "line", "and there's a trailing comma ->", ] } "# ); } #[test] fn simple_constant_list_with_trailing_comma_and_multiple_items_per_line_is_packed() { assert_format_rewrite!( r#"pub const list = [ "hello", "wibble wobble", "these are all simple strings", "and the list will be packed since the following strings are", "on the same", "line", "and there's a trailing comma ->", ] "#, r#"pub const list = [ "hello", "wibble wobble", "these are all simple strings", "and the list will be packed since the following strings are", "on the same", "line", "and there's a trailing comma ->", ] "# ); } #[test] fn simple_packed_list_with_trailing_comma_is_kept_with_multiple_items_per_line() { assert_format!( r#"pub fn main() { [ "hello", "wibble wobble", "these are all simple strings", "and the list will be kept packed since it ends with a trailing comma", "right here! ->", ] } "# ); } #[test] fn simple_single_line_list_with_trailing_comma_is_split_one_item_per_line() { assert_format_rewrite!( r#"pub fn main() { ["these are all simple strings", "but the list won't be packed", "since it ends with a trailing comma ->",] } "#, r#"pub fn main() { [ "these are all simple strings", "but the list won't be packed", "since it ends with a trailing comma ->", ] } "# ); } #[test] fn simple_single_line_list_with_no_trailing_comma_is_split_one_item_per_line() { assert_format_rewrite!( r#"pub fn main() { ["these are all simple strings", "but the list won't be packed", "even if it doesn't end with a trailing comma!"] } "#, r#"pub fn main() { [ "these are all simple strings", "but the list won't be packed", "even if it doesn't end with a trailing comma!", ] } "# ); } #[test] fn empty_lines_in_list_are_not_ignored() { assert_format_rewrite!( "pub fn main() { [1, 2, 3 ] } ", "pub fn main() { [ 1, 2, 3, ] } " ); } #[test] fn empty_lines_in_const_list_are_not_ignored() { assert_format_rewrite!( "const list = [1, 2, 3 ] ", "const list = [ 1, 2, 3, ] " ); } #[test] fn lists_with_empty_lines_are_always_broken() { assert_format_rewrite!( "pub fn main() { [ 1, 2, 3, 4, 5 ] } ", "pub fn main() { [ 1, 2, 3, 4, 5, ] } " ); } #[test] fn const_lists_with_empty_lines_are_always_broken() { assert_format_rewrite!( "const list = [ 1, 2, 3, 4, 5 ] ", "const list = [ 1, 2, 3, 4, 5, ] " ); } ================================================ FILE: compiler-core/src/format/tests/pipeline.rs ================================================ use crate::{assert_format, assert_format_rewrite}; #[test] pub fn single_line_pipeline_longer_than_line_limit_gets_split() { assert_format_rewrite!( r#"pub fn main() { wibble |> wobble |> loooooooooooooooooooooooooooooooooooooooooooong_function_name } "#, r#"pub fn main() { wibble |> wobble |> loooooooooooooooooooooooooooooooooooooooooooong_function_name } "#, ); } #[test] pub fn single_line_pipeline_shorter_than_line_limit_is_kept_on_a_single_line() { assert_format!( r#"pub fn main() { wibble(1) |> wobble } "# ); } #[test] pub fn multi_line_pipeline_is_split_no_matter_the_length() { assert_format!( r#"pub fn main() { wibble(1) |> wobble } "# ); } #[test] pub fn adding_a_newline_to_a_pipeline_splits_all() { assert_format_rewrite!( r#"pub fn main() { wibble |> wobble |> wabble } "#, r#"pub fn main() { wibble |> wobble |> wabble } "#, ); } #[test] pub fn multiline_function_inside_pipeline_function_argument_is_indented_properly() { assert_format!( r#"pub fn main() { function( arg0, thing |> string.replace( "{something something}", date.month_to_string(month, config.l10n.context), ), ) } "#, ); } #[test] pub fn multiline_function_inside_pipeline_in_list_is_indented_properly() { assert_format!( r#"pub fn main() { [ item1, thing |> string.replace( "{something something}", date.month_to_string(month, config.l10n.context), ), ] } "#, ); } #[test] pub fn multiline_function_inside_pipeline_in_tuple_is_indented_properly() { assert_format!( r#"pub fn main() { #( item1, thing |> string.replace( "{something something}", date.month_to_string(month, config.l10n.context), ), ) } "#, ); } #[test] fn pipe_with_labelled_first_argument_capture() { assert_format!( "fn wibble(label1 a, label2 b, lots c, of d, labels e) { a + b * c - d / e } fn main() { 1 |> wibble(label1: _, label2: 2, lots: 3, of: 4, labels: 5) } " ); } #[test] fn pipe_with_labelled_only_argument_capture() { assert_format!( "fn wibble(descriptive_label value) { value } fn main() { 42 |> wibble(descriptive_label: _) } " ); } ================================================ FILE: compiler-core/src/format/tests/record_update.rs ================================================ use crate::assert_format; #[test] fn one() { assert_format!( "pub type Counter { Counter(a: Int, b: Int) } fn main() { let c = Counter(0, 0) let c = Counter(..c, a: c.a + 1, b: c.a + c.b) c } " ); } #[test] fn two() { // Long record updates are split onto multiple lines assert_format!( "pub type Counter { Counter(loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong: Int) } fn main() { let c = Counter(0) let c = Counter( ..c, loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong: 1, ) c } " ); } // https://github.com/gleam-lang/gleam/issues/1872 #[test] fn comment_before_spread() { assert_format!( r#"fn main() { Thingy( // Def? // Def! ..thingy.defaults, one: One, ) } "# ); } // https://github.com/gleam-lang/gleam/issues/1872 #[test] fn comment_before_update_label() { assert_format!( r#"fn main() { Thingy( ..thingy.defaults, // Def? // Def! one: One, ) } "# ); } // https://github.com/gleam-lang/gleam/issues/1872 #[test] fn multiple_line_custom_type_field_comments() { assert_format!( r#"fn main() { Thingy( // Def? // Def! ..thingy.defaults, // One? // One! one: One, // Two? // Two! two: Two, ) } "# ); } // https://github.com/gleam-lang/gleam/issues/4120 #[test] fn record_update_gets_formatted_like_a_function_call() { assert_format!( r#"pub fn example() { Record(..record, field: { use _ <- list.map(record.field) io.print("Example") }) } "# ); } #[test] fn record_with_record_and_spread_field_is_not_needlessly_broken() { assert_format!( "pub fn main() { case todo { Wibble( some_field: Wobble(something: 1, ..), other_field_1:, other_field_2:, other_field_3:, .., ) -> todo } } " ); } ================================================ FILE: compiler-core/src/format/tests/tuple.rs ================================================ use crate::{assert_format, assert_format_rewrite}; // https://github.com/gleam-lang/gleam/issues/2083 #[test] fn nested_index_block() { assert_format!( r#"pub fn main() { { #(1, 2).1 }.1 } "# ); } // https://github.com/gleam-lang/gleam/issues/2083 #[test] fn index_block() { assert_format!( r#"pub fn main() { { 1 #(1, 2) }.1 } "# ); } #[test] fn tuple_with_last_splittable_arg() { assert_format!( r#"fn on_attribute_change() -> Dict(String, Decoder(Msg)) { dict.from_list([ #("value", fn(attr) { attr |> dynamic.int |> result.map(Value) |> result.map(AttributeChanged) }), ]) } "# ); assert_format!( r#"pub fn main() { #("value", [ "a long list that needs to be split on multiple lines", "another long string", ]) } "# ); } // https://github.com/gleam-lang/gleam/issues/3070 #[test] fn constant_long_list_of_tuples() { assert_format!( r#"const wibble = [ #(1, 2), #(3, 4), #(5, 6), #(7, 8), #(9, 10), #(11, 12), #(1, 2), #(3, 4), #(5, 6), #(7, 8), #(9, 10), #(11, 12), ] pub fn main() { todo } "# ); } #[test] fn nested_tuple_access() { assert_format!( r#"pub fn main() { wibble.1.0 } "# ); } #[test] fn nested_tuple_with_needless_block() { assert_format_rewrite!( r#"pub fn main() { { wibble.1 }.0 } "#, r#"pub fn main() { wibble.1.0 } "# ); } #[test] fn nested_literal_tuple_with_needless_block_is_not_changed() { assert_format!( r#"pub fn main() { { #(wibble, wobble).1 }.0 } "# ); } ================================================ FILE: compiler-core/src/format/tests/use_.rs ================================================ use crate::{assert_format, assert_format_rewrite}; #[test] fn use_1() { assert_format!( r#"pub fn main() { use <- benchmark("thingy") todo } "# ); } #[test] fn use_2() { assert_format!( r#"pub fn main() { use user <- login() todo } "# ); } #[test] fn use_3() { assert_format!( r#"pub fn main() { use one, two, three, four <- get_multiple_things() todo } "# ); } #[test] fn use_4() { assert_format!( r#"pub fn main() { use one, two, three, four, five, six, seven, eight, nine, ten, eleven, twelve, thirteen <- get_multiple_things_with_a_longer_function todo } "# ); } #[test] fn use_5() { assert_format!( r#"pub fn main() { use one, two, three, four, five, six, seven, eight, nine, ten, eleven, twelve, thirteen <- get_multiple_things_with_a_longer_function(a, b, c, d) todo } "# ); } #[test] fn use_6() { assert_format!( r#"pub fn main() { use one, two, three, four, five, six, seven, eight, nine, ten, eleven, twelve, thirteen <- get_multiple_things_with_a_longer_function( "one", "two", "three", "four", "five", "six", "seven", "eight", ) todo } "# ); } #[test] fn pipe_call() { assert_format!( r#"pub fn main() { use <- a |> b c } "# ); } #[test] fn use_pipe_everything() { assert_format!( r#"pub fn main() { { use <- a todo } |> b c } "# ); } #[test] fn long_right_hand_side_0_arguments() { assert_format!( r#"pub fn main() { use <- some_really_long_function_call( "one", "two", "three", "four", "five", "six", "seven", "eight", ) todo } "# ); } #[test] fn long_right_hand_side_1_argument() { assert_format!( r#"pub fn main() { use x <- some_really_long_function_call( "one", "two", "three", "four", "five", "six", "seven", "eight", ) todo } "# ); } #[test] fn long_right_hand_side_2_arguments() { assert_format!( r#"pub fn main() { use x, y <- some_really_long_function_call( "one", "two", "three", "four", "five", "six", "seven", "eight", ) todo } "# ); } #[test] fn arity_1_var_call() { assert_format!( r#"pub fn main() { use x, y <- await( file.read() |> promise.map(something), ) todo } "# ); } #[test] fn arity_1_access_call() { assert_format!( r#"pub fn main() { use x, y <- promise.await( file.read() |> promise.map(something), ) todo } "# ); } #[test] fn patterns() { assert_format!( r#"pub fn main() { use Box(x) <- apply(Box(1)) x } "# ); } #[test] fn patterns_with_annotation() { assert_format!( r#"pub fn main() { use Box(x): Box(Int) <- apply(Box(1)) x } "# ); } #[test] fn long_patterns() { assert_format!( r#"pub fn main() { use Box( xxxxxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzzzzzzzzzz, ) <- apply(Box(1)) x } "# ); } #[test] fn multiple_long_patterns() { assert_format!( r#"pub fn main() { use Box( xxxxxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzzzzzzzzzz, ), Box(_), Box(_), Box(_) <- apply(Box(1)) x } "# ); } #[test] fn multiple_long_patterns_with_annotations() { assert_format!( r#"pub fn main() { use Box( xxxxxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzzzzzzzzzz, ): Box(Int, Bool, String), Box(_) <- apply(Box(1)) x } "# ); } #[test] fn multiple_long_annotations() { assert_format!( r#"pub fn main() { use Box(_, _): Box( Xxzxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, Yyyyyyyyyyyyyyyyyyyyyyyy, ), Box(_) <- apply(Box(1)) x } "# ); } // https://github.com/gleam-lang/gleam/issues/2114 #[test] fn comment() { assert_format!( r#"fn main() { // comment use x <- result.then(y) todo } "# ); } // https://github.com/gleam-lang/gleam/issues/3605 #[test] fn use_with_empty_callback_body_is_rewritten_to_have_a_todo() { assert_format_rewrite!( r#"fn main() { use wibble, wobble <- woo } "#, r#"fn main() { use wibble, wobble <- woo todo } "# ); } ================================================ FILE: compiler-core/src/format/tests.rs ================================================ use itertools::Itertools; use pretty_assertions::assert_eq; mod asignments; mod binary_operators; mod bit_array; mod blocks; mod cases; mod conditional_compilation; mod constant; mod custom_type; mod echo; mod external_fn; mod external_types; mod function; mod guards; mod imports; mod lists; mod pipeline; mod record_update; mod tuple; mod use_; #[macro_export] macro_rules! assert_format { ($src:expr $(,)?) => { let mut writer = String::new(); $crate::format::pretty(&mut writer, &$src.into(), camino::Utf8Path::new("")) .unwrap(); assert_eq!($src, writer); }; } #[macro_export] macro_rules! assert_format_rewrite { ($src:expr, $expected:expr $(,)?) => { let mut writer = String::new(); $crate::format::pretty(&mut writer, &$src.into(), camino::Utf8Path::new("")) .unwrap(); assert_eq!(writer, $expected); }; } #[test] fn imports() { assert_format!("\n"); assert_format!("import one\n"); assert_format!("import one\nimport two\n"); assert_format!("import one/two/three\n"); assert_format!("import four/five\nimport one/two/three\n"); assert_format!("import one.{fun, fun2, fun3}\n"); assert_format!("import one.{One, Two, fun1, fun2}\n"); assert_format!("import one.{main as entrypoint}\n"); assert_format!("import one/two/three as free\n"); assert_format!("import one/two/three.{thunk} as free\n"); assert_format!("import one/two/three.{thunk as funky} as free\n"); assert_format!( "import my/cool/module.{ Ane, Bwo, Chree, Dour, Eive, Fix, Geven, Hight, Iine, Jen, Kleven, Lwelve, Mhirteen, Nifteen, Oixteen, } " ); assert_format!( "import gleam/result.{ Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, Abcde, End, } " ); } #[test] fn multiple_statements_test() { assert_format!( r#"import one import three import two pub type One pub type Two pub type Three pub type Four "# ); } #[test] fn type_alias() { assert_format!( "type Option(a) = Result(a, Nil) " ); assert_format!( "pub type Option(a) = Result(a, Nil) " ); assert_format!( "pub type Pair(a, b) = #(a, b) " ); assert_format!( "pub type Sixteen(element) = #( element, element, element, element, element, element, element, element, element, element, element, element, element, element, element, element, ) " ); assert_format!( "pub type Sixteen(element) = fn( element, element, element, element, element, element, element, element, element, element, element, element, element, element, element, element, ) -> #( element, element, element, element, element, element, element, element, element, element, element, element, element, element, element, element, ) " ); // assert_format!( // "pub type Curried(element) = // fn() -> // elementttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt //" // ); // assert_format!( // "pub type Sixteen(element) = // fn(element) -> // #( // element, // element, // element, // element, // element, // element, // element, // element, // element, // element, // element, // element, // element, // element, // element, // element, // ) //" // ); assert_format!( "pub type Curried(element) = fn(element) -> fn(element) -> element " ); // assert_format!( // "pub type Curried(element) = // fn(element) // -> fn(element) // -> fn(element) // -> fn(element) // -> fn(element) // -> element //" // ); assert_format!( "type WowThisTypeHasJustTheLongestName = WowThisTypeHasAnEvenLongerNameHowIsThatPossible " ); assert_format!( "type WowThisTypeHasJustTheLongestName = Container( Int, String, List(a), SomethingElse, WowThisTypeHasJustTheLongestName, ) " ); assert_format!( "type WowThisTypeHasJustTheLongestName( some_long_type_variable, and_another, and_another_again, ) = Container( Int, String, List(a), SomethingElse, WowThisTypeHasJustTheLongestName, ) " ); assert_format!( "/// type Many(a) = List(a) " ); } #[test] fn expr_fn() { assert_format!( r#"fn main() { fn(x) { x } } "# ); assert_format!( r#"fn main() { fn(_) { x } } "# ); assert_format!( r#"fn main() { fn(_discarded) { x } } "# ); assert_format!( r#"fn main() { fn() { 1 2 } } "# ); assert_format!( r#"fn main() { fn() { let y = x y } } "# ); assert_format!( r#"fn main() { fn() { let x: Int = 1 x } } "# ); assert_format!( r#"fn main() { fn() { let x: Box(_) = call() x } } "# ); assert_format!( r#"fn main() { fn() { let x: Box(_whatever) = call() x } } "# ); assert_format!( r#"fn main() { fn(_) { 1 2 } } "# ); assert_format!( r#"fn main() { fn(_) -> Int { 1 2 } } "# ); assert_format!( r#"fn main() { fn(_: Int) -> Int { 2 } } "# ); assert_format!( r#"fn main() { fn(x) { case x { Ok(i) -> i + 1 Error(_) -> 0 } } } "# ); } #[test] fn expr_call() { assert_format!( r#"fn main() { run() } "# ); assert_format!( r#"fn main() { run(1) } "# ); assert_format!( r#"fn main() { run(with: 1) } "# ); assert_format!( r#"fn main() { run( with: 1, loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong: 1, ) } "# ); assert_format!( r#"fn main() { run( with: something(1, 2, 3), loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong: 1, ) } "# ); assert_format!( r#"fn main() { run( with: something( loooooooooooooooooooooooooooooooooooooooong: 1, looooooooooooooooooooooooooooooooooooooooong: 2, ), loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong: 1, ) } "# ); assert_format!( "fn main() { succ(1) } " ); assert_format!( "fn main() { add(1)(2)(3) } " ); assert_format!( "fn main() { Ok(1) } " ); assert_format!( "fn main() { Ok(1, { 1 2 }) } " ); assert_format!( r#"fn main() { Person("Al", is_cool: VeryTrue) } "# ); assert_format!( r#"fn main() { Person(name: "Al", is_cool: VeryTrue) } "# ); } #[test] fn compact_single_argument_call() { assert_format!( r#"fn main() { thingy(fn(x) { 1 2 }) } "# ); assert_format!( r#"fn main() { thingy([ // ok! one(), two(), ]) } "# ); assert_format!( r#"fn main() { thingy(<< // ok! one(), two(), >>) } "# ); assert_format!( r#"fn main() { thingy(#( // ok! one(), two(), )) } "# ); assert_format!( r#"fn main() { thingy( wiggle(my_function( // ok! one(), two(), )), ) } "# ); assert_format!( r#"fn main() { thingy(case x { 1 -> 1 _ -> 0 }) } "# ); assert_format!( r#"fn main() { thingy({ 1 2 3 }) } "# ); assert_format!( r#"fn main() { thingy({ let x = 1 x }) } "# ); } #[test] fn expr_tuple() { assert_format!( r#"fn main(one, two, three) { #(1, { 1 2 }) } "# ); assert_format!( r#"fn main() { #( atom.create_from_string("module"), atom.create_from_string("gleam@otp@actor"), ) } "# ); assert_format!( r#"fn main() { #() } "# ); assert_format!( r#"fn main() { #(1) } "# ); assert_format!( r#"fn main() { #(1, 2) } "# ); assert_format!( r#"fn main() { #(1, 2, 3) } "# ); assert_format!( r#"fn main() { #( really_long_variable_name, really_long_variable_name, really_long_variable_name, really_long_variable_name, really_long_variable_name, ) } "# ); } #[test] fn statement_fn() { assert_format!( r#"fn main(one, two, three) { Nil } "# ); } #[test] fn statement_fn1() { assert_format!( r#"fn main(label_one one, label_two two, label_three three) { Nil } "# ); } #[test] fn statement_fn2() { assert_format!( r#"fn main(label_one one: One, label_two two: Two) { Nil } "# ); } #[test] fn statement_fn3() { assert_format!( r#"fn main( label_one one: One, label_two two: Two, label_three three: Three, label_four four: Four, ) { Nil } "# ); assert_format!( r#"fn main(_discarded) { Nil } "# ); } #[test] fn statement_fn4() { assert_format!( r#"fn main(label _discarded) { Nil } "# ); } #[test] fn statement_fn5() { // https://github.com/gleam-lang/gleam/issues/613 assert_format!( r#"fn main() { Nil // Done } "# ); } #[test] fn statement_fn6() { // // Module function return annotations // assert_format!( r#"fn main() -> Nil { Nil } "# ); } #[test] fn statement_fn7() { assert_format!( r#"fn main() -> Loooooooooooooooooooong( Looooooooooooooong, Looooooooooooooooooong, Loooooooooooooooooooooong, Looooooooooooooooooooooooong, ) { Nil } "# ); } #[test] fn statement_fn8() { assert_format!( r#"fn main() -> Loooooooooooooooooooong( Loooooooooooooooooooooooooooooooooooooooooong, ) { Nil } "# ); } #[test] fn statement_fn9() { assert_format!( r#"fn main() -> program.Exit { Nil } "# ); } #[test] fn statement_fn10() { assert_format!( "fn order( first: Set(member), second: Set(member), ) -> #(Set(member), Set(member), a) { Nil } " ); } #[test] fn statement_fn11() { assert_format!( "/// pub fn try_map( over list: List(a), with fun: fn(a) -> Result(b, e), ) -> Result(List(b), e) { Nil } " ); } #[test] fn binary_operators() { assert_format!( r#"fn main() { True && False } "# ); assert_format!( r#"fn main() { True || False } "# ); assert_format!( r#"fn main() { 1 < 1 } "# ); assert_format!( r#"fn main() { 1 <= 1 } "# ); assert_format!( r#"fn main() { 1.0 <. 1.0 } "# ); assert_format!( r#"fn main() { 1.0 <=. 1.0 } "# ); assert_format!( r#"fn main() { 1 == 1 } "# ); assert_format!( r#"fn main() { 1 != 1 } "# ); assert_format!( r#"fn main() { 1 >= 1 } "# ); assert_format!( r#"fn main() { 1 > 1 } "# ); assert_format!( r#"fn main() { 1.0 >=. 1.0 } "# ); assert_format!( r#"fn main() { 1.0 >. 1.0 } "# ); assert_format!( r#"fn main() { 1 + 1 } "# ); assert_format!( r#"fn main() { 1.0 +. 1.0 } "# ); assert_format!( r#"fn main() { 1 - 1 } "# ); assert_format!( r#"fn main() { 1.0 -. 1.0 } "# ); assert_format!( r#"fn main() { 1 * 1 } "# ); assert_format!( r#"fn main() { 1.0 *. 1.0 } "# ); assert_format!( r#"fn main() { 1 / 1 } "# ); assert_format!( r#"fn main() { 1.0 /. 1.0 } "# ); assert_format!( r#"fn main() { 1 % 1 } "# ); } #[test] fn expr_int() { assert_format!( r#"fn i() { 1 } "# ); assert_format!( r#"fn i() { 121_234_345_989_000 } "# ); assert_format!( r#"fn i() { -12_928_347_925 } "# ); assert_format!( r#"fn i() { 1_234_567_890 } "# ); assert_format!( r#"fn i() { 123_456_789 } "# ); assert_format!( r#"fn i() { 12_345_678 } "# ); assert_format!( r#"fn i() { 1_234_567 } "# ); assert_format!( r#"fn i() { 123_456 } "# ); assert_format!( r#"fn i() { 12_345 } "# ); assert_format!( r#"fn i() { 1234 } "# ); assert_format!( r#"fn i() { 123 } "# ); assert_format!( r#"fn i() { 12 } "# ); assert_format!( r#"fn i() { 1 } "# ); assert_format!( r#"fn i() { -1_234_567_890 } "# ); assert_format!( r#"fn i() { -123_456_789 } "# ); assert_format!( r#"fn i() { -12_345_678 } "# ); assert_format!( r#"fn i() { -1_234_567 } "# ); assert_format!( r#"fn i() { -123_456 } "# ); assert_format!( r#"fn i() { -12_345 } "# ); assert_format!( r#"fn i() { -1234 } "# ); assert_format!( r#"fn i() { -123 } "# ); assert_format!( r#"fn i() { -12 } "# ); assert_format!( r#"fn i() { -1 } "# ); assert_format_rewrite!( r#"fn i() { 1_234 } "#, r#"fn i() { 1234 } "# ); assert_format_rewrite!( r#"fn i() { 12_34 } "#, r#"fn i() { 1234 } "# ); assert_format_rewrite!( r#"fn i() { 123_4 } "#, r#"fn i() { 1234 } "# ); assert_format_rewrite!( r#"fn i() { 1234_5 } "#, r#"fn i() { 12_345 } "# ); assert_format_rewrite!( r#"fn i() { 12345_6 } "#, r#"fn i() { 123_456 } "# ); assert_format_rewrite!( r#"fn i() { 123456_7 } "#, r#"fn i() { 1_234_567 } "# ); assert_format_rewrite!( r#"fn i() { 1234567_8 } "#, r#"fn i() { 12_345_678 } "# ); assert_format_rewrite!( r#"fn i() { -1_234 } "#, r#"fn i() { -1234 } "# ); assert_format_rewrite!( r#"fn i() { -12_34 } "#, r#"fn i() { -1234 } "# ); assert_format_rewrite!( r#"fn i() { -123_4 } "#, r#"fn i() { -1234 } "# ); assert_format_rewrite!( r#"fn i() { -1234_5 } "#, r#"fn i() { -12_345 } "# ); assert_format_rewrite!( r#"fn i() { -12345_6 } "#, r#"fn i() { -123_456 } "# ); assert_format_rewrite!( r#"fn i() { -123456_7 } "#, r#"fn i() { -1_234_567 } "# ); assert_format_rewrite!( r#"fn i() { -1234567_8 } "#, r#"fn i() { -12_345_678 } "# ); assert_format_rewrite!( r#"fn i() { let #(1_234, _) = #(1_234, Nil) } "#, r#"fn i() { let #(1234, _) = #(1234, Nil) } "# ); assert_format_rewrite!( r#"fn i() { let #(12_34, _) = #(12_34, Nil) } "#, r#"fn i() { let #(1234, _) = #(1234, Nil) } "# ); assert_format_rewrite!( r#"fn i() { let #(1234567_8, _) = #(1234567_8, Nil) } "#, r#"fn i() { let #(12_345_678, _) = #(12_345_678, Nil) } "# ); assert_format_rewrite!( r#"fn i() { let #(-1_234, _) = #(-1_234, Nil) } "#, r#"fn i() { let #(-1234, _) = #(-1234, Nil) } "# ); assert_format_rewrite!( r#"fn i() { let #(-12_34, _) = #(-12_34, Nil) } "#, r#"fn i() { let #(-1234, _) = #(-1234, Nil) } "# ); assert_format_rewrite!( r#"fn i() { let #(-1234567_8, _) = #(-1234567_8, Nil) } "#, r#"fn i() { let #(-12_345_678, _) = #(-12_345_678, Nil) } "# ); assert_format_rewrite!( r#"const an_int = 1_234 "#, r#"const an_int = 1234 "# ); assert_format_rewrite!( r#"const an_int = 12_34 "#, r#"const an_int = 1234 "# ); assert_format_rewrite!( r#"const an_int = 1234567_8 "#, r#"const an_int = 12_345_678 "# ); assert_format_rewrite!( r#"const an_int = -1_234 "#, r#"const an_int = -1234 "# ); assert_format_rewrite!( r#"const an_int = -12_34 "#, r#"const an_int = -1234 "# ); assert_format_rewrite!( r#"const an_int = -1234567_8 "#, r#"const an_int = -12_345_678 "# ); assert_format!("fn n() {\n 1_234_567\n}\n"); assert_format!("fn h() {\n 0xCAB005E\n}\n"); assert_format!("fn h() {\n 0xC_AB_00_5E\n}\n"); assert_format!("fn h() {\n 0xCA_B0_05_E\n}\n"); assert_format!("fn b() {\n 0b10100001\n}\n"); assert_format!("fn b() {\n 0b_1010_0001\n}\n"); assert_format!("fn o() {\n 0o1234567\n}\n"); assert_format!("fn o() {\n 0o1_234_567\n}\n"); assert_format!("fn o() {\n 0o_123_456_7\n}\n"); } #[test] fn expr_float() { assert_format_rewrite!( r#"fn f() { 1. } "#, r#"fn f() { 1.0 } "# ); assert_format_rewrite!( r#"fn f() { 1.00 } "#, r#"fn f() { 1.0 } "# ); assert_format_rewrite!( r#"fn f() { 1.00100 } "#, r#"fn f() { 1.001 } "# ); assert_format_rewrite!( r#"fn f() { 1.001001 } "#, r#"fn f() { 1.001001 } "# ); assert_format_rewrite!( r#"fn f() { 1.00e100_100 } "#, r#"fn f() { 1.0e100_100 } "# ); assert_format_rewrite!( r#"fn f() { 1.00100e100_100 } "#, r#"fn f() { 1.001e100_100 } "# ); assert_format_rewrite!( r#"fn f() { 1.001001e100_100 } "#, r#"fn f() { 1.001001e100_100 } "# ); assert_format!( r#"fn f() { 1.0 } "# ); assert_format!( r#"fn f() { -1.0 } "# ); assert_format!( r#"fn f() { 9999.6666 } "# ); assert_format!( r#"fn f() { -1_234_567_890.0 } "# ); assert_format!( r#"fn f() { -1_234_567_890.0 } "# ); assert_format!( r#"fn f() { -123_456_789.0 } "# ); assert_format!( r#"fn f() { -12_345_678.0 } "# ); assert_format!( r#"fn f() { -1_234_567.0 } "# ); assert_format!( r#"fn f() { -123_456.0 } "# ); assert_format!( r#"fn f() { -12_345.0 } "# ); assert_format!( r#"fn f() { -1234.0 } "# ); assert_format!( r#"fn f() { -123.0 } "# ); assert_format!( r#"fn f() { -12.0 } "# ); assert_format!( r#"fn f() { -1.0 } "# ); assert_format!( r#"fn f() { -0.0 } "# ); assert_format!( r#"fn f() { -1_234_567_890.1 } "# ); assert_format!( r#"fn f() { -123_456_789.1 } "# ); assert_format!( r#"fn f() { -12_345_678.1 } "# ); assert_format!( r#"fn f() { -1_234_567.1 } "# ); assert_format!( r#"fn f() { -123_456.1 } "# ); assert_format!( r#"fn f() { -12_345.1 } "# ); assert_format!( r#"fn f() { -1234.1 } "# ); assert_format!( r#"fn f() { -123.1 } "# ); assert_format!( r#"fn f() { -12.1 } "# ); assert_format!( r#"fn f() { -1.1 } "# ); assert_format!( r#"fn f() { -0.1 } "# ); assert_format!( r#"fn f() { -1_234_567_890.123456 } "# ); assert_format!( r#"fn f() { -123_456_789.123456 } "# ); assert_format!( r#"fn f() { -12_345_678.123456 } "# ); assert_format!( r#"fn f() { -1_234_567.123456 } "# ); assert_format!( r#"fn f() { -123_456.123456 } "# ); assert_format!( r#"fn f() { -12_345.123456 } "# ); assert_format!( r#"fn f() { -1234.123456 } "# ); assert_format!( r#"fn f() { -123.123456 } "# ); assert_format!( r#"fn f() { -12.123456 } "# ); assert_format!( r#"fn f() { -1.123456 } "# ); assert_format!( r#"fn f() { -0.123456 } "# ); assert_format_rewrite!( r#"fn f() { 1_234.0 } "#, r#"fn f() { 1234.0 } "# ); assert_format_rewrite!( r#"fn f() { 12_34.0 } "#, r#"fn f() { 1234.0 } "# ); assert_format_rewrite!( r#"fn f() { 123_4.0 } "#, r#"fn f() { 1234.0 } "# ); assert_format_rewrite!( r#"fn f() { 1234_5.0 } "#, r#"fn f() { 12_345.0 } "# ); assert_format_rewrite!( r#"fn f() { 12345_6.0 } "#, r#"fn f() { 123_456.0 } "# ); assert_format_rewrite!( r#"fn f() { 123456_7.0 } "#, r#"fn f() { 1_234_567.0 } "# ); assert_format_rewrite!( r#"fn f() { 1234567_8.0 } "#, r#"fn f() { 12_345_678.0 } "# ); assert_format_rewrite!( r#"fn f() { -1_234.0 } "#, r#"fn f() { -1234.0 } "# ); assert_format_rewrite!( r#"fn f() { -12_34.0 } "#, r#"fn f() { -1234.0 } "# ); assert_format_rewrite!( r#"fn f() { -123_4.0 } "#, r#"fn f() { -1234.0 } "# ); assert_format_rewrite!( r#"fn f() { -1234_5.0 } "#, r#"fn f() { -12_345.0 } "# ); assert_format_rewrite!( r#"fn f() { -12345_6.0 } "#, r#"fn f() { -123_456.0 } "# ); assert_format_rewrite!( r#"fn f() { -123456_7.0 } "#, r#"fn f() { -1_234_567.0 } "# ); assert_format_rewrite!( r#"fn f() { -1234567_8.0 } "#, r#"fn f() { -12_345_678.0 } "# ); assert_format_rewrite!( r#"fn f() { let #(1_234.0, _) = #(1_234.0, Nil) } "#, r#"fn f() { let #(1234.0, _) = #(1234.0, Nil) } "# ); assert_format_rewrite!( r#"fn f() { let #(12_34.0, _) = #(12_34.0, Nil) } "#, r#"fn f() { let #(1234.0, _) = #(1234.0, Nil) } "# ); assert_format_rewrite!( r#"fn f() { let #(1234567_8.0, _) = #(1234567_8.0, Nil) } "#, r#"fn f() { let #(12_345_678.0, _) = #(12_345_678.0, Nil) } "# ); assert_format_rewrite!( r#"fn f() { let #(-1_234.0, _) = #(-1_234.0, Nil) } "#, r#"fn f() { let #(-1234.0, _) = #(-1234.0, Nil) } "# ); assert_format_rewrite!( r#"fn f() { let #(-12_34.0, _) = #(-12_34.0, Nil) } "#, r#"fn f() { let #(-1234.0, _) = #(-1234.0, Nil) } "# ); assert_format_rewrite!( r#"fn f() { let #(-1234567_8.0, _) = #(-1234567_8.0, Nil) } "#, r#"fn f() { let #(-12_345_678.0, _) = #(-12_345_678.0, Nil) } "# ); assert_format_rewrite!( r#"const a_float = 1_234.0 "#, r#"const a_float = 1234.0 "# ); assert_format_rewrite!( r#"const a_float = 12_34.0 "#, r#"const a_float = 1234.0 "# ); assert_format_rewrite!( r#"const a_float = 1234567_8.0 "#, r#"const a_float = 12_345_678.0 "# ); assert_format_rewrite!( r#"const a_float = -1_234.0 "#, r#"const a_float = -1234.0 "# ); assert_format_rewrite!( r#"const a_float = -12_34.0 "#, r#"const a_float = -1234.0 "# ); assert_format_rewrite!( r#"const a_float = -1234567_8.0 "#, r#"const a_float = -12_345_678.0 "# ); assert_format_rewrite!( r#"const a_float = 1234.00 "#, r#"const a_float = 1234.0 "# ); assert_format_rewrite!( r#"const a_float = 1234.00100 "#, r#"const a_float = 1234.001 "# ); assert_format_rewrite!( r#"const a_float = 1234.001001 "#, r#"const a_float = 1234.001001 "# ); assert_format!( r#"fn f() { 1.0e1 } "# ); assert_format!( r#"fn f() { 1.0e-1 } "# ); assert_format!( r#"fn f() { -1.0e1 } "# ); assert_format!( r#"fn f() { -1.0e-1 } "# ); assert_format!( r#"fn f() { 1.0e10 } "# ); assert_format!( r#"fn f() { 1.0e-10 } "# ); assert_format!( r#"fn f() { -1.0e10 } "# ); assert_format!( r#"fn f() { -11.0e-10 } "# ); assert_format!( r#"fn f() { 1.0e100 } "# ); assert_format!( r#"fn f() { 1.0e-100 } "# ); assert_format!( r#"fn f() { -1.0e100 } "# ); assert_format!( r#"fn f() { -11.0e-100 } "# ); assert_format!( r#"fn f() { 1.0e100 } "# ); assert_format!( r#"fn f() { 1.0e100_100 } "# ); assert_format!( r#"fn f() { 1.0e100_100 } "# ); assert_format!( r#"fn f() { 1.001e100_100 } "# ); } #[test] fn expr_string() { assert_format!( r#"fn main() { "Hello" } "# ); assert_format!( r#"fn main() { "Hello World" } "# ); assert_format!( r#"fn main() { "\\n\\t" } "# ); } #[test] fn expr_seq() { assert_format!( r#"fn main() { 1 2 3 } "# ); assert_format!( r#"fn main() { first(1) 1 } "# ); } #[test] fn expr_lists() { assert_format!( "fn main() { [] } " ); assert_format!( "fn main() { [1] } " ); assert_format!( "fn main() { [ { 1 2 }, ] } " ); assert_format!( "fn main() { [1, 2, 3] } " ); assert_format!( "fn main() { [ 1, { 2 3 }, 3, ] } " ); assert_format!( "fn main() { [ really_long_variable_name, really_long_variable_name, really_long_variable_name, [1, 2, 3], really_long_variable_name, ] } " ); assert_format!( "fn main() { [ really_long_variable_name, really_long_variable_name, really_long_variable_name, [ really_long_variable_name, really_long_variable_name, really_long_variable_name, 2, 3, [1, 2, 3, 4], ], really_long_variable_name, ] } " ); assert_format!( "fn main() { [1, 2, 3, ..x] } " ); assert_format!( "fn main() { [ really_long_variable_name, really_long_variable_name, really_long_variable_name, ..tail ] } " ); assert_format!( "fn main() { [ really_long_variable_name, really_long_variable_name, really_long_variable_name, [ really_long_variable_name, really_long_variable_name, really_long_variable_name, 2, 3, [1, 2, 3, 4], ..tail ], really_long_variable_name, ] } " ); } #[test] fn expr_pipe() { assert_format!( r#"fn main() { 1 |> really_long_variable_name |> really_long_variable_name |> really_long_variable_name |> really_long_variable_name |> really_long_variable_name |> really_long_variable_name } "# ); assert_format!( r#"fn main() { #( 1 |> succ |> succ, 2, 3, ) } "# ); assert_format!( r#"fn main() { some_call( 1 |> succ |> succ, 2, 3, ) } "# ); assert_format!( r#"fn main() { [ 1 |> succ |> succ, 2, 3, ] } "# ); assert_format!( r#"fn main() { let x = 1 |> succ |> succ x } "# ); assert_format!( r#"fn main() { #(1, 2) |> pair.first |> should.equal(1) } "# ); assert_format!( r#"fn main() { #(1, 2) |> pair.first(1, 2, 4) |> should.equal(1) } "# ); assert_format!( r#"fn main() { 1 // 1 |> func1 // 2 |> func2 } "# ); // https://github.com/gleam-lang/gleam/issues/618 assert_format!( r#"fn main() { { 1 2 } |> func } "# ); assert_format!( r#"fn main() { 1 |> { 1 2 } } "# ); // https://github.com/gleam-lang/gleam/issues/658 assert_format!( r#"fn main() { { os.system_time(os.Millisecond) < june_12_2020 * 1_000_000 } |> should.equal(True) } "# ); assert_format!( r#"fn main() { { os.system_time(os.Millisecond) < june_12_2020 * 1_000_000 } |> transform |> should.equal(True) } "# ); } #[test] fn expr_let() { assert_format!( r#"fn main() { let x = 1 Nil } "# ); } #[test] fn expr_let1() { assert_format!( r#"fn main() { let assert x = 1 Nil } "# ); } #[test] fn expr_let2() { assert_format!( r#"fn main() { let x = { let y = 1 y } Nil } "# ); } #[test] fn expr_let3() { assert_format!( r#"fn main() { let x = { 1 2 } Nil } "# ); } #[test] fn expr_let4() { assert_format!( r#"fn main() { let y = case x { 1 -> 1 _ -> 0 } y } "# ); } #[test] fn expr_let5() { assert_format!( r#"fn main() { let y = case x { 1 -> 1 _ -> 0 } y } "# ); } #[test] fn expr_let6() { assert_format!( r#"fn main() { let x = fn(x) { x } x } "# ); } #[test] fn expr_let7() { assert_format!( r#"fn main() { let x = fn() { 1 2 } x } "# ); } #[test] fn expr_let8() { assert_format!( r#"fn main() { let x = fn( state: state, acc: visitor_acc, visitor: fn(visitor_acc, Pid(a)) -> new_visitor_acc, ) { 1 2 } x } "# ); } #[test] fn expr_let9() { assert_format!( r#"fn main() { let x = fn( state: state, acc: visitor_acc, visitor: fn(visitor_acc, Pid(a)) -> new_visitor_acc, ) { 2 } x } "# ); } #[test] fn expr_let10() { assert_format!( r#"fn main() { let dict = map.from_list([#("a", 0), #("b", 1), #("c", 2), #("d", 3)]) 1 } "# ); } #[test] fn pattern_simple() { // Pattern::Float assert_format!( r#"fn main() { let 1 = 1 Nil } "# ); // Pattern::String assert_format!( r#"fn main() { let 1.0 = 1 Nil } "# ); // Pattern::Var assert_format!( r#"fn main() { let x = 1 let y = 1 Nil } "# ); } #[test] fn breakable_pattern() { assert_format!( r#"fn main() { let Ok(Thingybob( one: _one, two: _two, three: _three, four: _four, five: _five, six: _six, )) = 1 Nil } "# ); } #[test] fn pattern_let() { assert_format!( r#"fn main() { let x as y = 1 Nil } "# ); assert_format!( r#"fn main() { let #(x, y, 123 as z) = 1 Nil } "# ); } #[test] fn pattern_discard() { assert_format!( r#"fn main() { let _ = 1 Nil } "# ); assert_format!( r#"fn main() { let _wibble = 1 Nil } "# ); } #[test] fn pattern_lists() { assert_format!( r#"fn main() { let [] = 1 Nil } "# ); assert_format!( r#"fn main() { let [1] = 1 Nil } "# ); assert_format!( r#"fn main() { let [1, 2, 3, 4] = 1 Nil } "# ); assert_format!( r#"fn main() { let [1, 2, 3, 4, ..x] = 1 Nil } "# ); assert_format!( r#"fn main() { let [ really_long_variable_name, really_long_variable_name, really_long_variable_name, [1, 2, 3, 4, xyz], ..thingy ] = 1 Nil } "# ); } #[test] fn pattern_constructor() { assert_format!( r#"fn main() { let True = 1 Nil } "# ); assert_format!( r#"fn main() { let False = 1 Nil } "# ); assert_format!( r#"fn main() { let Ok(1) = 1 Nil } "# ); assert_format!( r#"fn main() { let Person(name, age: the_age) = 1 Nil } "# ); assert_format!( r#"fn main() { let Person(name: the_name, age: the_age) = 1 Nil } "# ); assert_format!( r#"fn main() { let Person(age: age, name: name) = 1 Nil } "# ); assert_format!( r#"fn main() { let Person(age: really_long_variable_name, name: really_long_variable_name) = 1 Nil } "# ); } #[test] fn pattern_tuple() { assert_format!( r#"fn main() { let #() = 1 Nil } "# ); assert_format!( r#"fn main() { let #(x) = 1 Nil } "# ); assert_format!( r#"fn main() { let #(x, y) = 1 Nil } "# ); assert_format!( r#"fn main() { let #(x, y, z) = 1 Nil } "# ); } #[test] fn expr_case() { assert_format!( r#"fn main() { case 1 { 1 -> { 1 2 } 1 -> 1 } } "# ); assert_format!( r#"fn main() { case 1 { 1 -> { let x = 1 x } 1 -> 1 } } "# ); assert_format!( r#"fn do() { case list { [x, ..xs] -> { let x = 1 x } } } "# ); assert_format!( r#"fn do() { case list { [x, ..xs] -> { let x = 1 x 1 2 3 4 } } } "# ); assert_format!( "fn main() { case x { 1 -> 2 2 -> 3 _ -> 0 } } " ); assert_format!( r#"fn main() { case bool { True -> { "Wibble" |> io.println "Wobble" |> io.println Nil } False -> Nil } } "# ); } #[test] fn expr_case_nested() { assert_format!( r#"fn main() { case 1 { 1 -> case x { 1 -> 1 _ -> 0 } 1 -> 1 } } "# ); assert_format!( r#"fn main() { case list { [x] -> case x { _ -> 1 } } } "# ); } #[test] fn expr_case_then_fn() { assert_format!( r#"fn main() { case 1 { 1 -> fn(x) { x } 1 -> 1 } } "# ); assert_format!( r#"fn main() { case 1 { 1 -> fn() { 1 2 } 1 -> 1 } } "# ); } #[test] fn expr_case_multiple_subjects() { assert_format!( r#"fn main() { case 1 { 1 -> 1 1 -> 1 } } "# ); assert_format!( r#"fn main() { case 1, 2, 3, 4 { 1, 2, 3, 4 -> 1 1, 2, 3, 4 -> 1 } } "# ); } #[test] fn expr_case_alternative_patterns() { assert_format!( r#"fn main() { case 1 { 1 | 2 | 3 -> Nil } } "# ); assert_format!( r#"fn main() { case 1, 2 { 1, 1 | 2, 2 | 3, 3 -> Nil 1, 1 | 2, 2 | 3, 3 -> Nil 1, 1 | 2, 2 | 3, 3 -> Nil 1, 1 | 2, 2 | 3, 3 -> Nil } } "# ); assert_format!( r#"fn main() { case pat { pat.Typeof("Boolean", pat) | pat.Typeof("Number", pat) | pat.Typeof("String", pat) -> Nil } } "# ); } #[test] fn expr_case_clause_guards() { assert_format!( r#"fn main() { case 1 { _ if x == y -> Nil } } "# ); assert_format!( r#"fn main() { case "x" { _ if x == "x" -> Nil } } "# ); assert_format!( r#"fn main() { case #(1, 2, 3) { _ if x == #(1, 2, 3) -> Nil } } "# ); assert_format!( r#"type Test { Test(x: Int, y: Float) } pub fn main() { let x = Test(1, 3.0) case x { _ if x == Test(1, 1.0) -> 1 _ if x == Test(y: 2.0, x: 2) -> 2 _ if x != Test(2, 3.0) -> 2 _ -> 0 } } "# ); assert_format!( r#"fn main() { case 1 { _ if x != y -> Nil } } "# ); assert_format!( r#"fn main() { case 1 { _ if x || y -> Nil } } "# ); assert_format!( r#"fn main() { case 1 { _ if x && y -> Nil } } "# ); assert_format!( r#"fn main() { case 1 { _ if x != y && x == z -> Nil } } "# ); } #[test] fn expr_case_clause_comments() { assert_format!( r#"fn main() { case 1 { // Hello Louis! 1 | 2 | 3 -> Nil } } "# ); assert_format!( r#"fn main() { case 1 { // Hello José! 1 | 2 -> Nil // Hello Louis! n -> Nil } } "# ); assert_format!( r#"fn main() { case 1 { // Hello Joe! 1 | 2 -> Nil // Hello Louis! n -> Nil } } "# ); assert_format!( r#"fn main() { case pat { // Hello Ada pat.Typeof("Boolean", pat) | pat.Typeof("Number", pat) -> True // Hello Alan pat.Typeof("Boolean", pat) | pat.Typeof("Number", pat) | pat.Typeof("String", pat) -> False } } "# ); } #[test] fn field_access() { assert_format!( r#"fn main() { one.two } "# ); assert_format!( r#"fn main() { one.two.three.four } "# ); } #[test] fn tuple_access() { assert_format!( r#"fn main() { tup.0 } "# ); } #[test] fn tuple_access1() { assert_format!( r#"fn main() { tup.1 } "# ); } #[test] fn tuple_access2() { assert_format!( r#"fn main() { tup.777 } "# ); } #[test] fn tuple_access3() { assert_format!( r#"fn main() { tup.1.2 } "# ); } #[test] fn expr_panic() { assert_format!( "fn main() { panic } " ); } #[test] fn expr_panic_as() { assert_format!( r#"fn main() { panic as "panicking" } "# ); } #[test] fn expr_panic_as_value() { assert_format!( r#"fn main() { let x = "panicking" <> "with a value" panic as x } "# ); } #[test] fn expr_todo_as_value() { assert_format!( r#"fn main() { let x = "Need to" <> "do this" todo as x } "# ); } #[test] fn expr_todo() { assert_format!( "fn main() { todo } " ); } #[test] fn expr_todo_with_label() { assert_format!( r#"fn main() { todo as "todo with a label" } "# ); } #[test] fn expr_todo1() { assert_format_rewrite!( r#"fn main() { fn() {} } "#, r#"fn main() { fn() { todo } } "# ); } #[test] fn doc_comments_test() { assert_format!( "/// one fn main() { Nil } " ); } #[test] fn doc_comments_1_test() { assert_format!( "/// one ///two fn main() { Nil } " ); } #[test] fn doc_comments_2_test() { assert_format!( r#"/// one ///two @external(javascript, "", "") fn whatever() -> Nil "# ); } #[test] fn doc_comments_3_test() { assert_format!( r#"/// one ///two type Thingy "# ); } #[test] fn doc_comments_4_test() { assert_format!( r#"/// one ///two type Thingy "# ); } #[test] fn doc_comments_5_test() { assert_format!( r#"/// one ///two type Whatever { Whatever } "# ); } #[test] fn doc_comments_6_test() { assert_format!( r#"/// one ///two type Whatever = Int "# ); } #[test] fn comments3() { assert_format!( "// one fn main() { Nil } " ); } #[test] fn comments4() { assert_format!( "// one //two fn main() { Nil } " ); } #[test] fn comments5() { assert_format!( r#"// one //two @external(javascript, "", "") fn whatever() -> Nil "# ); } #[test] fn comments9() { assert_format!( r#"// one //two type Whatever = Int "# ); } #[test] fn comment23() { assert_format!( "fn main() { // Hello // world 1 } " ); } #[test] fn comment24() { assert_format!( "fn main() { // Hello // world 1.0 } " ); } #[test] fn comment25() { assert_format!( "fn main() { // Hello // world Nil } " ); } #[test] fn comment14() { assert_format!( "fn main() { // Hello // world [] } " ); } #[test] fn comment15() { assert_format!( "fn main() { // Hello // world [ // One 1, // Two 2, ] } " ); } #[test] fn comment16() { assert_format!( "fn main() { // Hello // world [ // One 1, // Two 2, [ // Five 3, [ // Four 4, ], ], ] } " ); } #[test] fn comment17() { assert_format!( "fn main() { // Hello // world one( // One 1, // Two 2, two( // Five 3, three( // Four 4, ), ), ) } " ); } #[test] fn comment18() { assert_format!( "fn main() { // Hello 1 // world 2 } " ); } #[test] fn comment19() { assert_format!( "fn main() { let // hello x = 1 x } " ); } #[test] fn comment20() { assert_format!( "fn main() { let [ // 1 1, // 2 2, ] = xs x } " ); } #[test] fn comment21() { assert_format!( "pub type Spec { Spec( // Hello hello: Int, // World world: Int, ) } " ); } #[test] fn comment22() { assert_format!( "/// ß↑e̊ /// pub fn one() { 1 } pub fn two() { 2 } ", ); } #[test] fn trailing_comments() { assert_format!( "fn main() { x } // Hello world // ok! " ); assert_format!( "fn main() { x } /// Hello world /// ok! " ); assert_format!( "fn main() { x } /// Hello world /// ok! // Hello world // ok! " ); } #[test] fn commented_fn_arguments() { assert_format!( "fn main( // comment label argument: Type, ) { x } " ); } #[test] fn commented_fn_arguments1() { assert_format!( "fn main( // comment1 label argument1: Type, // comment2 label argument2: Type, ) { x } " ); } #[test] fn commented_fn_arguments2() { assert_format!( "@external(erlang, \"\", \"\") pub fn main( // comment1 argument1: Type, // comment2 argument2: Type, ) -> Int " ); } #[test] fn commented_binop() { assert_format!( "fn main() { 1 // hello + 2 } " ); assert_format!( "fn main() { // one 1 // two + 2 // three + 3 } " ); } #[test] fn commented_constructors() { assert_format!( "pub type Number { // 1 One // 2 Two // 3 Three // ??? More } " ); assert_format!( "pub type Number { /// 1 One /// 2 Two /// 3 Three /// ??? More } " ); assert_format!( "pub type Number { // a /// 1 One // b /// 2 Two // c /// 3 Three // defg /// ??? More } " ); assert_format!( "pub type Number { /// 1 One(value: Int) /// > 1 Many(value: Int) } " ); } #[test] fn function_captures_test() { assert_format_rewrite!( "pub fn main() { run(_) } ", "pub fn main() { run } " ); assert_format!( "pub fn main() { run(1, 2, _, 4, 5) } " ); assert_format_rewrite!( "pub fn main() { run(1, 2, _, 4, 5)(_) } ", "pub fn main() { run(1, 2, _, 4, 5) } " ); } #[test] fn pattern_record_spread() { assert_format!( "type Triple { Triple(a: Int, b: Int, c: Int) } fn main() { let triple = Triple(1, 2, 3) let Triple(the_a, c: the_c, ..) = triple the_c } " ); // Formats the operator spread syntax with long names assert_format!( "type Triple { Triple(a: Int, b: Int, c: Int) } fn main() { let triple = Triple(1, 2, 3) let Triple( really_really_long_variable_name_a, c: really_really_long_variable_name_c, .., ) = triple really_really_long_variable_name_c } " ); // https://github.com/gleam-lang/gleam/issues/776 assert_format!( "fn main() { let Triple(..) = triple() 1 } " ); } #[test] fn empty_lines() { assert_format!( "pub fn main() { 1 2 } " ); assert_format!( "pub fn main() { // one 1 // two 2 } " ); assert_format!( "pub fn main() { // one 1 // two 2 // three 3 } " ); assert_format!( "pub type Number { One Two Three } " ); assert_format!( "pub fn main() { let x = 1 x } " ); // Lines with only spaces are treated as empty assert_format_rewrite!( "pub fn main() { let x = 1\n \n x } ", "pub fn main() { let x = 1 x } " ); assert_format!( "pub fn main() { let inc = fn(a) { a + 1 } pair.map_first(#(1, 2), inc) |> should.equal(#(2, 2)) pair.map_first(#(1, 2), inc) |> should.equal(#(2, 2)) } " ); } #[test] fn modules_docs() { assert_format!( "//// One //// Two //// Three pub fn main() { let x = 1 x } " ); assert_format!( "//// //// //// //// //// type X { X } // Hello " ); } #[test] fn binary_operator_precedence() { assert_format!( "fn main() { { 1 + 2 } * 3 } " ); assert_format!( "fn main() { 3 * { 1 + 2 } } " ); assert_format!( "fn main() { 3 * { 1 |> inc } } " ); assert_format!( "fn main() { { 1 |> inc } * 3 } " ); assert_format!( "fn main() { 1 |> { a || b } } " ); assert_format!( "fn main() { { a || b } |> go } " ); } // https://github.com/gleam-lang/gleam/issues/868 #[test] fn precedence_rhs() { assert_format!( "fn main() { True != { a == b } } " ); assert_format!( "fn main() { True != { a == { b != c } } } " ); } #[test] fn module_constants() { assert_format!( "pub const str = \"a string\" const my_constant: String = \"Hello\" pub const int = 4 pub const float = 3.14 " ); } #[test] fn concise_wrapping_of_simple_lists() { assert_format!( "pub fn main() { [ 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900, 2000, ] } " ); assert_format!( "pub fn main() { [ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 1.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 2.0, ] } " ); assert_format!( r#"pub fn main() { [ "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", ] } "# ); assert_format!( "const values = [ 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900, 2000, ] " ); assert_format!( "const values = [ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 1.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 2.0, ] " ); assert_format!( r#"const values = [ "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", ] "# ); } #[test] fn commented_labelled_arguments() { assert_format!( "fn main() { Emulator( // one one: 1, // two two: 1, ) } " ); assert_format!( "fn main() { my_func( // one one: 1, // two two: 1, ) } " ); } #[test] fn module_rewrites_test() { // Module comments are moved to the top assert_format_rewrite!( "//// One //// Two fn main() { 1 } //// Three //// Four ", "//// One //// Two //// Three //// Four fn main() { 1 } ", ); // Superfluous function captures are removed from pipe expressions assert_format_rewrite!( "fn main() { 1 |> run(_, 1) } ", "fn main() { 1 |> run(1) } ", ); assert_format_rewrite!( "fn main() { 1 |> run(_) } ", "fn main() { 1 |> run } ", ); assert_format_rewrite!( "fn main() { let some_really_long_variable_name_to_force_wrapping = 1 let bits = << some_really_long_variable_name_to_force_wrapping, some_really_long_variable_name_to_force_wrapping, >> bits } ", "fn main() { let some_really_long_variable_name_to_force_wrapping = 1 let bits = << some_really_long_variable_name_to_force_wrapping, some_really_long_variable_name_to_force_wrapping, >> bits } ", ); } // TODO: improve. This is too wide #[test] // https://github.com/gleam-lang/gleam/issues/748 fn assignments_break_value_first_test() { assert_format!( r#"fn main() { let assert Ok(1) = [ 10_000_000_000_000_000_000_000_000_001, 20_000_000_000_000_000_000_000_000_001, 30_000_000_000_000_000_000_000_000_001, 40_000_000_000_000_000_000_000_000_001, ] Nil } "# ); assert_format!( r#"fn main() { let assert Ok(1) = [ 1_000_000_000_000_000_000_000_000_000, 2_000_000_000_000_000_000_000_000_000, 3_000_000_000_000_000_000_000_000_000, 4_000_000_000_000_000_000_000_000_000, ] Nil } "# ); assert_format!( r#"fn main() { let assert <<11, 2, 4, 5, 6>> = [ 10_000_000_000_000_000_000_000_000_001, 20_000_000_000_000_000_000_000_000_001, 30_000_000_000_000_000_000_000_000_001, 40_000_000_000_000_000_000_000_000_001, ] Nil } "# ); assert_format!( r#"fn main() { let assert <<11, 2, 4, 5, 6>> = [ 1_000_000_000_000_000_000_000_000_000, 2_000_000_000_000_000_000_000_000_000, 3_000_000_000_000_000_000_000_000_000, 4_000_000_000_000_000_000_000_000_000, ] Nil } "# ); assert_format!( r#"fn main() { let assert [11, 2, 4, 5, 6] = [ 10_000_000_000_000_000_000_000_000_001, 20_000_000_000_000_000_000_000_000_001, 30_000_000_000_000_000_000_000_000_001, 40_000_000_000_000_000_000_000_000_001, ] Nil } "# ); assert_format!( r#"fn main() { let assert [11, 2, 4, 5, 6] = [ 1_000_000_000_000_000_000_000_000_000, 2_000_000_000_000_000_000_000_000_000, 3_000_000_000_000_000_000_000_000_000, 4_000_000_000_000_000_000_000_000_000, ] Nil } "# ); } #[test] fn function_type_type() { assert_format!( "type F = fn(some, really, long, set, of, arguments) -> #(some, really, long, set, of, arguments) " ); } #[test] fn tuple_constant() { assert_format!( "const x: #(Int, Int) = #(1, 2) " ); } #[test] fn var_constant() { assert_format!( r#"const x = 1 const x_alias = x fn f(i: Int) -> Int { i } const f_alias: fn(Int) -> Int = f "# ); } #[test] fn let_as_expression() { assert_format!( "pub fn main() { let x = 1 } " ); assert_format!( "pub fn main() { let x = { let y = 1 } } " ); } #[test] fn assert_as_expression() { assert_format!( "pub fn main() { let assert x = 1 } " ); assert_format!( "pub fn main() { let assert x = { let assert y = 1 } } " ); } #[test] fn case_in_call() { assert_format!( "fn clause_guard_tests(_fns) -> List(Test) { example(fn() { assert_equal(0, case Nil { _ if yes -> 0 _ -> 1 }) }) } " ); } // https://github.com/gleam-lang/gleam/issues/1390 #[test] fn list_spread_pattern() { assert_format!( "pub fn main(x) { case x { [y, ..] -> y _ -> 0 } } " ); } // https://github.com/gleam-lang/gleam/issues/1431 #[test] fn first_argument_capture_special_case_list() { assert_format!( r#"pub fn main(x) { wibble(_, [ "one argument that is both breakable and long enough to cause it to wrap", ]) } "# ); } // https://github.com/gleam-lang/gleam/issues/1431 #[test] fn first_argument_capture_special_case_fn() { assert_format!( r#"pub fn main(x) { wibble(_, fn() { "one argument that is both breakable and long enough to cause it to wrap" }) } "# ); } #[test] fn negation() { assert_format!( "pub fn negate(x) { !x } " ); } #[test] fn negation_block() { assert_format!( "pub fn negate(x) { !{ 123 x } } " ); } #[test] fn empty_lines_work_with_trailing_space() { let src = "pub fn main() { let inc = fn(a) { a + 1 } pair.map_first(#(1, 2), inc) |> should.equal(#(2, 2)) // Comment 1 // Comment 2 } "; let expected = "pub fn main() { let inc = fn(a) { a + 1 } pair.map_first(#(1, 2), inc) |> should.equal(#(2, 2)) // Comment 1 // Comment 2 } "; // We first make extra sure we've not messed up the expected output and // check it's well formatted. assert_format!(expected); assert_format_rewrite!(src, expected); } #[test] fn empty_lines_work_with_eol_normalisation() { let src = "pub fn main() { let inc = fn(a) { a + 1 } pair.map_first(#(1, 2), inc) |> should.equal(#(2, 2)) // Comment 1 // Comment 2 } "; let expected = "pub fn main() { let inc = fn(a) { a + 1 } pair.map_first(#(1, 2), inc) |> should.equal(#(2, 2)) // Comment 1 // Comment 2 } "; // We first make extra sure we've not messed up the expected output and // check it's well formatted. assert_format!(expected); assert_format_rewrite!(&src.replace('\n', "\r\n"), expected); assert_format_rewrite!(&src.replace('\n', "\r"), expected); } #[test] fn empty_lines_work_with_trailing_space_and_eol_normalisation() { let src = "pub fn main() { let inc = fn(a) { a + 1 } pair.map_first(#(1, 2), inc) |> should.equal(#(2, 2)) // Comment 1 // Comment 2 } "; let expected = "pub fn main() { let inc = fn(a) { a + 1 } pair.map_first(#(1, 2), inc) |> should.equal(#(2, 2)) // Comment 1 // Comment 2 } "; // We first make extra sure we've not messed up the expected output and // check it's well formatted. assert_format!(expected); assert_format_rewrite!(src.replace('\n', "\r\n"), expected); assert_format_rewrite!(&src.replace('\n', "\r"), expected); } #[test] fn single_empty_line_between_comments() { // empty line isn't added if it's not already present assert_format!( "pub fn wibble() { // wibble // wobble 123 } " ); } #[test] fn single_empty_line_between_comments1() { // single empty line between comments/statement preserved assert_format!( "pub fn wibble() { // wibble // wobble 123 } " ); } #[test] fn single_empty_line_between_comments2() { // multiple consecutive empty lines condensed into one assert_format_rewrite!( "pub fn wibble() { // wibble // wobble 123 } ", "pub fn wibble() { // wibble // wobble 123 } " ); } #[test] fn single_empty_line_between_comments3() { // freestanding comments keep empty lines assert_format!( "// wibble // wobble " ); } #[test] fn single_empty_line_between_comments4() { // freestanding comments condense consecutive empty lines assert_format_rewrite!( "// wibble // wobble ", "// wibble // wobble ", ); } // https://github.com/gleam-lang/gleam/issues/1640 #[test] fn no_newline_before_comments() { assert_format!( "// wibble // wobble " ); } // https://github.com/gleam-lang/gleam/issues/1647 #[test] fn list_at_end_of_long_expr_line() { assert_format!( "pub fn example() { Ok( RecordConstructorWithALongName( a_field: RecordConstructorWithALongName(a_field: Record(a_field: [])), ), ) } " ); } // https://github.com/gleam-lang/gleam/issues/1647 #[test] fn list_at_end_of_long_pattern_line() { assert_format!( "pub fn example() { let assert LongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLong([]) = 1 } " ); } // https://github.com/gleam-lang/gleam/issues/1647 #[test] fn list_at_end_of_long_constant_line() { assert_format!( "const longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong = [] " ); } // https://github.com/gleam-lang/gleam/issues/1649 #[test] fn dont_remove_braces_when_accessing_tuple() { assert_format!( r#"fn main() { { typed.0 }.type_ } "# ); } // https://github.com/gleam-lang/gleam/issues/1681 #[test] fn wrap_case_subjects() { assert_format!( r#"fn main() { case "This is a really really long string to force wrapping", "This is a really really long string to force wrapping", "This is a really really long string to force wrapping", "This is a really really long string to force wrapping" { _, _, _, _ -> Nil } } "# ); } // A bug reported on Discord. This would cause a compiler crash. #[test] fn multiple_empty_line_collapse_bug() { assert_format_rewrite!( r#"// Comment const x = 1 "#, r#"// Comment const x = 1 "# ); } #[test] fn do_not_remove_required_braces_case_guard() { assert_format!( "fn main() { let is_enabled = False let is_confirmed = False let is_admin = True case is_enabled, is_confirmed, is_admin { is_enabled, is_confirmed, is_admin if is_enabled && { is_confirmed || is_admin } -> Nil _, _, _ -> Nil } } " ); assert_format!( "fn main() { let wibble = True case wibble { wibble if True != { 1 == 2 } -> Nil _ -> Nil } } " ); assert_format!( "fn main() { let wibble = True let wobble = False case wibble { wibble if True != { 1 == { wobble == wibble } } -> Nil _ -> Nil } } " ); assert_format!( "fn main() { let wibble = #(10, [0]) case wibble { wibble if True && { wibble.0 == 10 || wibble.0 == 1 } -> Nil _ -> Nil } } " ); } #[test] fn do_not_remove_braces_from_case_guard() { assert_format!( "fn main() { let is_enabled = False let is_confirmed = False let is_admin = True case is_enabled, is_confirmed, is_admin { is_enabled, is_confirmed, is_admin if { is_enabled && is_confirmed } || is_admin -> Nil _, _, _ -> Nil } } " ); } #[test] fn do_not_remove_braces_from_case_guard_2() { assert_format!( "fn main() { let wibble = #(10, [0]) case wibble { wibble if True && { wibble.0 == 10 } -> Nil _ -> Nil } } " ); } #[test] fn const_multi_line_string_breaks() { assert_format!( r#"const string = [ "hello world", ] "# ); } #[test] fn expr_multi_line_string_breaks() { assert_format!( r#"pub fn main() { let string = [ "hello world", ] } "# ); } // https://github.com/gleam-lang/gleam/issues/1724 #[test] fn case_subject_block() { assert_format!( r#"pub fn main() { case { let assert Ok(x) = thing() let assert Ok(y) = thing() x + y } { _ -> Nil } } "# ); } #[test] fn qualified_const_fn() { assert_format!( r#"import other const x = other.function "# ); } #[test] fn qualified_const_fn_fn_after() { assert_format!( r#"import other const x = other.function pub fn main() { io.println("Hello, Joe!") } "# ); } // https://github.com/gleam-lang/gleam/issues/1872 #[test] fn multiple_line_spread_list_comments() { assert_format!( r#"fn main() { [ // First! // First? 1, // Spread! // Spread? ..[2, 3] ] } "# ); } // https://github.com/gleam-lang/gleam/issues/1872 #[test] fn list_spread_comment_pattern() { assert_format!( r#"fn main() { let assert [ 1, // Spread! // Spread? ..rest ] = x } "# ); } // https://github.com/gleam-lang/gleam/issues/1872 #[test] fn list_spread_discard_comment_pattern() { assert_format!( r#"fn main() { let assert [ 1, // Spread! // Spread? .. ] = x } "# ); } // https://github.com/gleam-lang/gleam/issues/1786 #[test] fn multiple_line_documentation_comment_statement_grouping() { assert_format!( r#"/// This is the first line of the documentation comment. /// This is the second line of the documentation comment. /// This is the third line of the documentation comment. pub type Map(key, value) "# ); } #[test] fn not_and() { assert_format!( r#"pub fn main() { !{ True && False } } "# ); } #[test] fn not_or() { assert_format!( r#"pub fn main() { !{ True || False } } "# ); } #[test] fn not_add() { assert_format!( r#"pub fn main() { !{ 1 + 3 } } "# ); } #[test] fn deprecated_assert() { assert_format_rewrite!( r#"fn main(x) { let assert True = x } "#, r#"fn main(x) { let assert True = x } "# ); } #[test] fn negate() { assert_format_rewrite!( r#"pub fn main() { let a = 3 let b = - a } "#, r#"pub fn main() { let a = 3 let b = -a } "# ); } #[test] fn double_negate() { assert_format_rewrite!( r#"pub fn main() { let a = 3 let b = --a } "#, r#"pub fn main() { let a = 3 let b = a } "# ); } #[test] fn triple_negate() { assert_format_rewrite!( r#"pub fn main() { let a = 3 let b = - - - a } "#, r#"pub fn main() { let a = 3 let b = -a } "# ); } #[test] fn binary_negate() { assert_format_rewrite!( r#"pub fn main() { let a = 3 let b = -{a+3} } "#, r#"pub fn main() { let a = 3 let b = -{ a + 3 } } "# ); } #[test] fn binary_double_negate() { assert_format_rewrite!( r#"pub fn main() { let a = 3 let b = --{a + 3} } "#, r#"pub fn main() { let a = 3 let b = { a + 3 } } "# ); } #[test] fn even_repeated_negate_after_subtract() { assert_format_rewrite!( r#"pub fn main() { let a = 3 let b = 4 let c = a-------b } "#, r#"pub fn main() { let a = 3 let b = 4 let c = a - b } "# ); } #[test] fn odd_repeated_negate_after_subtract() { assert_format_rewrite!( r#"pub fn main() { let a = 3 let b = 4 let c = a--------b } "#, r#"pub fn main() { let a = 3 let b = 4 let c = a - -b } "# ); } #[test] fn double_negation_on_bools_is_removed() { assert_format_rewrite!( r#"pub fn main() { !!True } "#, "pub fn main() { True } " ); } #[test] fn wrap_long_line_with_int_negation() { assert_format_rewrite!( r#"pub fn main() { let a = 3 let b = a * a * a * a * a * a * a * a * a * a * a * a * a * { a * a * a * a * a * a * a * a * a * a } let c = c * c * c * c * c * c * c * c * c * c * c * c * c * - { c * c * c * c * c * c * c * c * c * c } } "#, r#"pub fn main() { let a = 3 let b = a * a * a * a * a * a * a * a * a * a * a * a * a * { a * a * a * a * a * a * a * a * a * a } let c = c * c * c * c * c * c * c * c * c * c * c * c * c * -{ c * c * c * c * c * c * c * c * c * c } } "# ); } #[test] fn wrap_long_line_with_bool_negation() { assert_format_rewrite!( r#"pub fn main() { let a = True let b = a || a || a || a || a || a || a || a || a || a || a || a || a || { a || a || a || a || a || a || a || a || a || a } let c = c || c || c || c || c || c || c || c || c || c || c || c || c || ! { c || c || c || c || c || c || c || c || c || c } } "#, r#"pub fn main() { let a = True let b = a || a || a || a || a || a || a || a || a || a || a || a || a || { a || a || a || a || a || a || a || a || a || a } let c = c || c || c || c || c || c || c || c || c || c || c || c || c || !{ c || c || c || c || c || c || c || c || c || c } } "# ); } // https://github.com/gleam-lang/gleam/issues/1977 #[test] fn preserve_single_expression_blocks() { assert_format!( r#"pub fn main(x) { case x { 1 -> { 1 } _ -> 2 } } "# ); } #[test] fn calling_pipeline0() { assert_format!( r#"pub fn main() { { one |> two }() } "# ); } #[test] fn calling_pipeline1() { assert_format!( r#"pub fn main() { { one |> two }(1) } "# ); } #[test] fn calling_pipeline2() { assert_format!( r#"pub fn main() { { one |> two }(1, 2) } "# ); } #[test] fn calling_pipeline_1_list() { assert_format!( r#"pub fn main() { { one |> two }([1, 2, 3]) } "# ); } // https://github.com/gleam-lang/gleam/issues/2119 #[test] fn empty_line_after_fn_with_return_annotation() { assert_format!( r#"fn main() { fn() -> String { "" } 0 } "# ); } // https://github.com/gleam-lang/gleam/issues/2174 #[test] fn empty_line_after_crash() { assert_format_rewrite!( r#"pub type One { One // Comment } "#, r#"pub type One { One // Comment } "# ); } // https://github.com/gleam-lang/gleam/issues/2196 #[test] fn comment_at_end_of_type() { assert_format!( r#"pub type X { X // Afterwards } "# ); } #[test] fn deprecated_type_alias() { assert_format!( r#"@deprecated("Deprecated type") pub type Tiger = Nil "# ); } // https://github.com/gleam-lang/gleam/issues/2423 #[test] fn prefix_as() { assert_format!( r#"pub fn main(x) { case x { "0" as digit <> rest | "1" as digit <> rest -> rest } } "# ); } #[test] fn case_splits_function_on_newline() { assert_format!( r#"pub fn main() { case x { 1 -> some_module.some_long_name_function([ some_module.some_long_name_function(), ]) _ -> todo } } "# ); } // https://github.com/gleam-lang/gleam/issues/2442 #[test] fn single_argument_list() { assert_format!( r#"pub fn main() { Ok([ some_long_variable_name_to_force_wrapping, some_long_variable_name_to_force_wrapping, some_long_variable_name_to_force_wrapping, ]) } "# ); } // https://github.com/gleam-lang/gleam/issues/2442 #[test] fn single_argument_function() { assert_format!( r#"pub fn main() { Ok(fn() { some_long_variable_name_to_force_wrapping() some_long_variable_name_to_force_wrapping() some_long_variable_name_to_force_wrapping() }) } "# ); } // https://github.com/gleam-lang/gleam/issues/2442 #[test] fn single_argument_tuple() { assert_format!( r#"pub fn main() { Ok(#( some_long_variable_name_to_force_wrapping, some_long_variable_name_to_force_wrapping, some_long_variable_name_to_force_wrapping, )) } "# ); } // https://github.com/gleam-lang/gleam/issues/2442 #[test] fn single_argument_call() { assert_format!( r#"pub fn main() { Ok(do_something( some_long_variable_name_to_force_wrapping, some_long_variable_name_to_force_wrapping, some_long_variable_name_to_force_wrapping, )) } "# ); } // https://github.com/gleam-lang/gleam/issues/2442 #[test] fn single_argument_call_nested() { assert_format!( r#"pub fn main() { Ok( do_something(do_something_else( some_long_variable_name_to_force_wrapping, some_long_variable_name_to_force_wrapping, some_long_variable_name_to_force_wrapping, )), ) } "# ); } // https://github.com/gleam-lang/gleam/issues/2442 #[test] fn single_argument_call_nested_nested() { assert_format!( r#"pub fn main() { Ok( do_something( do_something_else(do_a_last_thing( some_long_variable_name_to_force_wrapping, some_long_variable_name_to_force_wrapping, some_long_variable_name_to_force_wrapping, )), ), ) } "# ); } // https://github.com/gleam-lang/gleam/issues/2512 #[test] fn list_with_pipe_format() { assert_format!( r#"pub fn main() { [ "Success!" |> ansi(apply: [1, 31]), "", "Wrote `" <> bin <> "`, `" <> pwsh_bin <> "`", ] } "# ); } #[test] fn function_call_close_to_line_limit() { assert_format!( r#"pub fn main() { function_call( that, is, super, close, to, the, max, line, limit, of, 80, chars, ) } "# ); } #[test] fn multiline_string_are_not_broken_with_string_concatenation_if_they_fit() { assert_format!( r#"pub fn main() { "pub fn wibble(" <> arg <> ") ->" <> type_ <> "{ body }" } "# ); } #[test] fn nesting_goes_back_to_normal_after_multiline_string() { assert_format!( r#"pub fn main() { let x = { " 1 2 " <> long_name_function_call( 1_111_111_111_111_111, 222_222_222_222, 3_333_333_333_333_333, ) } } "# ); } #[test] fn multiline_string_get_broken_on_newlines_as_function_arguments() { assert_format!( r#"pub fn main() { wibble( wobble, "wobble wibble wobble", wibble, wobble, ) } "# ); } #[test] fn pipeline_used_as_function_arguments_gets_nested() { assert_format!( r#"pub fn main() { wibble( a_variable_with_a_long_name |> another_variable_with_a_long_name |> yet_another_variable_with_a_long_name, wobble, ) } "# ); } #[test] fn pipeline_used_as_function_arguments_is_not_nested_if_it_is_the_only_argument() { assert_format!( r#"pub fn main() { wibble( a_variable_with_a_long_name |> another_variable_with_a_long_name |> yet_another_variable_with_a_long_name, ) } "# ); } #[test] fn pipeline_inside_list_gets_nested() { assert_format!( r#"pub fn main() { [ wibble, a_variable_with_a_long_name |> another_variable_with_a_long_name |> yet_another_variable_with_a_long_name, ] } "# ); } #[test] fn pipeline_inside_list_is_not_nested_if_only_item() { assert_format!( r#"pub fn main() { [ a_variable_with_a_long_name |> another_variable_with_a_long_name |> yet_another_variable_with_a_long_name, ] } "# ); } #[test] fn pipeline_inside_tuple_gets_nested() { assert_format!( r#"pub fn main() { #( wibble, a_variable_with_a_long_name |> another_variable_with_a_long_name |> yet_another_variable_with_a_long_name, ) } "# ); } #[test] fn pipeline_inside_tuple_is_not_nested_if_only_item() { assert_format!( r#"pub fn main() { #( a_variable_with_a_long_name |> another_variable_with_a_long_name |> yet_another_variable_with_a_long_name, ) } "# ); } // github.com/gleam-lang/gleam/issues/2608 #[test] fn comments_are_not_moved_out_of_list_of_literals() { assert_format!( r#"fn main() { [ 1, 2, // list ] } "# ); } // github.com/gleam-lang/gleam/issues/2608 #[test] fn comments_are_not_moved_out_of_list() { assert_format!( r#"fn main() { [ wibble, wobble, // list ] } "# ); } // github.com/gleam-lang/gleam/issues/2608 #[test] fn comments_are_not_moved_out_of_case_expressions() { assert_format!( r#"fn main() { case True { _ -> Nil // case } } "# ); } // github.com/gleam-lang/gleam/issues/2608 #[test] fn comments_are_not_moved_out_of_tuples() { assert_format!( r#"fn main() { #( 1, 2, // tuple ) } "# ); } // github.com/gleam-lang/gleam/issues/2608 #[test] fn comments_are_not_moved_out_of_function_calls() { assert_format!( r#"fn main() { call( 1, 2, // function call ) } "# ); } // https://github.com/gleam-lang/gleam/issues/2607 #[test] fn function_arguments_after_comment_are_not_indented() { assert_format!( r#"pub fn main() { wibble( // Wobble 1 + 1, "wibble", ) } "# ); } // https://github.com/gleam-lang/gleam/issues/2607 #[test] fn tuple_items_after_comment_are_not_indented() { assert_format!( r#"pub fn main() { #( // Wobble 1 + 1, "wibble", ) } "# ); } // https://github.com/gleam-lang/gleam/issues/2607 #[test] fn list_items_after_comment_are_not_indented() { assert_format!( r#"pub fn main() { [ // Wobble 1 + 1, "wibble", ] } "# ); } // https://github.com/gleam-lang/gleam/issues/2990 #[test] fn comments_are_not_moved_out_of_empty_list() { assert_format!( r#"pub fn main() { // This is an empty list! [ // Nothing here... ] } "# ); } #[test] fn empty_lists_with_comment_inside_are_indented_properly() { assert_format!( r#"pub fn main() { fun( [ // Nothing here... ], wibble_wobble_wibble_wobble_wibble_wobble_wibble_wobble, [ // Nothing here as well! ], ) } "# ); } // https://github.com/gleam-lang/gleam/issues/2890 #[test] fn piped_blocks_are_not_needlessly_indented() { assert_format!( r#"pub fn main() { #( 1, { "long enough to need to wrap. blah blah blah blah blah blah blah blah blah" } |> wibble, 3, ) } "# ); } // https://github.com/gleam-lang/gleam/issues/2924 #[test] fn record_update_fields_are_not_needlessly_broken() { assert_format!( r#"pub fn main() { Model( ..model, wibble: wibble_wobble_wibble_wobble + 1, wobble: Some(wibble_wobble_wibble_wobble), ) } "# ); } // https://github.com/gleam-lang/gleam/issues/2890 #[test] fn piped_lists_are_not_needlessly_indented() { assert_format!( r#"pub fn main() { fun( [ ["wibble wobble", "wibble", "wobble"], ["long enough to go over", "line limit"], ] |> list.concat, todo, ) } "# ); } #[test] fn comments_inside_nested_pipe_chain() { assert_format!( r#"pub fn main() { fun( thing // A comment |> wibble // Another comment |> wobble, thing, ) } "# ); } #[test] fn comments_inside_nested_binop_chain() { assert_format!( r#"pub fn main() { fun( thing // A comment <> wibble // Another comment <> wobble, thing, ) } "# ); } #[test] fn comments_inside_binop_chain() { assert_format!( r#"pub fn main() { thing // A comment <> wibble // Another comment <> wobble } "# ); } #[test] fn internal_attribute_on_function() { assert_format!( r#"@internal pub fn main() { todo } "# ); } #[test] fn internal_attribute_on_type() { assert_format!( r#"@internal pub type Type "# ); } #[test] fn internal_attribute_on_const() { assert_format!( r#"@internal pub const wibble = 1 "# ); } #[test] fn comments_inside_contant_list() { assert_format!( r#"const wibble = [ // A comment 1, 2, // Another comment 3, // One last comment ] "# ); } #[test] fn comments_inside_contant_empty_list() { assert_format!( r#"const wibble = [ // A comment ] "# ); } #[test] fn comments_inside_contant_tuple() { assert_format!( r#"const wibble = #( // A comment 1, 2, // Another comment 3, // One last comment ) "# ); } #[test] fn comments_inside_contant_empty_tuple() { assert_format!( r#"const wibble = #( // A comment ) "# ); } #[test] fn comments_inside_empty_tuple() { assert_format!( r#"pub fn main() { #( // A comment! ) } "# ); } #[test] fn comments_at_the_end_of_anonymous_function() { assert_format!( r#"pub fn main() { fn() { 1 // a final comment // another final comment // at the end of the block } } "# ); } #[test] fn comments_in_anonymous_function_args() { assert_format!( r#"pub fn main() { fn( // A comment 1 // A comment 2 ) { 1 } } "# ); assert_format!( r#"pub fn main() { fn( // A comment 1 a, // A comment 2 ) { 1 } } "# ); } #[test] fn comments_after_last_argument_of_record_constructor() { assert_format!( r#"type Record { Record( field: String, // comment_line_1: String, // comment_line_2: String, ) } "# ); } #[test] fn only_comments_in_record_constructor() { assert_format!( r#"type Record { Record( // comment_line_1: String, // comment_line_2: String, ) } "# ); } #[test] fn comment_after_spread_operator() { assert_format!( "type Triple { Triple(a: Int, b: Int, c: Int) } fn main() { let triple = Triple(1, 2, 3) let Triple( really_really_long_variable_name_a, c: really_really_long_variable_name_c, .., // comment ) = triple really_really_long_variable_name_c } " ); } #[test] fn multiline_comment_in_case_block() { assert_format!( r#"pub fn do_len(list, acc) { case list { [] -> acc [_, ..rest] -> rest |> do_len(acc + 1) // Even the opposite wouldn't be optimised: // { acc + 1 } |> do_len(rest, _) } } "# ); } // https://github.com/gleam-lang/gleam/issues/3190 #[test] fn trailing_comments_inside_non_empty_bit_arrays_are_not_moved() { assert_format!( r#"pub fn main() { << 1, 2, // One and two are above me. >> } "# ); } // https://github.com/gleam-lang/gleam/issues/3210 #[test] fn newlines_are_not_stripped_if_two_consecutive_anonymous_function_are_passed_as_arguments() { assert_format!( r#"pub fn main() { fun( fn() { wibble wobble }, fn() { wibble }, ) } "# ); } #[test] fn const_long_concat_string() { assert_format_rewrite!( r#"const long_string = "some" <> " very" <> " long" <> " string" <> " indeed" <> " please" <> " break" "#, r#"const long_string = "some" <> " very" <> " long" <> " string" <> " indeed" <> " please" <> " break" "# ); } #[test] fn const_concat_short_unbroken() { assert_format!( r#"const x = "some" <> "short" <> "string" "# ); } #[test] fn const_concat_long_including_list() { assert_format_rewrite!( r#"const x = "some long string 1" <> "some long string 2" <> ["here is a list", "with several elements", "in order to make it be too long to fit on one line", "so we can see how it breaks", "onto multiple lines"] <> "and a last string" "#, r#"const x = "some long string 1" <> "some long string 2" <> [ "here is a list", "with several elements", "in order to make it be too long to fit on one line", "so we can see how it breaks", "onto multiple lines", ] <> "and a last string" "#, ); } // https://github.com/gleam-lang/gleam/issues/3397 #[test] fn comment_after_case_branch() { assert_format!( r#"pub fn main() { case x { _ -> // comment [123] } } "# ); } // https://github.com/gleam-lang/gleam/issues/3397 #[test] fn comment_after_case_branch_case() { assert_format!( r#"pub fn main() { case x { _ -> // comment case y { _ -> todo } } } "# ); } #[test] fn label_shorthand_call_arg_is_split_like_regular_labelled_args() { assert_format!( r#"pub fn main() { wibble( a_punned_arg_that_is_super_long:, another_punned_arg:, yet_another_pun:, ok_thats_enough: wibble, ) } "# ); } #[test] fn commented_label_shorthand_call_arg_is_split_like_regular_labelled_args() { assert_format!( r#"pub fn main() { wibble( // A comment here a_punned_arg_that_is_super_long:, another_punned_arg:, // And a comment there yet_another_pun:, ok_thats_enough: wibble, ) } "# ); } #[test] fn label_shorthand_pattern_arg_is_split_like_regular_labelled_patterns() { assert_format!( r#"pub fn main() { let Wibble( a_punned_arg_that_is_super_long:, another_punned_arg:, yet_another_pun:, ok_thats_enough: wibble, ) = todo } "# ); } #[test] fn record_pattern_with_no_label_shorthand() { assert_format!( r#"pub fn main() { let Wibble(x: x) = todo } "# ); } #[test] fn record_with_no_label_shorthand() { assert_format!( r#"pub fn main() { Wibble(x: x) } "# ); } #[test] fn function_without_label_shorthand() { assert_format!( r#"pub fn main() { wibble(x: x) } "# ); } // https://github.com/gleam-lang/gleam/issues/2015 #[test] fn doc_comments_are_split_by_regular_comments() { assert_format!( r#"/// Doc comment // Commented function // fn wibble() {} /// Other doc comment pub fn main() { todo } "# ); } // https://github.com/gleam-lang/gleam/issues/2015 #[test] fn it_is_easy_to_tell_two_different_doc_comments_apart_when_a_regular_comment_is_separating_those() { assert_format_rewrite!( r#"/// Doc comment // regular comment /// Other doc comment pub fn main() { todo } "#, r#"/// Doc comment // regular comment /// Other doc comment pub fn main() { todo } "# ); } // https://github.com/gleam-lang/gleam/issues/2015 #[test] fn multiple_commented_definitions_in_a_row_2() { assert_format!( r#"/// Stray comment // regular comment /// Stray comment // regular comment /// Doc comment pub fn wibble() { todo } "# ); } // https://github.com/gleam-lang/gleam/issues/2015 #[test] fn only_stray_comments_and_definition_with_no_doc_comments() { assert_format!( r#"/// Stray comment // regular comment /// Stray comment // regular comment pub fn wibble() { todo } "# ); } // https://github.com/gleam-lang/gleam/issues/2015 #[test] fn only_stray_comments_and_definition_with_no_doc_comments_2() { assert_format_rewrite!( r#"/// Stray comment // regular comment pub fn wibble () { todo } "#, r#"/// Stray comment // regular comment pub fn wibble() { todo } "# ); } #[test] fn discard_in_pipe_is_not_turned_into_shorthand_label() { assert_format!( r#"pub fn main() { wibble |> wobble(one: 1, label: _, two: 2) } "# ); } // Bug found by Louis #[test] fn internal_attribute_does_not_change_formatting_of_a_function() { assert_format!( r#"@internal pub fn init( start: #(SupervisorFlags, List(ChildSpecification)), ) -> Result(#(Dynamic, Dynamic), never) { todo } "# ); } // https://github.com/gleam-lang/gleam/issues/3627 #[test] fn big_grapheme_cluster() { assert_format!( r#"pub fn main() { sw("👩‍👩‍👧‍👦👩‍👩‍👧‍👦👩‍👩‍👧‍👦", []) } "# ); } // https://github.com/gleam-lang/gleam/issues/3720 #[test] fn record_inside_const_list() { assert_format_rewrite!( r#"const commands = [ Command( "dev", "Start a file watcher that automatically re-compiles your app on all file changes.", ), Command("help", "Show this help text."), ] "#, r#"const commands = [ Command( "dev", "Start a file watcher that automatically re-compiles your app on all file changes.", ), Command("help", "Show this help text."), ] "# ); } #[test] fn formatter_adds_todo_inside_empty_block() { assert_format_rewrite!( "pub fn main() {{}}", r#"pub fn main() { { todo } } "# ); } #[test] fn let_assert_as() { assert_format!( r#"pub fn main() { let assert 10 = 10 as "10 == 10" } "# ); } #[test] fn let_assert_as_long_message() { assert_format!( r#"pub fn main() { let assert Ok(10) = Ok(10) as "It's pretty obvious that this will never fail, but just in case, here is why." } "# ); } #[test] fn let_assert_as_long_message_and_value() { assert_format!( r#"pub fn main() { let assert Ok(something) = some_very_long_variable_which_always_represents_a_successful_result as "As you can see by the incredibly descriptive variable name, this operation never fails." } "# ); } #[test] fn let_assert_as_concatenated_message() { assert_format!( r#"pub fn main() { let assert 1 = 2 as { "This will" <> " " <> "crash" } } "# ); } #[test] fn let_assert_as_variable_message() { assert_format!( r#"pub fn main() { let message = "Hi :)" let assert 1 = 2 as message } "# ); } // https://github.com/gleam-lang/gleam/issues/4121 #[test] fn function_capture_formatted_like_regular_calls() { assert_format!( r#"pub fn main() { capture(a, _, [ really_long_thing_that_can_be_broken, something_else_for_good_measure, ]) } "# ); } #[test] fn function_capture_formatted_like_regular_calls_2() { assert_format!( r#"pub fn main() { capture( a, _, really_long_thing_that_cannot_be_broken, something_else_for_good_measure, ) } "# ); } #[test] fn function_capture_formatted_like_regular_calls_3() { assert_format!( r#"pub fn main() { list.fold(my_list, _, fn(a) { io.print("Meh") io.print("Meh") }) } "# ); } #[test] fn function_capture_formatted_like_regular_calls_inside_a_long_list() { assert_format!( r#"pub fn main() { [ capture(a, _, [ really_long_thing_that_can_be_broken, something_else_for_good_measure, ]), regular_call(a, [ really_long_thing_that_can_be_broken, something_else_for_good_measure, ]), ] } "# ); } #[test] fn function_capture_formatted_like_regular_calls_in_a_pipe() { assert_format!( r#"pub fn main() { [1, 2, 3] |> list.fold(from: 1, over: _, with: fn(a, b) { // a comment! a + b }) } "# ); } #[test] fn assert() { assert_format!( "pub fn main() { assert True } " ); } #[test] fn assert_with_long_expression() { assert_format!( "pub fn main() { assert some_function_with_a_very_long_name_that_exceeds_the_eighty_character_limit() } " ); } #[test] fn assert_with_message() { assert_format!( r#"pub fn main() { assert True as "This is always true" } "# ); } #[test] fn assert_with_long_message() { assert_format!( r#"pub fn main() { assert True as "This should never panic, because it is a literal True value, and so will always be true." } "# ); } #[test] fn assert_with_long_expression_and_long_message() { assert_format!( r#"pub fn main() { assert some_long_function_name_which_if_everything_is_right_should_always_be_true as "This should never panic, because the function only ever returns true." } "# ); } #[test] fn echo_with_long_binary_expression() { assert_format!( r#"pub fn main() { echo wibble_wobble_wibble_wobble_wibble_wobble_wibble >= wibble_wobble_wibble_wobble_wibble_wobble_wibble echo wibble_wobble_wibble_wobble_wibble_wobble_wibble + wibble_wobble_wibble_wobble_wibble_wobble_wibble echo wibble_wobble_wibble_wobble_wibble_wobble_wibble == wibble_wobble_wibble_wobble_wibble_wobble_wibble } "# ); } #[test] fn assert_with_long_binary_expression() { assert_format!( r#"pub fn main() { assert wibble_wobble_wibble_wobble_wibble_wobble_wibble >= wibble_wobble_wibble_wobble_wibble_wobble_wibble assert wibble_wobble_wibble_wobble_wibble_wobble_wibble + wibble_wobble_wibble_wobble_wibble_wobble_wibble assert wibble_wobble_wibble_wobble_wibble_wobble_wibble == wibble_wobble_wibble_wobble_wibble_wobble_wibble } "# ); } #[test] fn comment_is_not_moved_after_assert() { assert_format!( "pub fn main() { // Wibble! assert True } " ); } #[test] fn todo_as_with_comment() { assert_format!( r#"pub fn main() { todo as // A little comment explaining something "wibble" } "# ); } #[test] fn todo_as_with_comment_on_the_same_line() { assert_format_rewrite!( r#"pub fn main() { todo as // A little comment explaining something "wibble" } "#, r#"pub fn main() { todo as // A little comment explaining something "wibble" } "# ); } #[test] fn todo_as_with_comment_before_the_as() { assert_format_rewrite!( r#"pub fn main() { todo // A little comment explaining something as "wibble" } "#, r#"pub fn main() { todo as // A little comment explaining something "wibble" } "# ); } #[test] fn panic_as_with_comment() { assert_format!( r#"pub fn main() { panic as // A little comment explaining something "wibble" } "# ); } #[test] fn panic_as_with_comment_on_the_same_line() { assert_format_rewrite!( r#"pub fn main() { panic as // A little comment explaining something "wibble" } "#, r#"pub fn main() { panic as // A little comment explaining something "wibble" } "# ); } #[test] fn panic_as_with_comment_before_the_as() { assert_format_rewrite!( r#"pub fn main() { panic // A little comment explaining something as "wibble" } "#, r#"pub fn main() { panic as // A little comment explaining something "wibble" } "# ); } #[test] fn echo_as_with_comment() { assert_format!( r#"pub fn main() { echo 1 as // A little comment explaining something "wibble" } "# ); } #[test] fn echo_as_with_comment_on_the_same_line() { assert_format_rewrite!( r#"pub fn main() { echo 1 as // A little comment explaining something "wibble" } "#, r#"pub fn main() { echo 1 as // A little comment explaining something "wibble" } "# ); } #[test] fn echo_as_with_comment_before_the_as() { assert_format_rewrite!( r#"pub fn main() { echo 1 // A little comment explaining something as "wibble" } "#, r#"pub fn main() { echo 1 as // A little comment explaining something "wibble" } "# ); } #[test] fn assert_as_with_comment() { assert_format!( r#"pub fn main() { assert True as // A little comment explaining something "wibble" } "# ); } #[test] fn assert_as_with_comment_on_the_same_line() { assert_format_rewrite!( r#"pub fn main() { assert True as // A little comment explaining something "wibble" } "#, r#"pub fn main() { assert True as // A little comment explaining something "wibble" } "# ); } #[test] fn assert_as_with_comment_before_the_as() { assert_format_rewrite!( r#"pub fn main() { assert True // A little comment explaining something as "wibble" } "#, r#"pub fn main() { assert True as // A little comment explaining something "wibble" } "# ); } // https://github.com/gleam-lang/gleam/issues/4664 #[test] fn pattern_unused_discard() { assert_format_rewrite!( r#"pub fn main() { let a = 10 let _ = case a { _ as b -> b } } "#, r#"pub fn main() { let a = 10 let _ = case a { b -> b } } "# ); } // https://github.com/gleam-lang/gleam/issues/4929 #[test] fn format_panic_as_with_block_message() { assert_format!( r#"pub fn main() { panic as { // b a } } "# ); } // https://github.com/gleam-lang/gleam/issues/5056 #[test] fn remove_redundant_negation_from_literal_int_1() { assert_format_rewrite!( "pub fn main() { --1 } ", "pub fn main() { 1 } " ); } #[test] fn remove_redundant_negation_from_literal_int_2() { assert_format_rewrite!( "pub fn main() { ---1 } ", "pub fn main() { -1 } " ); } #[test] fn remove_redundant_negation_from_literal_int_3() { assert_format_rewrite!( "pub fn main() { ----1 } ", "pub fn main() { 1 } " ); } #[test] fn call_with_single_call_argument_and_trailing_comment() { assert_format!( "pub fn main() { call( wibble(wobble), // ... ) } " ); } #[test] fn call_with_single_call_argument_and_trailing_comment_2() { assert_format_rewrite!( "pub fn main() { call(wibble(wobble) // ... ) } ", "pub fn main() { call( wibble(wobble), // ... ) } " ); } #[test] fn can_format_big_list_without_stack_overflowing() { let items = std::iter::repeat_n(" 1,", 10_000).join("\n"); assert_format!(format!( "pub fn main() {{ [ {items} ] }} " )); } // https://github.com/gleam-lang/gleam/issues/5323 #[test] fn internal_const_list_is_kept_on_multiple_lines() { assert_format!( "@internal pub const list = [ LeftToRight, RightToLeft, ] " ); } // https://github.com/gleam-lang/gleam/issues/5401 #[test] fn multiple_field_access_are_not_put_in_a_block() { assert_format!( "pub fn main() { a.wib.wob } " ); } #[test] fn multiple_tuple_field_access_are_not_put_in_a_block() { assert_format!( "pub fn main() { #(1, 2).1.2 } " ); } ================================================ FILE: compiler-core/src/format.rs ================================================ #[cfg(test)] mod tests; use crate::{ Error, Result, ast::{ CustomType, Import, ModuleConstant, TypeAlias, TypeAstConstructor, TypeAstFn, TypeAstHole, TypeAstTuple, TypeAstVar, *, }, build::Target, docvec, io::Utf8Writer, parse::extra::{Comment, ModuleExtra}, pretty::{self, *}, warning::WarningEmitter, }; use ecow::{EcoString, eco_format}; use itertools::Itertools; use std::cmp::Ordering; use vec1::Vec1; use crate::type_::Deprecation; use camino::Utf8Path; const INDENT: isize = 2; pub fn pretty(writer: &mut impl Utf8Writer, src: &EcoString, path: &Utf8Path) -> Result<()> { let parsed = crate::parse::parse_module(path.to_owned(), src, &WarningEmitter::null()) .map_err(|error| Error::Parse { path: path.to_path_buf(), src: src.clone(), error: Box::new(error), })?; let intermediate = Intermediate::from_extra(&parsed.extra, src); Formatter::with_comments(&intermediate) .module(&parsed.module) .pretty_print(80, writer) } pub(crate) struct Intermediate<'a> { comments: Vec>, doc_comments: Vec>, module_comments: Vec>, empty_lines: &'a [u32], new_lines: &'a [u32], trailing_commas: &'a [u32], } impl<'a> Intermediate<'a> { pub fn from_extra(extra: &'a ModuleExtra, src: &'a EcoString) -> Intermediate<'a> { Intermediate { comments: extra .comments .iter() .map(|span| Comment::from((span, src))) .collect(), doc_comments: extra .doc_comments .iter() .map(|span| Comment::from((span, src))) .collect(), empty_lines: &extra.empty_lines, module_comments: extra .module_comments .iter() .map(|span| Comment::from((span, src))) .collect(), new_lines: &extra.new_lines, trailing_commas: &extra.trailing_commas, } } } #[derive(Debug)] enum FnCapturePosition { RightHandSideOfPipe, EverywhereElse, } #[derive(Debug)] /// One of the pieces making a record update arg list: it could be the starting /// record being updated, or one of the subsequent arguments. /// enum RecordUpdatePiece<'a, A> { Record(&'a RecordBeingUpdated), Argument(&'a RecordUpdateArg), } impl HasLocation for RecordUpdatePiece<'_, A> { fn location(&self) -> SrcSpan { match self { RecordUpdatePiece::Record(record) => record.location, RecordUpdatePiece::Argument(arg) => arg.location, } } } type UntypedRecordUpdatePiece<'a> = RecordUpdatePiece<'a, UntypedExpr>; /// Hayleigh's bane #[derive(Debug, Clone, Default)] pub struct Formatter<'a> { comments: &'a [Comment<'a>], doc_comments: &'a [Comment<'a>], module_comments: &'a [Comment<'a>], empty_lines: &'a [u32], new_lines: &'a [u32], trailing_commas: &'a [u32], } impl<'comments> Formatter<'comments> { pub fn new() -> Self { Default::default() } pub(crate) fn with_comments(extra: &'comments Intermediate<'comments>) -> Self { Self { comments: &extra.comments, doc_comments: &extra.doc_comments, module_comments: &extra.module_comments, empty_lines: extra.empty_lines, new_lines: extra.new_lines, trailing_commas: extra.trailing_commas, } } /// Returns true if there's any comment that comes before the given /// position. /// fn any_comments(&self, limit: u32) -> bool { self.comments .first() .is_some_and(|comment| comment.start < limit) } /// Returns true if there's any comment that appears inside the given span. /// fn any_comment_between(&self, start: u32, end: u32) -> bool { self.comments .binary_search_by(|comment| { if comment.start < start { Ordering::Less } else if comment.start > end { Ordering::Greater } else { Ordering::Equal } }) .is_ok() } fn any_empty_lines(&self, limit: u32) -> bool { self.empty_lines.first().is_some_and(|line| *line < limit) } /// Pop comments that occur before a byte-index in the source, consuming /// and retaining any empty lines contained within. /// Returns an iterator of comments with their start position. fn pop_comments_with_position( &mut self, limit: u32, ) -> impl Iterator)> + use<'comments> { let (popped, rest, empty_lines) = comments_before(self.comments, self.empty_lines, limit, true); self.comments = rest; self.empty_lines = empty_lines; popped } /// Pop comments that occur before a byte-index in the source, consuming /// and retaining any empty lines contained within. fn pop_comments( &mut self, limit: u32, ) -> impl Iterator> + use<'comments> { self.pop_comments_with_position(limit) .map(|(_position, comment)| comment) } /// Pop doc comments that occur before a byte-index in the source, consuming /// and dropping any empty lines contained within. fn pop_doc_comments( &mut self, limit: u32, ) -> impl Iterator> + use<'comments> { let (popped, rest, empty_lines) = comments_before(self.doc_comments, self.empty_lines, limit, false); self.doc_comments = rest; self.empty_lines = empty_lines; popped.map(|(_position, comment)| comment) } /// Remove between 0 and `limit` empty lines following the current position, /// returning true if any empty lines were removed. fn pop_empty_lines(&mut self, limit: u32) -> bool { let mut end = 0; for (i, &position) in self.empty_lines.iter().enumerate() { if position > limit { break; } end = i + 1; } self.empty_lines = self .empty_lines .get(end..) .expect("Pop empty lines slicing"); end != 0 } fn targeted_definition<'a>(&mut self, definition: &'a TargetedDefinition) -> Document<'a> { let target = definition.target; let definition = &definition.definition; let start = definition.location().start; let comments = self.pop_comments_with_position(start); let comments = self.printed_documented_comments(comments); let document = self.documented_definition(definition); let document = match target { None => document, Some(Target::Erlang) => docvec!["@target(erlang)", line(), document], Some(Target::JavaScript) => docvec!["@target(javascript)", line(), document], }; comments.to_doc().append(document.group()) } pub(crate) fn module<'a>(&mut self, module: &'a UntypedModule) -> Document<'a> { let mut documents = vec![]; let mut previous_was_a_definition = false; // Here we take consecutive groups of imports so that they can be sorted // alphabetically. for (is_import_group, definitions) in &module .definitions .iter() .chunk_by(|definition| definition.definition.is_import()) { if is_import_group { if previous_was_a_definition { documents.push(lines(2)); } documents.append(&mut self.imports(definitions.collect_vec())); previous_was_a_definition = false; } else { for definition in definitions { if !documents.is_empty() { documents.push(lines(2)); } documents.push(self.targeted_definition(definition)); } previous_was_a_definition = true; } } let definitions = concat(documents); // Now that definitions has been collected, only freestanding comments (//) // and doc comments (///) remain. Freestanding comments aren't associated // with any statement, and are moved to the bottom of the module. let doc_comments = join( self.doc_comments .iter() .map(|comment| "///".to_doc().append(EcoString::from(comment.content))), line(), ); let comments = match printed_comments(self.pop_comments(u32::MAX), false) { Some(comments) => comments, None => nil(), }; let module_comments = if !self.module_comments.is_empty() { let comments = self .module_comments .iter() .map(|s| "////".to_doc().append(EcoString::from(s.content))); join(comments, line()).append(line()) } else { nil() }; let non_empty = vec![module_comments, definitions, doc_comments, comments] .into_iter() .filter(|doc| !doc.is_empty()); join(non_empty, line()).append(line()) } /// Separates the imports in groups delimited by comments or empty lines and /// sorts each group alphabetically. /// /// The formatter needs to play nicely with import groups defined by the /// programmer. If one puts a comment before an import then that's a clue /// for the formatter that it has run into a gorup of related imports. /// /// So we can't just sort `imports` and format each one, we have to be a /// bit smarter and see if each import is preceded by a comment. /// Once we find a comment we know we're done with the current import /// group and a new one has started. /// /// ```gleam /// // This is an import group. /// import gleam/int /// import gleam/string /// /// // This marks the beginning of a new import group that can't /// // be mushed together with the previous one! /// import wibble /// import wobble /// ``` fn imports<'a>(&mut self, imports: Vec<&'a TargetedDefinition>) -> Vec> { let mut import_groups_docs = vec![]; let mut current_group = vec![]; let mut current_group_delimiter = nil(); for import in imports { let start = import.definition.location().start; // We need to start a new group if the `import` is preceded by one or // more empty lines or a `//` comment. let start_new_group = self.any_comments(start) || self.any_empty_lines(start); if start_new_group { // First we print the previous group and clear it out to start a // new empty group containing the import we've just ran into. if !current_group.is_empty() { import_groups_docs.push(docvec![ current_group_delimiter, self.sorted_import_group(¤t_group) ]); current_group.clear(); } // Now that we've taken care of the previous group we can start // the new one. We know it's preceded either by an empty line or // some comments se we have to be a bit more precise and save the // actual delimiter that we're going to put at the top of this // group. let comments = self.pop_comments(start); let _ = self.pop_empty_lines(start); current_group_delimiter = printed_comments(comments, true).unwrap_or(nil()); } // Lastly we add the import to the group. current_group.push(import); } // Let's not forget about the last import group! if !current_group.is_empty() { import_groups_docs.push(docvec![ current_group_delimiter, self.sorted_import_group(¤t_group) ]); } // We want all consecutive import groups to be separated by an empty line. // This should really be `.intersperse(line())` but I can't do that // because of https://github.com/rust-lang/rust/issues/48919. Itertools::intersperse(import_groups_docs.into_iter(), lines(2)).collect_vec() } /// Prints the imports as a single sorted group of import statements. /// fn sorted_import_group<'a>(&mut self, imports: &[&'a TargetedDefinition]) -> Document<'a> { let imports = imports .iter() .sorted_by(|one, other| match (&one.definition, &other.definition) { (Definition::Import(one), Definition::Import(other)) => { one.module.cmp(&other.module) } // It shouldn't really be possible for a non import to be here so // we just return a default value. _ => Ordering::Equal, }) .map(|import| self.targeted_definition(import)); // This should really be `.intersperse(line())` but I can't do that // because of https://github.com/rust-lang/rust/issues/48919. Itertools::intersperse(imports, line()) .collect_vec() .to_doc() } fn definition<'a>(&mut self, statement: &'a UntypedDefinition) -> Document<'a> { match statement { Definition::Function(function) => self.statement_fn(function), Definition::TypeAlias(alias) => self.type_alias(alias), Definition::CustomType(ct) => self.custom_type(ct), Definition::Import(Import { module, as_name, unqualified_values, unqualified_types, .. }) => { let second = if unqualified_values.is_empty() && unqualified_types.is_empty() { nil() } else { let unqualified_types = unqualified_types .iter() .sorted_by(|a, b| a.name.cmp(&b.name)) .map(|type_| docvec!["type ", type_]); let unqualified_values = unqualified_values .iter() .sorted_by(|a, b| a.name.cmp(&b.name)) .map(|value| value.to_doc()); let unqualified = join( unqualified_types.chain(unqualified_values), flex_break(",", ", "), ); let unqualified = break_("", "") .append(unqualified) .nest(INDENT) .append(break_(",", "")) .group(); ".{".to_doc().append(unqualified).append("}") }; let doc = docvec!["import ", module.as_str(), second]; let default_module_access_name = module.split('/').next_back().map(EcoString::from); match (default_module_access_name, as_name) { // If the `as name` is the same as the module name that would be // used anyways we won't render it. For example: // ```gleam // import gleam/int as int // ^^^^^^ this is redundant and removed // ``` (Some(module_name), Some((AssignName::Variable(name), _))) if &module_name == name => { doc } (_, None) => doc, (_, Some((AssignName::Variable(name) | AssignName::Discard(name), _))) => { doc.append(" as ").append(name) } } } Definition::ModuleConstant(ModuleConstant { publicity, name, annotation, value, deprecation, documentation: _, location: _, name_location: _, type_: _, implementations: _, }) => { let attributes = AttributesPrinter::new() .set_internal(*publicity) .set_deprecation(deprecation) .to_doc(); let head = attributes .append(pub_(*publicity)) .append("const ") .append(name.as_str()); let head = match annotation { None => head, Some(t) => head.append(": ").append(self.type_ast(t)), }; head.append(" = ").append(self.const_expr(value).group()) } } } fn const_expr<'a, A, B>(&mut self, value: &'a Constant) -> Document<'a> { let comments = self.pop_comments(value.location().start); let document = match value { Constant::Int { value, .. } => self.int(value), Constant::Float { value, .. } => self.float(value), Constant::String { value, .. } => self.string(value), Constant::List { elements, location, tail, .. } => self.const_list(elements, location, tail), Constant::Tuple { elements, location, .. } => self.const_tuple(elements, location), Constant::BitArray { segments, location, .. } => { let segment_docs = segments .iter() .map(|segment| bit_array_segment(segment, |e| self.const_expr(e))) .collect_vec(); let packing = self.items_sequence_packing( segments, None, |segment| segment.value.can_have_multiple_per_line(), *location, ); self.bit_array(segment_docs, packing, location) } Constant::Record { name, arguments, module: None, .. } if arguments.is_empty() => name.to_doc(), Constant::Record { name, arguments, module: Some((m, _)), .. } if arguments.is_empty() => m.to_doc().append(".").append(name.as_str()), Constant::Record { name, arguments, module: None, location, .. } => { let arguments = arguments .iter() .map(|argument| self.constant_call_arg(argument)) .collect_vec(); name.to_doc() .append(self.wrap_arguments(arguments, location.end)) .group() } Constant::Record { name, arguments, module: Some((m, _)), location, .. } => { let arguments = arguments .iter() .map(|argument| self.constant_call_arg(argument)) .collect_vec(); m.to_doc() .append(".") .append(name.as_str()) .append(self.wrap_arguments(arguments, location.end)) .group() } Constant::Var { name, module: None, .. } => name.to_doc(), Constant::Var { name, module: Some((module, _)), .. } => docvec![module, ".", name], Constant::StringConcatenation { left, right, .. } => self .const_expr(left) .append(break_("", " ").append("<>".to_doc())) .nest(INDENT) .append(" ") .append(self.const_expr(right)), Constant::RecordUpdate { module, name, record, arguments, location, .. } => self.const_record_update(module, name, record, arguments, location), Constant::Invalid { .. } => panic!("invalid constants can not be in an untyped ast"), }; commented(document, comments) } fn const_list<'a, A, B>( &mut self, elements: &'a [Constant], location: &SrcSpan, tail: &'a Option>>, ) -> Document<'a> { if elements.is_empty() { // We take all comments that come _before_ the end of the list, // that is all comments that are inside "[" and "]", if there's // any comment we want to put it inside the empty list! return match printed_comments(self.pop_comments(location.end), false) { None => "[]".to_doc(), Some(comments) => "[" .to_doc() .append(break_("", "").nest(INDENT)) .append(comments) .append(break_("", "")) .append("]") // vvv We want to make sure the comments are on a separate // line from the opening and closing brackets so we // force the breaks to be split on newlines. .force_break(), }; } let list_packing = self.items_sequence_packing( elements, tail.as_deref(), |element| element.can_have_multiple_per_line(), *location, ); let comma = match list_packing { ItemsPacking::FitMultiplePerLine => flex_break(",", ", "), ItemsPacking::FitOnePerLine | ItemsPacking::BreakOnePerLine => break_(",", ", "), }; let mut elements_doc = nil(); for element in elements.iter() { let empty_lines = self.pop_empty_lines(element.location().start); let element_doc = self.const_expr(element); elements_doc = if elements_doc.is_empty() { element_doc } else if empty_lines { // If there's empty lines before the list item we want to add an // empty line here. Notice how we're making sure no nesting is // added after the comma, otherwise we would be adding needless // whitespace in the empty line! docvec![ elements_doc, comma.clone().set_nesting(0), line(), element_doc ] } else { docvec![elements_doc, comma.clone(), element_doc] }; } elements_doc = elements_doc.next_break_fits(NextBreakFitsMode::Disabled); let doc = break_("[", "[").append(elements_doc); let (doc, final_break) = match tail { None => (doc.nest(INDENT), break_(",", "")), Some(tail) => { let comments = self.pop_comments(tail.location().start); let tail = commented(docvec!["..", self.const_expr(tail)], comments); ( doc.append(break_(",", ", ")).append(tail).nest(INDENT), break_("", ""), ) } }; // We get all remaining comments that come before the list's closing // square bracket. // If there's any we add those before the closing square bracket instead // of moving those out of the list. // Otherwise those would be moved out of the list. let comments = self.pop_comments(location.end); let doc = match printed_comments(comments, false) { None => doc.append(final_break).append("]"), Some(comment) => doc .append(final_break.nest(INDENT)) // ^ See how here we're adding the missing indentation to the // final break so that the final comment is as indented as the // list's items. .append(comment) .append(line()) .append("]") .force_break(), }; match list_packing { ItemsPacking::FitOnePerLine | ItemsPacking::FitMultiplePerLine => doc.group(), ItemsPacking::BreakOnePerLine => doc.force_break(), } } pub fn const_tuple<'a, A, B>( &mut self, elements: &'a [Constant], location: &SrcSpan, ) -> Document<'a> { if elements.is_empty() { // We take all comments that come _before_ the end of the tuple, // that is all comments that are inside "#(" and ")", if there's // any comment we want to put it inside the empty list! return match printed_comments(self.pop_comments(location.end), false) { None => "#()".to_doc(), Some(comments) => "#(" .to_doc() .append(break_("", "").nest(INDENT)) .append(comments) .append(break_("", "")) .append(")") // vvv We want to make sure the comments are on a separate // line from the opening and closing parentheses so we // force the breaks to be split on newlines. .force_break(), }; } let arguments_docs = elements.iter().map(|element| self.const_expr(element)); let tuple_doc = break_("#(", "#(") .append( join(arguments_docs, break_(",", ", ")) .next_break_fits(NextBreakFitsMode::Disabled), ) .nest(INDENT); let comments = self.pop_comments(location.end); match printed_comments(comments, false) { None => tuple_doc.append(break_(",", "")).append(")").group(), Some(comments) => tuple_doc .append(break_(",", "").nest(INDENT)) .append(comments) .append(line()) .append(")") .force_break(), } } fn documented_definition<'a>(&mut self, s: &'a UntypedDefinition) -> Document<'a> { let comments = self.doc_comments(s.location().start); comments.append(self.definition(s).group()).group() } fn doc_comments<'a>(&mut self, limit: u32) -> Document<'a> { let mut comments = self.pop_doc_comments(limit).peekable(); match comments.peek() { None => nil(), Some(_) => join( comments.map(|c| match c { Some(c) => "///".to_doc().append(EcoString::from(c)), None => unreachable!("empty lines dropped by pop_doc_comments"), }), line(), ) .append(line()) .force_break(), } } fn type_ast_constructor<'a>( &mut self, module: &'a Option<(EcoString, SrcSpan)>, name: &'a str, arguments: &'a [TypeAst], location: &SrcSpan, _name_location: &SrcSpan, ) -> Document<'a> { let head = module .as_ref() .map(|(qualifier, _)| qualifier.to_doc().append(".").append(name)) .unwrap_or_else(|| name.to_doc()); if arguments.is_empty() { head } else { head.append(self.type_arguments(arguments, location)) } } fn type_ast<'a>(&mut self, t: &'a TypeAst) -> Document<'a> { match t { TypeAst::Hole(TypeAstHole { name, .. }) => name.to_doc(), TypeAst::Constructor(TypeAstConstructor { name, arguments, module, location, name_location, start_parentheses: _, }) => self.type_ast_constructor(module, name, arguments, location, name_location), TypeAst::Fn(TypeAstFn { arguments, return_, location, }) => "fn" .to_doc() .append(self.type_arguments(arguments, location)) .group() .append(" ->") .append(break_("", " ").append(self.type_ast(return_)).nest(INDENT)), TypeAst::Var(TypeAstVar { name, .. }) => name.to_doc(), TypeAst::Tuple(TypeAstTuple { elements, location }) => { "#".to_doc().append(self.type_arguments(elements, location)) } } .group() } fn type_arguments<'a>(&mut self, arguments: &'a [TypeAst], location: &SrcSpan) -> Document<'a> { let arguments = arguments .iter() .map(|type_| self.type_ast(type_)) .collect_vec(); self.wrap_arguments(arguments, location.end) } pub fn type_alias<'a, A>(&mut self, alias: &'a TypeAlias) -> Document<'a> { let TypeAlias { alias: name, parameters: arguments, type_ast: type_, publicity, deprecation, location, name_location: _, type_: _, documentation: _, } = alias; let attributes = AttributesPrinter::new() .set_deprecation(deprecation) .set_internal(*publicity) .to_doc(); let head = docvec![attributes, pub_(*publicity), "type ", name]; let head = if arguments.is_empty() { head } else { let arguments = arguments.iter().map(|(_, e)| e.to_doc()).collect_vec(); head.append(self.wrap_arguments(arguments, location.end).group()) }; head.append(" =") .append(line().append(self.type_ast(type_)).group().nest(INDENT)) } fn fn_arg<'a, A>(&mut self, arg: &'a Arg) -> Document<'a> { let comments = self.pop_comments(arg.location.start); let doc = match &arg.annotation { None => arg.names.to_doc(), Some(a) => arg.names.to_doc().append(": ").append(self.type_ast(a)), } .group(); commented(doc, comments) } fn statement_fn<'a>(&mut self, function: &'a UntypedFunction) -> Document<'a> { let Function { location, body_start: _, end_position, name, arguments, body, publicity, deprecation, return_annotation, return_type: _, documentation: _, external_erlang, external_javascript, implementations: _, purity: _, } = function; let attributes = AttributesPrinter::new() .set_deprecation(deprecation) .set_internal(*publicity) .set_external_erlang(external_erlang) .set_external_javascript(external_javascript) .to_doc(); // Fn name and args let arguments = arguments .iter() .map(|argument| self.fn_arg(argument)) .collect_vec(); let signature = pub_(*publicity) .append("fn ") .append( &name .as_ref() .expect("Function in a statement must be named") .1, ) .append( self.wrap_arguments( arguments, // Calculate end location of arguments to not consume comments in // return annotation return_annotation .as_ref() .map_or(location.end, |ann| ann.location().start), ), ); // Add return annotation let signature = match &return_annotation { Some(anno) => signature.append(" -> ").append(self.type_ast(anno)), None => signature, } .group(); if body.is_empty() { return attributes.append(signature); } let head = attributes.append(signature); // Format body let body = self.statements(body); // Add any trailing comments let body = match printed_comments(self.pop_comments(*end_position), false) { Some(comments) => body.append(line()).append(comments), None => body, }; // Stick it all together head.append(" {") .append(line().append(body).nest(INDENT).group()) .append(line()) .append("}") } fn expr_fn<'a>( &mut self, arguments: &'a [UntypedArg], return_annotation: Option<&'a TypeAst>, body: &'a Vec1, location: &SrcSpan, end_of_head_byte_index: &u32, ) -> Document<'a> { let arguments_docs = arguments .iter() .map(|argument| self.fn_arg(argument)) .collect_vec(); let arguments = self .wrap_arguments(arguments_docs, *end_of_head_byte_index) .group() .next_break_fits(NextBreakFitsMode::Disabled); // ^^^ We add this so that when an expression function is passed as // the last argument of a function and it goes over the line // limit with just its arguments we don't get some strange // splitting. // See https://github.com/gleam-lang/gleam/issues/2571 // // There's many ways we could be smarter than this. For example: // - still split the arguments like it did in the example shown in the // issue if the expr_fn has more than one argument // - make sure that an anonymous function whose body is made up of a // single expression doesn't get split (I think that could boil down // to wrapping the body with a `next_break_fits(Disabled)`) // // These are some of the ways we could tweak the look of expression // functions in the future if people are not satisfied with it. let header = "fn".to_doc().append(arguments); let header = match return_annotation { None => header, Some(t) => header.append(" -> ").append(self.type_ast(t)), }; let statements = self.statements(body.as_vec()); let body = match printed_comments(self.pop_comments(location.end), false) { None => statements, Some(comments) => statements.append(line()).append(comments).force_break(), }; header.append(" ").append(wrap_block(body)).group() } fn statements<'a>(&mut self, statements: &'a [UntypedStatement]) -> Document<'a> { let mut previous_position = 0; let count = statements.len(); let mut documents = Vec::with_capacity(count * 2); for (i, statement) in statements.iter().enumerate() { let preceding_newline = self.pop_empty_lines(previous_position + 1); if i != 0 && preceding_newline { documents.push(lines(2)); } else if i != 0 { documents.push(line()); } previous_position = statement.location().end; documents.push(self.statement(statement).group()); // If the last statement is a use we make sure it's followed by a // todo to make it explicit it has an unimplemented callback. if statement.is_use() && i == count - 1 { documents.push(line()); documents.push("todo".to_doc()); } } if count == 1 && statements .first() .is_some_and(|statement| statement.is_expression()) { documents.to_doc() } else { documents.to_doc().force_break() } } fn assignment<'a>(&mut self, assignment: &'a UntypedAssignment) -> Document<'a> { let comments = self.pop_comments(assignment.location.start); let Assignment { pattern, value, kind, annotation, .. } = assignment; let _ = self.pop_empty_lines(pattern.location().end); let (keyword, message) = match kind { AssignmentKind::Let | AssignmentKind::Generated => ("let ", None), AssignmentKind::Assert { message, .. } => ("let assert ", message.as_ref()), }; let pattern = self.pattern(pattern); let annotation = annotation .as_ref() .map(|a| ": ".to_doc().append(self.type_ast(a))); let doc = keyword .to_doc() .append(pattern.append(annotation).group()) .append(" =") .append(self.assigned_value(value)); commented( self.append_as_message(doc, PrecedingAs::Expression, message), comments, ) } fn expr<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> { let comments = self.pop_comments(expr.start_byte_index()); let document = match expr { UntypedExpr::Panic { message, .. } => { self.append_as_message("panic".to_doc(), PrecedingAs::Keyword, message.as_deref()) } UntypedExpr::Todo { message, .. } => { self.append_as_message("todo".to_doc(), PrecedingAs::Keyword, message.as_deref()) } UntypedExpr::Echo { expression, location: _, keyword_end: _, message, } => self.echo(expression, message), UntypedExpr::PipeLine { expressions, .. } => self.pipeline(expressions, false), UntypedExpr::Int { value, .. } => self.int(value), UntypedExpr::Float { value, .. } => self.float(value), UntypedExpr::String { value, .. } => self.string(value), UntypedExpr::Block { statements, location, .. } => self.block(location, statements, false), UntypedExpr::Var { name, .. } if name == CAPTURE_VARIABLE => "_".to_doc(), UntypedExpr::Var { name, .. } => name.to_doc(), UntypedExpr::TupleIndex { tuple, index, .. } => self.tuple_index(tuple, *index), UntypedExpr::NegateInt { value, .. } => self.negate_int(value), UntypedExpr::NegateBool { value, .. } => self.negate_bool(value), UntypedExpr::Fn { kind, body, .. } if kind.is_capture() => { self.fn_capture(body, FnCapturePosition::EverywhereElse) } UntypedExpr::Fn { return_annotation, arguments, body, location, end_of_head_byte_index, .. } => self.expr_fn( arguments, return_annotation.as_ref(), body, location, end_of_head_byte_index, ), UntypedExpr::List { elements, tail, location, } => self.list(elements, tail.as_deref(), location), UntypedExpr::Call { fun, arguments, location, .. } => self.call(fun, arguments, location), UntypedExpr::BinOp { name, left, right, .. } => self.bin_op(name, left, right, false), UntypedExpr::Case { subjects, clauses, location, } => self.case(subjects, clauses.as_deref().unwrap_or_default(), location), UntypedExpr::FieldAccess { label, container, .. } => self.expr(container).append(".").append(label.as_str()), UntypedExpr::Tuple { elements, location } => self.tuple(elements, location), UntypedExpr::BitArray { segments, location, .. } => { let segment_docs = segments .iter() .map(|segment| bit_array_segment(segment, |e| self.bit_array_segment_expr(e))) .collect_vec(); let packing = self.items_sequence_packing( segments, None, |segment| segment.value.can_have_multiple_per_line(), *location, ); self.bit_array(segment_docs, packing, location) } UntypedExpr::RecordUpdate { constructor, record, arguments, location, .. } => self.record_update(constructor, record, arguments, location), }; commented(document, comments) } fn string<'a>(&self, string: &'a EcoString) -> Document<'a> { let doc = string.to_doc().surround("\"", "\""); if string.contains('\n') { doc.force_break() } else { doc } } fn bin_op_string<'a>(&self, string: &'a EcoString) -> Document<'a> { let lines = string.split('\n').collect_vec(); match lines.as_slice() { [] | [_] => string.to_doc().surround("\"", "\""), [first_line, lines @ ..] => { let mut doc = docvec!["\"", first_line]; for line in lines { doc = doc .append(pretty::line().set_nesting(0)) .append(line.to_doc()) } doc.append("\"".to_doc()).group() } } } fn float<'a>(&self, value: &'a str) -> Document<'a> { // Create parts let mut parts = value.split('.'); let integer_part = parts.next().unwrap_or_default(); // floating point part let fp_part = parts.next().unwrap_or_default(); let integer_doc = self.underscore_integer_string(integer_part); let dot_doc = ".".to_doc(); // Split fp_part into a regular fractional and maybe a scientific part let (fp_part_fractional, fp_part_scientific) = fp_part.split_at( fp_part .chars() .position(|ch| ch == 'e') .unwrap_or(fp_part.len()), ); // Trim right any consequtive '0's let mut fp_part_fractional = fp_part_fractional.trim_end_matches('0').to_string(); // If there is no fractional part left, add a '0', thus that 1. becomes 1.0 etc. if fp_part_fractional.is_empty() { fp_part_fractional.push('0'); } let fp_doc = fp_part_fractional.chars().collect::(); integer_doc .append(dot_doc) .append(fp_doc) .append(fp_part_scientific) } fn int<'a>(&self, value: &'a str) -> Document<'a> { if value.starts_with("0x") || value.starts_with("0b") || value.starts_with("0o") { return value.to_doc(); } self.underscore_integer_string(value) } fn underscore_integer_string<'a>(&self, value: &'a str) -> Document<'a> { let underscore_ch = '_'; let minus_ch = '-'; let len = value.len(); let underscore_ch_cnt = value.matches(underscore_ch).count(); let reformat_watershed = if value.starts_with(minus_ch) { 6 } else { 5 }; let insert_underscores = (len - underscore_ch_cnt) >= reformat_watershed; let mut new_value = String::new(); let mut j = 0; for (i, ch) in value.chars().rev().enumerate() { if ch == '_' { continue; } if insert_underscores && i != 0 && ch != minus_ch && i < len && j % 3 == 0 { new_value.push(underscore_ch); } new_value.push(ch); j += 1; } new_value.chars().rev().collect::().to_doc() } fn pattern_constructor<'a>( &mut self, name: &'a str, arguments: &'a [CallArg], module: &'a Option<(EcoString, SrcSpan)>, spread: Option, location: &SrcSpan, ) -> Document<'a> { fn is_breakable(expr: &UntypedPattern) -> bool { match expr { Pattern::Tuple { .. } | Pattern::List { .. } | Pattern::BitArray { .. } => true, Pattern::Constructor { arguments, .. } => !arguments.is_empty(), Pattern::Int { .. } | Pattern::Float { .. } | Pattern::String { .. } | Pattern::Variable { .. } | Pattern::BitArraySize(_) | Pattern::Assign { .. } | Pattern::Discard { .. } | Pattern::StringPrefix { .. } | Pattern::Invalid { .. } => false, } } let name = match module { Some((m, _)) => m.to_doc().append(".").append(name), None => name.to_doc(), }; if arguments.is_empty() && spread.is_some() { name.append("(..)") } else if arguments.is_empty() { name } else if spread.is_some() { let arguments = arguments .iter() .map(|argument| self.pattern_call_arg(argument)) .collect_vec(); name.append(self.wrap_arguments_with_spread(arguments, location.end)) .group() } else { match arguments { [argument] if is_breakable(&argument.value) => name .append("(") .append(self.pattern_call_arg(argument)) .append(")") .group(), _ => { let arguments = arguments .iter() .map(|argument| self.pattern_call_arg(argument)) .collect_vec(); name.append(self.wrap_arguments(arguments, location.end)) .group() } } } } fn call<'a>( &mut self, fun: &'a UntypedExpr, arguments: &'a [CallArg], location: &SrcSpan, ) -> Document<'a> { let expr = match fun { UntypedExpr::PipeLine { .. } => break_block(self.expr(fun)), UntypedExpr::BinOp { .. } | UntypedExpr::Int { .. } | UntypedExpr::Float { .. } | UntypedExpr::String { .. } | UntypedExpr::Block { .. } | UntypedExpr::Var { .. } | UntypedExpr::Fn { .. } | UntypedExpr::List { .. } | UntypedExpr::Call { .. } | UntypedExpr::Case { .. } | UntypedExpr::FieldAccess { .. } | UntypedExpr::Tuple { .. } | UntypedExpr::TupleIndex { .. } | UntypedExpr::Todo { .. } | UntypedExpr::Echo { .. } | UntypedExpr::Panic { .. } | UntypedExpr::BitArray { .. } | UntypedExpr::RecordUpdate { .. } | UntypedExpr::NegateBool { .. } | UntypedExpr::NegateInt { .. } => self.expr(fun), }; let arity = arguments.len(); self.append_inlinable_wrapped_arguments( expr, arguments, location, |argument| &argument.value, |self_, arg| self_.call_arg(arg, arity), ) } fn tuple<'a>(&mut self, elements: &'a [UntypedExpr], location: &SrcSpan) -> Document<'a> { if elements.is_empty() { // We take all comments that come _before_ the end of the tuple, // that is all comments that are inside "#(" and ")", if there's // any comment we want to put it inside the empty tuple! return match printed_comments(self.pop_comments(location.end), false) { None => "#()".to_doc(), Some(comments) => "#(" .to_doc() .append(break_("", "").nest(INDENT)) .append(comments) .append(break_("", "")) .append(")") // vvv We want to make sure the comments are on a separate // line from the opening and closing parentheses so we // force the breaks to be split on newlines. .force_break(), }; } self.append_inlinable_wrapped_arguments( "#".to_doc(), elements, location, |e| e, |self_, e| self_.comma_separated_item(e, elements.len()), ) } // Appends to the given docs a comma-separated list of documents wrapped by // parentheses. If the last item of the argument list is splittable the // resulting document will try to first split that before splitting all the // other arguments. // This is used for function calls and tuples. fn append_inlinable_wrapped_arguments<'a, 'b, T, ToExpr, ToDoc>( &mut self, doc: Document<'a>, values: &'b [T], location: &SrcSpan, to_expr: ToExpr, to_doc: ToDoc, ) -> Document<'a> where T: HasLocation, T: std::fmt::Debug, ToExpr: Fn(&T) -> &UntypedExpr, ToDoc: Fn(&mut Self, &'b T) -> Document<'a>, { match init_and_last(values) { Some((initial_values, last_value)) if is_breakable_argument(to_expr(last_value), values.len()) && !self.any_comments(last_value.location().start) && !self.any_comment_between(last_value.location().end, location.end) => { let mut docs = initial_values .iter() .map(|value| to_doc(self, value)) .collect_vec(); let last_value_doc = to_doc(self, last_value) .group() .next_break_fits(NextBreakFitsMode::Enabled); docs.append(&mut vec![last_value_doc]); doc.append(self.wrap_function_call_arguments(docs, location)) .next_break_fits(NextBreakFitsMode::Disabled) .group() } Some(_) | None => { let docs = values.iter().map(|value| to_doc(self, value)).collect_vec(); doc.append(self.wrap_function_call_arguments(docs, location)) .group() } } } pub fn case<'a>( &mut self, subjects: &'a [UntypedExpr], clauses: &'a [UntypedClause], location: &'a SrcSpan, ) -> Document<'a> { let subjects_doc = break_("case", "case ") .append(join( subjects.iter().map(|s| self.expr(s).group()), break_(",", ", "), )) .nest(INDENT) .append(break_("", " ")) .append("{") .next_break_fits(NextBreakFitsMode::Disabled) .group(); let clauses_doc = concat( clauses .iter() .enumerate() .map(|(i, c)| self.clause(c, i as u32).group()), ); // We get all remaining comments that come before the case's closing // bracket. If there's any we add those before the closing bracket // instead of moving those out of the case expression. // Otherwise those would be moved out of the case expression. let comments = self.pop_comments(location.end); let closing_bracket = match printed_comments(comments, false) { None => docvec![line(), "}"], Some(comment) => docvec![line(), comment] .nest(INDENT) .append(line()) .append("}"), }; subjects_doc .append(line().append(clauses_doc).nest(INDENT)) .append(closing_bracket) .force_break() } pub fn record_update<'a>( &mut self, constructor: &'a UntypedExpr, record: &'a RecordBeingUpdated, arguments: &'a [UntypedRecordUpdateArg], location: &SrcSpan, ) -> Document<'a> { let constructor_doc: Document<'a> = self.expr(constructor); let pieces = std::iter::once(UntypedRecordUpdatePiece::Record(record)) .chain(arguments.iter().map(UntypedRecordUpdatePiece::Argument)) .collect_vec(); self.append_inlinable_wrapped_arguments( constructor_doc, &pieces, location, |arg| match arg { UntypedRecordUpdatePiece::Argument(arg) => &arg.value, UntypedRecordUpdatePiece::Record(record) => record.base.as_ref(), }, |this, arg| match arg { UntypedRecordUpdatePiece::Argument(arg) => this.record_update_arg(arg), UntypedRecordUpdatePiece::Record(record) => { let comments = this.pop_comments(record.location.start); commented("..".to_doc().append(this.expr(&record.base)), comments) } }, ) } pub fn const_record_update<'a, A, B>( &mut self, module: &Option<(EcoString, SrcSpan)>, name: &'a EcoString, record: &'a RecordBeingUpdated>, arguments: &'a [RecordUpdateArg>], location: &SrcSpan, ) -> Document<'a> { let constructor_doc = match module { Some((m, _)) => m.to_doc().append(".").append(name.as_str()), None => name.to_doc(), }; let pieces = std::iter::once(RecordUpdatePiece::Record(record)) .chain(arguments.iter().map(RecordUpdatePiece::Argument)) .collect_vec(); let docs = pieces .iter() .map(|piece| match piece { RecordUpdatePiece::Argument(arg) => { let comments = self.pop_comments(arg.location.start); let doc = match arg { _ if arg.uses_label_shorthand() => arg.label.as_str().to_doc().append(":"), _ => arg .label .as_str() .to_doc() .append(": ") .append(self.const_expr(&arg.value)) .group(), }; commented(doc, comments) } RecordUpdatePiece::Record(record) => { let comments = self.pop_comments(record.location.start); commented( "..".to_doc().append(self.const_expr(&record.base)), comments, ) } }) .collect_vec(); constructor_doc .append(self.wrap_arguments(docs, location.end)) .group() } pub fn bin_op<'a>( &mut self, name: &'a BinOp, left: &'a UntypedExpr, right: &'a UntypedExpr, nest_steps: bool, ) -> Document<'a> { let left_side = self.bin_op_side(name, left, nest_steps); let comments = self.pop_comments(right.start_byte_index()); let name_doc = break_("", " ").append(commented(name.to_doc(), comments)); let right_side = self.bin_op_side(name, right, nest_steps); left_side .append(if nest_steps { name_doc.nest(INDENT) } else { name_doc }) .append(" ") .append(right_side) } fn bin_op_side<'a>( &mut self, operator: &'a BinOp, side: &'a UntypedExpr, nest_steps: bool, ) -> Document<'a> { let side_doc = match side { UntypedExpr::String { value, .. } => self.bin_op_string(value), UntypedExpr::BinOp { name, left, right, .. } => self.bin_op(name, left, right, nest_steps), UntypedExpr::Int { .. } | UntypedExpr::Float { .. } | UntypedExpr::Block { .. } | UntypedExpr::Var { .. } | UntypedExpr::Fn { .. } | UntypedExpr::List { .. } | UntypedExpr::Call { .. } | UntypedExpr::PipeLine { .. } | UntypedExpr::Case { .. } | UntypedExpr::FieldAccess { .. } | UntypedExpr::Tuple { .. } | UntypedExpr::TupleIndex { .. } | UntypedExpr::Todo { .. } | UntypedExpr::Panic { .. } | UntypedExpr::Echo { .. } | UntypedExpr::BitArray { .. } | UntypedExpr::RecordUpdate { .. } | UntypedExpr::NegateBool { .. } | UntypedExpr::NegateInt { .. } => self.expr(side), }; match side.bin_op_name() { // In case the other side is a binary operation as well and it can // be grouped together with the current binary operation, the two // docs are simply concatenated, so that they will end up in the // same group and the formatter will try to keep those on a single // line. Some(side_name) if side_name.can_be_grouped_with(operator) => side_doc, // In case the binary operations cannot be grouped together the // other side is treated as a group on its own so that it can be // broken independently of other pieces of the binary operations // chain. _ => self.operator_side( side_doc.group(), operator.precedence(), side.bin_op_precedence(), ), } } pub fn operator_side<'a>(&self, doc: Document<'a>, op: u8, side: u8) -> Document<'a> { if op > side { wrap_block(doc).group() } else { doc } } fn spans_multiple_lines(&self, start: u32, end: u32) -> bool { self.new_lines .binary_search_by(|newline| { if *newline <= start { Ordering::Less } else if *newline >= end { Ordering::Greater } else { // If the newline is in between the pipe start and end // then we've found it! Ordering::Equal } }) // If we couldn't find any newline between the start and end of // the pipeline then we will try and keep it on a single line. .is_ok() } /// Returns true if there's a trailing comma between `start` and `end`. /// fn has_trailing_comma(&self, start: u32, end: u32) -> bool { self.trailing_commas .binary_search_by(|comma| { if *comma < start { Ordering::Less } else if *comma > end { Ordering::Greater } else { Ordering::Equal } }) .is_ok() } fn pipeline<'a>( &mut self, expressions: &'a Vec1, nest_pipe: bool, ) -> Document<'a> { let mut docs = Vec::with_capacity(expressions.len() * 3); let first = expressions.first(); let first_precedence = first.bin_op_precedence(); let first = self.expr(first).group(); docs.push(self.operator_side(first, 5, first_precedence)); let pipeline_start = expressions.first().location().start; let pipeline_end = expressions.last().location().end; let try_to_keep_on_one_line = !self.spans_multiple_lines(pipeline_start, pipeline_end); for expr in expressions.iter().skip(1) { let comments = self.pop_comments(expr.location().start); let doc = if let UntypedExpr::Fn { kind, body, .. } = expr && kind.is_capture() { self.fn_capture(body, FnCapturePosition::RightHandSideOfPipe) } else { self.expr(expr) }; let doc = if nest_pipe { doc.nest(INDENT) } else { doc }; let space = if try_to_keep_on_one_line { break_("", " ") } else { line() }; let pipe = space.append(commented("|> ".to_doc(), comments)); let pipe = if nest_pipe { pipe.nest(INDENT) } else { pipe }; docs.push(pipe); docs.push(self.operator_side(doc, 4, expr.bin_op_precedence())); } if try_to_keep_on_one_line { docs.to_doc() } else { docs.to_doc().force_break() } } fn fn_capture<'a>( &mut self, call: &'a [UntypedStatement], position: FnCapturePosition, ) -> Document<'a> { // The body of a capture being multiple statements shouldn't be possible... if call.len() != 1 { panic!("Function capture found not to have a single statement call"); } let Some(Statement::Expression(UntypedExpr::Call { fun, arguments, location, })) = call.first() else { // The body of a capture being not a fn shouldn't be possible... panic!("Function capture body found not to be a call in the formatter") }; match (position, arguments.as_slice()) { // The capture has a single unlabelled hole: // // wibble |> wobble(_) // list.map([], wobble(_)) // // We want these to become: // // wibble |> wobble // list.map([], wobble) // (FnCapturePosition::RightHandSideOfPipe | FnCapturePosition::EverywhereElse, [arg]) if arg.is_capture_hole() && arg.label.is_none() => { self.expr(fun) } // The capture is on the right hand side of a pipe and its first // argument it an unlabelled capture hole: // // wibble |> wobble(_, woo) // // We want it to become: // // wibble |> wobble(woo) // (FnCapturePosition::RightHandSideOfPipe, [arg, rest @ ..]) if arg.is_capture_hole() && arg.label.is_none() => { let expr = self.expr(fun); let arity = rest.len(); self.append_inlinable_wrapped_arguments( expr, rest, location, |arg| &arg.value, |self_, arg| self_.call_arg(arg, arity), ) } // In all other cases we print it like a regular function call // without changing it. // ( FnCapturePosition::RightHandSideOfPipe | FnCapturePosition::EverywhereElse, arguments, ) => { let expr = self.expr(fun); let arity = arguments.len(); self.append_inlinable_wrapped_arguments( expr, arguments, location, |arg| &arg.value, |self_, arg| self_.call_arg(arg, arity), ) } } } pub fn record_constructor<'a, A>( &mut self, constructor: &'a RecordConstructor, ) -> Document<'a> { let comments = self.pop_comments(constructor.location.start); let doc_comments = self.doc_comments(constructor.location.start); let attributes = AttributesPrinter::new() .set_deprecation(&constructor.deprecation) .to_doc(); let doc = if constructor.arguments.is_empty() { if self.any_comments(constructor.location.end) { attributes .append(constructor.name.as_str().to_doc()) .append(self.wrap_arguments(vec![], constructor.location.end)) .group() } else { attributes.append(constructor.name.as_str().to_doc()) } } else { let arguments = constructor .arguments .iter() .map( |RecordConstructorArg { label, ast, location, .. }| { let arg_comments = self.pop_comments(location.start); let arg = match label { Some((_, l)) => l.to_doc().append(": ").append(self.type_ast(ast)), None => self.type_ast(ast), }; commented( self.doc_comments(location.start).append(arg).group(), arg_comments, ) }, ) .collect_vec(); attributes .append(constructor.name.as_str().to_doc()) .append( self.wrap_arguments(arguments, constructor.location.end) .group(), ) }; commented(doc_comments.append(doc).group(), comments) } pub fn custom_type<'a, A>(&mut self, type_: &'a CustomType) -> Document<'a> { let CustomType { location, end_position, name, name_location: _, publicity, constructors, documentation: _, deprecation, opaque, parameters, typed_parameters: _, external_erlang, external_javascript, } = type_; let _ = self.pop_empty_lines(location.end); let attributes = AttributesPrinter::new() .set_deprecation(deprecation) .set_internal(*publicity) .set_external_erlang(external_erlang) .set_external_javascript(external_javascript) .to_doc(); let doc = attributes .append(pub_(*publicity)) .append(if *opaque { "opaque type " } else { "type " }) .append(if parameters.is_empty() { name.clone().to_doc() } else { let arguments = type_ .parameters .iter() .map(|(_, e)| e.to_doc()) .collect_vec(); type_ .name .clone() .to_doc() .append(self.wrap_arguments(arguments, location.end)) .group() }); if constructors.is_empty() { return doc; } let doc = doc.append(" {"); let inner = concat(constructors.iter().map(|c| { if self.pop_empty_lines(c.location.start) { lines(2) } else { line() } .append(self.record_constructor(c)) })); // Add any trailing comments let inner = match printed_comments(self.pop_comments(*end_position), false) { Some(comments) => inner.append(line()).append(comments), None => inner, } .nest(INDENT) .group(); doc.append(inner).append(line()).append("}") } fn call_arg<'a>(&mut self, arg: &'a CallArg, arity: usize) -> Document<'a> { self.format_call_arg(arg, expr_call_arg_formatting, |this, value| { this.comma_separated_item(value, arity) }) } fn format_call_arg<'a, A, F, G>( &mut self, arg: &'a CallArg, figure_formatting: F, format_value: G, ) -> Document<'a> where F: Fn(&'a CallArg) -> CallArgFormatting<'a, A>, G: Fn(&mut Self, &'a A) -> Document<'a>, { match figure_formatting(arg) { CallArgFormatting::Unlabelled(value) => format_value(self, value), CallArgFormatting::ShorthandLabelled(label) => { let comments = self.pop_comments(arg.location.start); let label = label.as_ref().to_doc().append(":"); commented(label, comments) } CallArgFormatting::Labelled(label, value) => { let comments = self.pop_comments(arg.location.start); let label = label.as_ref().to_doc().append(": "); let value = format_value(self, value); commented(label, comments).append(value) } } } fn record_update_arg<'a>(&mut self, arg: &'a UntypedRecordUpdateArg) -> Document<'a> { let comments = self.pop_comments(arg.location.start); match arg { // Argument supplied with a label shorthand. _ if arg.uses_label_shorthand() => { commented(arg.label.as_str().to_doc().append(":"), comments) } // Labelled argument. _ => { let doc = arg .label .as_str() .to_doc() .append(": ") .append(self.expr(&arg.value)) .group(); if arg.value.is_binop() || arg.value.is_pipeline() { commented(doc, comments).nest(INDENT) } else { commented(doc, comments) } } } } fn tuple_index<'a>(&mut self, tuple: &'a UntypedExpr, index: u64) -> Document<'a> { // In case we have a block with a single variable tuple access we // remove that redundant wrapper: // // {tuple.1}.0 becomes // tuple.1.0 // if let UntypedExpr::Block { statements, .. } = tuple { match statements.as_slice() { [Statement::Expression(tuple @ UntypedExpr::TupleIndex { tuple: inner, .. })] // We can't apply this change if the inner thing is a // literal tuple because the compiler cannot currently parse // it: `#(1, #(2, 3)).1.0` is a syntax error at the moment. if !inner.is_tuple() => { self.expr(tuple) } _ => self.expr(tuple), } } else { self.expr(tuple) } .append(".") .append(index) } fn case_clause_value<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> { match expr { UntypedExpr::Fn { .. } | UntypedExpr::List { .. } | UntypedExpr::Tuple { .. } | UntypedExpr::BitArray { .. } => { let expression_comments = self.pop_comments(expr.location().start); let expression_doc = self.expr(expr); match printed_comments(expression_comments, true) { Some(comments) => line().append(comments).append(expression_doc).nest(INDENT), None => " ".to_doc().append(expression_doc), } } UntypedExpr::Case { .. } => line().append(self.expr(expr)).nest(INDENT), UntypedExpr::Block { statements, location, .. } => " ".to_doc().append(self.block(location, statements, true)), UntypedExpr::Int { .. } | UntypedExpr::Float { .. } | UntypedExpr::String { .. } | UntypedExpr::Var { .. } | UntypedExpr::Call { .. } | UntypedExpr::BinOp { .. } | UntypedExpr::PipeLine { .. } | UntypedExpr::FieldAccess { .. } | UntypedExpr::TupleIndex { .. } | UntypedExpr::Todo { .. } | UntypedExpr::Panic { .. } | UntypedExpr::Echo { .. } | UntypedExpr::RecordUpdate { .. } | UntypedExpr::NegateBool { .. } | UntypedExpr::NegateInt { .. } => { break_("", " ").append(self.expr(expr).group()).nest(INDENT) } } .next_break_fits(NextBreakFitsMode::Disabled) .group() } fn assigned_value<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> { match expr { UntypedExpr::Case { .. } => " ".to_doc().append(self.expr(expr)).group(), UntypedExpr::Int { .. } | UntypedExpr::Float { .. } | UntypedExpr::String { .. } | UntypedExpr::Block { .. } | UntypedExpr::Var { .. } | UntypedExpr::Fn { .. } | UntypedExpr::List { .. } | UntypedExpr::Call { .. } | UntypedExpr::BinOp { .. } | UntypedExpr::PipeLine { .. } | UntypedExpr::FieldAccess { .. } | UntypedExpr::Tuple { .. } | UntypedExpr::TupleIndex { .. } | UntypedExpr::Todo { .. } | UntypedExpr::Panic { .. } | UntypedExpr::Echo { .. } | UntypedExpr::BitArray { .. } | UntypedExpr::RecordUpdate { .. } | UntypedExpr::NegateBool { .. } | UntypedExpr::NegateInt { .. } => self.case_clause_value(expr), } } fn clause<'a>(&mut self, clause: &'a UntypedClause, index: u32) -> Document<'a> { let space_before = self.pop_empty_lines(clause.location.start); let comments = self.pop_comments(clause.location.start); let clause_doc = match &clause.guard { None => self.alternative_patterns(clause), Some(guard) => self .alternative_patterns(clause) .append(break_("", " ").nest(INDENT)) .append("if ") .append(self.clause_guard(guard).group().nest(INDENT)), }; // In case there's a guard or multiple subjects, if we decide to break // the patterns on multiple lines we also want the arrow to end up on // its own line to improve legibility. // // This looks like this: // ```gleam // case wibble, wobble { // Wibble(_), // pretend this goes over the line limit // Wobble(_) // -> todo // // Notice how the arrow is broken on its own line, the same goes // // for patterns with `if` guards. // } // ``` let has_guard = clause.guard.is_some(); let has_multiple_subjects = clause.pattern.len() > 1; let arrow_break = if has_guard || has_multiple_subjects { break_("", " ") } else { " ".to_doc() }; let clause_doc = clause_doc .append(arrow_break) .group() .append("->") .append(self.case_clause_value(&clause.then).group()) .group(); let clause_doc = match printed_comments(comments, false) { Some(comments) => comments.append(line()).append(clause_doc), None => clause_doc, }; if index == 0 { clause_doc } else if space_before { lines(2).append(clause_doc) } else { line().append(clause_doc) } } fn alternative_patterns<'a>(&mut self, clause: &'a UntypedClause) -> Document<'a> { let has_guard = clause.guard.is_some(); let has_multiple_subjects = clause.pattern.len() > 1; // In case there's an `if` guard but no multiple subjects we want to add // additional indentation before the vartical bar separating alternative // patterns `|`. // We're not adding the indentation if there's multiple subjects as that // would make things harder to read, aligning the vertical bar with the // different subjects: // ``` // case wibble, wobble { // Wibble, // Wobble // | Wibble, // <- we don't want this indentation! // Wobble -> todo // } // ``` let alternatives_separator = if has_guard && !has_multiple_subjects { break_("", " ").nest(INDENT).append("| ") } else { break_("", " ").append("| ") }; let alternative_patterns = std::iter::once(&clause.pattern) .chain(&clause.alternative_patterns) .enumerate() .map(|(alternative_index, p)| { // Here `p` is a single pattern that can be comprised of // multiple subjects. // ```gleam // case wibble, wobble { // True, False // //^^^^^^^^^^^ This is a single pattern with multiple subjects // | _, _ -> todo // } // ``` let is_first_alternative = alternative_index == 0; let subject_docs = p.iter().enumerate().map(|(subject_index, subject)| { // There's a small catch in turning each subject into a document. // Sadly we can't simply call `self.pattern` on each subject and // then nest each one in case it gets broken. // The first ever pattern that appears in a case clause (that is // the first subject of the first alternative) must not be nested // further; otherwise, when broken, it would have 2 extra spaces // of indentation: https://github.com/gleam-lang/gleam/issues/2940. let is_first_subject = subject_index == 0; let is_first_pattern_of_clause = is_first_subject && is_first_alternative; let subject_doc = self.pattern(subject); if is_first_pattern_of_clause { subject_doc } else { subject_doc.nest(INDENT) } }); // We join all subjects with a breakable comma (that's also // going to be nested) and make the subjects into a group to // make sure the formatter tries to keep them on a single line. join(subject_docs, break_(",", ", ").nest(INDENT)).group() }); // Last, we make sure that the formatter tries to keep each // alternative on a single line by making it a group! join(alternative_patterns, alternatives_separator).group() } fn list<'a>( &mut self, elements: &'a [UntypedExpr], tail: Option<&'a UntypedExpr>, location: &SrcSpan, ) -> Document<'a> { if elements.is_empty() { return match tail { Some(tail) => self.expr(tail), // We take all comments that come _before_ the end of the list, // that is all comments that are inside "[" and "]", if there's // any comment we want to put it inside the empty list! None => match printed_comments(self.pop_comments(location.end), false) { None => "[]".to_doc(), Some(comments) => "[" .to_doc() .append(break_("", "").nest(INDENT)) .append(comments) .append(break_("", "")) .append("]") // vvv We want to make sure the comments are on a separate // line from the opening and closing brackets so we // force the breaks to be split on newlines. .force_break(), }, }; } let list_packing = self.items_sequence_packing( elements, tail, UntypedExpr::can_have_multiple_per_line, *location, ); let comma = match list_packing { ItemsPacking::FitMultiplePerLine => flex_break(",", ", "), ItemsPacking::FitOnePerLine | ItemsPacking::BreakOnePerLine => break_(",", ", "), }; let list_size = elements.len() + match tail { Some(_) => 1, None => 0, }; let mut elements_doc = nil(); for element in elements.iter() { let empty_lines = self.pop_empty_lines(element.location().start); let element_doc = self.comma_separated_item(element, list_size); elements_doc = if elements_doc.is_empty() { element_doc } else if empty_lines { // If there's empty lines before the list item we want to add an // empty line here. Notice how we're making sure no nesting is // added after the comma, otherwise we would be adding needless // whitespace in the empty line! docvec![ elements_doc, comma.clone().set_nesting(0), line(), element_doc ] } else { docvec![elements_doc, comma.clone(), element_doc] }; } elements_doc = elements_doc.next_break_fits(NextBreakFitsMode::Disabled); let doc = break_("[", "[").append(elements_doc); // We need to keep the last break aside and do not add it immediately // because in case there's a final comment before the closing square // bracket we want to add indentation (to just that break). Otherwise, // the final comment would be less indented than list's elements. let (doc, last_break) = match tail { None => (doc.nest(INDENT), break_(",", "")), Some(tail) => { let comments = self.pop_comments(tail.location().start); let tail = commented(docvec!["..", self.expr(tail)], comments); ( doc.append(break_(",", ", ")).append(tail).nest(INDENT), break_("", ""), ) } }; // We get all remaining comments that come before the list's closing // square bracket. // If there's any we add those before the closing square bracket instead // of moving those out of the list. // Otherwise those would be moved out of the list. let comments = self.pop_comments(location.end); let doc = match printed_comments(comments, false) { None => doc.append(last_break).append("]"), Some(comment) => doc .append(last_break.nest(INDENT)) // ^ See how here we're adding the missing indentation to the // final break so that the final comment is as indented as the // list's items. .append(comment) .append(line()) .append("]") .force_break(), }; match list_packing { ItemsPacking::FitOnePerLine | ItemsPacking::FitMultiplePerLine => doc.group(), ItemsPacking::BreakOnePerLine => doc.force_break(), } } fn items_sequence_packing<'a, T: HasLocation>( &self, items: &'a [T], tail: Option<&'a T>, can_have_multiple_per_line: impl Fn(&'a T) -> bool, list_location: SrcSpan, ) -> ItemsPacking { let ends_with_trailing_comma = tail .map(|tail| tail.location().end) .or_else(|| items.last().map(|last| last.location().end)) .is_some_and(|last_element_end| { self.has_trailing_comma(last_element_end, list_location.end) }); let has_multiple_elements_per_line = self.has_items_on_the_same_line(items.iter().chain(tail)); let has_empty_lines_between_elements = match (items.first(), items.last().or(tail)) { (Some(first), Some(last)) => self.empty_lines.first().is_some_and(|empty_line| { *empty_line >= first.location().end && *empty_line < last.location().start }), _ => false, }; if has_empty_lines_between_elements { // If there's any empty line between elements we want to force each // item onto its own line to preserve the empty lines that were // intentionally added. ItemsPacking::BreakOnePerLine } else if !ends_with_trailing_comma { // If the list doesn't end with a trailing comma we try and pack it in // a single line; if we can't we'll put one item per line, no matter // the content of the list. ItemsPacking::FitOnePerLine } else if tail.is_none() && items.iter().all(can_have_multiple_per_line) && has_multiple_elements_per_line && self.spans_multiple_lines(list_location.start, list_location.end) { // If there's a trailing comma, we can have multiple items per line, // and there's already multiple items per line, we try and pack as // many items as possible on each line. // // Note how we only ever try and pack lists where all items are // unbreakable primitives. To pack a list we need to use put // `flex_break`s between each item. // If the items themselves had breaks we could end up in a situation // where an item gets broken making it span multiple lines and the // spaces are not, for example: // // ```gleam // [Constructor("wibble", "lorem ipsum dolor sit amet something something"), Other(1)] // ``` // // If we used flex breaks here the list would be formatted as: // // ```gleam // [ // Constructor( // "wibble", // "lorem ipsum dolor sit amet something something", // ), Other(1) // ] // ``` // // The first item is broken, meaning that once we get to the flex // space separating it from the following one the formatter is not // going to break it since there's enough space in the current line! ItemsPacking::FitMultiplePerLine } else { // If it ends with a trailing comma we will force the list on // multiple lines, with one item per line. ItemsPacking::BreakOnePerLine } } fn has_items_on_the_same_line<'a, L: HasLocation + 'a, T: Iterator>( &self, items: T, ) -> bool { let mut previous: Option = None; for item in items { let item_location = item.location(); // A list has multiple items on the same line if two consecutive // ones do not span multiple lines. if let Some(previous) = previous && !self.spans_multiple_lines(previous.end, item_location.start) { return true; } previous = Some(item_location); } false } /// Pretty prints an expression to be used in a comma separated list; for /// example as a list item, a tuple item or as an argument of a function call. fn comma_separated_item<'a>( &mut self, expression: &'a UntypedExpr, siblings: usize, ) -> Document<'a> { // If there's more than one item in the comma separated list and there's a // pipeline or long binary chain, we want to indent those to make it // easier to tell where one item ends and the other starts. // Othewise we just print the expression as a normal expr. match expression { UntypedExpr::BinOp { name, left, right, .. } if siblings > 1 => { let comments = self.pop_comments(expression.start_byte_index()); let doc = self.bin_op(name, left, right, true).group(); commented(doc, comments) } UntypedExpr::PipeLine { expressions } if siblings > 1 => { let comments = self.pop_comments(expression.start_byte_index()); let doc = self.pipeline(expressions, true).group(); commented(doc, comments) } UntypedExpr::Int { .. } | UntypedExpr::Float { .. } | UntypedExpr::String { .. } | UntypedExpr::Block { .. } | UntypedExpr::Var { .. } | UntypedExpr::Fn { .. } | UntypedExpr::List { .. } | UntypedExpr::Call { .. } | UntypedExpr::BinOp { .. } | UntypedExpr::PipeLine { .. } | UntypedExpr::Case { .. } | UntypedExpr::FieldAccess { .. } | UntypedExpr::Tuple { .. } | UntypedExpr::TupleIndex { .. } | UntypedExpr::Todo { .. } | UntypedExpr::Panic { .. } | UntypedExpr::Echo { .. } | UntypedExpr::BitArray { .. } | UntypedExpr::RecordUpdate { .. } | UntypedExpr::NegateBool { .. } | UntypedExpr::NegateInt { .. } => self.expr(expression).group(), } } fn pattern<'a>(&mut self, pattern: &'a UntypedPattern) -> Document<'a> { let comments = self.pop_comments(pattern.location().start); let doc = match pattern { Pattern::Int { value, .. } => self.int(value), Pattern::Float { value, .. } => self.float(value), Pattern::String { value, .. } => self.string(value), Pattern::Variable { name, .. } => name.to_doc(), Pattern::BitArraySize(size) => self.bit_array_size(size), Pattern::Assign { name, pattern, .. } => { if pattern.is_discard() { name.to_doc() } else { self.pattern(pattern).append(" as ").append(name.as_str()) } } Pattern::Discard { name, .. } => name.to_doc(), Pattern::List { elements, tail, .. } => self.list_pattern(elements, tail), Pattern::Constructor { name, arguments, module, spread, location, .. } => self.pattern_constructor(name, arguments, module, *spread, location), Pattern::Tuple { elements, location, .. } => { let arguments = elements .iter() .map(|element| self.pattern(element)) .collect_vec(); "#".to_doc() .append(self.wrap_arguments(arguments, location.end)) .group() } Pattern::BitArray { segments, location, .. } => { let segment_docs = segments .iter() .map(|segment| bit_array_segment(segment, |pattern| self.pattern(pattern))) .collect_vec(); self.bit_array(segment_docs, ItemsPacking::FitOnePerLine, location) } Pattern::StringPrefix { left_side_string: left, right_side_assignment: right, left_side_assignment: left_assign, .. } => { let left = self.string(left); let right = match right { AssignName::Variable(name) => name.to_doc(), AssignName::Discard(name) => name.to_doc(), }; match left_assign { Some((name, _)) => docvec![left, " as ", name, " <> ", right], None => docvec![left, " <> ", right], } } Pattern::Invalid { .. } => panic!("invalid patterns can not be in an untyped ast"), }; commented(doc, comments) } fn bit_array_size<'a>(&mut self, size: &'a BitArraySize<()>) -> Document<'a> { match size { BitArraySize::Int { value, .. } => self.int(value), BitArraySize::Variable { name, .. } => name.to_doc(), BitArraySize::BinaryOperator { left, right, operator, .. } => { let operator = match operator { IntOperator::Add => " + ", IntOperator::Subtract => " - ", IntOperator::Multiply => " * ", IntOperator::Divide => " / ", IntOperator::Remainder => " % ", }; docvec![ self.bit_array_size(left), operator, self.bit_array_size(right) ] } BitArraySize::Block { inner, .. } => self.bit_array_size(inner).surround("{ ", " }"), } } fn list_pattern<'a>( &mut self, elements: &'a [UntypedPattern], tail: &'a Option>, ) -> Document<'a> { if elements.is_empty() { return match tail { Some(tail) => self.pattern(&tail.pattern), None => "[]".to_doc(), }; } let elements = join( elements.iter().map(|element| self.pattern(element)), break_(",", ", "), ); let doc = break_("[", "[").append(elements); match tail { None => doc.nest(INDENT).append(break_(",", "")), Some(tail) => { let tail = &tail.pattern; let comments = self.pop_comments(tail.location().start); let tail = if tail.is_discard() { "..".to_doc() } else { docvec!["..", self.pattern(tail)] }; let tail = commented(tail, comments); doc.append(break_(",", ", ")) .append(tail) .nest(INDENT) .append(break_("", "")) } } .append("]") .group() } fn pattern_call_arg<'a>(&mut self, arg: &'a CallArg) -> Document<'a> { self.format_call_arg(arg, pattern_call_arg_formatting, |this, value| { this.pattern(value) }) } pub fn clause_guard_bin_op<'a>( &mut self, name: &'a BinOp, left: &'a UntypedClauseGuard, right: &'a UntypedClauseGuard, ) -> Document<'a> { self.clause_guard_bin_op_side(name, left, left.precedence()) .append(break_("", " ")) .append(name.to_doc()) .append(" ") .append(self.clause_guard_bin_op_side(name, right, right.precedence() - 1)) } fn clause_guard_bin_op_side<'a>( &mut self, name: &BinOp, side: &'a UntypedClauseGuard, // As opposed to `bin_op_side`, here we take the side precedence as an // argument instead of computing it ourselves. That's because // `clause_guard_bin_op` will reduce the precedence of any right side to // make sure the formatter doesn't remove any needed curly bracket. side_precedence: u8, ) -> Document<'a> { let side_doc = self.clause_guard(side); match side.bin_op_name() { // In case the other side is a binary operation as well and it can // be grouped together with the current binary operation, the two // docs are simply concatenated, so that they will end up in the // same group and the formatter will try to keep those on a single // line. Some(side_name) if side_name.can_be_grouped_with(name) => { self.operator_side(side_doc, name.precedence(), side_precedence) } // In case the binary operations cannot be grouped together the // other side is treated as a group on its own so that it can be // broken independently of other pieces of the binary operations // chain. _ => self.operator_side(side_doc.group(), name.precedence(), side_precedence), } } fn clause_guard<'a>(&mut self, clause_guard: &'a UntypedClauseGuard) -> Document<'a> { match clause_guard { ClauseGuard::BinaryOperator { operator, left, right, .. } => self.clause_guard_bin_op(operator, left, right), ClauseGuard::Var { name, .. } => name.to_doc(), ClauseGuard::TupleIndex { tuple, index, .. } => { self.clause_guard(tuple).append(".").append(*index).to_doc() } ClauseGuard::FieldAccess { container, label, .. } => self .clause_guard(container) .append(".") .append(label) .to_doc(), ClauseGuard::ModuleSelect { module_name, label, .. } => module_name.to_doc().append(".").append(label).to_doc(), ClauseGuard::Constant(constant) => self.const_expr(constant), ClauseGuard::Not { expression, .. } => docvec!["!", self.clause_guard(expression)], ClauseGuard::Block { value, .. } => wrap_block(self.clause_guard(value)).group(), } } fn constant_call_arg<'a, A, B>(&mut self, arg: &'a CallArg>) -> Document<'a> { self.format_call_arg(arg, constant_call_arg_formatting, |this, value| { this.const_expr(value) }) } fn negate_bool<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> { match expr { UntypedExpr::NegateBool { value, .. } => self.expr(value), UntypedExpr::BinOp { .. } => "!".to_doc().append(wrap_block(self.expr(expr))), UntypedExpr::Int { .. } | UntypedExpr::Float { .. } | UntypedExpr::String { .. } | UntypedExpr::Block { .. } | UntypedExpr::Var { .. } | UntypedExpr::Fn { .. } | UntypedExpr::List { .. } | UntypedExpr::Call { .. } | UntypedExpr::PipeLine { .. } | UntypedExpr::Case { .. } | UntypedExpr::FieldAccess { .. } | UntypedExpr::Tuple { .. } | UntypedExpr::TupleIndex { .. } | UntypedExpr::Todo { .. } | UntypedExpr::Panic { .. } | UntypedExpr::Echo { .. } | UntypedExpr::BitArray { .. } | UntypedExpr::RecordUpdate { .. } | UntypedExpr::NegateInt { .. } => docvec!["!", self.expr(expr)], } } fn negate_int<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> { match expr { UntypedExpr::NegateInt { value, .. } => self.expr(value), UntypedExpr::Int { value, .. } if value.starts_with('-') => self.int(&value[1..]), UntypedExpr::BinOp { .. } => "- ".to_doc().append(self.expr(expr)), UntypedExpr::Int { .. } | UntypedExpr::Float { .. } | UntypedExpr::String { .. } | UntypedExpr::Block { .. } | UntypedExpr::Var { .. } | UntypedExpr::Fn { .. } | UntypedExpr::List { .. } | UntypedExpr::Call { .. } | UntypedExpr::PipeLine { .. } | UntypedExpr::Case { .. } | UntypedExpr::FieldAccess { .. } | UntypedExpr::Tuple { .. } | UntypedExpr::TupleIndex { .. } | UntypedExpr::Todo { .. } | UntypedExpr::Panic { .. } | UntypedExpr::Echo { .. } | UntypedExpr::BitArray { .. } | UntypedExpr::RecordUpdate { .. } | UntypedExpr::NegateBool { .. } => docvec!["-", self.expr(expr)], } } fn use_<'a>(&mut self, use_: &'a UntypedUse) -> Document<'a> { let comments = self.pop_comments(use_.location.start); let call = if use_.call.is_call() { docvec![" ", self.expr(&use_.call)] } else { docvec![break_("", " "), self.expr(&use_.call)].nest(INDENT) } .group(); let doc = if use_.assignments.is_empty() { docvec!["use <-", call] } else { let assignments = use_.assignments.iter().map(|use_assignment| { let pattern = self.pattern(&use_assignment.pattern); let annotation = use_assignment .annotation .as_ref() .map(|a| ": ".to_doc().append(self.type_ast(a))); pattern.append(annotation).group() }); let assignments = Itertools::intersperse(assignments, break_(",", ", ")); let left = ["use".to_doc(), break_("", " ")] .into_iter() .chain(assignments); let left = concat(left).nest(INDENT).append(break_("", " ")).group(); docvec![left, "<-", call].group() }; commented(doc, comments) } fn assert<'a>(&mut self, assert: &'a UntypedAssert) -> Document<'a> { let comments = self.pop_comments(assert.location.start); let expression = if assert.value.is_binop() || assert.value.is_pipeline() { self.expr(&assert.value).nest(INDENT) } else { self.expr(&assert.value) }; let doc = self.append_as_message(expression, PrecedingAs::Expression, assert.message.as_ref()); commented(docvec!["assert ", doc], comments) } fn bit_array<'a>( &mut self, segments: Vec>, packing: ItemsPacking, location: &SrcSpan, ) -> Document<'a> { let comments = self.pop_comments(location.end); let comments_doc = printed_comments(comments, false); // Avoid adding illegal comma in empty bit array by explicitly handling it if segments.is_empty() { // We take all comments that come _before_ the end of the bit array, // that is all comments that are inside "<<" and ">>", if there's // any comment we want to put it inside the empty bit array! // Refer to the `list` function for a similar procedure. return match comments_doc { None => "<<>>".to_doc(), Some(comments) => "<<" .to_doc() .append(break_("", "").nest(INDENT)) .append(comments) .append(break_("", "")) .append(">>") // vvv We want to make sure the comments are on a separate // line from the opening and closing angle brackets so // we force the breaks to be split on newlines. .force_break(), }; } let comma = match packing { ItemsPacking::FitMultiplePerLine => flex_break(",", ", "), ItemsPacking::FitOnePerLine | ItemsPacking::BreakOnePerLine => break_(",", ", "), }; let last_break = break_(",", ""); let doc = break_("<<", "<<") .append(join(segments, comma)) .nest(INDENT); let doc = match comments_doc { None => doc.append(last_break).append(">>"), Some(comments) => doc .append(last_break.nest(INDENT)) // ^ Notice how in this case we nest the final break before // adding it: this way the comments are going to be as // indented as the bit array items. .append(comments.nest(INDENT)) .append(line()) .append(">>") .force_break(), }; match packing { ItemsPacking::FitOnePerLine | ItemsPacking::FitMultiplePerLine => doc.group(), ItemsPacking::BreakOnePerLine => doc.force_break(), } } fn bit_array_segment_expr<'a>(&mut self, expr: &'a UntypedExpr) -> Document<'a> { match expr { UntypedExpr::BinOp { .. } => wrap_block(self.expr(expr)), UntypedExpr::Int { .. } | UntypedExpr::Float { .. } | UntypedExpr::String { .. } | UntypedExpr::Var { .. } | UntypedExpr::Fn { .. } | UntypedExpr::List { .. } | UntypedExpr::Call { .. } | UntypedExpr::PipeLine { .. } | UntypedExpr::Case { .. } | UntypedExpr::FieldAccess { .. } | UntypedExpr::Tuple { .. } | UntypedExpr::TupleIndex { .. } | UntypedExpr::Todo { .. } | UntypedExpr::Panic { .. } | UntypedExpr::Echo { .. } | UntypedExpr::BitArray { .. } | UntypedExpr::RecordUpdate { .. } | UntypedExpr::NegateBool { .. } | UntypedExpr::NegateInt { .. } | UntypedExpr::Block { .. } => self.expr(expr), } } fn statement<'a>(&mut self, statement: &'a UntypedStatement) -> Document<'a> { match statement { Statement::Expression(expression) => self.expr(expression), Statement::Assignment(assignment) => self.assignment(assignment), Statement::Use(use_) => self.use_(use_), Statement::Assert(assert) => self.assert(assert), } } fn block<'a>( &mut self, location: &SrcSpan, statements: &'a Vec1, force_breaks: bool, ) -> Document<'a> { let statements_doc = docvec![break_("", " "), self.statements(statements.as_vec())].nest(INDENT); let trailing_comments = self.pop_comments(location.end); let trailing_comments = printed_comments(trailing_comments, false); let block_doc = match trailing_comments { Some(trailing_comments_doc) => docvec![ "{", statements_doc, line().nest(INDENT), trailing_comments_doc.nest(INDENT), line(), "}" ] .force_break(), None => docvec!["{", statements_doc, break_("", " "), "}"], }; if force_breaks { block_doc.force_break().group() } else { block_doc.group() } } pub fn wrap_function_call_arguments<'a, I>( &mut self, arguments: I, location: &SrcSpan, ) -> Document<'a> where I: IntoIterator>, { let mut arguments = arguments.into_iter().peekable(); if arguments.peek().is_none() { return "()".to_doc(); } let arguments_doc = break_("", "") .append(join(arguments, break_(",", ", "))) .nest_if_broken(INDENT); // We get all remaining comments that come before the call's closing // parenthesis. // If there's any we add those before the closing parenthesis instead // of moving those out of the call. // Otherwise those would be moved out of the call. let comments = self.pop_comments(location.end); let closing_parens = match printed_comments(comments, false) { None => docvec![break_(",", ""), ")"], Some(comment) => { docvec![break_(",", "").nest(INDENT), comment, line(), ")"].force_break() } }; "(".to_doc() .append(arguments_doc) .append(closing_parens) .group() } pub fn wrap_arguments<'a, I>(&mut self, arguments: I, comments_limit: u32) -> Document<'a> where I: IntoIterator>, { let mut arguments = arguments.into_iter().peekable(); if arguments.peek().is_none() { let comments = self.pop_comments(comments_limit); return match printed_comments(comments, false) { Some(comments) => "(" .to_doc() .append(break_("", "")) .append(comments) .nest_if_broken(INDENT) .force_break() .append(break_("", "")) .append(")"), None => "()".to_doc(), }; } let doc = break_("(", "(").append(join(arguments, break_(",", ", "))); // Include trailing comments if there are any let comments = self.pop_comments(comments_limit); match printed_comments(comments, false) { Some(comments) => doc .append(break_(",", "")) .append(comments) .nest_if_broken(INDENT) .force_break() .append(break_("", "")) .append(")"), None => doc .nest_if_broken(INDENT) .append(break_(",", "")) .append(")"), } } pub fn wrap_arguments_with_spread<'a, I>( &mut self, arguments: I, comments_limit: u32, ) -> Document<'a> where I: IntoIterator>, { let mut arguments = arguments.into_iter().peekable(); if arguments.peek().is_none() { return self.wrap_arguments(arguments, comments_limit); } let doc = break_("(", "(") .append(join(arguments, break_(",", ", "))) .append(break_(",", ", ")) .append(".."); // Include trailing comments if there are any let comments = self.pop_comments(comments_limit); match printed_comments(comments, false) { Some(comments) => doc .append(break_(",", "")) .append(comments) .nest_if_broken(INDENT) .force_break() .append(break_("", "")) .append(")"), None => doc .nest_if_broken(INDENT) .append(break_(",", "")) .append(")"), } } /// Given some regular comments it pretty prints those with any respective /// doc comment that might be preceding those. /// For example: /// /// ```gleam /// /// Doc /// // comment /// /// /// Doc /// pub fn wibble() {} /// ``` /// /// We don't want the first doc comment to be merged together with /// `wibble`'s doc comment, so when we run into comments like `// comment` /// we need to first print all documentation comments that come before it. /// fn printed_documented_comments<'a, 'b>( &mut self, comments: impl IntoIterator)>, ) -> Option> { let mut comments = comments.into_iter().peekable(); let _ = comments.peek()?; let mut doc = Vec::new(); while let Some(c) = comments.next() { let (is_doc_commented, c) = match c { (comment_start, Some(c)) => { let doc_comment = self.doc_comments(comment_start); let is_doc_commented = !doc_comment.is_empty(); doc.push(doc_comment); (is_doc_commented, c) } (_, None) => continue, }; doc.push("//".to_doc().append(EcoString::from(c))); match comments.peek() { // Next line is a comment Some((_, Some(_))) => doc.push(line()), // Next line is empty Some((_, None)) => { let _ = comments.next(); doc.push(lines(2)); } // We've reached the end, there are no more lines None => { if is_doc_commented { doc.push(lines(2)); } else { doc.push(line()); } } } } let doc = concat(doc); Some(doc.force_break()) } fn append_as_message<'a>( &mut self, doc: Document<'a>, preceding_as: PrecedingAs, message: Option<&'a UntypedExpr>, ) -> Document<'a> { let Some(message) = message else { return doc }; let comments = self.pop_comments(message.location().start); let comments = printed_comments(comments, false); let as_ = match preceding_as { PrecedingAs::Keyword => " as".to_doc(), PrecedingAs::Expression => docvec![break_("", " "), "as"].nest(INDENT), }; let doc = match comments { // If there's comments between the document and the message we want // the `as` bit to be on the same line as the original document and // go on a new indented line with the message and comments: // ```gleam // todo as // // comment! // "wibble" // ``` Some(comments) => docvec![ doc.group(), as_, docvec![line(), comments, line(), self.expr(message).group()].nest(INDENT) ], None => { let message = match (preceding_as, message) { // If we have `as` preceded by a keyword (like with `panic` and `todo`) // and the message is a block, we don't want to nest it any further. That is, // we want it to look like this: // ```gleam // panic as { // wibble wobble // } // ``` // instead of this: // ```gleam // panic as { // wibble wobble // } // ``` (PrecedingAs::Keyword, UntypedExpr::Block { .. }) => self.expr(message).group(), _ => self.expr(message).group().nest(INDENT), }; docvec![doc.group(), as_, " ", message] } }; doc.group() } fn echo<'a>( &mut self, expression: &'a Option>, message: &'a Option>, ) -> Document<'a> { let Some(expression) = expression else { return self.append_as_message( "echo".to_doc(), PrecedingAs::Keyword, message.as_deref(), ); }; // When a binary expression gets broken on multiple lines we don't want // it to be on the same line as echo, or it would look confusing; // instead it's nested onto a new line: // // ```gleam // echo first // |> wobble // |> wibble // ``` // // So it's easier to see echo is printing the whole thing. Otherwise, // it would look like echo is printing just the first item: // // ```gleam // echo first // |> wobble // |> wibble // ``` // let doc = self.expr(expression); if expression.is_binop() || expression.is_pipeline() { let doc = self.append_as_message( doc.nest(INDENT), PrecedingAs::Expression, message.as_deref(), ); docvec!["echo ", doc] } else { docvec![ "echo ", self.append_as_message(doc, PrecedingAs::Expression, message.as_deref()) ] } } } /// This is used to describe the kind of things that might preceding an `as` /// message that can be added to various places: `panic`, `echo`, `let assert`, /// `assert`, `todo`. /// /// It might be preceded by a keyword, like with `echo` and `panic`, or by /// an expression, like in `assert` or `let assert`. /// enum PrecedingAs { /// An expression is preceding the `as` message: /// ```gleam /// echo 1 as "message" /// assert 1 == 2 as "message" /// let assert Ok(_) = result as "message" /// ``` /// Expression, /// A keyword is preceding the `as` message: /// ```gleam /// 1 |> echo as "message" /// panic as "message" /// todo as "message" /// ``` /// Keyword, } fn init_and_last(vec: &[T]) -> Option<(&[T], &T)> { match vec { [] => None, _ => match vec.split_at(vec.len() - 1) { (init, [last]) => Some((init, last)), _ => panic!("unreachable"), }, } } impl<'a> Documentable<'a> for &'a ArgNames { fn to_doc(self) -> Document<'a> { match self { ArgNames::Named { name, .. } | ArgNames::Discard { name, .. } => name.to_doc(), ArgNames::LabelledDiscard { label, name, .. } | ArgNames::NamedLabelled { label, name, .. } => { docvec![label, " ", name] } } } } fn pub_(publicity: Publicity) -> Document<'static> { match publicity { Publicity::Public | Publicity::Internal { .. } => "pub ".to_doc(), Publicity::Private => nil(), } } impl<'a> Documentable<'a> for &'a UnqualifiedImport { fn to_doc(self) -> Document<'a> { self.name.as_str().to_doc().append(match &self.as_name { None => nil(), Some(s) => " as ".to_doc().append(s.as_str()), }) } } impl<'a> Documentable<'a> for &'a BinOp { fn to_doc(self) -> Document<'a> { match self { BinOp::And => "&&", BinOp::Or => "||", BinOp::LtInt => "<", BinOp::LtEqInt => "<=", BinOp::LtFloat => "<.", BinOp::LtEqFloat => "<=.", BinOp::Eq => "==", BinOp::NotEq => "!=", BinOp::GtEqInt => ">=", BinOp::GtInt => ">", BinOp::GtEqFloat => ">=.", BinOp::GtFloat => ">.", BinOp::AddInt => "+", BinOp::AddFloat => "+.", BinOp::SubInt => "-", BinOp::SubFloat => "-.", BinOp::MultInt => "*", BinOp::MultFloat => "*.", BinOp::DivInt => "/", BinOp::DivFloat => "/.", BinOp::RemainderInt => "%", BinOp::Concatenate => "<>", } .to_doc() } } #[allow(clippy::enum_variant_names)] #[derive(Debug)] /// This is used to determine how to fit the items of a list, or the segments of /// a bit array in a line. /// enum ItemsPacking { /// Try and fit everything on a single line; if the items don't fit, break /// the list putting each item into its own line. /// /// ```gleam /// // unbroken /// [1, 2, 3] /// /// // broken /// [ /// 1, /// 2, /// 3, /// ] /// ``` /// FitOnePerLine, /// Try and fit everything on a single line; if the items don't fit, break /// the list putting as many items as possible in a single line. /// /// ```gleam /// // unbroken /// [1, 2, 3] /// /// // broken /// [ /// 1, 2, 3, ... /// 4, 100, /// ] /// ``` /// FitMultiplePerLine, /// Always break the list, putting each item into its own line: /// /// ```gleam /// [ /// 1, /// 2, /// 3, /// ] /// ``` /// BreakOnePerLine, } pub fn break_block(doc: Document<'_>) -> Document<'_> { "{".to_doc() .append(line().append(doc).nest(INDENT)) .append(line()) .append("}") .force_break() } pub fn wrap_block(doc: Document<'_>) -> Document<'_> { break_("{", "{ ") .append(doc) .nest(INDENT) .append(break_("", " ")) .append("}") } fn printed_comments<'a, 'comments>( comments: impl IntoIterator>, trailing_newline: bool, ) -> Option> { let mut comments = comments.into_iter().peekable(); let _ = comments.peek()?; let mut doc = Vec::new(); while let Some(c) = comments.next() { let c = match c { Some(c) => c, None => continue, }; doc.push("//".to_doc().append(EcoString::from(c))); match comments.peek() { // Next line is a comment Some(Some(_)) => doc.push(line()), // Next line is empty Some(None) => { let _ = comments.next(); match comments.peek() { Some(_) => doc.push(lines(2)), None => { if trailing_newline { doc.push(lines(2)); } } } } // We've reached the end, there are no more lines None => { if trailing_newline { doc.push(line()); } } } } let doc = concat(doc); if trailing_newline { Some(doc.force_break()) } else { Some(doc) } } fn commented<'a, 'comments>( doc: Document<'a>, comments: impl IntoIterator>, ) -> Document<'a> { match printed_comments(comments, true) { Some(comments) => comments.append(doc.group()), None => doc, } } fn bit_array_segment( segment: &BitArraySegment, mut to_doc: ToDoc, ) -> Document<'_> where ToDoc: FnMut(&Value) -> Document<'_>, { match segment { BitArraySegment { value, options, .. } if options.is_empty() => to_doc(value), BitArraySegment { value, options, .. } => to_doc(value).append(":").append(join( options .iter() .map(|option| segment_option(option, |value| to_doc(value))), "-".to_doc(), )), } } fn segment_option(option: &BitArrayOption, mut to_doc: ToDoc) -> Document<'_> where ToDoc: FnMut(&Value) -> Document<'_>, { match option { BitArrayOption::Bytes { .. } => "bytes".to_doc(), BitArrayOption::Bits { .. } => "bits".to_doc(), BitArrayOption::Int { .. } => "int".to_doc(), BitArrayOption::Float { .. } => "float".to_doc(), BitArrayOption::Utf8 { .. } => "utf8".to_doc(), BitArrayOption::Utf16 { .. } => "utf16".to_doc(), BitArrayOption::Utf32 { .. } => "utf32".to_doc(), BitArrayOption::Utf8Codepoint { .. } => "utf8_codepoint".to_doc(), BitArrayOption::Utf16Codepoint { .. } => "utf16_codepoint".to_doc(), BitArrayOption::Utf32Codepoint { .. } => "utf32_codepoint".to_doc(), BitArrayOption::Signed { .. } => "signed".to_doc(), BitArrayOption::Unsigned { .. } => "unsigned".to_doc(), BitArrayOption::Big { .. } => "big".to_doc(), BitArrayOption::Little { .. } => "little".to_doc(), BitArrayOption::Native { .. } => "native".to_doc(), BitArrayOption::Size { value, short_form: false, .. } => "size" .to_doc() .append("(") .append(to_doc(value)) .append(")"), BitArrayOption::Size { value, short_form: true, .. } => to_doc(value), BitArrayOption::Unit { value, .. } => "unit" .to_doc() .append("(") .append(eco_format!("{value}")) .append(")"), } } pub fn comments_before<'a>( comments: &'a [Comment<'a>], empty_lines: &'a [u32], limit: u32, retain_empty_lines: bool, ) -> ( impl Iterator)>, &'a [Comment<'a>], &'a [u32], ) { let end_comments = comments .iter() .position(|c| c.start > limit) .unwrap_or(comments.len()); let end_empty_lines = empty_lines .iter() .position(|l| *l > limit) .unwrap_or(empty_lines.len()); let popped_comments = comments .get(0..end_comments) .expect("0..end_comments is guaranteed to be in bounds") .iter() .map(|c| (c.start, Some(c.content))); let popped_empty_lines = if retain_empty_lines { empty_lines } else { &[] } .get(0..end_empty_lines) .unwrap_or(&[]) .iter() .map(|i| (i, i)) // compact consecutive empty lines into a single line .coalesce(|(a_start, a_end), (b_start, b_end)| { if *a_end + 1 == *b_start { Ok((a_start, b_end)) } else { Err(((a_start, a_end), (b_start, b_end))) } }) .map(|l| (*l.0, None)); let popped = popped_comments .merge_by(popped_empty_lines, |(a, _), (b, _)| a < b) .skip_while(|(_, comment_or_line)| comment_or_line.is_none()); ( popped, comments.get(end_comments..).expect("in bounds"), empty_lines.get(end_empty_lines..).expect("in bounds"), ) } fn is_breakable_argument(expr: &UntypedExpr, arity: usize) -> bool { match expr { // A call is only breakable if it is the only argument UntypedExpr::Call { .. } => arity == 1, UntypedExpr::Fn { .. } | UntypedExpr::Block { .. } | UntypedExpr::Case { .. } | UntypedExpr::List { .. } | UntypedExpr::Tuple { .. } | UntypedExpr::BitArray { .. } => true, UntypedExpr::Int { .. } | UntypedExpr::Float { .. } | UntypedExpr::String { .. } | UntypedExpr::Var { .. } | UntypedExpr::BinOp { .. } | UntypedExpr::PipeLine { .. } | UntypedExpr::FieldAccess { .. } | UntypedExpr::TupleIndex { .. } | UntypedExpr::Todo { .. } | UntypedExpr::Panic { .. } | UntypedExpr::Echo { .. } | UntypedExpr::RecordUpdate { .. } | UntypedExpr::NegateBool { .. } | UntypedExpr::NegateInt { .. } => false, } } enum CallArgFormatting<'a, A> { ShorthandLabelled(&'a EcoString), Unlabelled(&'a A), Labelled(&'a EcoString, &'a A), } fn expr_call_arg_formatting(arg: &CallArg) -> CallArgFormatting<'_, UntypedExpr> { match arg { // An argument supplied using label shorthand syntax. _ if arg.uses_label_shorthand() => CallArgFormatting::ShorthandLabelled( arg.label.as_ref().expect("label shorthand with no label"), ), // A labelled argument. CallArg { label: Some(label), value, .. } => CallArgFormatting::Labelled(label, value), // An unlabelled argument. CallArg { value, .. } => CallArgFormatting::Unlabelled(value), } } fn pattern_call_arg_formatting( arg: &CallArg, ) -> CallArgFormatting<'_, UntypedPattern> { match arg { // An argument supplied using label shorthand syntax. _ if arg.uses_label_shorthand() => CallArgFormatting::ShorthandLabelled( arg.label.as_ref().expect("label shorthand with no label"), ), // A labelled argument. CallArg { label: Some(label), value, .. } => CallArgFormatting::Labelled(label, value), // An unlabelled argument. CallArg { value, .. } => CallArgFormatting::Unlabelled(value), } } fn constant_call_arg_formatting( arg: &CallArg>, ) -> CallArgFormatting<'_, Constant> { match arg { // An argument supplied using label shorthand syntax. _ if arg.uses_label_shorthand() => CallArgFormatting::ShorthandLabelled( arg.label.as_ref().expect("label shorthand with no label"), ), // A labelled argument. CallArg { label: Some(label), value, .. } => CallArgFormatting::Labelled(label, value), // An unlabelled argument. CallArg { value, .. } => CallArgFormatting::Unlabelled(value), } } struct AttributesPrinter<'a> { external_erlang: &'a Option<(EcoString, EcoString, SrcSpan)>, external_javascript: &'a Option<(EcoString, EcoString, SrcSpan)>, deprecation: &'a Deprecation, internal: bool, } impl<'a> AttributesPrinter<'a> { pub fn new() -> Self { Self { external_erlang: &None, external_javascript: &None, deprecation: &Deprecation::NotDeprecated, internal: false, } } pub fn set_external_erlang( mut self, external: &'a Option<(EcoString, EcoString, SrcSpan)>, ) -> Self { self.external_erlang = external; self } pub fn set_external_javascript( mut self, external: &'a Option<(EcoString, EcoString, SrcSpan)>, ) -> Self { self.external_javascript = external; self } pub fn set_internal(mut self, publicity: Publicity) -> Self { self.internal = publicity.is_internal(); self } pub fn set_deprecation(mut self, deprecation: &'a Deprecation) -> Self { self.deprecation = deprecation; self } } impl<'a> Documentable<'a> for AttributesPrinter<'a> { fn to_doc(self) -> Document<'a> { let mut attributes = vec![]; // @deprecated attribute if let Deprecation::Deprecated { message } = self.deprecation { attributes.push(docvec!["@deprecated(\"", message, "\")"]) }; // @external attributes if let Some((m, f, _)) = self.external_erlang { attributes.push(docvec!["@external(erlang, \"", m, "\", \"", f, "\")"]) }; if let Some((m, f, _)) = self.external_javascript { attributes.push(docvec!["@external(javascript, \"", m, "\", \"", f, "\")"]) }; // @internal attribute if self.internal { attributes.push("@internal".to_doc()); }; if attributes.is_empty() { nil() } else { join(attributes, line()).append(line()) } } } ================================================ FILE: compiler-core/src/graph.rs ================================================ //! General functions for working with graphs. use petgraph::{Direction, prelude::NodeIndex, stable_graph::StableGraph}; /// Sort a graph into a sequence from the leaves to the roots. /// /// Nodes are returned in their smallest possible groups, which is either a leaf /// or a cycle. /// /// This function is implemented using `pop_leaf_or_cycle`. /// pub fn into_dependency_order(mut graph: StableGraph) -> Vec> { let mut items = vec![]; // Remove all self-edges from the graph. graph.retain_edges(|graph, edge| match graph.edge_endpoints(edge) { Some((a, b)) => a != b, None => false, }); loop { let current = pop_leaf_or_cycle(&mut graph); if current.is_empty() { return items; } else { items.push(current); } } } /// The same as `leaf_or_cycle` but removes the nodes from the graph. /// See the docs there for more details. /// /// # Panics /// /// Panics if the graph contains a self-edge. /// fn pop_leaf_or_cycle(graph: &mut StableGraph) -> Vec { let nodes = leaf_or_cycle(graph); for node in &nodes { _ = graph.remove_node(*node); } nodes } /// Return a leaf from the graph. If there are no leaves then the largest cycle /// is returned instead. /// /// If there are no leaves or cycles then an empty vector is returned. /// /// The nodes returned are not removed from the graph. /// /// # Panics /// /// Panics if the graph contains a self-edge. /// fn leaf_or_cycle(graph: &StableGraph) -> Vec { if graph.node_count() == 0 { return vec![]; } // Find a leaf, returning one if found. for node in graph.node_indices() { let mut outgoing = graph.neighbors_directed(node, Direction::Outgoing); let referenced = outgoing.next(); if referenced == Some(node) { panic!("Self edge found in graph"); } // This is a leaf. if referenced.is_none() { return vec![node]; } } // No leaves were found, so find a cycle. // We use a toposort to find the start of the cycle. let start = petgraph::algo::toposort(&graph, None) .expect_err("Non-empty graph has no leaves or cycles") .node_id(); // Then traverse the graph to find nodes in the cycle. // This traverses all possible paths to find a cycle, this can likely be // optimised. There's not a large number of functions in a module however so // this is tolerable in this specific instance. #[derive(Debug)] enum Step { Backtrack, Next(NodeIndex), } let mut path = vec![]; let mut stack = vec![Step::Next(start)]; let mut cycles = vec![]; while let Some(step) = stack.pop() { let node = match step { // We have processed all the nodes in the branch so backtrack, // popping the node off the path. Step::Backtrack => { _ = path.pop(); continue; } Step::Next(node) => node, }; if path.contains(&node) { continue; } // Add this node to the path and record the point at which we need to // backtrack in order to go back up the tree. stack.push(Step::Backtrack); path.push(node); // Check each child & add them to the stack if they are not the target. for node in graph.neighbors_directed(node, Direction::Outgoing) { if node == start { cycles.push(path.clone()); } else { stack.push(Step::Next(node)); } } } cycles .into_iter() .max_by_key(|x| x.len()) .expect("Could not find cycle for toposort returned start node") } #[cfg(test)] mod tests { use super::*; #[test] fn leaf_or_cycle_empty() { let mut graph: StableGraph<(), ()> = StableGraph::new(); assert!(pop_leaf_or_cycle(&mut graph).is_empty()); } #[test] fn leaf_or_cycle_1() { let mut graph: StableGraph<(), ()> = StableGraph::new(); let a = graph.add_node(()); assert_eq!(into_dependency_order(graph), vec![vec![a]]); } #[test] fn leaf_or_cycle_2() { let mut graph: StableGraph<(), ()> = StableGraph::new(); let a = graph.add_node(()); let b = graph.add_node(()); assert_eq!(into_dependency_order(graph), vec![vec![a], vec![b]]); } #[test] fn leaf_or_cycle_3() { let mut graph: StableGraph<(), ()> = StableGraph::new(); // Here a depends on b so b must come before a let a = graph.add_node(()); let b = graph.add_node(()); let c = graph.add_node(()); _ = graph.add_edge(a, b, ()); assert_eq!( into_dependency_order(graph), vec![vec![b], vec![a], vec![c]] ); } #[test] fn leaf_or_cycle_4() { let mut graph: StableGraph<(), ()> = StableGraph::new(); let a = graph.add_node(()); let b = graph.add_node(()); let c = graph.add_node(()); _ = graph.add_edge(a, b, ()); _ = graph.add_edge(a, c, ()); assert_eq!( into_dependency_order(graph), vec![vec![b], vec![c], vec![a]] ); } #[test] fn leaf_or_cycle_5() { let mut graph: StableGraph<(), ()> = StableGraph::new(); let a = graph.add_node(()); let b = graph.add_node(()); let c = graph.add_node(()); _ = graph.add_edge(a, b, ()); _ = graph.add_edge(b, a, ()); assert_eq!(into_dependency_order(graph), vec![vec![c], vec![b, a]]); } #[test] fn leaf_or_cycle_6() { let mut graph: StableGraph<(), ()> = StableGraph::new(); let a = graph.add_node(()); let b = graph.add_node(()); let c = graph.add_node(()); let d = graph.add_node(()); _ = graph.add_edge(a, b, ()); _ = graph.add_edge(b, c, ()); _ = graph.add_edge(c, a, ()); _ = graph.add_edge(d, a, ()); assert_eq!(into_dependency_order(graph), vec![vec![c, a, b], vec![d]]); } #[test] fn leaf_or_cycle_7() { let mut graph: StableGraph<(), ()> = StableGraph::new(); let a = graph.add_node(()); let b = graph.add_node(()); _ = graph.add_edge(a, a, ()); _ = graph.add_edge(a, b, ()); _ = graph.add_edge(b, b, ()); // Here there are no true leafs, only cycles. However, b is in a loop // with itself so counts as a leaf as far as we are concerned. assert_eq!(into_dependency_order(graph), vec![vec![b], vec![a]]); } #[test] fn leaf_or_cycle_8() { let mut graph: StableGraph<(), ()> = StableGraph::new(); let a = graph.add_node(()); let b = graph.add_node(()); _ = graph.add_edge(a, a, ()); _ = graph.add_edge(a, b, ()); _ = graph.add_edge(b, b, ()); _ = graph.add_edge(b, b, ()); _ = graph.add_edge(b, b, ()); // Here there are no true leafs, only cycles. However, b is in a loop // with itself so counts as a leaf as far as we are concerned. // This is different from the previous test as there are multiple self // references for node b. assert_eq!(into_dependency_order(graph), vec![vec![b], vec![a]]); } #[test] fn leaf_or_cycle_9() { let mut graph: StableGraph<(), ()> = StableGraph::new(); let a = graph.add_node(()); let b = graph.add_node(()); let c = graph.add_node(()); _ = graph.add_edge(a, a, ()); _ = graph.add_edge(a, b, ()); _ = graph.add_edge(b, b, ()); _ = graph.add_edge(b, c, ()); _ = graph.add_edge(c, b, ()); _ = graph.add_edge(c, c, ()); // Here there are no true leafs, only cycles. However, b is in a loop // with itself so counts as a leaf as far as we are concerned. // This is different from the previous test as there are multiple self // references for node b. assert_eq!(into_dependency_order(graph), vec![vec![c, b], vec![a]]); } } ================================================ FILE: compiler-core/src/hex.rs ================================================ use camino::Utf8Path; use debug_ignore::DebugIgnore; use flate2::read::GzDecoder; use futures::future; use hexpm::{ApiError, WriteActionCredentials, version::Version}; use tar::Archive; use crate::{ Error, Result, io::{FileSystemReader, FileSystemWriter, HttpClient, TarUnpacker}, manifest::{ManifestPackage, ManifestPackageSource}, paths::{self, ProjectPaths}, }; pub const HEXPM_PUBLIC_KEY: &[u8] = b"-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApqREcFDt5vV21JVe2QNB Edvzk6w36aNFhVGWN5toNJRjRJ6m4hIuG4KaXtDWVLjnvct6MYMfqhC79HAGwyF+ IqR6Q6a5bbFSsImgBJwz1oadoVKD6ZNetAuCIK84cjMrEFRkELtEIPNHblCzUkkM 3rS9+DPlnfG8hBvGi6tvQIuZmXGCxF/73hU0/MyGhbmEjIKRtG6b0sJYKelRLTPW XgK7s5pESgiwf2YC/2MGDXjAJfpfCd0RpLdvd4eRiXtVlE9qO9bND94E7PgQ/xqZ J1i2xWFndWa6nfFnRxZmCStCOZWYYPlaxr+FZceFbpMwzTNs4g3d4tLNUcbKAIH4 0wIDAQAB -----END PUBLIC KEY----- "; fn key_name(hostname: &str) -> String { format!("gleam-{hostname}") } pub async fn publish_package( release_tarball: Vec, version: String, name: &str, api_key: &WriteActionCredentials, config: &hexpm::Config, replace: bool, http: &Http, ) -> Result<()> { tracing::info!("Publishing package, replace: {}", replace); let request = hexpm::api_publish_package_request(release_tarball, api_key, config, replace); let response = http.send(request).await?; hexpm::api_publish_package_response(response).map_err(|e| match e { ApiError::NotReplacing => Error::HexPublishReplaceRequired { version }, ApiError::Forbidden => Error::HexPublishAccessDenied { name: name.into(), version, }, ApiError::Json(_) | ApiError::Io(_) | ApiError::RateLimited | ApiError::InvalidCredentials | ApiError::UnexpectedResponse(..) | ApiError::InvalidPackageNameFormat(_) | ApiError::IncorrectPayloadSignature | ApiError::InvalidProtobuf(_) | ApiError::InvalidVersionFormat(_) | ApiError::NotFound | ApiError::InvalidVersionRequirementFormat(_) | ApiError::IncorrectChecksum | ApiError::OAuthTimeout | ApiError::OAuthAccessDenied | ApiError::ExpiredToken | ApiError::OAuthRefreshTokenRejected | ApiError::IncorrectOneTimePassword | ApiError::LateModification => Error::hex(e), }) } pub async fn transfer_owner( api_key: &WriteActionCredentials, package_name: String, new_owner_username_or_email: String, config: &hexpm::Config, http: &Http, ) -> Result<()> { tracing::info!( "Transferring ownership of `{}` to {}", package_name, new_owner_username_or_email ); let request = hexpm::api_transfer_owner_request( &package_name, &new_owner_username_or_email, api_key, config, ); let response = http.send(request).await?; hexpm::api_transfer_owner_response(response).map_err(Error::hex) } #[derive(Debug, strum::EnumString, strum::VariantNames, Clone, Copy, PartialEq, Eq)] #[strum(serialize_all = "lowercase")] pub enum RetirementReason { Other, Invalid, Security, Deprecated, Renamed, } impl RetirementReason { pub fn to_library_enum(&self) -> hexpm::RetirementReason { match self { RetirementReason::Other => hexpm::RetirementReason::Other, RetirementReason::Invalid => hexpm::RetirementReason::Invalid, RetirementReason::Security => hexpm::RetirementReason::Security, RetirementReason::Deprecated => hexpm::RetirementReason::Deprecated, RetirementReason::Renamed => hexpm::RetirementReason::Renamed, } } } pub async fn retire_release( package: &str, version: &str, reason: RetirementReason, message: Option<&str>, api_key: &WriteActionCredentials, config: &hexpm::Config, http: &Http, ) -> Result<()> { tracing::info!(package=%package, version=%version, "retiring_hex_release"); let request = hexpm::api_retire_release_request( package, version, reason.to_library_enum(), message, api_key, config, ); let response = http.send(request).await?; hexpm::api_retire_release_response(response).map_err(Error::hex) } pub async fn unretire_release( package: &str, version: &str, api_key: &WriteActionCredentials, config: &hexpm::Config, http: &Http, ) -> Result<()> { tracing::info!(package=%package, version=%version, "retiring_hex_release"); let request = hexpm::api_unretire_release_request(package, version, api_key, config); let response = http.send(request).await?; hexpm::api_unretire_release_response(response).map_err(Error::hex) } pub async fn remove_api_key( hostname: &str, config: &hexpm::Config, auth_key: &WriteActionCredentials, http: &Http, ) -> Result<()> { tracing::info!("Deleting API key from Hex"); let request = hexpm::api_remove_api_key_request(&key_name(hostname), auth_key, config); let response = http.send(request).await?; hexpm::api_remove_api_key_response(response).map_err(Error::hex) } #[derive(Debug)] pub struct Downloader { fs_reader: DebugIgnore>, fs_writer: DebugIgnore>, http: DebugIgnore>, untar: DebugIgnore>, hex_config: hexpm::Config, paths: ProjectPaths, } impl Downloader { pub fn new( fs_reader: Box, fs_writer: Box, http: Box, untar: Box, paths: ProjectPaths, ) -> Self { Self { fs_reader: DebugIgnore(fs_reader), fs_writer: DebugIgnore(fs_writer), http: DebugIgnore(http), untar: DebugIgnore(untar), hex_config: hexpm::Config::new(), paths, } } pub async fn ensure_package_downloaded( &self, package: &ManifestPackage, ) -> Result { let outer_checksum = match &package.source { ManifestPackageSource::Hex { outer_checksum } => outer_checksum, ManifestPackageSource::Git { .. } | ManifestPackageSource::Local { .. } => { panic!("Attempt to download non-hex package from hex") } }; let tarball_path = paths::global_package_cache_package_tarball(outer_checksum); if self.fs_reader.is_file(&tarball_path) { tracing::info!( package = package.name.as_str(), version = %package.version, "package_in_cache" ); return Ok(false); } tracing::info!( package = &package.name.as_str(), version = %package.version, "downloading_package_to_cache" ); let request = hexpm::repository_get_package_tarball_request( &package.name, &package.version.to_string(), None, &self.hex_config, ); let response = self.http.send(request).await?; let tarball = hexpm::repository_get_package_tarball_response(response, &outer_checksum.0) .map_err(|error| Error::DownloadPackageError { package_name: package.name.to_string(), package_version: package.version.to_string(), error: error.to_string(), })?; self.fs_writer.write_bytes(&tarball_path, &tarball)?; Ok(true) } pub async fn ensure_package_in_build_directory( &self, package: &ManifestPackage, ) -> Result { let _ = self.ensure_package_downloaded(package).await?; self.extract_package_from_cache(package) } // It would be really nice if this was async but the library is sync pub fn extract_package_from_cache(&self, package: &ManifestPackage) -> Result { let contents_path = Utf8Path::new("contents.tar.gz"); let destination = self.paths.build_packages_package(&package.name); let outer_checksum = match &package.source { ManifestPackageSource::Hex { outer_checksum } => outer_checksum, ManifestPackageSource::Git { .. } | ManifestPackageSource::Local { .. } => { panic!("Attempt to download non-hex package from hex") } }; // If the directory already exists then there's nothing for us to do if self.fs_reader.is_directory(&destination) { tracing::info!( package = package.name.as_str(), "Package already in build directory" ); return Ok(false); } tracing::info!(package = package.name.as_str(), "writing_package_to_target"); let tarball = paths::global_package_cache_package_tarball(outer_checksum); let reader = self.fs_reader.reader(&tarball)?; let mut archive = Archive::new(reader); // Find the source code from within the outer tarball for entry in self.untar.entries(&mut archive)? { let file = entry.map_err(Error::expand_tar)?; let path = file.header().path().map_err(Error::expand_tar)?; if path.as_ref() == contents_path { // Expand this inner source code and write to the file system let archive = Archive::new(GzDecoder::new(file)); let result = self.untar.unpack(&destination, archive); // If we failed to expand the tarball remove any source code // that was partially written so that we don't mistakenly think // the operation succeeded next time we run. return match result { Ok(()) => Ok(true), Err(err) => { self.fs_writer.delete_directory(&destination)?; Err(err) } }; } } Err(Error::ExpandTar { error: "Unable to locate Hex package contents.tar.gz".into(), }) } pub async fn download_hex_packages<'a, Packages: Iterator>( &self, packages: Packages, project_name: &str, ) -> Result<()> { let futures = packages .filter(|package| project_name != package.name) .map(|package| self.ensure_package_in_build_directory(package)); // Run the futures to download the packages concurrently let results = future::join_all(futures).await; // Count the number of packages downloaded while checking for errors for result in results { let _ = result?; } Ok(()) } } pub async fn publish_documentation( name: &str, version: &Version, archive: Vec, api_key: &WriteActionCredentials, config: &hexpm::Config, http: &Http, ) -> Result<()> { tracing::info!("publishing_documentation"); let request = hexpm::api_publish_docs_request(name, &version.to_string(), archive, api_key, config) .map_err(Error::hex)?; let response = http.send(request).await?; hexpm::api_publish_docs_response(response).map_err(Error::hex) } pub async fn get_package_release( name: &str, version: &Version, config: &hexpm::Config, http: &Http, ) -> Result> { let version = version.to_string(); tracing::info!( name = name, version = version.as_str(), "looking_up_package_release" ); let request = hexpm::api_get_package_release_request(name, &version, None, config); let response = http.send(request).await?; hexpm::api_get_package_release_response(response).map_err(Error::hex) } ================================================ FILE: compiler-core/src/inline.rs ================================================ //! This module implements the function inlining optimisation. This allows //! function calls to be inlined at the callsite, and replaced with the contents //! of the function which is being called. //! //! Function inlining is useful for two main reasons: //! - It removes the overhead of calling other functions and jumping around //! execution too much //! - It removes the barrier of the function call between the code around the //! call, and the code inside the called function. //! //! For example, the following Gleam code make heavy use of `use` sugar and higher //! order functions: //! //! ```gleam //! pub fn try_sum(list: List(Result(String, Nil)), sum: Int) -> Result(Int, Nil) { //! use <- bool.guard(when: sum >= 1000, return: Ok(sum)) //! case list { //! [] -> Ok(sum) //! [first, ..rest] -> { //! use number <- result.try(int.parse(first)) //! try_sum(rest, sum + number) //! } //! } //! } //! ``` //! //! This can make the code easier to read, but it normally would have a performance //! cost. There are two called functions, and two implicit anonymous functions. //! This function is also not tail recursive, as it uses higher order functions //! inside its body. //! //! However, with function inlining, the above code can be optimised to: //! //! ```gleam //! pub fn try_sum(list: List(Result(String, Nil)), sum: Int) -> Result(Int, Nil) { //! case sum >= 1000 { //! True -> Ok(sum) //! False -> case list { //! [] -> Ok(sum) //! [first, ..rest] -> { //! case int.parse(first) { //! Ok(number) -> try_sum(rest, sum + number) //! Error(error) -> Error(error) //! } //! } //! } //! } //! } //! ``` //! //! Which now has no extra function calls, and is tail recursive! //! //! The process of function inlining is quite simple really. It is implemented //! using an AST folder, which traverses each node of the AST, and potentially //! alters it as it goes. //! //! Every time we encounter a function call, we decide whether or not we can //! inline it. For now, the criteria for inlining is very simple, although a //! more complex heuristic-based approach will likely be implemented in the //! future. For now though, a function can be inlined if: //! It is a standard library function within the hardcoded list - which can be //! found in the `inline_function` function - or, it is an anonymous function. //! //! Inlining anonymous functions allows us to: //! - Remove calls to parameters of higher-order functions once those higher- //! order functions have been inlined. For example, the following example using //! `result.map`: //! ```gleam //! result.map(Ok(10), fn(x) { x + 1 }) //! ``` //! //! Without inlining of anonymous function would be turned into: //! ```gleam //! case Ok(10) { //! Ok(value) -> Ok(fn(x) { x + 1 }(value)) //! Error(error) -> Error(error) //! } //! ``` //! //! However if we inline anonymous functions also, we remove every call, and //! so it becomes: //! //! ```gleam //! case Ok(10) { //! Ok(value) -> Ok(value + 1) //! Error(error) -> Error(error) //! } //! ``` //! //! - Remove calls to anonymous functions in pipelines. Sometimes, an anonymous //! function is used in a pipeline, which can sometimes be the result of an //! expanded function capture. For example: //! //! ```gleam //! "10" |> int.parse |> result.unwrap(0) |> fn(x) { x * x } |> something_else //! ``` //! //! This can now be desugared to: //! ```gleam //! let _pipe1 = "10" //! let _pipe2 = int.parse(_pipe1) //! let _pipe3 = result.unwrap(_pipe2, 0) //! let _pipe4 = _pipe3 * _pipe3 //! something_else(_pipe4) //! ``` //! //! See documentation of individual functions to explain better how the process //! works. //! #![allow(dead_code)] use std::{ collections::{HashMap, HashSet}, sync::Arc, }; use ecow::{EcoString, eco_format}; use itertools::Itertools; use vec1::Vec1; use crate::{ STDLIB_PACKAGE_NAME, analyse::Inferred, ast::{ self, ArgNames, Assert, AssignName, Assignment, AssignmentKind, BitArrayOption, BitArraySegment, BitArraySize, CallArg, Clause, FunctionLiteralKind, Pattern, PipelineAssignmentKind, Publicity, SrcSpan, Statement, TailPattern, TypedArg, TypedAssert, TypedAssignment, TypedBitArraySize, TypedClause, TypedDefinitions, TypedExpr, TypedExprBitArraySegment, TypedFunction, TypedModule, TypedPattern, TypedPipelineAssignment, TypedStatement, TypedUse, visit::Visit, }, exhaustiveness::{Body, CompiledCase, Decision}, type_::{ self, Deprecation, ModuleInterface, ModuleValueConstructor, PRELUDE_MODULE_NAME, PatternConstructor, Type, TypedCallArg, ValueConstructor, ValueConstructorVariant, collapse_links, error::VariableOrigin, expression::{Implementations, Purity}, }, }; /// Perform function inlining across an entire module, applying it to each /// individual function. pub fn module( mut module: TypedModule, modules: &im::HashMap, ) -> TypedModule { let mut inliner = Inliner::new(modules); module.definitions = TypedDefinitions { functions: module .definitions .functions .into_iter() .map(|function| inliner.function(function)) .collect(), ..module.definitions }; module } struct Inliner<'a> { /// Importable modules, containing information about functions which can be /// inlined modules: &'a im::HashMap, /// Any variables which can be inlined. This is used when inlining the body /// of function calls. Let's look at an example inlinable function: /// ```gleam /// pub fn add(a, b) { /// a + b /// } /// ``` /// If it is called - `add(1, 2)` - it can be inlined to the following: /// ```gleam /// { /// let a = 1 /// let b = 2 /// a + b /// } /// ``` /// /// However, this can be inlined further. Since `a` and `b` are only used /// once each in the body, the whole expression can be reduced to `1 + 2`. /// /// In the above example, this variable would contain `{a: 1, b: 2}`, /// indicating the names of the variables to be inlined, as well as the /// values to replace them with. inline_variables: HashMap, /// The number we append to variable names in order to ensure uniqueness. variable_number: usize, /// Set of in-scope variables, used to determine when a conflict between /// variable names occurs during inlining. in_scope: HashSet, /// If two variables conflict in names during inlining, we need to rename /// one to avoid the conflict. Any variables renamed this way are stored /// here. renamed_variables: im::HashMap, /// The current position, whether we are inside the body of an inlined /// function or not. position: Position, } #[derive(Debug, Clone, Copy, PartialEq)] enum Position { RegularFunction, InlinedFunction, } impl Inliner<'_> { fn new(modules: &im::HashMap) -> Inliner<'_> { Inliner { modules, inline_variables: HashMap::new(), variable_number: 0, renamed_variables: im::HashMap::new(), in_scope: HashSet::new(), position: Position::RegularFunction, } } /// Defines a variable in the current scope, renaming it if necessary. /// Currently, this duplicates work performed in the code generators, where /// variables are renamed in a similar way. But since inlining can change /// scope boundaries, it needs to be performed here too. Ideally, we would /// move all the deduplicating logic from the code generators to here where /// we perform inlining, but that is a fairly large item of work. fn define_variable(&mut self, name: EcoString) -> EcoString { let unique_in_scope = self.in_scope.insert(name.clone()); // If the variable name is already defined, and we are inlining a function, // that means there is a potential conflict in names and we need to rename // the variable. if !unique_in_scope && self.position == Position::InlinedFunction { // Prefixing the variable name with `_inline_` ensures it does // not conflict with other defined variables. let new_name = eco_format!("_inline_{name}_{}", self.variable_number); self.variable_number += 1; _ = self.renamed_variables.insert(name, new_name.clone()); new_name } else { name } } /// Get the name we are using for a variable, in case it is renamed. fn variable_name(&self, name: EcoString) -> EcoString { self.renamed_variables.get(&name).cloned().unwrap_or(name) } fn function(&mut self, mut function: TypedFunction) -> TypedFunction { for argument in function.arguments.iter() { match &argument.names { ArgNames::Discard { .. } | ArgNames::LabelledDiscard { .. } => {} ArgNames::Named { name, .. } | ArgNames::NamedLabelled { name, .. } => { _ = self.in_scope.insert(name.clone()); } } } function.body = function .body .into_iter() .map(|statement| self.statement(statement)) .collect_vec(); function } fn statement(&mut self, statement: TypedStatement) -> TypedStatement { match statement { Statement::Expression(expression_ast) => { Statement::Expression(self.expression(expression_ast)) } Statement::Assignment(assignment_ast) => { Statement::Assignment(Box::new(self.assignment(*assignment_ast))) } Statement::Use(use_ast) => Statement::Use(self.use_(use_ast)), Statement::Assert(assert_ast) => Statement::Assert(self.assert(assert_ast)), } } fn assert(&mut self, assert: TypedAssert) -> TypedAssert { let Assert { location, value, message, } = assert; Assert { location, value: self.expression(value), message: message.map(|expression| self.expression(expression)), } } fn use_(&mut self, mut use_: TypedUse) -> TypedUse { use_.call = self.boxed_expression(use_.call); use_ } fn assignment(&mut self, assignment: TypedAssignment) -> TypedAssignment { let Assignment { location, value, pattern, kind, annotation, compiled_case, } = assignment; Assignment { location, value: self.expression(value), pattern: self.register_pattern_variables(pattern), kind: self.assignment_kind(kind), annotation, compiled_case, } } /// Register variables defined in a pattern so we correctly keep track of /// the scope, and rename any which conflict with existing variables. fn register_pattern_variables(&mut self, pattern: TypedPattern) -> TypedPattern { match pattern { Pattern::Int { .. } | Pattern::Float { .. } | Pattern::String { .. } | Pattern::Discard { .. } | Pattern::Invalid { .. } => pattern, Pattern::Variable { location, name, type_, origin, } => Pattern::Variable { location, name: self.define_variable(name), type_, origin, }, Pattern::BitArraySize(size) => Pattern::BitArraySize(self.bit_array_size(size)), Pattern::Assign { name, location, pattern, } => Pattern::Assign { name: self.define_variable(name), location, pattern: Box::new(self.register_pattern_variables(*pattern)), }, Pattern::List { location, elements, tail, type_, } => Pattern::List { location, elements: elements .into_iter() .map(|element| self.register_pattern_variables(element)) .collect(), tail: tail.map(|tail| { Box::new(TailPattern { location: tail.location, pattern: self.register_pattern_variables(tail.pattern), }) }), type_, }, Pattern::Constructor { location, name_location, name, arguments, module, constructor, spread, type_, } => Pattern::Constructor { location, name_location, name, arguments: arguments .into_iter() .map( |CallArg { label, location, value, implicit, }| CallArg { label, location, value: self.register_pattern_variables(value), implicit, }, ) .collect(), module, constructor, spread, type_, }, Pattern::Tuple { location, elements } => Pattern::Tuple { location, elements: elements .into_iter() .map(|element| self.register_pattern_variables(element)) .collect(), }, Pattern::BitArray { location, segments } => Pattern::BitArray { location, segments: segments .into_iter() .map(|segment| { self.bit_array_segment(segment, Self::register_pattern_variables) }) .collect(), }, Pattern::StringPrefix { location, left_location, left_side_assignment, right_location, left_side_string, right_side_assignment, } => Pattern::StringPrefix { location, left_location, left_side_assignment: left_side_assignment .map(|(name, location)| (self.define_variable(name), location)), right_location, left_side_string, right_side_assignment: match right_side_assignment { AssignName::Variable(name) => AssignName::Variable(self.define_variable(name)), AssignName::Discard(name) => AssignName::Discard(name), }, }, } } fn bit_array_size(&mut self, size: TypedBitArraySize) -> TypedBitArraySize { match size { BitArraySize::Int { .. } => size, BitArraySize::Variable { location, name, constructor, type_, } => BitArraySize::Variable { location, name: self.variable_name(name), constructor, type_, }, BitArraySize::BinaryOperator { location, operator, left, right, } => BitArraySize::BinaryOperator { location, operator, left: Box::new(self.bit_array_size(*left)), right: Box::new(self.bit_array_size(*right)), }, BitArraySize::Block { location, inner } => BitArraySize::Block { location, inner: Box::new(self.bit_array_size(*inner)), }, } } fn assignment_kind(&mut self, kind: AssignmentKind) -> AssignmentKind { match kind { AssignmentKind::Let | AssignmentKind::Generated => kind, AssignmentKind::Assert { location, assert_keyword_start, message, } => AssignmentKind::Assert { location, assert_keyword_start, message: message.map(|expression| self.expression(expression)), }, } } fn boxed_expression(&mut self, boxed: Box) -> Box { Box::new(self.expression(*boxed)) } fn expressions(&mut self, expressions: Vec) -> Vec { expressions .into_iter() .map(|expression| self.expression(expression)) .collect() } /// Perform inlining over an expression. This function is recursive, as /// expressions can be deeply nested. Most expressions just recursively /// call this function on each of their component parts, but some have /// special handling. fn expression(&mut self, mut expression: TypedExpr) -> TypedExpr { match expression { TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Fn { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Invalid { .. } => expression, TypedExpr::Var { ref constructor, ref mut name, .. } => match &constructor.variant { // If this variable can be inlined, replace it with its value. // See the `inline_variables` documentation for an explanation. ValueConstructorVariant::LocalVariable { .. } => { // We remove the variable as inlined variables can only be // inlined once. `inline_variables` only contains variables // which we have already checked are possible to inline, as // we check for variables which are only used once when converting // to an `InlinableFunction`. match self.inline_variables.remove(name) { Some(inlined_expression) => inlined_expression, None => match self.renamed_variables.get(name) { Some(new_name) => { *name = new_name.clone(); expression } None => expression, }, } } ValueConstructorVariant::ModuleConstant { .. } | ValueConstructorVariant::ModuleFn { .. } | ValueConstructorVariant::Record { .. } => expression, }, TypedExpr::Block { location, statements, } => TypedExpr::Block { location, statements: statements.mapped(|statement| self.statement(statement)), }, TypedExpr::NegateBool { location, value } => TypedExpr::NegateBool { location, value: self.boxed_expression(value), }, TypedExpr::NegateInt { location, value } => TypedExpr::NegateInt { location, value: self.boxed_expression(value), }, TypedExpr::Pipeline { location, first_value, assignments, finally, finally_kind, } => self.pipeline(location, first_value, assignments, finally, finally_kind), TypedExpr::List { location, type_, elements, tail, } => TypedExpr::List { location, type_, elements: self.expressions(elements), tail: tail.map(|boxed_expression| self.boxed_expression(boxed_expression)), }, TypedExpr::Call { location, type_, fun, arguments, } => self.call(location, type_, fun, arguments), TypedExpr::BinOp { location, type_, name, name_location, left, right, } => TypedExpr::BinOp { location, type_, name, name_location, left: self.boxed_expression(left), right: self.boxed_expression(right), }, TypedExpr::Case { location, type_, subjects, clauses, compiled_case, } => self.case(location, type_, subjects, clauses, compiled_case), TypedExpr::RecordAccess { location, field_start, type_, label, index, record, documentation, } => TypedExpr::RecordAccess { location, field_start, type_, label, index, record: self.boxed_expression(record), documentation, }, TypedExpr::PositionalAccess { location, type_, index, record, } => TypedExpr::PositionalAccess { location, type_, index, record: self.boxed_expression(record), }, TypedExpr::Tuple { location, type_, elements, } => TypedExpr::Tuple { location, type_, elements: self.expressions(elements), }, TypedExpr::TupleIndex { location, type_, index, tuple, } => TypedExpr::TupleIndex { location, type_, index, tuple: self.boxed_expression(tuple), }, TypedExpr::Todo { location, message, kind, type_, } => TypedExpr::Todo { location, message: message.map(|boxed_expression| self.boxed_expression(boxed_expression)), kind, type_, }, TypedExpr::Panic { location, message, type_, } => TypedExpr::Panic { location, message: message.map(|boxed_expression| self.boxed_expression(boxed_expression)), type_, }, TypedExpr::Echo { location, type_, expression, message, } => TypedExpr::Echo { location, expression: expression.map(|expression| self.boxed_expression(expression)), message: message.map(|message| self.boxed_expression(message)), type_, }, TypedExpr::BitArray { location, type_, segments, } => self.bit_array(location, type_, segments), TypedExpr::RecordUpdate { location, type_, record_assignment, constructor, arguments, } => TypedExpr::RecordUpdate { location, type_, record_assignment: record_assignment .map(|assignment| Box::new(self.assignment(*assignment))), constructor: self.boxed_expression(constructor), arguments: self.arguments(arguments), }, } } fn arguments(&mut self, arguments: Vec) -> Vec { arguments .into_iter() .map( |TypedCallArg { label, location, value, implicit, }| TypedCallArg { label, location, value: self.expression(value), implicit, }, ) .collect() } /// Where the magic happens. First, we check the left-hand side of the call /// to see if it's something we can inline. If not, we continue to walk the /// tree like all the other expressions do. If it can be inlined, we follow /// a three-step process: /// /// - Inlining: Here, we replace the reference to the function with an /// anonymous function with the same contents. If the left-hand side is /// already an anonymous function, we skip this step. /// /// - Beta reduction: The call to the anonymous function it transformed into /// a block with assignments for each argument at the beginning /// /// - Optimisation: We then recursively optimise the block. This allows us /// to, for example, inline anonymous functions passed to higher-order /// functions. /// /// Here is an example of inlining `result.map`: /// /// Initial code: /// ```gleam /// let x = Ok(10) /// result.map(x, fn(x) { /// let y = x + 4 /// int.to_string(y) /// }) /// ``` /// /// After inlining: /// ```gleam /// let x = Ok(10) /// fn(result, function) { /// case result { /// Ok(value) -> Ok(function(value)) /// Error(error) -> Error(error) /// } /// }(x, fn(x) { /// let y = x + 4 /// int.to_string(y) /// }) /// ``` /// /// After beta reduction: /// ```gleam /// let x = Ok(10) /// { /// let result = x /// let function = fn(x) { /// let y = x + 4 /// int.to_string(y) /// } /// case result { /// Ok(value) -> Ok(function(value)) /// Error(error) -> Error(error) /// } /// } /// ``` /// /// And finally, after the final optimising pass, where this inlining process /// is repeated: /// ```gleam /// let x = Ok(10) /// case x { /// Ok(value) -> Ok({ /// let y = x + 4 /// int.to_string(y) /// }) /// Error(error) -> Error(error) /// } /// ``` /// fn call( &mut self, location: SrcSpan, type_: Arc, function: Box, arguments: Vec, ) -> TypedExpr { let arguments = self.arguments(arguments); // First, we traverse the left-hand side of this call. If this is called // inside another inlined function, this could potentially inline an // argument, allowing further inlining. let function = self.expression(*function); // If the left-hand side is in a block for some reason, for example // `{ fn(x) { x + 1 } }(10)`, we still want to be able to inline it. let function = expand_block(function); let function = match function { TypedExpr::Var { ref constructor, ref name, .. } => match &constructor.variant { ValueConstructorVariant::ModuleFn { module, .. } => { // If the function is in the list of inlinable functions in // the module it belongs to, we can inline it! if let Some(function) = self .modules .get(module) .and_then(|module| module.inline_functions.get(name)) { // First, we do the actual inlining, by converting it to // an anonymous function. let (parameters, body) = function.to_anonymous_function(); // Then, we perform beta reduction, inlining the call to // the anonymous function. return self.inline_anonymous_function_call( ¶meters, arguments, body, &function.inlinable_parameters, ); } else { function } } // We cannot inline local variables or constants, as we do not // have enough information to inline them. Records are not actually // function calls, so they also cannot be inlined. ValueConstructorVariant::LocalVariable { .. } | ValueConstructorVariant::ModuleConstant { .. } | ValueConstructorVariant::Record { .. } => function, }, TypedExpr::ModuleSelect { ref constructor, label: ref name, ref module_name, .. } => match constructor { // We use the same logic here as for `TypedExpr::Var` above. ModuleValueConstructor::Fn { .. } => { if let Some(function) = self .modules .get(module_name) .and_then(|module| module.inline_functions.get(name)) { let (parameters, body) = function.to_anonymous_function(); return self.inline_anonymous_function_call( ¶meters, arguments, body, &function.inlinable_parameters, ); } else { function } } ModuleValueConstructor::Record { .. } | ModuleValueConstructor::Constant { .. } => { function } }, // Direct calls to anonymous functions can always be inlined TypedExpr::Fn { arguments: parameters, body, .. } => { let inlinable_parameters = find_inlinable_parameters(¶meters, &body); return self.inline_anonymous_function_call( ¶meters, arguments, body, &inlinable_parameters, ); } TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => function, }; TypedExpr::Call { location, type_, fun: Box::new(function), arguments, } } /// Turn a call to an anonymous function into a block with assignments. fn inline_anonymous_function_call( &mut self, parameters: &[TypedArg], arguments: Vec, body: Vec1, inlinable_parameters: &[EcoString], ) -> TypedExpr { // Arguments to this call that can be inlined, and do not need an assignment. let mut inline = HashMap::new(); // We start by collecting all the assignments for parameters which cannot // be inlined. let mut statements = parameters .iter() .zip(arguments) .filter_map(|(parameter, argument)| { let name = parameter.get_variable_name().cloned().unwrap_or("_".into()); // An argument can be inlined if it is only used once (stored in // the `InlineFunction` structure), and it is pure. Sometime impure // arguments can be inlined, but for simplicity we avoid inlining // all impure arguments for now. This heuristic can be improved // later. if inlinable_parameters.contains(&name) && argument.value.is_pure_value_constructor() { _ = inline.insert(name, argument.value); return None; } let type_ = argument.value.type_(); // Register the variable in scope, so that it is renamed if // necessary. let name = self.define_variable(name); // Otherwise, we make an assignment which assigns the value of // the argument to the correct parameter name. Some(Statement::Assignment(Box::new(Assignment { location: BLANK_LOCATION, value: argument.value, pattern: TypedPattern::Variable { location: BLANK_LOCATION, name: name.clone(), type_: type_.clone(), origin: VariableOrigin::generated(), }, kind: AssignmentKind::Generated, compiled_case: CompiledCase::simple_variable_assignment(name, type_), annotation: None, }))) }) .collect_vec(); // If we are performing inlining within an already inlined function, there // might be inlinable variables in the outer scope. However, these cannot be // inlined inside a nested function, so they are saved and restored afterwards. let inline_variables = std::mem::replace(&mut self.inline_variables, inline); let position = self.position; self.position = Position::InlinedFunction; let variables = self.renamed_variables.clone(); // Perform inlining on each of the statements in this function's body, // potentially inlining parameters and function calls inside this function. statements.extend(body.into_iter().map(|statement| self.statement(statement))); // Restore scope self.inline_variables = inline_variables; self.position = position; self.renamed_variables = variables; // We try to expand this block, so a function which is inlined as a // single expression does not get wrapped unnecessarily expand_block(TypedExpr::Block { location: BLANK_LOCATION, statements: statements .try_into() .expect("Type checking ensures there is at least one statement"), }) } fn pipeline( &mut self, location: SrcSpan, first_value: TypedPipelineAssignment, assignments: Vec<(TypedPipelineAssignment, PipelineAssignmentKind)>, finally: Box, finally_kind: PipelineAssignmentKind, ) -> TypedExpr { let first_value = self.pipeline_assignment(first_value); let assignments = assignments .into_iter() .map(|(assignment, kind)| (self.pipeline_assignment(assignment), kind)) .collect(); let finally = self.boxed_expression(finally); TypedExpr::Pipeline { location, first_value, assignments, finally, finally_kind, } } fn pipeline_assignment( &mut self, assignment: TypedPipelineAssignment, ) -> TypedPipelineAssignment { let TypedPipelineAssignment { location, name, value, } = assignment; TypedPipelineAssignment { location, name, value: self.boxed_expression(value), } } fn bit_array( &mut self, location: SrcSpan, type_: Arc, segments: Vec, ) -> TypedExpr { let segments = segments .into_iter() .map(|segment| self.bit_array_segment(segment, Self::expression)) .collect(); TypedExpr::BitArray { location, type_, segments, } } fn bit_array_segment( &mut self, segment: BitArraySegment>, function: fn(&mut Self, Value) -> Value, ) -> BitArraySegment> { let BitArraySegment { location, value, options, type_, } = segment; BitArraySegment { location, value: Box::new(function(self, *value)), options: options .into_iter() .map(|option| self.bit_array_option(option, function)) .collect(), type_, } } fn bit_array_option( &mut self, option: BitArrayOption, function: fn(&mut Self, Value) -> Value, ) -> BitArrayOption { match option { BitArrayOption::Bytes { .. } | BitArrayOption::Int { .. } | BitArrayOption::Float { .. } | BitArrayOption::Bits { .. } | BitArrayOption::Utf8 { .. } | BitArrayOption::Utf16 { .. } | BitArrayOption::Utf32 { .. } | BitArrayOption::Utf8Codepoint { .. } | BitArrayOption::Utf16Codepoint { .. } | BitArrayOption::Utf32Codepoint { .. } | BitArrayOption::Signed { .. } | BitArrayOption::Unsigned { .. } | BitArrayOption::Big { .. } | BitArrayOption::Little { .. } | BitArrayOption::Native { .. } | BitArrayOption::Unit { .. } => option, BitArrayOption::Size { location, value, short_form, } => BitArrayOption::Size { location, value: Box::new(function(self, *value)), short_form, }, } } fn case( &mut self, location: SrcSpan, type_: Arc, subjects: Vec, clauses: Vec, compiled_case: CompiledCase, ) -> TypedExpr { let subjects = self.expressions(subjects); let clauses = clauses .into_iter() .map(|clause| self.case_clause(clause)) .collect(); // Since JavaScript code generation uses the decision tree to generate // code for `case` expressions, we need to rename the variables bound // in the decision tree too. Because we have already renamed the variables // in the pattern, we can simply look up the rebound names. let compiled_case = CompiledCase { tree: self.decision(compiled_case.tree), subject_variables: compiled_case.subject_variables, }; TypedExpr::Case { location, type_, subjects, clauses, compiled_case, } } fn case_clause(&mut self, clause: TypedClause) -> TypedClause { let Clause { location, pattern, alternative_patterns, guard, then, } = clause; let pattern = pattern .into_iter() .map(|pattern| self.register_pattern_variables(pattern)) .collect(); let alternative_patterns = alternative_patterns .into_iter() .map(|patterns| { patterns .into_iter() .map(|pattern| self.register_pattern_variables(pattern)) .collect() }) .collect(); let then = self.expression(then); Clause { location, pattern, alternative_patterns, guard, then, } } fn decision(&self, decision: Decision) -> Decision { match decision { Decision::Run { body } => Decision::Run { body: self.case_body(body), }, Decision::Guard { guard, if_true, if_false, } => Decision::Guard { guard, if_true: self.case_body(if_true), if_false: Box::new(self.decision(*if_false)), }, Decision::Switch { var, choices, fallback, fallback_check, } => Decision::Switch { var, choices: choices .into_iter() .map(|(check, decision)| (check, self.decision(decision))) .collect(), fallback: Box::new(self.decision(*fallback)), fallback_check, }, Decision::Fail => Decision::Fail, } } fn case_body(&self, body: Body) -> Body { let Body { bindings, clause_index, } = body; let bindings = bindings .into_iter() // We do this after renaming the variables in the pattern, so we can // just lookup the new name, rather than renaming again. .map(|(name, value)| (self.variable_name(name), value)) .collect(); Body { bindings, clause_index, } } } fn find_inlinable_parameters(parameters: &[TypedArg], body: &[TypedStatement]) -> Vec { let mut parameter_map = HashMap::new(); for parameter in parameters { let (name, location) = match ¶meter.names { ArgNames::Discard { .. } | ArgNames::LabelledDiscard { .. } => continue, ArgNames::Named { name, location } | ArgNames::NamedLabelled { name, name_location: location, .. } => (name, location), }; _ = parameter_map.insert((name.clone(), *location), false); } let mut finder = FindInlinableParameters { parameters: parameter_map, position: FunctionPosition::Body, }; for statement in body { finder.visit_typed_statement(statement); } // Inlinable parameters are those that are used exactly once. Any parameters // used more than once will be removed from this map and so will not be // considered inlinable. finder .parameters .into_iter() .filter_map(|((name, _), used)| used.then_some(name)) .collect() } /// A struct for finding the inlinable parameters of an anonymous function. Since /// we want to inline all anonymous functions, not just a subset of them like we /// do with regular functions, this must be implemented slightly differently, but /// it means we can take advantage of the AST visitor, since we don't need to /// transform the anonymous function into an intermediate representation. struct FindInlinableParameters { parameters: HashMap<(EcoString, SrcSpan), bool>, position: FunctionPosition, } #[derive(Debug, Clone, Copy)] enum FunctionPosition { Body, NestedFunction, } impl FindInlinableParameters { fn register_reference(&mut self, name: &EcoString, location: SrcSpan) { let key = (name.clone(), location); match self.position { FunctionPosition::Body => {} // We don't inline any parameters which are referenced in nested // anonymous function, as our system for inlining parameters cannot // properly handle that case; it requires more complex rewriting of // the code in some cases. FunctionPosition::NestedFunction => { _ = self.parameters.remove(&key); return; } } match self.parameters.get_mut(&key) { Some(true) => _ = self.parameters.remove(&key), Some(used @ false) => { *used = true; } None => {} } } } impl<'ast> Visit<'ast> for FindInlinableParameters { fn visit_typed_expr_var( &mut self, _location: &'ast SrcSpan, constructor: &'ast ValueConstructor, name: &'ast EcoString, ) { if let ValueConstructorVariant::LocalVariable { location, .. } = constructor.variant { self.register_reference(name, location); } } fn visit_typed_clause_guard_var( &mut self, _location: &'ast SrcSpan, name: &'ast EcoString, _type_: &'ast Arc, location: &'ast SrcSpan, _origin: &'ast VariableOrigin, ) { self.register_reference(name, *location); } fn visit_typed_bit_array_size_variable( &mut self, _location: &'ast SrcSpan, name: &'ast EcoString, constructor: &'ast Option>, _type_: &'ast Arc, ) { let variant = match constructor { Some(constructor) => &constructor.variant, None => return, }; if let ValueConstructorVariant::LocalVariable { location, .. } = variant { self.register_reference(name, *location); } } fn visit_typed_expr_fn( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, kind: &'ast FunctionLiteralKind, args: &'ast [TypedArg], body: &'ast Vec1, return_annotation: &'ast Option, ) { let previous_position = self.position; self.position = FunctionPosition::NestedFunction; ast::visit::visit_typed_expr_fn(self, location, type_, kind, args, body, return_annotation); self.position = previous_position; } } /// Removes any blocks which are acting as brackets (they hold a single expression) fn expand_block(expression: TypedExpr) -> TypedExpr { match expression { TypedExpr::Block { location, statements, } if statements.len() == 1 => { let (first, _rest) = statements.split_off_first(); match first { // If this is several blocks inside each other, we want to // expand them all. Statement::Expression(inner) => expand_block(inner), Statement::Assignment(_) | Statement::Use(_) | Statement::Assert(_) => { TypedExpr::Block { location, statements: Vec1::new(first), } } } } TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => expression, } } /// Converts a function from the Gleam AST into a special "inlinable function", /// which is a simplified version, containing just enough information for us to /// perform inlining, while keeping the cache files to a minimum size. /// /// This function also determines whether a function is inlinable. Currently this /// just checks it against a list of stdlib functions we want to prioritise /// inlining, but later it will be changed to a more complicated heuristic. /// pub fn function_to_inlinable( package: &str, module: &str, function: &TypedFunction, ) -> Option { let (_, name) = function.name.as_ref()?; if !is_inlinable(package, module, name) { return None; } let parameters = function .arguments .iter() .map(|argument| match &argument.names { ArgNames::Discard { name, .. } | ArgNames::Named { name, .. } => InlinableParameter { label: None, name: name.clone(), }, ArgNames::LabelledDiscard { label, name, .. } | ArgNames::NamedLabelled { label, name, .. } => InlinableParameter { label: Some(label.clone()), name: name.clone(), }, }) .collect(); let mut converter = FunctionToInlinable::new(&function.arguments); let body = function .body .iter() .map(|statement| converter.statement(statement)) .collect::>()?; // Figure out which parameters can be inlined within the body of this function. // When we inline a function, we convert it to a block with assignments for // the parameters. Then, if those parameters contain no side effects and are // only referenced once within the body, they can be inlined into the place // they are referenced. // // This code checks for the parameters which are used exactly once, which // can then be inlined. We can't inline parameters which are never used, as // there is nowhere to inline them to, so it doesn't make sense. We still // need to evaluate them though, as there could be side effects caused by // the values passed to them. let inlinable_parameters = converter .parameter_references .into_iter() .filter_map(|((name, _), used)| used.then_some(name)) .collect(); Some(InlinableFunction { parameters, body, inlinable_parameters, }) } /// The heuristic to determine whether a function is inlinable. For now, this /// just checks against a list of standard library functions. fn is_inlinable(package: &str, module: &str, name: &str) -> bool { // For now we only offer inlining of standard library functions if package != STDLIB_PACKAGE_NAME { return false; } match (module, name) { // These are the functions which we currently inline ("gleam/bool", "guard") => true, ("gleam/bool", "lazy_guard") => true, ("gleam/result", "try") => true, ("gleam/result", "map") => true, ("gleam/result", "map_error") => true, // For testing purposes it's useful to have a function which will always // be inlined, which we can define however we want. We only inline this // when we are in test mode though, because we wouldn't want this detail // leaking out into real-world code and causing unexpected behaviour. ("testing", "always_inline") => cfg!(test), _ => false, } } /// Holds state for converting a `TypedFunction` into an `InlinableFunction`. struct FunctionToInlinable { /// A map of parameters to a boolean of whether they have been used. Since /// Gleam has variable shadowing, we must also store the definition location /// of each parameter to ensure that it is not a variable shadowing the parameter /// name. /// If a parameter is used more than once, it is removed from the map, so it /// is no longer tracked as an inlinable parameter. parameter_references: HashMap<(EcoString, SrcSpan), bool>, } impl FunctionToInlinable { fn new(arguments: &[TypedArg]) -> Self { let parameter_references = arguments .iter() .filter_map(|argument| { let (name, location) = match &argument.names { ArgNames::Discard { .. } | ArgNames::LabelledDiscard { .. } => return None, ArgNames::Named { name, location } => (name.clone(), *location), ArgNames::NamedLabelled { name, name_location, .. } => (name.clone(), *name_location), }; Some(((name, location), false)) }) .collect(); Self { parameter_references, } } fn statement(&mut self, statement: &TypedStatement) -> Option { match statement { Statement::Expression(expression) => self.expression(expression), Statement::Assignment(_) | Statement::Use(_) | Statement::Assert(_) => None, } } /// Converts an expression to an `InlinableExpression`. We only convert a /// small subset of the AST for now, enough to compile our desired inlinable /// stdlib functions. Anything else returns `None`, indicating a function /// cannot be inlined. fn expression(&mut self, expression: &TypedExpr) -> Option { match expression { TypedExpr::Case { subjects, clauses, compiled_case, type_, .. } => { let subjects = subjects .iter() .map(|expression| self.expression(expression)) .collect::>()?; let clauses = clauses .iter() .map(|clause| self.clause(clause)) .collect::>()?; Some(InlinableExpression::Case { subjects, clauses, compiled_case: Box::new(compiled_case.clone()), type_: self.type_(type_), }) } TypedExpr::Var { constructor, name, .. } => { match &constructor.variant { ValueConstructorVariant::LocalVariable { location, .. } => { let key = (name.clone(), *location); match self.parameter_references.get_mut(&key) { Some(true) => { _ = self.parameter_references.remove(&key); } Some(usage) => *usage = true, None => {} } } ValueConstructorVariant::ModuleConstant { .. } | ValueConstructorVariant::ModuleFn { .. } | ValueConstructorVariant::Record { .. } => {} } Some(InlinableExpression::Variable { name: name.clone(), constructor: self.value_constructor(constructor)?, type_: self.type_(&constructor.type_), }) } TypedExpr::Call { fun, arguments, type_, .. } => { let function = self.expression(fun)?; let arguments = arguments .iter() .map(|argument| { Some(InlinableArgument { label: argument.label.clone(), value: self.expression(&argument.value)?, }) }) .collect::>()?; Some(InlinableExpression::Call { function: Box::new(function), arguments, type_: self.type_(type_), }) } TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::BinOp { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => None, } } fn type_(&self, type_: &Arc) -> InlinableType { match collapse_links(type_.clone()).as_ref() { Type::Fn { arguments, return_ } => InlinableType::Function { arguments: arguments .iter() .map(|argument| self.type_(argument)) .collect(), return_: Box::new(self.type_(return_)), }, Type::Named { module, name, arguments, .. } if module == PRELUDE_MODULE_NAME => self.prelude_type(name, arguments), Type::Named { .. } | Type::Var { .. } | Type::Tuple { .. } => InlinableType::Other, } } fn prelude_type(&self, name: &str, arguments: &[Arc]) -> InlinableType { match (name, arguments) { ("BitArray", _) => InlinableType::BitArray, ("Bool", _) => InlinableType::Bool, ("Float", _) => InlinableType::Float, ("Int", _) => InlinableType::Int, ("List", [element]) => InlinableType::List(Box::new(self.type_(element))), ("Nil", _) => InlinableType::Nil, ("Result", [ok, error]) => InlinableType::Result { ok: Box::new(self.type_(ok)), error: Box::new(self.type_(error)), }, ("String", _) => InlinableType::String, ("UtfCodepoint", _) => InlinableType::UtfCodepoint, _ => InlinableType::Other, } } fn value_constructor( &mut self, constructor: &ValueConstructor, ) -> Option { match &constructor.variant { ValueConstructorVariant::LocalVariable { .. } => { Some(InlinableValueConstructor::LocalVariable) } ValueConstructorVariant::ModuleConstant { .. } => None, ValueConstructorVariant::ModuleFn { name, module, .. } => { Some(InlinableValueConstructor::Function { name: name.clone(), module: module.clone(), }) } ValueConstructorVariant::Record { name, module, .. } => { Some(InlinableValueConstructor::Record { name: name.clone(), module: module.clone(), }) } } } fn clause(&mut self, clause: &TypedClause) -> Option { let pattern = clause .pattern .iter() .map(Self::pattern) .collect::>()?; let body = self.expression(&clause.then)?; Some(InlinableClause { pattern, body }) } fn pattern(pattern: &TypedPattern) -> Option { match pattern { TypedPattern::Variable { name, .. } => { Some(InlinablePattern::Variable { name: name.clone() }) } TypedPattern::Constructor { name, arguments, constructor: Inferred::Known(inferred), .. } => { let arguments = arguments .iter() .map(|argument| { Some(InlinableArgument { label: argument.label.clone(), value: Self::pattern(&argument.value)?, }) }) .collect::>()?; Some(InlinablePattern::Constructor { name: name.clone(), module: inferred.module.clone(), arguments, }) } TypedPattern::Constructor { constructor: Inferred::Unknown, .. } => None, TypedPattern::Int { .. } | TypedPattern::Float { .. } | TypedPattern::String { .. } | TypedPattern::BitArraySize { .. } | TypedPattern::Assign { .. } | TypedPattern::Discard { .. } | TypedPattern::List { .. } | TypedPattern::Tuple { .. } | TypedPattern::BitArray { .. } | TypedPattern::StringPrefix { .. } | TypedPattern::Invalid { .. } => None, } } } /// A simplified version of a `TypedFunction`. #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct InlinableFunction { pub parameters: Vec, pub body: Vec, /// A list of parameters which are only referenced once and can therefore /// be inlined within the body of this function. pub inlinable_parameters: Vec, } /// Location information is not stored for inlinable functions, to reduce cache /// size. The only reason we should need location information is for generating /// code for panicking keywords, like `panic` or `todo`. /// /// Those are not supported yet, and when they are they will likely require some /// more thought as to how they are implemented, as inlining a function completely /// changes its location in the codebase. const BLANK_LOCATION: SrcSpan = SrcSpan { start: 0, end: 0 }; impl InlinableFunction { /// Converts an `InlinableFunction` to an anonymous function, which can then /// be inlined within another function. fn to_anonymous_function(&self) -> (Vec, Vec1) { let parameters = self .parameters .iter() .map(|parameter| parameter.to_typed_arg()) .collect(); let body = self .body .iter() .map(|ast| Statement::Expression(ast.to_expression())) .collect_vec(); ( parameters, body.try_into() .expect("Type-checking ensured that the body has at least 1 statement"), ) } } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum InlinableExpression { Case { subjects: Vec, clauses: Vec, compiled_case: Box, type_: InlinableType, }, Variable { name: EcoString, constructor: InlinableValueConstructor, type_: InlinableType, }, Call { function: Box, arguments: Vec>, type_: InlinableType, }, } impl InlinableExpression { fn to_expression(&self) -> TypedExpr { match self { InlinableExpression::Case { subjects, clauses, compiled_case, type_, } => TypedExpr::Case { location: BLANK_LOCATION, type_: type_.to_type(), subjects: subjects .iter() .map(|subject| subject.to_expression()) .collect(), clauses: clauses .iter() .map(|clause| clause.to_typed_clause()) .collect(), compiled_case: compiled_case.as_ref().clone(), }, InlinableExpression::Variable { name, constructor, type_, } => TypedExpr::Var { location: BLANK_LOCATION, constructor: constructor.to_value_constructor(type_.to_type()), name: name.clone(), }, InlinableExpression::Call { function, arguments, type_, } => TypedExpr::Call { location: BLANK_LOCATION, type_: type_.to_type(), fun: Box::new(function.to_expression()), arguments: arguments .iter() .map(|argument| argument.to_call_arg(Self::to_expression)) .collect(), }, } } } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct InlinableClause { pub pattern: Vec, pub body: InlinableExpression, } impl InlinableClause { fn to_typed_clause(&self) -> TypedClause { TypedClause { location: BLANK_LOCATION, pattern: self .pattern .iter() .map(|pattern| pattern.to_typed_pattern()) .collect(), alternative_patterns: Vec::new(), guard: None, then: self.body.to_expression(), } } } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum InlinablePattern { Constructor { name: EcoString, module: EcoString, arguments: Vec>, }, Variable { name: EcoString, }, } impl InlinablePattern { fn to_typed_pattern(&self) -> TypedPattern { match self { InlinablePattern::Constructor { name, module, arguments, } => TypedPattern::Constructor { location: BLANK_LOCATION, name_location: BLANK_LOCATION, name: name.clone(), arguments: arguments .iter() .map(|argument| argument.to_call_arg(Self::to_typed_pattern)) .collect(), module: None, constructor: Inferred::Known(PatternConstructor { name: name.clone(), field_map: None, documentation: None, module: module.clone(), location: BLANK_LOCATION, constructor_index: 0, }), spread: None, type_: unknown_type(), }, InlinablePattern::Variable { name } => TypedPattern::Variable { location: BLANK_LOCATION, name: name.clone(), type_: unknown_type(), origin: VariableOrigin::generated(), }, } } } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum InlinableValueConstructor { LocalVariable, Function { name: EcoString, module: EcoString }, Record { name: EcoString, module: EcoString }, } impl InlinableValueConstructor { fn to_value_constructor(&self, type_: Arc) -> ValueConstructor { let variant = match self { InlinableValueConstructor::LocalVariable => ValueConstructorVariant::LocalVariable { location: BLANK_LOCATION, origin: VariableOrigin::generated(), }, InlinableValueConstructor::Function { name, module } => { ValueConstructorVariant::ModuleFn { name: name.clone(), field_map: None, module: module.clone(), arity: 0, location: BLANK_LOCATION, documentation: None, implementations: Implementations::supporting_all(), external_erlang: None, external_javascript: None, purity: Purity::Unknown, } } InlinableValueConstructor::Record { name, module } => ValueConstructorVariant::Record { name: name.clone(), arity: 0, field_map: None, location: BLANK_LOCATION, module: module.clone(), variants_count: 0, variant_index: 0, documentation: None, }, }; ValueConstructor { publicity: Publicity::Private, deprecation: Deprecation::NotDeprecated, variant, type_, } } } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct InlinableArgument { pub label: Option, pub value: T, } impl InlinableArgument { fn to_call_arg(&self, convert_value: F) -> CallArg where F: FnOnce(&T) -> U, { CallArg { label: self.label.clone(), location: BLANK_LOCATION, value: convert_value(&self.value), implicit: None, } } } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct InlinableParameter { pub label: Option, pub name: EcoString, } impl InlinableParameter { fn to_typed_arg(&self) -> TypedArg { let is_discard = self.name.starts_with('_'); let names = match &self.label { Some(label) if is_discard => ArgNames::LabelledDiscard { label: label.clone(), label_location: BLANK_LOCATION, name: self.name.clone(), name_location: BLANK_LOCATION, }, Some(label) => ArgNames::NamedLabelled { label: label.clone(), label_location: BLANK_LOCATION, name: self.name.clone(), name_location: BLANK_LOCATION, }, None if is_discard => ArgNames::Discard { name: self.name.clone(), location: BLANK_LOCATION, }, None => ArgNames::Named { name: self.name.clone(), location: BLANK_LOCATION, }, }; TypedArg { names, location: BLANK_LOCATION, annotation: None, type_: unknown_type(), } } } /// A simplified version of `Type`, which only cares about prelude types. Code /// generation needs this type information, as some prelude types are handled /// specially in certain cases. Custom type don't matter though, so they all get /// reduced into a single value, which decreases cache size. #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum InlinableType { BitArray, Bool, Float, Int, List(Box), Nil, Result { ok: Box, error: Box, }, String, UtfCodepoint, Function { arguments: Vec, return_: Box, }, Other, } fn unknown_type() -> Arc { type_::generic_var(0) } impl InlinableType { fn to_type(&self) -> Arc { match self { InlinableType::BitArray => type_::bit_array(), InlinableType::Bool => type_::bool(), InlinableType::Float => type_::float(), InlinableType::Int => type_::int(), InlinableType::List(element) => type_::list(element.to_type()), InlinableType::Nil => type_::nil(), InlinableType::Result { ok, error } => type_::result(ok.to_type(), error.to_type()), InlinableType::String => type_::string(), InlinableType::UtfCodepoint => type_::utf_codepoint(), InlinableType::Function { arguments, return_ } => type_::fn_( arguments.iter().map(Self::to_type).collect(), return_.to_type(), ), // Code generation doesn't care about custom types at all, only // prelude types are handled specially, so we treat custom types as // opaque generic type variables. InlinableType::Other => unknown_type(), } } } ================================================ FILE: compiler-core/src/io/memory.rs ================================================ use super::*; use std::ops::Deref; use std::{ cell::RefCell, collections::{HashMap, HashSet}, rc::Rc, time::Duration, }; use camino::{Utf8Path, Utf8PathBuf}; /// An in memory sharable collection of pretend files that can be used in place /// of a real file system. It is a shared reference to a set of buffer than can /// be cheaply cloned, all resulting copies pointing to the same internal /// buffers. /// /// Useful in tests and in environments like the browser where there is no file /// system. /// /// Not thread safe. The compiler is single threaded, so that's OK. /// /// Only supports absolute paths. The root directory ("/") is always guaranteed /// to exist. /// #[derive(Clone, Debug, PartialEq, Eq)] pub struct InMemoryFileSystem { files: Rc>>, } impl Default for InMemoryFileSystem { fn default() -> Self { let mut files = HashMap::new(); // Ensure root directory always exists. let _ = files.insert(Utf8PathBuf::from("/"), InMemoryFile::directory()); Self { files: Rc::new(RefCell::new(files)), } } } impl InMemoryFileSystem { pub fn new() -> Self { Self::default() } pub fn reset(&self) { self.files.deref().borrow_mut().clear(); } /// Returns the contents of each file, excluding directories. /// /// # Panics /// /// Panics if this is not the only reference to the underlying files. /// pub fn into_contents(self) -> HashMap { Rc::try_unwrap(self.files) .expect("InMemoryFileSystem::into_files called on a clone") .into_inner() .into_iter() .filter_map(|(path, file)| file.into_content().map(|content| (path, content))) .collect() } /// All files currently in the filesystem (directories are not included). pub fn files(&self) -> Vec { self.files .borrow() .iter() .filter(|(_, f)| !f.is_directory()) .map(|(path, _)| path) .cloned() .collect() } #[cfg(test)] /// Set the modification time of a file. /// /// Panics if the file does not exist. /// pub fn set_modification_time(&self, path: &Utf8Path, time: SystemTime) { self.files .deref() .borrow_mut() .get_mut(path) .unwrap() .modification_time = time; } pub fn try_set_modification_time( &self, path: &Utf8Path, time: SystemTime, ) -> Result<(), Error> { self.files .deref() .borrow_mut() .get_mut(path) .ok_or_else(|| Error::FileIo { kind: FileKind::File, action: FileIoAction::Open, path: path.to_path_buf(), err: None, })? .modification_time = time; Ok(()) } } impl FileSystemWriter for InMemoryFileSystem { fn delete_directory(&self, path: &Utf8Path) -> Result<(), Error> { let mut files = self.files.deref().borrow_mut(); if files.get(path).is_some_and(|f| !f.is_directory()) { return Err(Error::FileIo { kind: FileKind::Directory, action: FileIoAction::Delete, path: path.to_path_buf(), err: None, }); } let root = Utf8Path::new("/"); if path != root { // Ensure the root path always exists. // Deleting other files is fine. let _ = files.remove(path); } // Remove any files in the directory while let Some(file) = files .keys() .find(|file| file.as_path() != root && file.starts_with(path)) { let file = file.clone(); let _ = files.remove(&file); } Ok(()) } fn copy(&self, from: &Utf8Path, to: &Utf8Path) -> Result<(), Error> { self.write_bytes(to, &self.read_bytes(from)?) } fn copy_dir(&self, _: &Utf8Path, _: &Utf8Path) -> Result<(), Error> { panic!("unimplemented") // TODO } fn mkdir(&self, path: &Utf8Path) -> Result<(), Error> { // Traverse ancestors from parent to root. // Create each missing ancestor. for ancestor in path.ancestors() { if ancestor == "" { // Ignore the final ancestor of a relative path. continue; } // Ensure we don't overwrite an existing file. // We can ignore existing directories though. let mut files = self.files.deref().borrow_mut(); if files.get(ancestor).is_some_and(|f| !f.is_directory()) { return Err(Error::FileIo { kind: FileKind::Directory, action: FileIoAction::Create, path: ancestor.to_path_buf(), err: None, }); } let dir = InMemoryFile::directory(); _ = files.insert(ancestor.to_path_buf(), dir); } Ok(()) } fn hardlink(&self, _: &Utf8Path, _: &Utf8Path) -> Result<(), Error> { panic!("unimplemented") // TODO } fn symlink_dir(&self, _: &Utf8Path, _: &Utf8Path) -> Result<(), Error> { panic!("unimplemented") // TODO } fn delete_file(&self, path: &Utf8Path) -> Result<(), Error> { let mut files = self.files.deref().borrow_mut(); if files.get(path).is_some_and(|f| f.is_directory()) { return Err(Error::FileIo { kind: FileKind::File, action: FileIoAction::Delete, path: path.to_path_buf(), err: None, }); } let _ = files.remove(path); Ok(()) } fn write(&self, path: &Utf8Path, content: &str) -> Result<(), Error> { self.write_bytes(path, content.as_bytes()) } fn write_bytes(&self, path: &Utf8Path, content: &[u8]) -> Result<(), Error> { // Ensure directories exist if let Some(parent) = path.parent() { self.mkdir(parent)?; } let mut file = InMemoryFile::default(); _ = io::Write::write(&mut file, content).expect("channel buffer write"); _ = self .files .deref() .borrow_mut() .insert(path.to_path_buf(), file); Ok(()) } fn exists(&self, path: &Utf8Path) -> bool { self.files.deref().borrow().contains_key(path) } } impl FileSystemReader for InMemoryFileSystem { fn canonicalise(&self, path: &Utf8Path) -> Result { Ok(path.to_path_buf()) } fn read(&self, path: &Utf8Path) -> Result { let path = path.to_path_buf(); let files = self.files.deref().borrow(); let buffer = files .get(&path) .and_then(|file| file.node.as_file_buffer()) .ok_or_else(|| Error::FileIo { kind: FileKind::File, action: FileIoAction::Open, path: path.clone(), err: None, })?; let bytes = buffer.borrow(); let unicode = String::from_utf8(bytes.clone()).map_err(|err| Error::FileIo { kind: FileKind::File, action: FileIoAction::Read, path: path.clone(), err: Some(err.to_string()), })?; Ok(unicode) } fn read_bytes(&self, path: &Utf8Path) -> Result, Error> { let path = path.to_path_buf(); let files = self.files.deref().borrow(); let buffer = files .get(&path) .and_then(|file| file.node.as_file_buffer()) .ok_or_else(|| Error::FileIo { kind: FileKind::File, action: FileIoAction::Open, path: path.clone(), err: None, })?; let bytes = buffer.borrow().clone(); Ok(bytes) } fn is_file(&self, path: &Utf8Path) -> bool { self.files .deref() .borrow() .get(path) .is_some_and(|file| !file.is_directory()) } fn is_directory(&self, path: &Utf8Path) -> bool { self.files .deref() .borrow() .get(path) .is_some_and(|file| file.is_directory()) } fn reader(&self, _path: &Utf8Path) -> Result { // TODO unreachable!("Memory reader unimplemented") } fn read_dir(&self, path: &Utf8Path) -> Result { let read_dir = ReadDir::from_iter( self.files .deref() .borrow() .keys() .map(|file_path| file_path.to_path_buf()) .filter(|file_path| file_path.parent().is_some_and(|parent| path == parent)) .map(DirEntry::from_pathbuf) .map(Ok), ); Ok(read_dir) } fn modification_time(&self, path: &Utf8Path) -> Result { let files = self.files.deref().borrow(); let file = files.get(path).ok_or_else(|| Error::FileIo { kind: FileKind::File, action: FileIoAction::ReadMetadata, path: path.to_path_buf(), err: None, })?; Ok(file.modification_time) } } /// The representation of a file or directory in the in-memory filesystem. /// /// Stores a file's buffer of contents. /// #[derive(Debug, Clone, PartialEq, Eq)] pub enum InMemoryFileNode { File(Rc>>), Directory, } impl InMemoryFileNode { /// Returns this file's file buffer if this isn't a directory. fn as_file_buffer(&self) -> Option<&Rc>>> { match self { Self::File(buffer) => Some(buffer), Self::Directory => None, } } /// Returns this file's file buffer if this isn't a directory. fn into_file_buffer(self) -> Option>>> { match self { Self::File(buffer) => Some(buffer), Self::Directory => None, } } } /// An in memory sharable that can be used in place of a real file. It is a /// shared reference to a buffer than can be cheaply cloned, all resulting copies /// pointing to the same internal buffer. /// /// Useful in tests and in environments like the browser where there is no file /// system. /// /// This struct holds common properties of different types of filesystem nodes /// (files and directories). The `node` field contains the file's content /// buffer, if this is not a directory. /// /// Not thread safe. The compiler is single threaded, so that's OK. /// #[derive(Debug, Clone, PartialEq, Eq)] pub struct InMemoryFile { node: InMemoryFileNode, modification_time: SystemTime, } impl InMemoryFile { /// Creates a directory. pub fn directory() -> Self { Self { node: InMemoryFileNode::Directory, ..Default::default() } } /// Checks whether this is a directory's entry. pub fn is_directory(&self) -> bool { matches!(self.node, InMemoryFileNode::Directory) } /// Returns this file's contents if this is not a directory. /// /// # Panics /// /// Panics if this is not the only reference to the underlying files. /// pub fn into_content(self) -> Option { let buffer = self.node.into_file_buffer()?; let contents = Rc::try_unwrap(buffer) .expect("InMemoryFile::into_content called with multiple references") .into_inner(); // All null bytes are usually from when a binary file is empty, and // aren't particularly useful as text, so we treat them as binary. if contents.iter().all(|byte| *byte == 0) { return Some(Content::Binary(contents)); } match String::from_utf8(contents) { Ok(s) => Some(Content::Text(s)), Err(e) => Some(Content::Binary(e.into_bytes())), } } } impl Default for InMemoryFile { fn default() -> Self { Self { node: InMemoryFileNode::File(Default::default()), // We use a fixed time here so that the tests are deterministic. In // future we may want to inject this in some fashion. modification_time: SystemTime::UNIX_EPOCH + Duration::from_secs(663112800), } } } impl io::Write for InMemoryFile { fn write(&mut self, buf: &[u8]) -> io::Result { let Some(buffer) = self.node.as_file_buffer() else { // Not a file return Err(io::Error::from(io::ErrorKind::NotFound)); }; let mut reference = (*buffer).borrow_mut(); reference.write(buf) } fn flush(&mut self) -> io::Result<()> { let Some(buffer) = self.node.as_file_buffer() else { // Not a file return Err(io::Error::from(io::ErrorKind::NotFound)); }; let mut reference = (*buffer).borrow_mut(); reference.flush() } } impl CommandExecutor for InMemoryFileSystem { fn exec(&self, _command: Command) -> Result { Ok(0) // Always succeed. } } impl BeamCompiler for InMemoryFileSystem { fn compile_beam( &self, _out: &Utf8Path, _lib: &Utf8Path, _modules: &HashSet, _stdio: Stdio, ) -> Result, Error> { Ok(Vec::new()) // Always succeed. } } #[test] fn test_empty_in_memory_fs_has_root() { let imfs = InMemoryFileSystem::new(); assert!(imfs.exists(Utf8Path::new("/"))); } #[test] fn test_cannot_remove_root_from_in_memory_fs() -> Result<(), Error> { let imfs = InMemoryFileSystem::new(); imfs.write(&Utf8PathBuf::from("/a/b/c.txt"), "a")?; imfs.delete_directory(Utf8Path::new("/"))?; assert!(!imfs.exists(Utf8Path::new("/a/b/c.txt"))); assert!(imfs.exists(Utf8Path::new("/"))); Ok(()) } #[test] fn test_files() -> Result<(), Error> { let imfs = InMemoryFileSystem::new(); imfs.write(&Utf8PathBuf::from("/a/b/c.txt"), "a")?; imfs.write(&Utf8PathBuf::from("/d/e.txt"), "a")?; let mut files = imfs.files(); // Sort for test determinism due to hash map usage. files.sort_unstable(); assert_eq!( vec![ Utf8PathBuf::from("/a/b/c.txt"), Utf8PathBuf::from("/d/e.txt"), ], files ); Ok(()) } #[test] fn test_in_memory_dir_walking() -> Result<(), Error> { use itertools::Itertools; let imfs = InMemoryFileSystem::new(); imfs.write(&Utf8PathBuf::from("/a/b/a.txt"), "a")?; imfs.write(&Utf8PathBuf::from("/a/b/b.txt"), "a")?; imfs.write(&Utf8PathBuf::from("/a/b/c.txt"), "a")?; imfs.write(&Utf8PathBuf::from("/b/d.txt"), "a")?; imfs.write(&Utf8PathBuf::from("/a/c/e.txt"), "a")?; imfs.write(&Utf8PathBuf::from("/a/c/d/f.txt"), "a")?; imfs.write(&Utf8PathBuf::from("/a/g.txt"), "a")?; imfs.write(&Utf8PathBuf::from("/h.txt"), "a")?; imfs.mkdir(&Utf8PathBuf::from("/a/e"))?; let mut walked_entries: Vec = DirWalker::new(Utf8PathBuf::from("/a/")) .into_file_iter(&imfs) .map_ok(Utf8PathBuf::into_string) .try_collect()?; // Keep test deterministic due to hash map usage walked_entries.sort_unstable(); assert_eq!( vec![ "/a/b/a.txt".to_owned(), "/a/b/b.txt".to_owned(), "/a/b/c.txt".to_owned(), "/a/c/d/f.txt".to_owned(), "/a/c/e.txt".to_owned(), "/a/g.txt".to_owned(), ], walked_entries, ); Ok(()) } ================================================ FILE: compiler-core/src/io.rs ================================================ pub mod memory; use crate::error::{Error, FileIoAction, FileKind, Result}; use async_trait::async_trait; use debug_ignore::DebugIgnore; use flate2::read::GzDecoder; use std::{ collections::{HashMap, HashSet, VecDeque}, fmt::Debug, io, iter::Extend, time::SystemTime, vec::IntoIter, }; use tar::{Archive, Entry}; use camino::{Utf8Path, Utf8PathBuf}; /// Takes in a source path and a target path and determines a relative path /// from source -> target. /// If given a relative target path, no calculation occurs. /// # Panics /// The provided source path should be absolute, otherwise will panic. pub fn make_relative(source_path: &Utf8Path, target_path: &Utf8Path) -> Utf8PathBuf { assert!(source_path.is_absolute()); // Input target will always be canonicalised whereas source will not // This causes problems with diffing on windows since canonicalised paths have a special root // As such we are attempting to strip the target path // Based on https://github.com/rust-lang/rust/issues/42869#issuecomment-1712317081 #[cfg(target_family = "windows")] let binding = target_path.to_string(); #[cfg(target_family = "windows")] let target_path = Utf8Path::new(binding.trim_start_matches(r"\\?\")); match target_path.is_absolute() { true => pathdiff::diff_utf8_paths(target_path, source_path) .expect("Should not fail on two absolute paths"), false => target_path.into(), } } pub trait Reader: io::Read { /// A wrapper around `std::io::Read` that has Gleam's error handling. fn read_bytes(&mut self, buffer: &mut [u8]) -> Result { self.read(buffer).map_err(|e| self.convert_err(e)) } fn convert_err(&self, error: E) -> Error; } pub trait Utf8Writer: std::fmt::Write { /// A wrapper around `fmt::Write` that has Gleam's error handling. fn str_write(&mut self, str: &str) -> Result<()> { self.write_str(str).map_err(|e| self.convert_err(e)) } fn convert_err(&self, err: E) -> Error; } impl Utf8Writer for String { fn convert_err(&self, error: E) -> Error { Error::FileIo { action: FileIoAction::WriteTo, kind: FileKind::File, path: Utf8PathBuf::from(""), err: Some(error.to_string()), } } } pub trait Writer: io::Write + Utf8Writer { /// A wrapper around `io::Write` that has Gleam's error handling. fn write(&mut self, bytes: &[u8]) -> Result<(), Error> { io::Write::write(self, bytes) .map(|_| ()) .map_err(|e| self.convert_err(e)) } } #[derive(Debug, PartialEq, Eq, Clone)] pub enum Content { Binary(Vec), Text(String), } impl Content { pub fn as_bytes(&self) -> &[u8] { match self { Content::Binary(data) => data, Content::Text(data) => data.as_bytes(), } } pub fn text(&self) -> Option<&str> { match self { Content::Binary(_) => None, Content::Text(s) => Some(s), } } } impl From> for Content { fn from(bytes: Vec) -> Self { Content::Binary(bytes) } } impl From<&[u8]> for Content { fn from(bytes: &[u8]) -> Self { Content::Binary(bytes.to_vec()) } } impl From for Content { fn from(text: String) -> Self { Content::Text(text) } } impl From<&str> for Content { fn from(text: &str) -> Self { Content::Text(text.to_string()) } } #[derive(Debug, PartialEq, Eq, Clone)] pub struct OutputFile { pub content: Content, pub path: Utf8PathBuf, } #[derive(Debug)] pub struct ReadDir { entries: Vec>, } impl FromIterator> for ReadDir { fn from_iter>>(iter: I) -> Self { ReadDir { entries: iter.into_iter().collect(), } } } impl ReadDir { pub fn extend(mut self, other: ReadDir) -> Self { self.entries.extend(other); ReadDir { entries: self.entries, } } } impl IntoIterator for ReadDir { type Item = io::Result; type IntoIter = IntoIter; fn into_iter(self) -> Self::IntoIter { self.entries.into_iter() } } #[derive(Debug, Clone)] pub struct DirEntry { pub pathbuf: Utf8PathBuf, } impl DirEntry { pub fn from_path>(path: P) -> DirEntry { DirEntry { pathbuf: path.as_ref().to_path_buf(), } } pub fn from_pathbuf(pathbuf: Utf8PathBuf) -> DirEntry { DirEntry { pathbuf } } pub fn as_path(&self) -> &Utf8Path { self.pathbuf.as_path() } pub fn into_path(self) -> Utf8PathBuf { self.pathbuf } } /// Structure holding state to walk across a directory's descendant files at /// any level. Note that each descendant directory is only visited once /// regardless of symlinks, avoiding infinite symlink loops. #[derive(Debug, Clone)] pub struct DirWalker { walk_queue: VecDeque, dirs_walked: im::HashSet, } impl DirWalker { /// Create a directory walker starting at the given path. pub fn new(dir: Utf8PathBuf) -> Self { Self { walk_queue: VecDeque::from([dir]), dirs_walked: im::HashSet::new(), } } /// Convert this walker to an iterator over file paths. /// /// This iterator calls [`Self::next_file`]. Errors are returned if certain /// directories cannot be read. pub fn into_file_iter( mut self, io: &impl FileSystemReader, ) -> impl Iterator> + '_ { std::iter::from_fn(move || self.next_file(io).transpose()) } /// Advance the directory walker to the next file. The returned path will /// be relative to the starting directory's path, even with symlinks /// (it is not canonicalised). pub fn next_file(&mut self, io: &impl FileSystemReader) -> Result> { while let Some(next_path) = self.walk_queue.pop_front() { let real_path = io.canonicalise(&next_path)?; if io.is_file(&real_path) { // Return the path relative to the starting directory, not the // canonicalised path (which we only use to check for already // visited directories). return Ok(Some(next_path)); } // If it's not a directory then it contains no other files, so there's nothing to do. if !io.is_directory(&real_path) { continue; } // If we have already processed this directory then we don't need to do it again. // This could be due to symlinks. let already_seen = self.dirs_walked.insert(real_path.clone()).is_some(); if already_seen { continue; } for entry in io.read_dir(&next_path)? { let Ok(entry) = entry else { return Err(Error::FileIo { kind: FileKind::Directory, action: FileIoAction::Read, path: next_path, err: None, }); }; self.walk_queue.push_back(entry.into_path()) } } Ok(None) } } /// A trait used to read files. /// Typically we use an implementation that reads from the file system, /// but in tests and in other places other implementations may be used. pub trait FileSystemReader { fn read_dir(&self, path: &Utf8Path) -> Result; fn read(&self, path: &Utf8Path) -> Result; fn read_bytes(&self, path: &Utf8Path) -> Result, Error>; fn reader(&self, path: &Utf8Path) -> Result; fn is_file(&self, path: &Utf8Path) -> bool; fn is_directory(&self, path: &Utf8Path) -> bool; fn modification_time(&self, path: &Utf8Path) -> Result; fn canonicalise(&self, path: &Utf8Path) -> Result; } /// Iterates over files with the given extension in a certain directory. /// Symlinks are followed. pub fn files_with_extension<'a>( io: &'a impl FileSystemReader, dir: &'a Utf8Path, extension: &'a str, ) -> impl Iterator + 'a { DirWalker::new(dir.to_path_buf()) .into_file_iter(io) .filter_map(Result::ok) .filter(|path| path.extension() == Some(extension)) } /// A trait used to run other programs. pub trait CommandExecutor { fn exec(&self, command: Command) -> Result; } /// A command one can run with a `CommandExecutor` #[derive(Debug, Eq, PartialEq)] pub struct Command { pub program: String, pub args: Vec, pub env: Vec<(String, String)>, pub cwd: Option, pub stdio: Stdio, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Stdio { Inherit, Null, } impl Stdio { pub fn get_process_stdio(&self) -> std::process::Stdio { match self { Stdio::Inherit => std::process::Stdio::inherit(), Stdio::Null => std::process::Stdio::null(), } } } /// A trait used to compile Erlang and Elixir modules to BEAM bytecode. pub trait BeamCompiler { fn compile_beam( &self, out: &Utf8Path, lib: &Utf8Path, modules: &HashSet, stdio: Stdio, ) -> Result, Error>; } /// A trait used to write files. /// Typically we use an implementation that writes to the file system, /// but in tests and in other places other implementations may be used. pub trait FileSystemWriter { fn mkdir(&self, path: &Utf8Path) -> Result<(), Error>; fn write(&self, path: &Utf8Path, content: &str) -> Result<(), Error>; fn write_bytes(&self, path: &Utf8Path, content: &[u8]) -> Result<(), Error>; fn delete_directory(&self, path: &Utf8Path) -> Result<(), Error>; fn copy(&self, from: &Utf8Path, to: &Utf8Path) -> Result<(), Error>; fn copy_dir(&self, from: &Utf8Path, to: &Utf8Path) -> Result<(), Error>; fn hardlink(&self, from: &Utf8Path, to: &Utf8Path) -> Result<(), Error>; fn symlink_dir(&self, from: &Utf8Path, to: &Utf8Path) -> Result<(), Error>; fn delete_file(&self, path: &Utf8Path) -> Result<(), Error>; fn exists(&self, path: &Utf8Path) -> bool; } #[derive(Debug)] /// A wrapper around a Read implementing object that has Gleam's error handling. pub struct WrappedReader { path: Utf8PathBuf, inner: DebugIgnore>, } impl WrappedReader { pub fn new(path: &Utf8Path, inner: Box) -> Self { Self { path: path.to_path_buf(), inner: DebugIgnore(inner), } } fn read(&mut self, buffer: &mut [u8]) -> io::Result { self.inner.read(buffer) } } impl io::Read for WrappedReader { fn read(&mut self, buffer: &mut [u8]) -> io::Result { self.read(buffer) } } impl Reader for WrappedReader { fn convert_err(&self, err: E) -> Error { Error::FileIo { kind: FileKind::File, action: FileIoAction::Read, path: self.path.clone(), err: Some(err.to_string()), } } } #[async_trait] pub trait HttpClient { async fn send(&self, request: http::Request>) -> Result>, Error>; } pub trait TarUnpacker { // FIXME: The reader types are restrictive here. We should be more generic // than this. fn io_result_entries<'a>( &self, archive: &'a mut Archive, ) -> io::Result>; fn entries<'a>( &self, archive: &'a mut Archive, ) -> Result> { tracing::debug!("iterating through tar archive"); self.io_result_entries(archive) .map_err(|e| Error::ExpandTar { error: e.to_string(), }) } fn io_result_unpack( &self, path: &Utf8Path, archive: Archive>>, ) -> io::Result<()>; fn unpack( &self, path: &Utf8Path, archive: Archive>>, ) -> Result<()> { tracing::debug!(path = ?path, "unpacking tar archive"); self.io_result_unpack(path, archive) .map_err(|e| Error::FileIo { action: FileIoAction::WriteTo, kind: FileKind::Directory, path: path.to_path_buf(), err: Some(e.to_string()), }) } } #[inline] pub fn is_native_file_extension(extension: &str) -> bool { matches!( extension, "erl" | "hrl" | "ex" | "js" | "mjs" | "cjs" | "ts" ) } pub fn ordered_map(value: &HashMap, serializer: S) -> Result where S: serde::Serializer, K: serde::Serialize + Ord, V: serde::Serialize, { use serde::Serialize; let ordered: std::collections::BTreeMap<_, _> = value.iter().collect(); ordered.serialize(serializer) } ================================================ FILE: compiler-core/src/javascript/decision.rs ================================================ use super::{ INDENT, bit_array_segment_int_value_to_bytes, expression::{self, Generator, Ordering, float, float_from_value}, }; use crate::{ ast::{AssignmentKind, Endianness, SrcSpan, TypedClause, TypedExpr, TypedPattern}, docvec, exhaustiveness::{ BitArrayMatchedValue, BitArrayTest, Body, BoundValue, CompiledCase, Decision, FallbackCheck, MatchTest, Offset, ReadAction, ReadSize, ReadType, RuntimeCheck, SizeOperator, SizeTest, Variable, VariableUsage, }, format::break_block, javascript::{ expression::{eco_string_int, string}, maybe_escape_property, }, pretty::{Document, Documentable, break_, concat, join, line, nil}, strings::{convert_string_escape_chars, length_utf16}, }; use ecow::{EcoString, eco_format}; use itertools::Itertools; use num_bigint::BigInt; use std::{collections::HashMap, sync::OnceLock}; pub static ASSIGNMENT_VAR: &str = "$"; pub fn case<'a>( compiled_case: &'a CompiledCase, clauses: &'a [TypedClause], subjects: &'a [TypedExpr], expression_generator: &mut Generator<'_, 'a>, ) -> Document<'a> { let scope_position = expression_generator.scope_position.clone(); let mut variables = Variables::new(expression_generator, VariableAssignment::Declare); let assignments = variables.assign_case_subjects(compiled_case, subjects); let mut printer = CasePrinter { variables, assignments: &assignments, kind: DecisionKind::Case { clauses }, }; let decision = match &compiled_case.tree { // Printing needs extra care if we're dealing with a sort of "degenerate" // tree that immediately starts with a guard node. // Code generation for guard nodes require defining variables outside of // the safe scope of the generated `if` statement. So if we were to just // generate code like usual we run the risk of leaking variables in the // outer scope: // // ```case // case 11 { // n if n == 10 -> todo // _ -> todo // } // // let n = 12 // ``` // // That case would have us generate something like this: // // ```js // let n = 11 // if (n === 10) { todo } else { todo } // // // If we don't wrap it in a block that `n = 11` definition that was // // introduced would end up clashing with the `let n = 12` that comes // // later! // ``` // // So in this special case we have to wrap everything in a block. tree @ Decision::Guard { .. } if !scope_position.is_tail() => break_block( printer .inside_new_scope(|this| this.decision(tree)) .into_doc(), ), tree @ (Decision::Run { .. } | Decision::Guard { .. } | Decision::Switch { .. } | Decision::Fail) => printer.decision(tree).into_doc(), }; docvec![assignments_to_doc(assignments), decision].force_break() } /// The generated code for a decision tree. enum CaseBody<'a> { /// A JavaScript `if`` statement by itself. This can be merged with any /// preceding `else` statements to form an `else if` construct. If { check: Document<'a>, body: Document<'a>, }, /// A sequence of statements. This must be wrapped as the body of an `if` or /// `else` statement. Statements(Document<'a>), /// A JavaScript `if` statement followed by a single `else` clause. In some /// cases this can be flattened to reduce the size of the generated decision /// tree. IfElse { check: Document<'a>, if_body: Document<'a>, else_body: Document<'a>, /// The decision in the tree that is used to generate the code for the /// `else` clause of this statement. If this is the same as another `if`- /// `else` statement, the two can be merged into one. fallback_decision: &'a Decision, }, /// A JavaScript `if` statement followed by more than one `else` clause. This /// can sometimes be merged with preceding `else` statements in the same way /// that `if` can. IfElseChain(Document<'a>), } impl<'a> CaseBody<'a> { fn into_doc(self) -> Document<'a> { match self { CaseBody::If { check, body } => docvec![ "if (", break_("", "") .append(check) .nest(INDENT) .append(break_("", "")) .group(), ") ", break_block(body) ], // If we have some code like the following: // ```javascript // if (some_condition) { // // } else { // fallback() // } // ``` // // Here, the body of the `if` statement is empty. This can happen // sometimes when generating decision trees for `let assert`. // // Instead, we can write this more concisely: // ```javascript // if (!some_condition) { // fallback() // } // ``` CaseBody::IfElse { check, if_body, else_body, .. } if if_body.is_empty() => docvec![ "if (!(", break_("", "") .append(check) .nest(INDENT) .append(break_("", "")) .group(), ")) ", else_body, ], CaseBody::IfElse { check, if_body, else_body, .. } => docvec![ "if (", break_("", "") .append(check) .nest(INDENT) .append(break_("", "")) .group(), ") ", break_block(if_body), " else ", else_body, ], CaseBody::Statements(document) | CaseBody::IfElseChain(document) => document, } } /// Convert this value into the required document to put directly after an /// `else` keyword. fn document_after_else(self) -> Document<'a> { match self { // `if` and `if-else` statements can come directly after an `else` keyword CaseBody::If { .. } | CaseBody::IfElse { .. } => self.into_doc(), CaseBody::IfElseChain(document) => document, // Lists of statements must be wrapped in a block CaseBody::Statements(document) => break_block(document), } } fn is_empty(&self) -> bool { match self { CaseBody::If { .. } | CaseBody::IfElse { .. } => false, CaseBody::Statements(document) | CaseBody::IfElseChain(document) => document.is_empty(), } } } struct CasePrinter<'module, 'generator, 'a, 'assignments> { variables: Variables<'generator, 'module, 'a>, assignments: &'assignments Vec>, kind: DecisionKind<'a>, } /// Information specific to the different kinds of decision trees: `case` /// expressions and `let assert` statements. enum DecisionKind<'a> { Case { clauses: &'a [TypedClause], }, LetAssert { kind: &'a AssignmentKind, subject_location: SrcSpan, pattern_location: SrcSpan, subject: EcoString, }, } enum BodyExpression<'a> { /// This happens when a case expression branch returns the same value that /// is being matched on. So instead of rebuilding it from scratch we can /// return the case subject directly. For example: /// `Ok(1) -> Ok(1)` /// `a -> a` /// `[1, ..rest] -> [1, ..rest]` /// Variable(Document<'a>), /// This happens when a case expression has a complex body that is not just /// returning the matched subject. For example: /// `Ok(1) -> Ok(2)` /// `_ -> [1, 2, 3]` /// `1 -> "wibble"` /// Expressions(Document<'a>), } /// Code generation for decision trees can look a bit daunting at a first glance /// so let's go over the big idea to hopefully make it easier to understand why /// the code is organised the way it is :) /// /// > It might be helpful to go over the `exhaustiveness` module first and get /// > familiar with the structure of the decision tree! /// /// A decision tree has nodes that perform checks on pattern variables until /// it reaches a body with an expression to run. This will be turned into a /// series of if-else checks. /// /// While on the surface it might sound pretty straightforward, the code generation /// needs to take care of a couple of tricky aspects: when a check succeeds it's /// not just allowing us to move to the next check, but it also introduces new /// variables in the scope that we can reference. Let's look at an example: /// /// ```gleam /// case value { /// [1, ..rest] -> rest /// _ -> [] /// } /// ``` /// /// Here we will first need to check that the list is not empty: /// /// ```js /// if (value instanceOf $NonEmptyList) { /// // ... /// } else { /// return []; /// } /// ``` /// /// Once that check succeeds we know that now we can access two new values: the /// first element of the list and the rest of the list! So we need to keep track /// of that in case further checks need to use those values; and in the example /// above they actually do! The second check we need to perform will be on the /// first item of the list, so we need to actually create a variable for it: /// /// ```js /// if (value instanceOf $NonEmptyList) { /// let $ = value.head; /// if ($ === 1) { /// // ... /// } else { /// // ... /// } /// } else { /// return []; /// } /// ``` /// /// So, as we're generating code for each check and move further down the decision /// tree, we will have to keep track of all the variables that we've discovered /// after each successful check. /// /// In order to do that we'll be using a `Variables` data structure to hold all /// this information about the current scope. /// impl<'a> CasePrinter<'_, '_, 'a, '_> { fn decision(&mut self, decision: &'a Decision) -> CaseBody<'a> { match decision { Decision::Fail => { if let DecisionKind::LetAssert { kind, subject_location, pattern_location, subject, } = &self.kind { CaseBody::Statements(self.assignment_no_match( subject.to_doc(), kind, *subject_location, *pattern_location, )) } else { unreachable!("Invalid decision tree reached code generation") } } Decision::Run { body } => { let bindings = self.variables.bindings_doc(&body.bindings); let body = self.body_expression(body.clause_index); let body = match body { BodyExpression::Variable(variable) => variable, BodyExpression::Expressions(body) => join_with_line(bindings, body), }; CaseBody::Statements(body) } Decision::Switch { var, choices, fallback, fallback_check, } => self.switch(var, choices, fallback, fallback_check), Decision::Guard { guard, if_true, if_false, } => self.decision_guard(*guard, if_true, if_false), } } fn body_expression(&mut self, clause_index: usize) -> BodyExpression<'a> { // If we are not in a `case` expression, there is no additional code to // execute when a branch matches; we only assign variables bound in the // pattern. let DecisionKind::Case { clauses } = &self.kind else { return BodyExpression::Expressions(nil()); }; let clause = &clauses.get(clause_index).expect("invalid clause index"); let body = &clause.then; if let Some(subject_index) = clause.returned_subject() { let variable = self .assignments .get(subject_index) .expect("case with no subjects") .name(); BodyExpression::Variable( self.variables .expression_generator .wrap_return(variable.to_doc()), ) } else { BodyExpression::Expressions( self.variables .expression_generator .expression_flattening_blocks(body), ) } } fn switch( &mut self, var: &'a Variable, choices: &'a [(RuntimeCheck, Decision)], fallback: &'a Decision, fallback_check: &'a FallbackCheck, ) -> CaseBody<'a> { // If there's just a single choice we can just generate the code for // it: no need to do any checking, we know it must match! if choices.is_empty() { // However, if the choice had an associated check (that is, it was // not just a simple catch all) we need to keep track of all the // variables brought into scope by the (always) successfull check. if let FallbackCheck::RuntimeCheck { check } = fallback_check { self.variables.record_check_assignments(var, check); } return self.inside_new_scope(|this| this.decision(fallback)); } // Otherwise we'll have to generate a series of if-else to check which // pattern is going to match! let mut assignments = vec![]; if !self.variables.is_bound_in_scope(var) { // If the variable we need to perform a check on is not already bound // in scope we will be binding it to a new made up name. This way we // can also reference this exact name in further checks instead of // recomputing the value each time. let name = self.variables.next_local_var(&ASSIGNMENT_VAR.into()); let value = self.variables.get_value(var); self.variables.bind(name.clone(), var); assignments.push(let_doc(name, value.to_doc())) }; let mut if_ = CaseBody::Statements(nil()); for (i, (check, decision)) in choices.iter().enumerate() { self.variables.record_check_assignments(var, check); // For each check we generate: // - the document to perform such check // - the body to run if the check is successful // - the assignments we need to bring all the bit array segments // referenced by this check let (check_doc, body, mut segment_assignments) = self.inside_new_scope(|this| { let segment_assignments = this.variables.bit_array_segment_assignments(check); let check_doc = this.variables.runtime_check(var, check); let body = this.decision(decision); (check_doc, body, segment_assignments) }); assignments.append(&mut segment_assignments); let (check_doc, body) = match body { // If we have a statement like this: // ```javascript // if (x) { // if (y) { // ... // } // } // ``` // // We can transform it into: // ```javascript // if (x && y) { // ... // } // ``` CaseBody::If { check, body } => { (docvec![check_doc, break_(" &&", " && "), check], body) } // The following code is a pretty common pattern in the code // generated by decision trees: // // ```javascript // if (something) { // if (something_else) { // do_thing() // } else { // do_fallback() // } // } else { // do_fallback() // } // ``` // // Here, the `do_fallback()` branch is repeated, which we want // to avoid if possible. In this case, we can transform the above // code into the following: // // ```javascript // if (something && something_else) { // do_thing() // } else { // do_fallback() // } // ``` // // This only works if both `else` branches run the same code, // otherwise we would be losing information. // It also only works if the inner statement has only a single // `else` clause, and not multiple `else if`s. CaseBody::IfElse { check, if_body, fallback_decision: decision, .. } if decision == fallback => { (docvec![check_doc, break_(" &&", " && "), check], if_body) } if_else @ CaseBody::IfElse { .. } => (check_doc, if_else.into_doc()), CaseBody::Statements(document) | CaseBody::IfElseChain(document) => { (check_doc, document) } }; if_ = match if_ { // The first statement will always be an `if` _ if i == 0 => CaseBody::If { check: check_doc, body, }, // If this is the second check, the `if` becomes `else if` CaseBody::If { .. } | CaseBody::IfElse { .. } => CaseBody::IfElseChain(docvec![ if_.into_doc(), " else if (", break_("", "") .append(check_doc) .nest(INDENT) .append(break_("", "")) .group(), ") ", break_block(body) ]), CaseBody::IfElseChain(document) | CaseBody::Statements(document) => { CaseBody::IfElseChain(docvec![ document, " else if (", break_("", "") .append(check_doc) .nest(INDENT) .append(break_("", "")) .group(), ") ", break_block(body) ]) } }; } // In case there's some new variables we can extract after the // successful final check we store those. But we don't need to perform // the check itself: the type system ensures that, if we ever get here, // the check is going to match no matter what! if let FallbackCheck::RuntimeCheck { check } = fallback_check { self.variables.record_check_assignments(var, check); } let else_body = self.inside_new_scope(|this| this.decision(fallback)); let document = if else_body.is_empty() { if_ } else if let CaseBody::If { check, body: if_body, } = if_ { CaseBody::IfElse { check, if_body, else_body: else_body.document_after_else(), fallback_decision: fallback, } } else { CaseBody::IfElseChain(docvec![ if_.into_doc(), " else ", else_body.document_after_else() ]) }; if assignments.is_empty() { document } else { CaseBody::Statements(join_with_line( join(assignments, line()), document.into_doc(), )) } } fn inside_new_scope(&mut self, run: F) -> A where F: Fn(&mut Self) -> A, { // Since we use reassignment for `let assert`, we can't reset the scope // as it loses data about the assigned variables. let old_scope = match &self.kind { DecisionKind::Case { .. } => self .variables .expression_generator .current_scope_vars .clone(), DecisionKind::LetAssert { .. } => Default::default(), }; let old_names = self.variables.scoped_variable_names.clone(); let old_segments = self.variables.segment_values.clone(); let old_segment_names = self.variables.scoped_segment_names.clone(); let output = run(self); match &self.kind { DecisionKind::Case { .. } => { self.variables.expression_generator.current_scope_vars = old_scope } DecisionKind::LetAssert { .. } => {} } self.variables.scoped_variable_names = old_names; self.variables.segment_values = old_segments; self.variables.scoped_segment_names = old_segment_names; output } fn decision_guard( &mut self, guard: usize, if_true: &'a Body, if_false: &'a Decision, ) -> CaseBody<'a> { let DecisionKind::Case { clauses } = &self.kind else { unreachable!("Guards cannot appear in let assert decision trees") }; let guard = clauses .get(guard) .expect("invalid clause index") .guard .as_ref() .expect("missing guard"); // Before generating the if-else condition we want to generate all the // assignments that will be needed by the guard condition so we can rest // assured they are in scope and the guard check can use those. let guard_variables = guard.referenced_variables(); let (check_bindings, if_true_bindings): (Vec<_>, Vec<_>) = if_true .bindings .iter() .partition(|(variable, _)| guard_variables.contains(variable)); let (check_bindings, check, if_true) = self.inside_new_scope(|this| { // check_bindings and if_true generation have to be in this scope so that pattern-bound // variables used in guards don't leak into other case branches (if_false). let check_bindings = this.variables.bindings_ref_doc(&check_bindings); let check = this.variables.expression_generator.guard(guard); // All the other bindings that are not needed by the guard check will // end up directly in the body of the if clause. let if_true_bindings = this.variables.bindings_ref_doc(&if_true_bindings); let if_true_body = this.body_expression(if_true.clause_index); let if_true = match if_true_body { BodyExpression::Variable(variable) => variable, BodyExpression::Expressions(if_true_body) => { join_with_line(if_true_bindings, if_true_body) } }; (check_bindings, check, if_true) }); let if_false_body = self.inside_new_scope(|this| this.decision(if_false)); // We can now piece everything together into a case body! let if_ = if if_false_body.is_empty() { CaseBody::If { check, body: if_true, } } else { CaseBody::IfElse { check, if_body: if_true, else_body: if_false_body.document_after_else(), fallback_decision: if_false, } }; if check_bindings.is_empty() { if_ } else { CaseBody::Statements(join_with_line(check_bindings, if_.into_doc())) } } fn assignment_no_match( &mut self, subject: Document<'a>, kind: &'a AssignmentKind, subject_location: SrcSpan, pattern_location: SrcSpan, ) -> Document<'a> { let AssignmentKind::Assert { location, message, .. } = kind else { unreachable!("inexhaustive let made it to code generation"); }; let generator = &mut self.variables.expression_generator; let message = match message { None => string("Pattern match failed, no pattern matched the value."), Some(message) => generator .not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(message)), }; generator.throw_error( "let_assert", &message, *location, [ ("value", subject), ("start", location.start.to_doc()), ("end", subject_location.end.to_doc()), ("pattern_start", pattern_location.start.to_doc()), ("pattern_end", pattern_location.end.to_doc()), ], ) } } pub fn let_<'a>( compiled_case: &'a CompiledCase, subject: &'a TypedExpr, kind: &'a AssignmentKind, expression_generator: &mut Generator<'_, 'a>, pattern: &'a TypedPattern, ) -> Document<'a> { let scope_position = expression_generator.scope_position.clone(); let mut variables = Variables::new(expression_generator, VariableAssignment::Reassign); let assignment = variables.assign_let_subject(compiled_case, subject); let assignment_name = assignment.name(); let assignments = vec![assignment]; let decision = CasePrinter { variables, assignments: &assignments, kind: DecisionKind::LetAssert { kind, subject_location: subject.location(), pattern_location: pattern.location(), subject: assignment_name.clone(), }, } .decision(&compiled_case.tree); // When we generate `let assert` statements, we want to produce code like // this: // ```javascript // let some_var; // let other_var; // if (condition_to_check_pattern) { // some_var = x; // other_var = y; // } // ``` // This generates the code for binding the initial variables before the // check so the scoping of them is correct. // // We must generate this after we generate the code for the decision tree // itself as we might be re-binding variables which are used in the checks // to determine whether the pattern matches or not. let beginning_assignments = pattern.bound_variables().into_iter().map(|bound_variable| { docvec![ "let ", expression_generator.local_var(&bound_variable.name()), ";", line() ] }); let doc = docvec![ assignments_to_doc(assignments), concat(beginning_assignments), decision.into_doc() ]; match scope_position { expression::Position::Expression(_) | expression::Position::Statement => doc, expression::Position::Tail => docvec![doc, line(), "return ", assignment_name, ";"], expression::Position::Assign(variable) => { docvec![doc, line(), variable, " = ", assignment_name, ";"] } } } enum VariableAssignment { Declare, Reassign, } /// This is a useful piece of state that is kept separate from the generator /// itself so we can reuse it both with `case`s and `let`s without rewriting /// everything from scratch. /// struct Variables<'generator, 'module, 'a> { expression_generator: &'generator mut Generator<'module, 'a>, /// Whether to bind variables using `let` as we do in `case` expressions, /// or to reassign them as we do in `let assert` statements. variable_assignment: VariableAssignment, /// All the pattern variables will be assigned a specific value: being bound /// to a constructor field, tuple element and so on. Pattern variables never /// end up in the generated code but we replace them with their actual value. /// We store those values as `EcoString`s in this map; the key is the pattern /// variable's unique id. /// variable_values: HashMap, /// The same happens for bit array segments. Unlike pattern variables, we /// identify those using their names and store their value as a `Document`. segment_values: HashMap>, /// When we discover new variables after a runtime check we don't immediately /// generate assignments for each of them, because that could lead to wasted /// work. Let's consider the following check: /// /// ```txt /// a is Wibble(3, c, 1) -> c /// a is _ -> 1 /// ``` /// /// If we generated variables for it as soon as we enter its corresponding /// branch we would find ourselves with this piece of code: /// /// ```js /// if (a instanceof Wibble) { /// let a$0 = wibble.0; /// let a$1 = wibble.1; /// let a$2 = wibble.2; /// /// // and now we go on checking these new variables /// } /// ``` /// /// However, by extracting all the fields immediately we might end up doing /// wasted work: as soon as we find out that `a$0 != 3` we don't even need /// to check the other fields, we know the pattern can't match! So we /// extracted two fields we're not even checking. /// /// To avoid this situation, we only bind a variable to a name right before /// we're checking it so we're sure we're never generating useless bindings. /// The previous example would become something like this: /// /// ```js /// if (a instanceof Wibble) { /// let a$0 = wibble.0; /// if (a$0 === 3) { /// let a$2 = wibble.2 /// // further checks /// } else { /// return 1; /// } /// } /// ``` /// /// In this map we store the name a variable is bound to in the current /// scope. For example here we know that `wibble.0` is bound to the name /// `a$0`. /// scoped_variable_names: HashMap, /// Once again, this is the same as `scoped_variable_names` with the /// difference that a segment is identified by its name. /// scoped_segment_names: HashMap, } impl<'generator, 'module, 'a> Variables<'generator, 'module, 'a> { fn new( expression_generator: &'generator mut Generator<'module, 'a>, variable_assignment: VariableAssignment, ) -> Self { Variables { expression_generator, variable_assignment, variable_values: HashMap::new(), scoped_variable_names: HashMap::new(), segment_values: HashMap::new(), scoped_segment_names: HashMap::new(), } } /// Give a unique name to each of the subjects of a case expression and keep /// track of each of those names in case it needs to be referenced later. /// fn assign_case_subjects( &mut self, compiled_case: &'a CompiledCase, subjects: &'a [TypedExpr], ) -> Vec> { let assignments = subjects .iter() .map(|subject| assign_subject(self.expression_generator, subject, Ordering::Strict)) .collect_vec(); for (variable, assignment) in compiled_case .subject_variables .iter() .zip(assignments.iter()) { // We need to record the fact that each subject corresponds to a // pattern variable. self.set_value(variable, assignment.name()); self.bind(assignment.name(), variable); } assignments } /// Give a unique name to the subject of a let expression (if it needs one /// and it's not already a variable) and keep track of that name in case it /// needs to be referenced later. /// fn assign_let_subject( &mut self, compiled_case: &'a CompiledCase, subject: &'a TypedExpr, ) -> SubjectAssignment<'a> { let variable = compiled_case .subject_variables .first() .expect("decision tree with no subjects"); let assignment = assign_subject(self.expression_generator, subject, Ordering::Loose); self.set_value(variable, assignment.name()); self.bind(assignment.name(), variable); assignment } fn local_var(&mut self, name: &EcoString) -> EcoString { self.expression_generator.local_var(name) } fn next_local_var(&mut self, name: &EcoString) -> EcoString { self.expression_generator.next_local_var(name) } /// Records that a given pattern `variable` has been assigned a runtime /// `value`. For example if we had something like this: /// /// ```txt /// a is Wibble(1, b) -> todo /// ``` /// /// After a successful `is Wibble` check, we know we'd end up with two /// additional checks that look like this: /// /// ```txt /// a0 is 1, a1 is b -> todo /// ``` /// /// But what's the runtime value of `a0` and `a1`? To get those we'd have to /// extract the two fields from `a`, so they would have a value that looks /// like this: `a[0]` and `a[1]`; these values are set with this `set_value` /// function as we discover them. /// fn set_value(&mut self, variable: &Variable, value: EcoString) { let _ = self.variable_values.insert(variable.id, value); } /// This is conceptually the same as set value, but it's for bit array /// segments instead of pattern variables. fn set_segment_value( &mut self, bit_array: &Variable, segment_name: EcoString, read_action: &ReadAction, ) { let value = self.read_action_to_doc(bit_array, read_action); let _ = self.segment_values.insert(segment_name, value); } /// During the code generation process we might end up having to generate /// code to materialises one of the pattern variables and gives it a name to /// be used to avoid repeating it every single time. /// /// For example if a pattern variable is referencing the fifth element in a /// list it's runtime value would look something like this: /// `list.tail.tail.tail.tail.head`; if we where to perform additional /// checks on this value, it would be quite wasteful to recompute it every /// single time. Imagine this piece of code: /// /// ```gleam /// case list { /// [_, _, _, _, 1] -> todo /// [_, _, _, _, 2] -> todo /// // ... /// _ -> todo /// } /// ``` /// /// The corresponding check would end up looking something like this: /// /// ```js /// if (list.tail.tail.tail.tail.head === 1) {} /// else if (list.tail.tail.tail.tail.head === 2) {} /// // ... /// else {} /// ``` /// /// So before a check we might want to bind a pattern variable to a name so /// we can use that to reference it in the check: /// /// ```js /// let $ = list.tail.tail.tail.tail.head; /// if ($ === 1) {} /// else if ($ === 2) {} /// // ... /// else {} /// ``` /// /// This makes for neater code! These bindings are kept track of with this /// function. /// fn bind(&mut self, name: EcoString, variable: &Variable) { let _ = self.scoped_variable_names.insert(variable.id, name); } /// This has the exact same purpose as `bind` but works with bit array /// segments instead of pattern variables introduced during the decision /// tree compilation. /// fn bind_segment(&mut self, bound_to_variable: EcoString, segment: EcoString) { let _ = self.scoped_segment_names.insert(segment, bound_to_variable); } fn bindings_doc(&mut self, bindings: &'a [(EcoString, BoundValue)]) -> Document<'a> { let bindings = (bindings.iter()).map(|(variable, value)| self.body_binding_doc(variable, value)); join(bindings, line()) } fn bindings_ref_doc(&mut self, bindings: &[&'a (EcoString, BoundValue)]) -> Document<'a> { let bindings = (bindings.iter()).map(|(variable, value)| self.body_binding_doc(variable, value)); join(bindings, line()) } fn body_binding_doc( &mut self, variable_name: &'a EcoString, value: &'a BoundValue, ) -> Document<'a> { let local_variable_name = self.next_local_var(variable_name); let assigned_value = match value { BoundValue::Variable(variable) => self.get_value(variable).to_doc(), BoundValue::LiteralString(value) => string(value), BoundValue::LiteralFloat(value) => float(value), BoundValue::LiteralInt(value) => eco_string_int(eco_format!("{value}")), BoundValue::BitArraySlice { bit_array, read_action, } => self .get_segment_value(variable_name) .unwrap_or_else(|| self.read_action_to_doc(bit_array, read_action)), }; match self.variable_assignment { VariableAssignment::Declare => let_doc(local_variable_name.clone(), assigned_value), VariableAssignment::Reassign => { reassignment_doc(local_variable_name.clone(), assigned_value) } } } /// Generates the document to perform a (possibly negated) runtime check on /// the given variable. /// fn runtime_check( &mut self, variable: &Variable, runtime_check: &'a RuntimeCheck, ) -> Document<'a> { let value = self.get_value(variable); let equality = " === "; match runtime_check { RuntimeCheck::String { value: expected } => docvec![value, equality, string(expected)], RuntimeCheck::Float { float_value: expected, } => docvec![value, equality, float_from_value(expected.value())], RuntimeCheck::Int { int_value: expected, } => docvec![value, equality, expected.clone()], RuntimeCheck::StringPrefix { prefix, .. } => { docvec![value, ".startsWith(", string(prefix), ")"] } RuntimeCheck::BitArray { test } => match test { // In this case we need to check that the remaining part of the // bit array has a whole number of bytes. BitArrayTest::CatchAllIsBytes { size_so_far } => { if size_so_far.is_zero() { docvec![value, ".bitSize % 8", equality, "0"] } else { let size_so_far = self.offset_to_doc(size_so_far, true); let remaining_bits = docvec![value, ".bitSize - ", size_so_far]; docvec!["(", remaining_bits, ") % 8", equality, "0"] } } BitArrayTest::ReadSizeIsNotNegative { size } => { docvec![self.read_size_to_doc(size), " >= 0"] } BitArrayTest::SegmentIsFiniteFloat { read_action: ReadAction { from: start, size, endianness, .. }, } => { let start_doc = self.offset_to_doc(start, false); let end = match (start.constant_bits(), size.constant_bits()) { (Some(start), _) if start == BigInt::ZERO => self .read_size_to_doc(size) .expect("unexpected catch all size"), (Some(start), Some(end)) => (start + end).to_doc(), (_, _) => docvec![start_doc.clone(), " + ", self.read_size_to_doc(size)], }; let check = self.bit_array_slice_to_float(value, start_doc, end, endianness); docvec!["Number.isFinite(", check, ")"] } // Here we need to make sure that the bit array has a specific // size. BitArrayTest::Size(SizeTest { operator, size }) => { let operator = match operator { SizeOperator::GreaterEqual => " >= ", SizeOperator::Equal => equality, }; let size = self.offset_to_doc(size, false); docvec![value, ".bitSize", operator, size] } // Finally, here we need to check that a given portion of the // bit array matches a given value. BitArrayTest::Match(MatchTest { value: expected, read_action, }) => match expected { BitArrayMatchedValue::LiteralString { value: _, encoding: _, bytes: expected, } => self.literal_string_segment_bytes_check(value, expected, read_action), BitArrayMatchedValue::LiteralFloat(expected) => { self.literal_float_segment_bytes_check(value, expected, read_action) } BitArrayMatchedValue::LiteralInt { value: expected, .. } => self.literal_int_segment_bytes_check(value, expected.clone(), read_action), BitArrayMatchedValue::Variable(..) | BitArrayMatchedValue::Discard(..) | BitArrayMatchedValue::Assign { .. } => { panic!("unreachable") } }, }, // When checking on a tuple there's always going to be a single choice // and the code generation will always skip generating the check for it // as the type system ensures it must match. RuntimeCheck::Tuple { .. } => unreachable!("tried generating runtime check for tuple"), // Some variants like `Bool` and `Result` are special cased and checked // in a different way from all other variants. RuntimeCheck::Variant { match_, .. } if variable.type_.is_bool() => { match match_.name().as_str() { "True" => value.to_doc(), _ => docvec!["!", value], } } RuntimeCheck::Variant { match_, index, .. } => { if variable.type_.is_result() && match_.module().is_none() { if *index == 0 { self.expression_generator.tracker.ok_used = true; } else { self.expression_generator.tracker.error_used = true; } } let qualification = match_ .module() .map(|module| eco_format!("${module}.")) .unwrap_or_default(); docvec![value, " instanceof ", qualification, match_.name()] } RuntimeCheck::NonEmptyList { .. } => { self.expression_generator.tracker.list_non_empty_class_used = true; docvec![value, " instanceof $NonEmpty"] } RuntimeCheck::EmptyList => { self.expression_generator.tracker.list_empty_class_used = true; docvec![value, " instanceof $Empty"] } } } /// Turns a read action into a document that can be used to extract the /// corresponding value from the given bit array and assign it to a /// variable. /// fn read_action_to_doc( &mut self, bit_array: &Variable, read_action: &ReadAction, ) -> Document<'a> { let ReadAction { from, size, type_, endianness, signed, } = read_action; let bit_array = self.get_value(bit_array); let from_bits = from.constant_bits(); // There's two special cases we need to take care of: match (size, &from_bits) { // If we're reading a single byte as un unsigned int from a byte aligned // offset then we can optimise this call as a `.byteAt` call! (ReadSize::ConstantBits(size), Some(from_bits)) if type_.is_int() && *size == BigInt::from(8) && !signed && from_bits.clone() % 8 == BigInt::ZERO => { let from_byte: BigInt = from_bits / 8; return docvec![bit_array, ".byteAt(", from_byte, ")"]; } // If we're reading all the remaining bits/bytes of an array we'll // take the remaining slice. (ReadSize::RemainingBits | ReadSize::RemainingBytes, _) => { return self.bit_array_slice(bit_array, from); } _ => (), } // Otherwise we'll take a regular slice out of the bit array, depending // on the type of the segment. let (start, end) = if let (ReadSize::ConstantBits(size), Some(from_bits)) = (size, from_bits) { // If both the start and and are known at compile time we can use // those directly in the slice call and perform no addition at // runtime. let start = from_bits.clone().to_doc(); let end = (from_bits + size).to_doc(); (start, end) } else { // Otherwise we'll have to sum the variable part and the constant // one to tell how long the slice should be. let size = self.read_size_to_doc(size).expect("no variable size"); let start = self.offset_to_doc(from, false); let end = if from.is_zero() { size } else { docvec![start.clone(), " + ", size] }; (start, end) }; match type_ { ReadType::Int => { self.bit_array_slice_to_int(bit_array, start, end, endianness, *signed) } ReadType::Float => self.bit_array_slice_to_float(bit_array, start, end, endianness), ReadType::BitArray => self.bit_array_slice_with_end(bit_array, from, end), ReadType::String | ReadType::UtfCodepoint => { panic!("invalid slice type made it to code generation: {type_:#?}") } } } fn offset_to_doc(&mut self, offset: &Offset, parenthesise: bool) -> Document<'a> { if offset.is_zero() { return "0".to_doc(); } let mut pieces = vec![]; if offset.constant != BigInt::ZERO { pieces.push(eco_string_int(offset.constant.to_string().into())); } for (variable, times) in offset .variables .iter() .sorted_by(|(one, _), (other, _)| one.name().cmp(other.name())) { let mut variable = match variable { VariableUsage::PatternSegment(segment_name, _) => self .get_segment_value(segment_name) .expect("segment referenced in a check before being created"), VariableUsage::OutsideVariable(name) => self.local_var(name).to_doc(), }; if *times != 1 { variable = variable.append(" * ").append(*times) } pieces.push(variable.to_doc()) } for calculation in offset.calculations.iter() { let left = self.offset_to_doc(&calculation.left, true); let right = self.offset_to_doc(&calculation.right, true); let calculation = self.expression_generator.bin_op_with_doc_operands( calculation.operator.to_bin_op(), left, right, &crate::type_::int(), ); if parenthesise { pieces.push(calculation.surround("(", ")")) } else { pieces.push(calculation) } } if pieces.len() > 1 && parenthesise { docvec!["(", join(pieces, " + ".to_doc()), ")"] } else { join(pieces, " + ".to_doc()) } } /// If the read size has a constant value (that is, it's not a "read all the /// remaining bits/bytes") this returns a document representing that size. /// fn read_size_to_doc(&mut self, size: &ReadSize) -> Option> { match size { ReadSize::ConstantBits(value) => Some(value.clone().to_doc()), ReadSize::VariableBits { variable, unit } => { let variable = self.local_var(variable.name()); Some(if *unit == 1 { variable.to_doc() } else { docvec![variable, " * ", *unit as i64] }) } ReadSize::RemainingBits | ReadSize::RemainingBytes => None, ReadSize::BinaryOperator { left, right, operator, } => { let left = if self.read_size_must_be_wrapped(left) { self.read_size_to_doc(left)?.surround("(", ")") } else { self.read_size_to_doc(left)? }; let right = if self.read_size_must_be_wrapped(right) { self.read_size_to_doc(right)?.surround("(", ")") } else { self.read_size_to_doc(right)? }; Some(self.expression_generator.bin_op_with_doc_operands( operator.to_bin_op(), left, right, &crate::type_::int(), )) } } } fn read_size_must_be_wrapped(&self, size: &ReadSize) -> bool { match size { ReadSize::ConstantBits(_) | ReadSize::RemainingBits | ReadSize::RemainingBytes => false, ReadSize::VariableBits { unit, .. } => *unit != 1, ReadSize::BinaryOperator { .. } => true, } } /// Generates the document that calls the `bitArraySliceToInt` function, with /// the given arguments. /// fn bit_array_slice_to_int( &mut self, bit_array: impl Documentable<'a>, start: impl Documentable<'a>, end: impl Documentable<'a>, endianness: &Endianness, signed: bool, ) -> Document<'a> { self.expression_generator .tracker .bit_array_slice_to_int_used = true; let endianness = match endianness { Endianness::Big => "true", Endianness::Little => "false", }; let signed = if signed { "true" } else { "false" }; let arguments = join( [ bit_array.to_doc(), start.to_doc(), end.to_doc(), endianness.to_doc(), signed.to_doc(), ], ", ".to_doc(), ); docvec!["bitArraySliceToInt(", arguments, ")"] } /// Generates the document that calls the `bitArraySliceToFloat` function, /// with the given arguments. /// fn bit_array_slice_to_float( &mut self, bit_array: impl Documentable<'a>, start: impl Documentable<'a>, end: impl Documentable<'a>, endianness: &Endianness, ) -> Document<'a> { self.expression_generator .tracker .bit_array_slice_to_float_used = true; let endianness = match endianness { Endianness::Big => "true", Endianness::Little => "false", }; let arguments = join( [ bit_array.to_doc(), start.to_doc(), end.to_doc(), endianness.to_doc(), ], ", ".to_doc(), ); docvec!["bitArraySliceToFloat(", arguments, ")"] } /// Generates the document that calls the `bitArraySlice` function, with /// an end argument as well. If you need to take a slice that starts at a /// given offset and read the entire array you can use `bit_array_slice`. /// fn bit_array_slice_with_end( &mut self, bit_array: impl Documentable<'a>, from: &Offset, end: impl Documentable<'a>, ) -> Document<'a> { self.expression_generator.tracker.bit_array_slice_used = true; let from = self.offset_to_doc(from, false); docvec!["bitArraySlice(", bit_array, ", ", from, ", ", end, ")"] } /// Generates the document that calls the `bitArraySlice` function, starting /// at a given offset. This will read the entire remaining bit of the array, /// if you know that the slice should end at a given offset you can use /// `bit_array_slice_with_end` instead. /// fn bit_array_slice(&mut self, bit_array: impl Documentable<'a>, from: &Offset) -> Document<'a> { self.expression_generator.tracker.bit_array_slice_used = true; let from = self.offset_to_doc(from, false); docvec!["bitArraySlice(", bit_array, ", ", from, ")"] } /// This generates all the checks that need to be performed to make sure a /// bit array segment (obtained with the read action passed as argument) /// matches with a literal string. /// fn literal_string_segment_bytes_check( &mut self, // A string representing the bit array value we read bits from. bit_array: EcoString, // The bytes of the literal string we should be matching on. string_bytes: &Vec, read_action: &ReadAction, ) -> Document<'a> { let ReadAction { from: start, endianness, signed, .. } = read_action; let mut checks = vec![]; let equality = " === "; let bytes = string_bytes.as_slice(); if let Some(mut from_byte) = start.constant_bytes() { // If the string starts at a compile-time known byte, then we can // optimise this by reading all the subsequent bytes and checking // they have a specific value. for byte in bytes { let byte_access = docvec![bit_array.clone(), ".byteAt(", from_byte.clone(), ")"]; checks.push(docvec![byte_access, equality, byte]); from_byte += 1; } } else { let mut start = start.clone(); // If the string doesn't start at a byte aligned offset then we'll // have to take slices out of it to check that each byte matches. for byte in bytes { let start_doc = self.offset_to_doc(&start, false); let end = start.add_constant(8); let end_doc = self.offset_to_doc(&end, false); let byte_access = self .bit_array_slice_to_int(&bit_array, start_doc, end_doc, endianness, *signed); checks.push(docvec![byte_access, equality, byte]); start = end; } } // Otherwise the check succeeds if all the byte checks succeed. join(checks, break_(" &&", " && ")).nest(INDENT).group() } /// This generates all the checks that need to be performed to make sure a /// bit array segment (obtained with the read action passed as argument) /// matches with a literal int. /// fn literal_int_segment_bytes_check( &mut self, // A string representing the bit array value we read bits from. bit_array: EcoString, literal_int: BigInt, read_action: &ReadAction, ) -> Document<'a> { let ReadAction { from: start, size, endianness, signed, .. } = read_action; let equality = " === "; if let (Some(mut from_byte), Some(size)) = (start.constant_bytes(), size.constant_bytes()) { // If the number starts at a byte-aligned offset and is made of a // whole number of bytes then we can optimise this by checking that // all the bytes starting at the given offset match the int bytes. let mut checks = vec![]; for byte in bit_array_segment_int_value_to_bytes(literal_int, size * 8, *endianness) { let byte_access = docvec![bit_array.clone(), ".byteAt(", from_byte.clone(), ")"]; checks.push(docvec![byte_access, equality, byte]); from_byte += 1; } join(checks, break_(" &&", " && ")).nest(INDENT).group() } else { // Otherwise we have to take an int slice out of the bit array and // check it matches the expected value. let start_doc = self.offset_to_doc(start, false); let end = match (start.constant_bits(), size.constant_bits()) { (Some(start), _) if start == BigInt::ZERO => self .read_size_to_doc(size) .expect("unexpected catch all size"), (Some(start), Some(end)) => (start + end).to_doc(), (_, _) => docvec![start_doc.clone(), " + ", self.read_size_to_doc(size)], }; let check = self.bit_array_slice_to_int(bit_array, start_doc, end, endianness, *signed); docvec![check, equality, literal_int] } } /// This generates all the checks that need to be performed to make sure a /// bit array segment (obtained with the read action passed as argument) /// matches with a literal float. /// fn literal_float_segment_bytes_check( &mut self, // A string representing the bit array value we read bits from. bit_array: EcoString, expected: &EcoString, read_action: &ReadAction, ) -> Document<'a> { let ReadAction { from: start, size, endianness, .. } = read_action; let equality = " === "; // Unlike literal integers and strings, for now we don't try and apply any // optimisation in the way we match on those: we take an entire slice, // convert it to a float and check if it matches the expected value. let start_doc = self.offset_to_doc(start, false); let end = match (start.constant_bits(), size.constant_bits()) { (Some(start), _) if start == BigInt::ZERO => self .read_size_to_doc(size) .expect("unexpected catch all size"), (Some(start), Some(end)) => (start + end).to_doc(), (_, _) => docvec![start_doc.clone(), " + ", self.read_size_to_doc(size)], }; let check = self.bit_array_slice_to_float(bit_array, start_doc, end, endianness); docvec![check, equality, expected] } #[must_use] fn is_bound_in_scope(&self, variable: &Variable) -> bool { self.scoped_variable_names.contains_key(&variable.id) } #[must_use] fn segment_is_bound_in_scope(&self, segment_name: &EcoString) -> bool { self.scoped_segment_names.contains_key(segment_name) } /// In case the check introduces new variables, this will record their /// actual value to be used by later checks and assignments. /// fn record_check_assignments(&mut self, variable: &Variable, check: &RuntimeCheck) { let value = self.get_value(variable); match check { RuntimeCheck::Int { .. } | RuntimeCheck::Float { .. } | RuntimeCheck::String { .. } | RuntimeCheck::EmptyList => (), RuntimeCheck::BitArray { test } => { for (segment_name, read_action) in test.referenced_segment_patterns() { self.set_segment_value(variable, segment_name.clone(), read_action) } } RuntimeCheck::StringPrefix { rest, prefix } => { let prefix_size = utf16_no_escape_len(prefix); self.set_value(rest, eco_format!("{value}.slice({prefix_size})")); } RuntimeCheck::Tuple { elements, .. } => { for (i, element) in elements.iter().enumerate() { self.set_value(element, eco_format!("{value}[{i}]")); } } RuntimeCheck::Variant { fields, labels, .. } => { for (i, field) in fields.iter().enumerate() { let access = match labels.get(&i) { Some(label) => eco_format!("{value}.{}", maybe_escape_property(label)), None => eco_format!("{value}[{i}]"), }; self.set_value(field, access); } } RuntimeCheck::NonEmptyList { first, rest } => { self.set_value(first, eco_format!("{value}.head")); self.set_value(rest, eco_format!("{value}.tail")); } } } /// A runtime check might need to reference some bit array segments in its /// check (for example if a bit array length depends on a previous segment). /// This function returns a vector with all the assignments needed to bring /// the referenced segments into scope, so they're available to use for the /// runtime check. /// fn bit_array_segment_assignments(&mut self, check: &RuntimeCheck) -> Vec> { let mut check_assignments = vec![]; for (segment, _) in check.referenced_segment_patterns() { // If the segment was already bound to a variable in this scope we // don't need to generate any further assignment for it. We will just // reuse that existing variable when we need to access this segment if self.segment_is_bound_in_scope(segment) { continue; } let variable_name = self.next_local_var(segment); let segment_value = self .get_segment_value(segment) .expect("segment referenced in a check before being created"); self.bind_segment(variable_name.clone(), segment.clone()); check_assignments.push(let_doc(variable_name, segment_value)) } check_assignments } /// Returns a string representing the value of a pattern variable: it might /// be the code needed to obtain such variable (for example accessing a /// list item `wibble.head`), or it could be a name this variable was bound /// to in the current scope to avoid doing any repeated work! /// fn get_value(&self, variable: &Variable) -> EcoString { // If the pattern variable was already assigned to a variable that is // in scope we use that variable name! if let Some(name) = self.scoped_variable_names.get(&variable.id) { return name.clone(); } // Otherwise we fallback to using its value directly. self.variable_values .get(&variable.id) .expect("pattern variable used before assignment") .clone() } fn get_segment_value(&self, segment_name: &EcoString) -> Option> { // If the segment was already assigned to a variable that is in scope // we use that variable name! if let Some(name) = self.scoped_segment_names.get(segment_name) { return Some(name.clone().to_doc()); } // Otherwise we fallback to using its value directly. self.segment_values.get(segment_name).cloned() } } /// When going over the subjects of a case expression/let we might end up in two /// situation: the subject might be a variable or it could be a more complex /// expression (like a function call, a complex expression, ...). /// /// ```gleam /// case a_variable { ... } /// case a_function_call(wobble) { ... } /// ``` /// /// When checking on a case we might end up repeating the subjects multiple times /// (as they need to appear in various checks), this means that if we ended up /// doing the simple thing of just repeating the subject as it is, we might end /// up dramatically changing the meaning of the program when the subject is a /// complex expression! Imagine this example: /// /// ```gleam /// case wibble("a") { /// 1 -> todo /// 2 -> todo /// _ -> todo /// } /// ``` /// /// If we just repeated the subject every time we need to check it, the decision /// tree would end up looking something like this: /// /// ```js /// if (wibble("a") === 1) {} /// else if (wibble("a") === 2) {} /// else {} /// ``` /// /// It would be quite bad as we would end up running the same function multiple /// times instead of just once! /// /// So we need to split each subject in two categories: if it is a simple /// variable already, it's no big deal and we can repeat that name as many times /// as we want; however, if it's anything else we first need to bind that subject /// to a variable we can then reference multiple times. /// enum SubjectAssignment<'a> { /// The subject is a complex expression with a `value` that has to be /// assigned to a variable with the given `name` as repeating the `value` /// multiple times could possibly change the meaning of the program. BindToVariable { name: EcoString, value: Document<'a>, }, /// The subject is already a simple variable with the given name, we will /// keep using that name to reference it. AlreadyAVariable { name: EcoString }, } impl SubjectAssignment<'_> { fn name(&self) -> EcoString { match self { SubjectAssignment::BindToVariable { name, value: _ } | SubjectAssignment::AlreadyAVariable { name } => name.clone(), } } } fn assign_subject<'a>( expression_generator: &mut Generator<'_, 'a>, subject: &'a TypedExpr, ordering: Ordering, ) -> SubjectAssignment<'a> { static ASSIGNMENT_VAR_ECO_STR: OnceLock = OnceLock::new(); // If the value is a variable we don't need to assign it to a new // variable, we can use the value expression safely without worrying about // performing computation or side effects multiple times. if let TypedExpr::Var { name, constructor, .. } = subject && constructor.is_local_variable() { SubjectAssignment::AlreadyAVariable { name: expression_generator.local_var(name), } } else { // If it's not a variable we need to assign it to a variable // to avoid rendering the subject expression multiple times let name = expression_generator .next_local_var(ASSIGNMENT_VAR_ECO_STR.get_or_init(|| ASSIGNMENT_VAR.into())); let value = expression_generator .not_in_tail_position(Some(ordering), |this| this.wrap_expression(subject)); SubjectAssignment::BindToVariable { value, name } } } fn assignments_to_doc(assignments: Vec>) -> Document<'_> { let mut assignments_docs = vec![]; for assignment in assignments.into_iter() { let SubjectAssignment::BindToVariable { name, value } = assignment else { continue; }; assignments_docs.push(docvec![let_doc(name, value), line()]) } assignments_docs.to_doc() } /// Appends the second document to the first one separating the two with a newline. /// However, if the second document is empty the empty line is not added. /// fn join_with_line<'a>(one: Document<'a>, other: Document<'a>) -> Document<'a> { if one.is_empty() { other } else if other.is_empty() { one } else { docvec![one, line(), other] } } fn reassignment_doc(variable_name: EcoString, value: Document<'_>) -> Document<'_> { docvec![variable_name, " = ", value, ";"] } fn let_doc(variable_name: EcoString, value: Document<'_>) -> Document<'_> { docvec!["let ", variable_name, " = ", value, ";"] } /// Calculates the length of str as utf16 without escape characters. /// fn utf16_no_escape_len(str: &EcoString) -> usize { length_utf16(&convert_string_escape_chars(str)) } ================================================ FILE: compiler-core/src/javascript/expression.rs ================================================ use num_bigint::BigInt; use vec1::Vec1; use super::{decision::ASSIGNMENT_VAR, *}; use crate::{ ast::*, exhaustiveness::StringEncoding, line_numbers::LineNumbers, pretty::*, type_::{ ModuleValueConstructor, Type, TypedCallArg, ValueConstructor, ValueConstructorVariant, }, }; use std::sync::Arc; #[derive(Debug, Clone)] pub enum Position { /// We are compiling the last expression in a function, meaning that it should /// use `return` to return the value it produces from the function. Tail, /// We are inside a function, but the value of this expression isn't being /// used, so we don't need to do anything with the returned value. Statement, /// The value of this expression needs to be used inside another expression, /// so we need to use the value that is returned by this expression. Expression(Ordering), /// We are compiling an expression inside a block, meaning we must assign /// to the `_block` variable at the end of the scope, because blocks are not /// expressions in JS. /// Since JS doesn't have variable shadowing, we must store the name of the /// variable being used, which will include the incrementing counter. /// For example, `block$2` Assign(EcoString), } impl Position { /// Returns `true` if the position is [`Tail`]. /// /// [`Tail`]: Position::Tail #[must_use] pub fn is_tail(&self) -> bool { matches!(self, Self::Tail) } #[must_use] pub fn ordering(&self) -> Ordering { match self { Self::Expression(ordering) => *ordering, Self::Tail | Self::Assign(_) | Self::Statement => Ordering::Loose, } } } #[derive(Debug, Clone, Copy)] /// Determines whether we can lift blocks into statement level instead of using /// immediately invoked function expressions. Consider the following piece of code: /// /// ```gleam /// some_function(function_with_side_effect(), { /// let a = 10 /// other_function_with_side_effects(a) /// }) /// ``` /// Here, if we lift the block that is the second argument of the function, we /// would end up running `other_function_with_side_effects` before /// `function_with_side_effects`. This would be invalid, as code in Gleam should be /// evaluated left-to-right, top-to-bottom. In this case, the ordering would be /// `Strict`, indicating that we cannot lift the block. /// /// However, in this example: /// /// ```gleam /// let value = !{ /// let value = False /// some_function_with_side_effect() /// value /// } /// ``` /// The only expression is the block, meaning it can be safely lifted without /// changing the evaluation order of the program. So the ordering is `Loose`. /// pub enum Ordering { Strict, Loose, } /// Tracking where the current function is a module function or an anonymous function. #[derive(Debug)] enum CurrentFunction { /// The current function is a module function /// /// ```gleam /// pub fn main() -> Nil { /// // we are here /// } /// ``` Module, /// The current function is a module function, but one of its arguments shadows /// the reference to itself so it cannot recurse. /// /// ```gleam /// pub fn main(main: fn() -> Nil) -> Nil { /// // we are here /// } /// ``` ModuleWithShadowingArgument, /// The current function is an anonymous function /// /// ```gleam /// pub fn main() -> Nil { /// fn() { /// // we are here /// } /// } /// ``` Anonymous, } impl CurrentFunction { #[inline] fn can_recurse(&self) -> bool { match self { CurrentFunction::Module => true, CurrentFunction::ModuleWithShadowingArgument => false, CurrentFunction::Anonymous => false, } } } #[derive(Debug)] pub(crate) struct Generator<'module, 'ast> { module_name: EcoString, src_path: EcoString, line_numbers: &'module LineNumbers, function_name: EcoString, function_arguments: Vec>, current_function: CurrentFunction, pub current_scope_vars: im::HashMap, pub function_position: Position, pub scope_position: Position, // We register whether these features are used within an expression so that // the module generator can output a suitable function if it is needed. pub tracker: &'module mut UsageTracker, // We track whether tail call recursion is used so that we can render a loop // at the top level of the function to use in place of pushing new stack // frames. pub tail_recursion_used: bool, /// Statements to be compiled when lifting blocks into statement scope. /// For example, when compiling the following code: /// ```gleam /// let a = { /// let b = 1 /// b + 1 /// } /// ``` /// There will be 2 items in `statement_level`: The first will be `let _block;` /// The second will be the generated code for the block being assigned to `a`. /// This lets use return `_block` as the value that the block evaluated to, /// while still including the necessary code in the output at the right place. /// /// Once the `let` statement has compiled its value, it will add anything accumulated /// in `statement_level` to the generated code, so it will result in: /// /// ```javascript /// let _block; /// {...} /// let a = _block; /// ``` /// statement_level: Vec>, /// This will be true if we've generated a `let assert` statement that we know /// is guaranteed to throw. /// This means we can stop code generation for all the following statements /// in the same block! pub let_assert_always_panics: bool, } impl<'module, 'a> Generator<'module, 'a> { #[allow(clippy::too_many_arguments)] // TODO: FIXME pub fn new( module_name: EcoString, src_path: EcoString, line_numbers: &'module LineNumbers, function_name: EcoString, function_arguments: Vec>, tracker: &'module mut UsageTracker, mut current_scope_vars: im::HashMap, ) -> Self { let mut current_function = CurrentFunction::Module; for &name in function_arguments.iter().flatten() { // Initialise the function arguments let _ = current_scope_vars.insert(name.clone(), 0); // If any of the function arguments shadow the current function then // recursion is no longer possible. if function_name.as_ref() == name { current_function = CurrentFunction::ModuleWithShadowingArgument; } } Self { tracker, module_name, src_path, line_numbers, function_name, function_arguments, tail_recursion_used: false, current_scope_vars, current_function, function_position: Position::Tail, scope_position: Position::Tail, statement_level: Vec::new(), let_assert_always_panics: false, } } pub fn local_var(&mut self, name: &EcoString) -> EcoString { match self.current_scope_vars.get(name) { None => { let _ = self.current_scope_vars.insert(name.clone(), 0); maybe_escape_identifier(name) } Some(0) => maybe_escape_identifier(name), Some(n) if name == "$" => eco_format!("${n}"), Some(n) => eco_format!("{name}${n}"), } } pub fn next_local_var(&mut self, name: &EcoString) -> EcoString { let next = self.current_scope_vars.get(name).map_or(0, |i| i + 1); let _ = self.current_scope_vars.insert(name.clone(), next); self.local_var(name) } pub fn function_body( &mut self, body: &'a [TypedStatement], arguments: &'a [TypedArg], ) -> Document<'a> { let body = self.statements(body); if self.tail_recursion_used { self.tail_call_loop(body, arguments) } else { body } } fn tail_call_loop(&mut self, body: Document<'a>, arguments: &'a [TypedArg]) -> Document<'a> { let loop_assignments = concat(arguments.iter().flat_map(Arg::get_variable_name).map( |name| { let var = maybe_escape_identifier(name); docvec!["let ", var, " = loop$", name, ";", line()] }, )); docvec![ "while (true) {", docvec![line(), loop_assignments, body].nest(INDENT), line(), "}" ] } fn statement(&mut self, statement: &'a TypedStatement) -> Document<'a> { let expression_doc = match statement { Statement::Expression(expression) => self.expression(expression), Statement::Assignment(assignment) => self.assignment(assignment), Statement::Use(use_) => self.expression(&use_.call), Statement::Assert(assert) => self.assert(assert), }; self.add_statement_level(expression_doc) } fn add_statement_level(&mut self, expression: Document<'a>) -> Document<'a> { if self.statement_level.is_empty() { expression } else { let mut statements = std::mem::take(&mut self.statement_level); statements.push(expression); join(statements, line()) } } pub fn expression(&mut self, expression: &'a TypedExpr) -> Document<'a> { let document = match expression { TypedExpr::String { value, .. } => string(value), TypedExpr::Int { value, .. } => int(value), TypedExpr::Float { float_value, .. } => float_from_value(float_value.value()), TypedExpr::List { elements, tail, .. } => { self.not_in_tail_position(Some(Ordering::Strict), |this| match tail { Some(tail) => { this.tracker.prepend_used = true; let tail = this.wrap_expression(tail); prepend( elements.iter().map(|element| this.wrap_expression(element)), tail, ) } None => { this.tracker.list_used = true; list(elements.iter().map(|element| this.wrap_expression(element))) } }) } TypedExpr::Tuple { elements, .. } => self.tuple(elements), TypedExpr::TupleIndex { tuple, index, .. } => self.tuple_index(tuple, *index), TypedExpr::Case { subjects, clauses, compiled_case, .. } => decision::case(compiled_case, clauses, subjects, self), TypedExpr::Call { fun, arguments, .. } => self.call(fun, arguments), TypedExpr::Fn { arguments, body, .. } => self.fn_(arguments, body), TypedExpr::RecordAccess { record, label, .. } => self.record_access(record, label), TypedExpr::PositionalAccess { record, index, .. } => { self.positional_access(record, *index) } TypedExpr::RecordUpdate { record_assignment, constructor, arguments, .. } => self.record_update(record_assignment, constructor, arguments), TypedExpr::Var { name, constructor, .. } => self.variable(name, constructor), TypedExpr::Pipeline { first_value, assignments, finally, .. } => self.pipeline(first_value, assignments.as_slice(), finally), TypedExpr::Block { statements, .. } => self.block(statements), TypedExpr::BinOp { name, left, right, .. } => self.bin_op(name, left, right), TypedExpr::Todo { message, location, .. } => self.todo(message.as_ref().map(|m| &**m), location), TypedExpr::Panic { location, message, .. } => self.panic(location, message.as_ref().map(|m| &**m)), TypedExpr::BitArray { segments, .. } => self.bit_array(segments), TypedExpr::ModuleSelect { module_alias, label, constructor, .. } => self.module_select(module_alias, label, constructor), TypedExpr::NegateBool { value, .. } => self.negate_with("!", value), TypedExpr::NegateInt { value, .. } => self.negate_with("- ", value), TypedExpr::Echo { expression, message, location, .. } => { let expression = expression .as_ref() .expect("echo with no expression outside of pipe"); let expresion_doc = self.not_in_tail_position(None, |this| this.wrap_expression(expression)); self.echo(expresion_doc, message.as_deref(), location) } TypedExpr::Invalid { .. } => { panic!("invalid expressions should not reach code generation") } }; if expression.handles_own_return() { document } else { self.wrap_return(document) } } fn negate_with(&mut self, with: &'static str, value: &'a TypedExpr) -> Document<'a> { self.not_in_tail_position(None, |this| docvec![with, this.wrap_expression(value)]) } fn bit_array(&mut self, segments: &'a [TypedExprBitArraySegment]) -> Document<'a> { self.tracker.bit_array_literal_used = true; // Collect all the values used in segments. let segments_array = array(segments.iter().map(|segment| { let value = self.not_in_tail_position(Some(Ordering::Strict), |this| { this.wrap_expression(&segment.value) }); let details = self.bit_array_segment_details(segment); match details.type_ { BitArraySegmentType::BitArray => { if segment.size().is_some() { self.tracker.bit_array_slice_used = true; docvec!["bitArraySlice(", value, ", 0, ", details.size, ")"] } else { value } } BitArraySegmentType::Int => match (details.size_value, segment.value.as_ref()) { (Some(size_value), TypedExpr::Int { int_value, .. }) if size_value <= SAFE_INT_SEGMENT_MAX_SIZE.into() && (&size_value % BigInt::from(8) == BigInt::ZERO) => { let bytes = bit_array_segment_int_value_to_bytes( int_value.clone(), size_value, segment.endianness(), ); u8_slice(&bytes) } (Some(size_value), _) if size_value == 8.into() => value, (Some(size_value), _) if size_value <= 0.into() => nil(), _ => { self.tracker.sized_integer_segment_used = true; let size = details.size; let is_big = bool(segment.endianness().is_big()); docvec!["sizedInt(", value, ", ", size, ", ", is_big, ")"] } }, BitArraySegmentType::Float => { self.tracker.float_bit_array_segment_used = true; let size = details.size; let is_big = bool(details.endianness.is_big()); docvec!["sizedFloat(", value, ", ", size, ", ", is_big, ")"] } BitArraySegmentType::String(StringEncoding::Utf8) => { self.tracker.string_bit_array_segment_used = true; docvec!["stringBits(", value, ")"] } BitArraySegmentType::String(StringEncoding::Utf16) => { self.tracker.string_utf16_bit_array_segment_used = true; let is_big = bool(details.endianness.is_big()); docvec!["stringToUtf16(", value, ", ", is_big, ")"] } BitArraySegmentType::String(StringEncoding::Utf32) => { self.tracker.string_utf32_bit_array_segment_used = true; let is_big = bool(details.endianness.is_big()); docvec!["stringToUtf32(", value, ", ", is_big, ")"] } BitArraySegmentType::UtfCodepoint(StringEncoding::Utf8) => { self.tracker.codepoint_bit_array_segment_used = true; docvec!["codepointBits(", value, ")"] } BitArraySegmentType::UtfCodepoint(StringEncoding::Utf16) => { self.tracker.codepoint_utf16_bit_array_segment_used = true; let is_big = bool(details.endianness.is_big()); docvec!["codepointToUtf16(", value, ", ", is_big, ")"] } BitArraySegmentType::UtfCodepoint(StringEncoding::Utf32) => { self.tracker.codepoint_utf32_bit_array_segment_used = true; let is_big = bool(details.endianness.is_big()); docvec!["codepointToUtf32(", value, ", ", is_big, ")"] } } })); docvec!["toBitArray(", segments_array, ")"] } fn bit_array_segment_details( &mut self, segment: &'a TypedExprBitArraySegment, ) -> BitArraySegmentDetails<'a> { let size = segment.size(); let unit = segment.unit(); let (size_value, size) = match size { Some(TypedExpr::Int { int_value, .. }) => { let size_value = int_value * unit; let size = eco_format!("{}", size_value).to_doc(); (Some(size_value), size) } Some(size) => { let mut size = self.not_in_tail_position(Some(Ordering::Strict), |this| { this.wrap_expression(size) }); if unit != 1 { size = size.group().append(" * ".to_doc().append(unit.to_doc())); } (None, size) } None => { let size_value: usize = if segment.type_.is_int() { 8 } else { 64 }; (Some(BigInt::from(size_value)), docvec![size_value]) } }; let type_ = BitArraySegmentType::from_segment(segment); BitArraySegmentDetails { type_, size, size_value, endianness: segment.endianness(), } } pub fn wrap_return(&mut self, document: Document<'a>) -> Document<'a> { match &self.scope_position { Position::Tail => docvec!["return ", document, ";"], Position::Expression(_) | Position::Statement => document, Position::Assign(name) => docvec![name.clone(), " = ", document, ";"], } } pub fn not_in_tail_position( &mut self, // If ordering is None, it is inherited from the parent scope. // It will be None in cases like `!x`, where `x` can be lifted // only if the ordering is already loose. ordering: Option, compile: CompileFn, ) -> Output where CompileFn: Fn(&mut Self) -> Output, { let new_ordering = ordering.unwrap_or(self.scope_position.ordering()); let function_position = std::mem::replace( &mut self.function_position, Position::Expression(new_ordering), ); let scope_position = std::mem::replace(&mut self.scope_position, Position::Expression(new_ordering)); let result = compile(self); self.function_position = function_position; self.scope_position = scope_position; result } /// Use the `_block` variable if the expression is JS statement. pub fn wrap_expression(&mut self, expression: &'a TypedExpr) -> Document<'a> { match (expression, &self.scope_position) { (_, Position::Tail | Position::Assign(_)) => self.expression(expression), ( TypedExpr::Panic { .. } | TypedExpr::Todo { .. } | TypedExpr::Case { .. } | TypedExpr::Pipeline { .. } | TypedExpr::RecordUpdate { // Record updates that assign a variable generate multiple statements record_assignment: Some(_), .. }, Position::Expression(Ordering::Loose), ) => self.wrap_block(|this| this.expression(expression)), ( TypedExpr::Panic { .. } | TypedExpr::Todo { .. } | TypedExpr::Case { .. } | TypedExpr::Pipeline { .. } | TypedExpr::RecordUpdate { // Record updates that assign a variable generate multiple statements record_assignment: Some(_), .. }, Position::Expression(Ordering::Strict), ) => self.immediately_invoked_function_expression(expression, |this, expr| { this.expression(expr) }), _ => self.expression(expression), } } /// Wrap an expression using the `_block` variable if required due to being /// a JS statement, or in parens if required due to being an operator or /// a function literal. pub fn child_expression(&mut self, expression: &'a TypedExpr) -> Document<'a> { match expression { TypedExpr::BinOp { name, .. } if name.is_operator_to_wrap() => {} TypedExpr::Fn { .. } => {} TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => return self.wrap_expression(expression), } let document = self.expression(expression); match &self.scope_position { // Here the document is a return statement: `return ;` // or an assignment: `_block = ;` Position::Tail | Position::Assign(_) | Position::Statement => document, Position::Expression(_) => docvec!["(", document, ")"], } } /// Wrap an expression in an immediately invoked function expression fn immediately_invoked_function_expression( &mut self, statements: &'a T, to_doc: ToDoc, ) -> Document<'a> where ToDoc: FnOnce(&mut Self, &'a T) -> Document<'a>, { // Save initial state let scope_position = std::mem::replace(&mut self.scope_position, Position::Tail); let statement_level = std::mem::take(&mut self.statement_level); // Set state for in this iife let current_scope_vars = self.current_scope_vars.clone(); // Generate the expression let result = to_doc(self, statements); let doc = self.add_statement_level(result); let doc = immediately_invoked_function_expression_document(doc); // Reset self.current_scope_vars = current_scope_vars; self.scope_position = scope_position; self.statement_level = statement_level; self.wrap_return(doc) } fn wrap_block(&mut self, compile: CompileFn) -> Document<'a> where CompileFn: Fn(&mut Self) -> Document<'a>, { let block_variable = self.next_local_var(&BLOCK_VARIABLE.into()); // Save initial state let scope_position = std::mem::replace( &mut self.scope_position, Position::Assign(block_variable.clone()), ); let function_position = std::mem::replace( &mut self.function_position, Position::Expression(Ordering::Strict), ); // Generate the expression let statement_doc = compile(self); // Reset self.scope_position = scope_position; self.function_position = function_position; self.statement_level .push(docvec!["let ", block_variable.clone(), ";"]); self.statement_level.push(statement_doc); self.wrap_return(block_variable.to_doc()) } fn variable(&mut self, name: &'a EcoString, constructor: &'a ValueConstructor) -> Document<'a> { match &constructor.variant { ValueConstructorVariant::Record { arity, .. } => { let type_ = constructor.type_.clone(); let tracker = &mut self.tracker; record_constructor(type_, None, name, *arity, tracker) } ValueConstructorVariant::ModuleFn { .. } | ValueConstructorVariant::ModuleConstant { .. } | ValueConstructorVariant::LocalVariable { .. } => self.local_var(name).to_doc(), } } fn pipeline( &mut self, first_value: &'a TypedPipelineAssignment, assignments: &'a [(TypedPipelineAssignment, PipelineAssignmentKind)], finally: &'a TypedExpr, ) -> Document<'a> { let count = assignments.len(); let mut documents = Vec::with_capacity((count + 2) * 2); let all_assignments = std::iter::once(first_value) .chain(assignments.iter().map(|(assignment, _kind)| assignment)); let mut latest_local_var: Option = None; for assignment in all_assignments { // An echo in a pipeline won't result in an assignment, instead it // just prints the previous variable assigned in the pipeline. if let TypedExpr::Echo { expression: None, message, location, .. } = assignment.value.as_ref() { documents.push(self.not_in_tail_position(Some(Ordering::Strict), |this| { let var = latest_local_var .as_ref() .expect("echo with no previous step in a pipe"); this.echo(var.to_doc(), message.as_deref(), location) })) } else { // Otherwise we assign the intermediate pipe value to a variable. let assignment_document = self .not_in_tail_position(Some(Ordering::Strict), |this| { this.simple_variable_assignment(&assignment.name, &assignment.value) }); documents.push(self.add_statement_level(assignment_document)); latest_local_var = Some(self.local_var(&assignment.name)); } documents.push(line()); } if let TypedExpr::Echo { expression: None, message, location, .. } = finally { let var = latest_local_var.expect("echo with no previous step in a pipe"); documents.push(self.echo(var.to_doc(), message.as_deref(), location)); } else { let finally = self.expression(finally); documents.push(self.add_statement_level(finally)) } documents.to_doc().force_break() } pub(crate) fn expression_flattening_blocks( &mut self, expression: &'a TypedExpr, ) -> Document<'a> { if let TypedExpr::Block { statements, .. } = expression { self.statements(statements) } else { self.expression(expression) } } fn block(&mut self, statements: &'a Vec1) -> Document<'a> { if statements.len() == 1 { match statements.first() { Statement::Expression(expression) => return self.child_expression(expression), Statement::Assignment(assignment) => match &assignment.kind { AssignmentKind::Let | AssignmentKind::Generated => { return self.child_expression(&assignment.value); } // We can't just return the right-hand side of a `let assert` // assignment; we still need to check that the pattern matches. AssignmentKind::Assert { .. } => {} }, Statement::Use(use_) => return self.child_expression(&use_.call), // Similar to `let assert`, we can't immediately return the value // that is asserted; we have to actually perform the assertion. Statement::Assert(_) => {} } } match &self.scope_position { Position::Tail | Position::Assign(_) | Position::Statement => { self.block_document(statements) } Position::Expression(Ordering::Strict) => self .immediately_invoked_function_expression(statements, |this, statements| { this.statements(statements) }), Position::Expression(Ordering::Loose) => self.wrap_block(|this| { // Save previous scope let current_scope_vars = this.current_scope_vars.clone(); let document = this.block_document(statements); // Restore previous state this.current_scope_vars = current_scope_vars; document }), } } fn block_document(&mut self, statements: &'a Vec1) -> Document<'a> { let statements = self.statements(statements); docvec!["{", docvec![line(), statements].nest(INDENT), line(), "}"] } fn statements(&mut self, statements: &'a [TypedStatement]) -> Document<'a> { // If there are any statements that need to be printed at statement level, that's // for an outer scope so we don't want to print them inside this one. let statement_level = std::mem::take(&mut self.statement_level); let count = statements.len(); let mut documents = Vec::with_capacity(count * 3); for (i, statement) in statements.iter().enumerate() { if i + 1 < count { let function_position = std::mem::replace(&mut self.function_position, Position::Statement); let scope_position = std::mem::replace(&mut self.scope_position, Position::Statement); documents.push(self.statement(statement)); self.function_position = function_position; self.scope_position = scope_position; if requires_semicolon(statement) { documents.push(";".to_doc()); } documents.push(line()); } else { documents.push(self.statement(statement)); } // If we've generated code for a statement that always throws, we // can skip code generation for all the following ones. if self.let_assert_always_panics { self.let_assert_always_panics = false; break; } } self.statement_level = statement_level; if count == 1 { documents.to_doc() } else { documents.to_doc().force_break() } } fn simple_variable_assignment( &mut self, name: &'a EcoString, value: &'a TypedExpr, ) -> Document<'a> { // Subject must be rendered before the variable for variable numbering let subject = self.not_in_tail_position(Some(Ordering::Loose), |this| this.wrap_expression(value)); let js_name = self.next_local_var(name); let assignment = docvec!["let ", js_name.clone(), " = ", subject, ";"]; let assignment = match &self.scope_position { Position::Expression(_) | Position::Statement => assignment, Position::Tail => docvec![assignment, line(), "return ", js_name, ";"], Position::Assign(block_variable) => docvec![ assignment, line(), block_variable.clone(), " = ", js_name, ";" ], }; assignment.force_break() } fn assignment(&mut self, assignment: &'a TypedAssignment) -> Document<'a> { let TypedAssignment { pattern, kind, value, compiled_case, annotation: _, location: _, } = assignment; // In case the pattern is just a variable, we special case it to // generate just a simple assignment instead of using the decision tree // for the code generation step. if let TypedPattern::Variable { name, .. } = pattern { return self.simple_variable_assignment(name, value); } decision::let_(compiled_case, value, kind, self, pattern) } fn assert(&mut self, assert: &'a TypedAssert) -> Document<'a> { let TypedAssert { location, value, message, } = assert; let message = match message { Some(message) => { self.not_in_tail_position(Some(Ordering::Strict), |this| this.expression(message)) } None => string("Assertion failed."), }; let check = self.not_in_tail_position(Some(Ordering::Loose), |this| { this.assert_check(value, &message, *location) }); match &self.scope_position { Position::Expression(_) | Position::Statement => check, Position::Tail | Position::Assign(_) => { docvec![check, line(), self.wrap_return("undefined".to_doc())] } } } fn assert_check( &mut self, subject: &'a TypedExpr, message: &Document<'a>, location: SrcSpan, ) -> Document<'a> { let (subject_document, mut fields) = match subject { TypedExpr::Call { fun, arguments, .. } => { let argument_variables = arguments .iter() .map(|element| { self.not_in_tail_position(Some(Ordering::Strict), |this| { this.assign_to_variable(&element.value) }) }) .collect_vec(); ( self.call_with_doc_arguments(fun, argument_variables.clone()), vec![ ("kind", string("function_call")), ( "arguments", array(argument_variables.into_iter().zip(arguments).map( |(variable, argument)| { self.asserted_expression( AssertExpression::from_expression(&argument.value), Some(variable), argument.location(), ) }, )), ), ], ) } TypedExpr::BinOp { name, left, right, .. } => { match name { BinOp::And => return self.assert_and(left, right, message, location), BinOp::Or => return self.assert_or(left, right, message, location), BinOp::Eq | BinOp::NotEq | BinOp::LtInt | BinOp::LtEqInt | BinOp::LtFloat | BinOp::LtEqFloat | BinOp::GtEqInt | BinOp::GtInt | BinOp::GtEqFloat | BinOp::GtFloat | BinOp::AddInt | BinOp::AddFloat | BinOp::SubInt | BinOp::SubFloat | BinOp::MultInt | BinOp::MultFloat | BinOp::DivInt | BinOp::DivFloat | BinOp::RemainderInt | BinOp::Concatenate => {} } let left_document = self.not_in_tail_position(Some(Ordering::Loose), |this| { this.assign_to_variable(left) }); let right_document = self.not_in_tail_position(Some(Ordering::Loose), |this| { this.assign_to_variable(right) }); ( self.bin_op_with_doc_operands( *name, left_document.clone(), right_document.clone(), &left.type_(), ) .surround("(", ")"), vec![ ("kind", string("binary_operator")), ("operator", string(name.name())), ( "left", self.asserted_expression( AssertExpression::from_expression(left), Some(left_document), left.location(), ), ), ( "right", self.asserted_expression( AssertExpression::from_expression(right), Some(right_document), right.location(), ), ), ], ) } TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => ( self.wrap_expression(subject), vec![ ("kind", string("expression")), ( "expression", self.asserted_expression( AssertExpression::from_expression(subject), Some("false".to_doc()), subject.location(), ), ), ], ), }; fields.push(("start", location.start.to_doc())); fields.push(("end", subject.location().end.to_doc())); fields.push(("expression_start", subject.location().start.to_doc())); docvec![ "if (", docvec!["!", subject_document].nest(INDENT), break_("", ""), ") {", docvec![ line(), self.throw_error("assert", message, location, fields), ] .nest(INDENT), line(), "}", ] .group() } fn negate_bool_expression(&mut self, value: &'a TypedExpr) -> Document<'a> { match value { TypedExpr::BinOp { name, left, right, .. } => match name { BinOp::And => self.print_bin_op(left, right, "||"), BinOp::Or => self.print_bin_op(left, right, "&&"), BinOp::Eq => self.equal(left, right, false), BinOp::NotEq => self.equal(left, right, true), BinOp::LtInt | BinOp::LtFloat => self.print_bin_op(left, right, ">="), BinOp::LtEqInt | BinOp::LtEqFloat => self.print_bin_op(left, right, ">"), BinOp::GtInt | BinOp::GtFloat => self.print_bin_op(left, right, "<="), BinOp::GtEqInt | BinOp::GtEqFloat => self.print_bin_op(left, right, "<"), BinOp::AddInt | BinOp::AddFloat | BinOp::SubInt | BinOp::SubFloat | BinOp::MultInt | BinOp::MultFloat | BinOp::DivInt | BinOp::DivFloat | BinOp::RemainderInt | BinOp::Concatenate => unreachable!("type checking should make this impossible"), }, TypedExpr::NegateBool { value, .. } => self.wrap_expression(value), TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => docvec!["!", self.wrap_expression(value)], } } /// In Gleam, the `&&` operator is short-circuiting, meaning that we can't /// pre-evaluate both sides of it, and use them in the exception that is /// thrown. /// Instead, we need to implement this short-circuiting logic ourself. /// /// If we short-circuit, we must leave the second expression unevaluated, /// and signal that using the `unevaluated` variant, as detailed in the /// exception format. For the first expression, we know it must be `false`, /// otherwise we would have continued by evaluating the second expression. /// /// Similarly, if we do evaluate the second expression and fail, we know /// that the first expression must have evaluated to `true`, and the second /// to `false`. This way, we avoid needing to evaluate either expression /// twice. /// /// The generated code then looks something like this: /// ```javascript /// if (expr1) { /// if (!expr2) { /// /// } /// } else { /// /// } /// ``` /// fn assert_and( &mut self, left: &'a TypedExpr, right: &'a TypedExpr, message: &Document<'a>, location: SrcSpan, ) -> Document<'a> { let left_kind = AssertExpression::from_expression(left); let right_kind = AssertExpression::from_expression(right); let fields_if_short_circuiting = vec![ ("kind", string("binary_operator")), ("operator", string("&&")), ( "left", self.asserted_expression(left_kind, Some("false".to_doc()), left.location()), ), ( "right", self.asserted_expression(AssertExpression::Unevaluated, None, right.location()), ), ("start", location.start.to_doc()), ("end", right.location().end.to_doc()), ("expression_start", left.location().start.to_doc()), ]; let fields = vec![ ("kind", string("binary_operator")), ("operator", string("&&")), ( "left", self.asserted_expression(left_kind, Some("true".to_doc()), left.location()), ), ( "right", self.asserted_expression(right_kind, Some("false".to_doc()), right.location()), ), ("start", location.start.to_doc()), ("end", right.location().end.to_doc()), ("expression_start", left.location().start.to_doc()), ]; let left_value = self.not_in_tail_position(Some(Ordering::Loose), |this| this.wrap_expression(left)); let right_value = self.not_in_tail_position(Some(Ordering::Strict), |this| { this.negate_bool_expression(right) }); let right_check = docvec![ line(), "if (", right_value.nest(INDENT), ") {", docvec![ line(), self.throw_error("assert", message, location, fields) ] .nest(INDENT), line(), "}", ]; docvec![ "if (", left_value.nest(INDENT), ") {", right_check.nest(INDENT), line(), "} else {", docvec![ line(), self.throw_error("assert", message, location, fields_if_short_circuiting) ] .nest(INDENT), line(), "}" ] } /// Similar to `&&`, `||` is also short-circuiting in Gleam. However, if `||` /// short-circuits, that's because the first expression evaluated to `true`, /// meaning the whole assertion succeeds. This allows us to directly use the /// `||` operator in JavaScript. /// /// The only difference is that due to the nature of `||`, if the assertion fails, /// we know that both sides must have evaluated to `false`, so we don't /// need to store the values of them in variables beforehand. fn assert_or( &mut self, left: &'a TypedExpr, right: &'a TypedExpr, message: &Document<'a>, location: SrcSpan, ) -> Document<'a> { let fields = vec![ ("kind", string("binary_operator")), ("operator", string("||")), ( "left", self.asserted_expression( AssertExpression::from_expression(left), Some("false".to_doc()), left.location(), ), ), ( "right", self.asserted_expression( AssertExpression::from_expression(right), Some("false".to_doc()), right.location(), ), ), ("start", location.start.to_doc()), ("end", right.location().end.to_doc()), ("expression_start", left.location().start.to_doc()), ]; let left_value = self.not_in_tail_position(Some(Ordering::Loose), |this| this.child_expression(left)); let right_value = self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right)); docvec![ line(), "if (", docvec!["!(", left_value, " || ", right_value, ")"].nest(INDENT), ") {", docvec![ line(), self.throw_error("assert", message, location, fields) ] .nest(INDENT), line(), "}", ] } fn assign_to_variable(&mut self, value: &'a TypedExpr) -> Document<'a> { if let TypedExpr::Var { .. } = value { self.expression(value) } else { let value = self.wrap_expression(value); let variable = self.next_local_var(&ASSIGNMENT_VAR.into()); let assignment = docvec!["let ", variable.clone(), " = ", value, ";"]; self.statement_level.push(assignment); variable.to_doc() } } fn asserted_expression( &mut self, kind: AssertExpression, value: Option>, location: SrcSpan, ) -> Document<'a> { let kind = match kind { AssertExpression::Literal => string("literal"), AssertExpression::Expression => string("expression"), AssertExpression::Unevaluated => string("unevaluated"), }; let start = location.start.to_doc(); let end = location.end.to_doc(); let items = if let Some(value) = value { vec![ ("kind", kind), ("value", value), ("start", start), ("end", end), ] } else { vec![("kind", kind), ("start", start), ("end", end)] }; wrap_object( items .into_iter() .map(|(key, value)| (key.to_doc(), Some(value))), ) } fn tuple(&mut self, elements: &'a [TypedExpr]) -> Document<'a> { self.not_in_tail_position(Some(Ordering::Strict), |this| { array(elements.iter().map(|element| this.wrap_expression(element))) }) } fn call(&mut self, fun: &'a TypedExpr, arguments: &'a [TypedCallArg]) -> Document<'a> { let arguments = arguments .iter() .map(|element| { self.not_in_tail_position(Some(Ordering::Strict), |this| { this.wrap_expression(&element.value) }) }) .collect_vec(); self.call_with_doc_arguments(fun, arguments) } fn call_with_doc_arguments( &mut self, fun: &'a TypedExpr, arguments: Vec>, ) -> Document<'a> { match fun { // Qualified record construction TypedExpr::ModuleSelect { constructor: ModuleValueConstructor::Record { name, .. }, module_alias, .. } => self.wrap_return(construct_record(Some(module_alias), name, arguments)), // Record construction TypedExpr::Var { constructor: ValueConstructor { variant: ValueConstructorVariant::Record { .. }, type_, .. }, name, .. } => { if type_.is_result_constructor() { if name == "Ok" { self.tracker.ok_used = true; } else if name == "Error" { self.tracker.error_used = true; } } self.wrap_return(construct_record(None, name, arguments)) } // Tail call optimisation. If we are calling the current function // and we are in tail position we can avoid creating a new stack // frame, enabling recursion with constant memory usage. TypedExpr::Var { name, .. } if self.function_name == *name && self.current_function.can_recurse() && self.function_position.is_tail() && self.current_scope_vars.get(name) == Some(&0) => { let mut docs = Vec::with_capacity(arguments.len() * 4); // Record that tail recursion is happening so that we know to // render the loop at the top level of the function. self.tail_recursion_used = true; for (i, (element, argument)) in arguments .into_iter() .zip(&self.function_arguments) .enumerate() { if i != 0 { docs.push(line()); } // Create an assignment for each variable created by the function arguments if let Some(name) = argument { docs.push("loop$".to_doc()); docs.push(name.to_doc()); docs.push(" = ".to_doc()); } // Render the value given to the function. Even if it is not // assigned we still render it because the expression may // have some side effects. docs.push(element); docs.push(";".to_doc()); } docs.to_doc() } TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => { let fun = self.not_in_tail_position(None, |this| -> Document<'_> { let is_fn_literal = matches!(fun, TypedExpr::Fn { .. }); let fun = this.wrap_expression(fun); if is_fn_literal { docvec!["(", fun, ")"] } else { fun } }); let arguments = call_arguments(arguments); self.wrap_return(docvec![fun, arguments]) } } } fn fn_(&mut self, arguments: &'a [TypedArg], body: &'a [TypedStatement]) -> Document<'a> { // New function, this is now the tail position let function_position = std::mem::replace(&mut self.function_position, Position::Tail); let scope_position = std::mem::replace(&mut self.scope_position, Position::Tail); // And there's a new scope let scope = self.current_scope_vars.clone(); for name in arguments.iter().flat_map(Arg::get_variable_name) { let _ = self.current_scope_vars.insert(name.clone(), 0); } // This is a new function so track that so that we don't // mistakenly trigger tail call optimisation let mut current_function = CurrentFunction::Anonymous; std::mem::swap(&mut self.current_function, &mut current_function); // Generate the function body let result = self.statements(body); // Reset function name, scope, and tail position tracking self.function_position = function_position; self.scope_position = scope_position; self.current_scope_vars = scope; std::mem::swap(&mut self.current_function, &mut current_function); docvec![ docvec![ fun_arguments(arguments, false), " => {", break_("", " "), result ] .nest(INDENT) .append(break_("", " ")) .group(), "}", ] } fn record_access(&mut self, record: &'a TypedExpr, label: &'a str) -> Document<'a> { self.not_in_tail_position(None, |this| { let record = this.wrap_expression(record); docvec![record, ".", maybe_escape_property(label)] }) } fn positional_access(&mut self, record: &'a TypedExpr, index: u64) -> Document<'a> { self.not_in_tail_position(None, |this| { let record = this.wrap_expression(record); docvec![record, "[", index, "]"] }) } fn record_update( &mut self, record: &'a Option>, constructor: &'a TypedExpr, arguments: &'a [TypedCallArg], ) -> Document<'a> { match record.as_ref() { Some(record) => docvec![ self.not_in_tail_position(None, |this| this.assignment(record)), line(), self.call(constructor, arguments), ], None => self.call(constructor, arguments), } } fn tuple_index(&mut self, tuple: &'a TypedExpr, index: u64) -> Document<'a> { self.not_in_tail_position(None, |this| { let tuple = this.wrap_expression(tuple); docvec![tuple, eco_format!("[{index}]")] }) } fn bin_op( &mut self, name: &'a BinOp, left: &'a TypedExpr, right: &'a TypedExpr, ) -> Document<'a> { match name { BinOp::And => self.print_bin_op(left, right, "&&"), BinOp::Or => self.print_bin_op(left, right, "||"), BinOp::LtInt | BinOp::LtFloat => self.print_bin_op(left, right, "<"), BinOp::LtEqInt | BinOp::LtEqFloat => self.print_bin_op(left, right, "<="), BinOp::Eq => self.equal(left, right, true), BinOp::NotEq => self.equal(left, right, false), BinOp::GtInt | BinOp::GtFloat => self.print_bin_op(left, right, ">"), BinOp::GtEqInt | BinOp::GtEqFloat => self.print_bin_op(left, right, ">="), BinOp::Concatenate | BinOp::AddInt | BinOp::AddFloat => { self.print_bin_op(left, right, "+") } BinOp::SubInt | BinOp::SubFloat => self.print_bin_op(left, right, "-"), BinOp::MultInt | BinOp::MultFloat => self.print_bin_op(left, right, "*"), BinOp::RemainderInt => self.remainder_int(left, right), BinOp::DivInt => self.div_int(left, right), BinOp::DivFloat => self.div_float(left, right), } } fn div_int(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Document<'a> { let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left)); let right_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right)); // If we have a constant value divided by zero then it's safe to replace // it directly with 0. if left.is_literal() && right.is_zero_compile_time_number() { "0".to_doc() } else if right.is_non_zero_compile_time_number() { let division = if let TypedExpr::BinOp { .. } = left { docvec![left_doc.surround("(", ")"), " / ", right_doc] } else { docvec![left_doc, " / ", right_doc] }; docvec!["globalThis.Math.trunc", wrap_arguments([division])] } else { self.tracker.int_division_used = true; docvec!["divideInt", wrap_arguments([left_doc, right_doc])] } } fn remainder_int(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Document<'a> { let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left)); let right_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right)); // If we have a constant value divided by zero then it's safe to replace // it directly with 0. if left.is_literal() && right.is_zero_compile_time_number() { "0".to_doc() } else if right.is_non_zero_compile_time_number() { if let TypedExpr::BinOp { .. } = left { docvec![left_doc.surround("(", ")"), " % ", right_doc] } else { docvec![left_doc, " % ", right_doc] } } else { self.tracker.int_remainder_used = true; docvec!["remainderInt", wrap_arguments([left_doc, right_doc])] } } fn div_float(&mut self, left: &'a TypedExpr, right: &'a TypedExpr) -> Document<'a> { let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left)); let right_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right)); // If we have a constant value divided by zero then it's safe to replace // it directly with 0. if left.is_literal() && right.is_zero_compile_time_number() { "0.0".to_doc() } else if right.is_non_zero_compile_time_number() { if let TypedExpr::BinOp { .. } = left { docvec![left_doc.surround("(", ")"), " / ", right_doc] } else { docvec![left_doc, " / ", right_doc] } } else { self.tracker.float_division_used = true; docvec!["divideFloat", wrap_arguments([left_doc, right_doc])] } } fn equal( &mut self, left: &'a TypedExpr, right: &'a TypedExpr, should_be_equal: bool, ) -> Document<'a> { // If it is a simple scalar type then we can use JS' reference identity if is_js_scalar(left.type_()) { let left_doc = self .not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left)); let right_doc = self .not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right)); let operator = if should_be_equal { " === " } else { " !== " }; return docvec![left_doc, operator, right_doc]; } // For comparison with singleton custom types, ie, one with no fields. // If you have some code like this // ```gleam // pub type Wibble { // Wibble // Wobble // } // pub fn is_wibble(w: Wibble) -> Bool { // w == Wibble // } // ``` // Instead of `isEqual(w, new Wibble())`, generate `w instanceof Wibble` // because the first approach needs to construct a new Wibble, and then call the isEqual function, // which supports any shape of data, and so does a lot of extra logic which isn't necessary. if let Some(doc) = self.singleton_variant_equality(left, right, should_be_equal) { return doc; } if let Some(doc) = self.singleton_variant_equality(right, left, should_be_equal) { return doc; } // Other types must be compared using structural equality let left = self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(left)); let right = self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(right)); self.prelude_equal_call(should_be_equal, left, right) } fn singleton_variant_equality( &mut self, left: &'a TypedExpr, right: &'a TypedExpr, should_be_equal: bool, ) -> Option> { match right { TypedExpr::Var { constructor: ValueConstructor { variant: ValueConstructorVariant::Record { arity: 0, name, .. }, .. }, .. } => { let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { this.wrap_expression(left) }); Some(self.singleton_equal(left_doc, None, name, should_be_equal)) } TypedExpr::ModuleSelect { module_alias, constructor: ModuleValueConstructor::Record { arity: 0, name, .. }, .. } => { let left_doc = self.not_in_tail_position(Some(Ordering::Strict), |this| { this.wrap_expression(left) }); Some(self.singleton_equal(left_doc, Some(module_alias), name, should_be_equal)) } TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => None, } } fn singleton_equal( &self, value: Document<'a>, module: Option<&'a str>, name: &'a str, should_be_equal: bool, ) -> Document<'a> { let record = if let Some(module) = module { docvec!["$", module, ".", name] } else { name.to_doc() }; if should_be_equal { docvec![value, " instanceof ", record] } else { docvec!["!(", value, " instanceof ", record, ")"] } } fn equal_with_doc_operands( &mut self, left: Document<'a>, right: Document<'a>, type_: Arc, should_be_equal: bool, ) -> Document<'a> { // If it is a simple scalar type then we can use JS' reference identity if is_js_scalar(type_) { let operator = if should_be_equal { " === " } else { " !== " }; return docvec![left, operator, right]; } // Other types must be compared using structural equality self.prelude_equal_call(should_be_equal, left, right) } pub(super) fn prelude_equal_call( &mut self, should_be_equal: bool, left: Document<'a>, right: Document<'a>, ) -> Document<'a> { // Record that we need to import the prelude's isEqual function into the module self.tracker.object_equality_used = true; // Construct the call let arguments = wrap_arguments([left, right]); let operator = if should_be_equal { "isEqual" } else { "!isEqual" }; docvec![operator, arguments] } fn print_bin_op( &mut self, left: &'a TypedExpr, right: &'a TypedExpr, op: &'a str, ) -> Document<'a> { let left = self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(left)); let right = self.not_in_tail_position(Some(Ordering::Strict), |this| this.child_expression(right)); docvec![left, " ", op, " ", right] } pub(super) fn bin_op_with_doc_operands( &mut self, name: BinOp, left: Document<'a>, right: Document<'a>, type_: &Arc, ) -> Document<'a> { match name { BinOp::And => docvec![left, " && ", right], BinOp::Or => docvec![left, " || ", right], BinOp::LtInt | BinOp::LtFloat => docvec![left, " < ", right], BinOp::LtEqInt | BinOp::LtEqFloat => docvec![left, " <= ", right], BinOp::Eq => self.equal_with_doc_operands(left, right, type_.clone(), true), BinOp::NotEq => self.equal_with_doc_operands(left, right, type_.clone(), false), BinOp::GtInt | BinOp::GtFloat => docvec![left, " > ", right], BinOp::GtEqInt | BinOp::GtEqFloat => docvec![left, " >= ", right], BinOp::Concatenate | BinOp::AddInt | BinOp::AddFloat => { docvec![left, " + ", right] } BinOp::SubInt | BinOp::SubFloat => docvec![left, " - ", right], BinOp::MultInt | BinOp::MultFloat => docvec![left, " * ", right], BinOp::RemainderInt => { self.tracker.int_remainder_used = true; docvec!["remainderInt", wrap_arguments([left, right])] } BinOp::DivInt => { self.tracker.int_division_used = true; docvec!["divideInt", wrap_arguments([left, right])] } BinOp::DivFloat => { self.tracker.float_division_used = true; docvec!["divideFloat", wrap_arguments([left, right])] } } } fn todo(&mut self, message: Option<&'a TypedExpr>, location: &'a SrcSpan) -> Document<'a> { let message = match message { Some(m) => self.not_in_tail_position(None, |this| this.wrap_expression(m)), None => string("`todo` expression evaluated. This code has not yet been implemented."), }; self.throw_error("todo", &message, *location, vec![]) } fn panic(&mut self, location: &'a SrcSpan, message: Option<&'a TypedExpr>) -> Document<'a> { let message = match message { Some(m) => self.not_in_tail_position(None, |this| this.wrap_expression(m)), None => string("`panic` expression evaluated."), }; self.throw_error("panic", &message, *location, vec![]) } pub(crate) fn throw_error( &mut self, error_name: &'a str, message: &Document<'a>, location: SrcSpan, fields: Fields, ) -> Document<'a> where Fields: IntoIterator)>, { self.tracker.make_error_used = true; let module = self.module_name.clone().to_doc().surround('"', '"'); let function = self.function_name.clone().to_doc().surround("\"", "\""); let line = self.line_numbers.line_number(location.start).to_doc(); let fields = wrap_object(fields.into_iter().map(|(k, v)| (k.to_doc(), Some(v)))); docvec![ "throw makeError", wrap_arguments([ string(error_name), "FILEPATH".to_doc(), module, line, function, message.clone(), fields ]), ] } fn module_select( &mut self, module: &'a str, label: &'a EcoString, constructor: &'a ModuleValueConstructor, ) -> Document<'a> { match constructor { ModuleValueConstructor::Fn { .. } | ModuleValueConstructor::Constant { .. } => { docvec!["$", module, ".", maybe_escape_identifier(label)] } ModuleValueConstructor::Record { name, arity, type_, .. } => record_constructor(type_.clone(), Some(module), name, *arity, self.tracker), } } fn echo( &mut self, expression: Document<'a>, message: Option<&'a TypedExpr>, location: &'a SrcSpan, ) -> Document<'a> { self.tracker.echo_used = true; let message = match message { Some(message) => self .not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(message)), None => "undefined".to_doc(), }; let echo_arguments = call_arguments(vec![ expression, message, self.src_path.clone().to_doc(), self.line_numbers.line_number(location.start).to_doc(), ]); self.wrap_return(docvec!["echo", echo_arguments]) } pub(crate) fn constant_expression( &mut self, context: Context, expression: &'a TypedConstant, ) -> Document<'a> { match expression { Constant::Int { value, .. } => int(value), Constant::Float { value, .. } => float(value), Constant::String { value, .. } => string(value), Constant::Tuple { elements, .. } => array( elements .iter() .map(|element| self.constant_expression(context, element)), ), Constant::List { elements, tail, .. } => { self.tracker.list_used = true; let tail_elements = tail .as_deref() .and_then(|tail| tail.list_elements()) .unwrap_or_default(); let list = list( elements .iter() .chain(tail_elements) .map(|element| self.constant_expression(context, element)), ); match context { Context::Constant => docvec!["/* @__PURE__ */ ", list], Context::Guard => list, } } Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => { "true".to_doc() } Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => { "false".to_doc() } Constant::Record { type_, .. } if type_.is_nil() => "undefined".to_doc(), Constant::Record { arguments, module, name, tag, type_, .. } => { if module.is_none() && type_.is_result() { if tag == "Ok" { self.tracker.ok_used = true; } else { self.tracker.error_used = true; } } // If there's no arguments and the type is a function that takes // arguments then this is the constructor being referenced, not the // function being called. if let Some(arity) = type_.fn_arity() && arguments.is_empty() && arity != 0 { let arity = arity as u16; return record_constructor(type_.clone(), None, name, arity, self.tracker); } // Record updates are fully expanded during type checking, so we just handle arguments let field_values = arguments .iter() .map(|argument| self.constant_expression(context, &argument.value)) .collect_vec(); let constructor = construct_record( module.as_ref().map(|(module, _)| module.as_str()), name, field_values, ); match context { Context::Constant => docvec!["/* @__PURE__ */ ", constructor], Context::Guard => constructor, } } Constant::BitArray { segments, .. } => { let bit_array = self.constant_bit_array(segments, context); match context { Context::Constant => docvec!["/* @__PURE__ */ ", bit_array], Context::Guard => bit_array, } } Constant::Var { name, module, .. } => { match (module, context) { (None, Context::Guard) => self.local_var(name).to_doc(), (None, Context::Constant) => maybe_escape_identifier(name).to_doc(), (Some((module, _)), _) => { // JS keywords can be accessed here, but we must escape anyway // as we escape when exporting such names in the first place, // and the imported name has to match the exported name. docvec!["$", module, ".", maybe_escape_identifier(name)] } } } Constant::StringConcatenation { left, right, .. } => { let left = self.constant_expression(context, left); let right = self.constant_expression(context, right); docvec![left, " + ", right] } Constant::RecordUpdate { .. } => { panic!("record updates should not reach code generation") } Constant::Invalid { .. } => { panic!("invalid constants should not reach code generation") } } } fn constant_bit_array( &mut self, segments: &'a [TypedConstantBitArraySegment], context: Context, ) -> Document<'a> { self.tracker.bit_array_literal_used = true; let segments_array = array(segments.iter().map(|segment| { let value = match context { Context::Constant => self.constant_expression(context, &segment.value), Context::Guard => self.guard_constant_expression(&segment.value), }; let details = self.constant_bit_array_segment_details(segment, context); match details.type_ { BitArraySegmentType::BitArray => { if segment.size().is_some() { self.tracker.bit_array_slice_used = true; docvec!["bitArraySlice(", value, ", 0, ", details.size, ")"] } else { value } } BitArraySegmentType::Int => match (details.size_value, segment.value.as_ref()) { (Some(size_value), Constant::Int { int_value, .. }) if size_value <= SAFE_INT_SEGMENT_MAX_SIZE.into() && (&size_value % BigInt::from(8) == BigInt::ZERO) => { let bytes = bit_array_segment_int_value_to_bytes( int_value.clone(), size_value, segment.endianness(), ); u8_slice(&bytes) } (Some(size_value), _) if size_value == 8.into() => value, (Some(size_value), _) if size_value <= 0.into() => nil(), _ => { self.tracker.sized_integer_segment_used = true; let size = details.size; let is_big = bool(segment.endianness().is_big()); docvec!["sizedInt(", value, ", ", size, ", ", is_big, ")"] } }, BitArraySegmentType::Float => { self.tracker.float_bit_array_segment_used = true; let size = details.size; let is_big = bool(details.endianness.is_big()); docvec!["sizedFloat(", value, ", ", size, ", ", is_big, ")"] } BitArraySegmentType::String(StringEncoding::Utf8) => { self.tracker.string_bit_array_segment_used = true; docvec!["stringBits(", value, ")"] } BitArraySegmentType::String(StringEncoding::Utf16) => { self.tracker.string_utf16_bit_array_segment_used = true; let is_big = bool(details.endianness.is_big()); docvec!["stringToUtf16(", value, ", ", is_big, ")"] } BitArraySegmentType::String(StringEncoding::Utf32) => { self.tracker.string_utf32_bit_array_segment_used = true; let is_big = bool(details.endianness.is_big()); docvec!["stringToUtf32(", value, ", ", is_big, ")"] } BitArraySegmentType::UtfCodepoint(StringEncoding::Utf8) => { self.tracker.codepoint_bit_array_segment_used = true; docvec!["codepointBits(", value, ")"] } BitArraySegmentType::UtfCodepoint(StringEncoding::Utf16) => { self.tracker.codepoint_utf16_bit_array_segment_used = true; let is_big = bool(details.endianness.is_big()); docvec!["codepointToUtf16(", value, ", ", is_big, ")"] } BitArraySegmentType::UtfCodepoint(StringEncoding::Utf32) => { self.tracker.codepoint_utf32_bit_array_segment_used = true; let is_big = bool(details.endianness.is_big()); docvec!["codepointToUtf32(", value, ", ", is_big, ")"] } } })); docvec!["toBitArray(", segments_array, ")"] } fn constant_bit_array_segment_details( &mut self, segment: &'a TypedConstantBitArraySegment, context: Context, ) -> BitArraySegmentDetails<'a> { let size = segment.size(); let unit = segment.unit(); let (size_value, size) = match size { Some(Constant::Int { int_value, .. }) => { let size_value = int_value * unit; let size = eco_format!("{}", size_value).to_doc(); (Some(size_value), size) } Some(size) => { let mut size = match context { Context::Constant => self.constant_expression(context, size), Context::Guard => self.guard_constant_expression(size), }; if unit != 1 { size = size.group().append(" * ".to_doc().append(unit.to_doc())); } (None, size) } None => { let size_value: usize = if segment.type_.is_int() { 8 } else { 64 }; (Some(BigInt::from(size_value)), docvec![size_value]) } }; let type_ = BitArraySegmentType::from_segment(segment); BitArraySegmentDetails { type_, size, size_value, endianness: segment.endianness(), } } pub(crate) fn guard(&mut self, guard: &'a TypedClauseGuard) -> Document<'a> { match guard { ClauseGuard::Block { value, .. } => self.guard(value).surround("(", ")"), ClauseGuard::BinaryOperator { left, right, operator, .. } => { let left_document = self.wrapped_guard(left); let right_document = self.wrapped_guard(right); let operator = match operator { BinOp::Eq if is_js_scalar(left.type_()) => "===", BinOp::NotEq if is_js_scalar(left.type_()) => "!==", BinOp::Eq | BinOp::NotEq => { let should_be_equal = *operator == BinOp::Eq; // Handle singleton equality optimization for guards if let Some(doc) = self.singleton_variant_guard_equality(left, right, should_be_equal) { return doc; } if let Some(doc) = self.singleton_variant_guard_equality(right, left, should_be_equal) { return doc; } let left_doc = self.guard(left); let right_doc = self.guard(right); return self.prelude_equal_call(should_be_equal, left_doc, right_doc); } BinOp::GtFloat | BinOp::GtInt => ">", BinOp::GtEqFloat | BinOp::GtEqInt => ">=", BinOp::LtFloat | BinOp::LtInt => "<", BinOp::LtEqFloat | BinOp::LtEqInt => "<=", BinOp::AddFloat | BinOp::AddInt | BinOp::Concatenate => "+", BinOp::SubFloat | BinOp::SubInt => "-", BinOp::MultFloat | BinOp::MultInt => "*", BinOp::DivFloat => { self.tracker.float_division_used = true; return docvec![ "divideFloat", wrap_arguments([left_document, right_document]) ]; } BinOp::DivInt => { self.tracker.int_division_used = true; return docvec![ "divideInt", wrap_arguments([left_document, right_document]) ]; } BinOp::RemainderInt => { self.tracker.int_remainder_used = true; return docvec![ "remainderInt", wrap_arguments([left_document, right_document]) ]; } BinOp::And => "&&", BinOp::Or => "||", }; docvec![left_document, " ", operator, " ", right_document] } ClauseGuard::Var { name, .. } => self.local_var(name).to_doc(), ClauseGuard::TupleIndex { tuple, index, .. } => { docvec![self.guard(tuple,), "[", index, "]"] } ClauseGuard::FieldAccess { label, container, .. } => docvec![self.guard(container), ".", maybe_escape_property(label)], ClauseGuard::ModuleSelect { module_alias, label, .. } => docvec!["$", module_alias, ".", label], ClauseGuard::Not { expression, .. } => docvec!["!", self.guard(expression,)], ClauseGuard::Constant(constant) => self.guard_constant_expression(constant), } } fn singleton_variant_guard_equality( &mut self, left: &'a TypedClauseGuard, right: &'a TypedClauseGuard, should_be_equal: bool, ) -> Option> { if let ClauseGuard::Constant(Constant::Record { record_constructor: Some(constructor), module, name, .. }) = right && let ValueConstructorVariant::Record { arity: 0, .. } = constructor.variant { let left_doc = self.guard(left); return Some(self.singleton_equal( left_doc, module.as_ref().map(|(module, _)| module.as_str()), name, should_be_equal, )); } None } fn wrapped_guard(&mut self, guard: &'a TypedClauseGuard) -> Document<'a> { match guard { ClauseGuard::Var { .. } | ClauseGuard::TupleIndex { .. } | ClauseGuard::Constant(_) | ClauseGuard::Not { .. } | ClauseGuard::FieldAccess { .. } | ClauseGuard::Block { .. } => self.guard(guard), ClauseGuard::BinaryOperator { .. } | ClauseGuard::ModuleSelect { .. } => { docvec!["(", self.guard(guard), ")"] } } } fn guard_constant_expression(&mut self, expression: &'a TypedConstant) -> Document<'a> { match expression { Constant::Tuple { elements, .. } => array( elements .iter() .map(|element| self.guard_constant_expression(element)), ), Constant::List { elements, .. } => { self.tracker.list_used = true; list( elements .iter() .map(|element| self.guard_constant_expression(element)), ) } Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => { "true".to_doc() } Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => { "false".to_doc() } Constant::Record { type_, .. } if type_.is_nil() => "undefined".to_doc(), Constant::Record { arguments, module, name, tag, type_, .. } => { if module.is_none() && type_.is_result() { if tag == "Ok" { self.tracker.ok_used = true; } else { self.tracker.error_used = true; } } // If there's no arguments and the type is a function that takes // arguments then this is the constructor being referenced, not the // function being called. if let Some(arity) = type_.fn_arity() && arguments.is_empty() && arity != 0 { let arity = arity as u16; return record_constructor(type_.clone(), None, name, arity, self.tracker); } // Record updates are fully expanded during type checking, so we just // handle arguments let field_values = arguments .iter() .map(|argument| self.guard_constant_expression(&argument.value)) .collect_vec(); construct_record( module.as_ref().map(|(module, _)| module.as_str()), name, field_values, ) } Constant::BitArray { segments, .. } => { self.constant_bit_array(segments, Context::Guard) } Constant::Var { name, .. } => self.local_var(name).to_doc(), Constant::Int { .. } | Constant::Float { .. } | Constant::String { .. } | Constant::RecordUpdate { .. } | Constant::StringConcatenation { .. } | Constant::Invalid { .. } => self.constant_expression(Context::Guard, expression), } } } #[derive(Clone, Copy)] enum AssertExpression { Literal, Expression, Unevaluated, } impl AssertExpression { fn from_expression(expression: &TypedExpr) -> Self { if expression.is_literal() { Self::Literal } else { Self::Expression } } } pub fn int(value: &str) -> Document<'_> { eco_string_int(value.into()) } pub fn eco_string_int<'a>(value: EcoString) -> Document<'a> { let mut out = EcoString::with_capacity(value.len()); if value.starts_with('-') { out.push('-'); } else if value.starts_with('+') { out.push('+'); }; let value = value.trim_start_matches(['+', '-'].as_ref()); let value = if value.starts_with("0x") { out.push_str("0x"); value.trim_start_matches("0x") } else if value.starts_with("0o") { out.push_str("0o"); value.trim_start_matches("0o") } else if value.starts_with("0b") { out.push_str("0b"); value.trim_start_matches("0b") } else { value }; let value = value.trim_start_matches(['0', '_']); if value.is_empty() { out.push('0'); } out.push_str(value); out.to_doc() } pub fn float(value: &str) -> Document<'_> { let mut out = EcoString::with_capacity(value.len()); if value.starts_with('-') { out.push('-'); } else if value.starts_with('+') { out.push('+'); }; let value = value.trim_start_matches(['+', '-'].as_ref()); let value = value.trim_start_matches(['0', '_']); if value.starts_with(['.', 'e', 'E']) { out.push('0'); } out.push_str(value); out.to_doc() } pub fn float_from_value(value: f64) -> Document<'static> { if value.is_infinite() { if value.is_sign_positive() { "Infinity".to_doc() } else { "-Infinity".to_doc() } } else if value.is_nan() { // NOTE: this case is probably unnecessary, as this function is only // invoked with `LiteralFloatValue` values, which cannot be nan. "NaN".to_doc() } else { value.to_doc() } } /// The context where the constant expression is used, it might be inside a /// function call, or in the definition of another constant. /// /// Based on the context we might want to annotate pure function calls as /// "@__PURE__". /// #[derive(Debug, Clone, Copy)] pub enum Context { Constant, Guard, } #[derive(Debug)] struct BitArraySegmentDetails<'a> { type_: BitArraySegmentType, size: Document<'a>, /// The size of the bit array segment stored as a BigInt. /// This has a value when the segment's size is known at compile time. size_value: Option, endianness: Endianness, } #[derive(Debug, Clone, Copy)] enum BitArraySegmentType { BitArray, Int, Float, String(StringEncoding), UtfCodepoint(StringEncoding), } impl BitArraySegmentType { fn from_segment(segment: &BitArraySegment>) -> Self { if segment.type_.is_int() { BitArraySegmentType::Int } else if segment.type_.is_float() { BitArraySegmentType::Float } else if segment.type_.is_bit_array() { BitArraySegmentType::BitArray } else if segment.type_.is_string() { let encoding = if segment.has_utf16_option() { StringEncoding::Utf16 } else if segment.has_utf32_option() { StringEncoding::Utf32 } else { StringEncoding::Utf8 }; BitArraySegmentType::String(encoding) } else if segment.type_.is_utf_codepoint() { let encoding = if segment.has_utf16_codepoint_option() { StringEncoding::Utf16 } else if segment.has_utf32_codepoint_option() { StringEncoding::Utf32 } else { StringEncoding::Utf8 }; BitArraySegmentType::UtfCodepoint(encoding) } else { panic!( "Invalid bit array segment type reached code generation: {:?}", segment.type_ ); } } } pub fn string(value: &str) -> Document<'_> { if value.contains('\n') { EcoString::from(value.replace('\n', r"\n")) .to_doc() .surround("\"", "\"") } else { value.to_doc().surround("\"", "\"") } } pub(crate) fn array<'a, Elements: IntoIterator>>( elements: Elements, ) -> Document<'a> { let elements = Itertools::intersperse(elements.into_iter(), break_(",", ", ")).collect_vec(); if elements.is_empty() { // Do not add a trailing comma since that adds an 'undefined' element "[]".to_doc() } else { docvec![ "[", docvec![break_("", ""), elements].nest(INDENT), break_(",", ""), "]" ] .group() } } pub(crate) fn list<'a, I: IntoIterator>>(elements: I) -> Document<'a> where I::IntoIter: DoubleEndedIterator, { let array = array(elements); docvec!["toList(", array, ")"] } fn prepend<'a, I: IntoIterator>>( elements: I, tail: Document<'a>, ) -> Document<'a> where I::IntoIter: DoubleEndedIterator + ExactSizeIterator, { elements.into_iter().rev().fold(tail, |tail, element| { let arguments = call_arguments([element, tail]); docvec!["listPrepend", arguments] }) } fn call_arguments<'a, Elements: IntoIterator>>( elements: Elements, ) -> Document<'a> { let elements = Itertools::intersperse(elements.into_iter(), break_(",", ", ")) .collect_vec() .to_doc(); if elements.is_empty() { return "()".to_doc(); } docvec![ "(", docvec![break_("", ""), elements].nest(INDENT), break_(",", ""), ")" ] .group() } pub(crate) fn construct_record<'a>( module: Option<&'a str>, name: &'a str, arguments: impl IntoIterator>, ) -> Document<'a> { let mut any_arguments = false; let arguments = join( arguments.into_iter().inspect(|_| { any_arguments = true; }), break_(",", ", "), ); let arguments = docvec![break_("", ""), arguments].nest(INDENT); let name = if let Some(module) = module { docvec!["$", module, ".", name] } else { name.to_doc() }; if any_arguments { docvec!["new ", name, "(", arguments, break_(",", ""), ")"].group() } else { docvec!["new ", name, "()"] } } impl TypedExpr { fn handles_own_return(&self) -> bool { match self { TypedExpr::Todo { .. } | TypedExpr::Call { .. } | TypedExpr::Case { .. } | TypedExpr::Panic { .. } | TypedExpr::Block { .. } | TypedExpr::Echo { .. } | TypedExpr::Pipeline { .. } | TypedExpr::RecordUpdate { .. } => true, TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::BinOp { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::BitArray { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => false, } } } impl BinOp { fn is_operator_to_wrap(&self) -> bool { match self { BinOp::And | BinOp::Or | BinOp::Eq | BinOp::NotEq | BinOp::LtInt | BinOp::LtEqInt | BinOp::LtFloat | BinOp::LtEqFloat | BinOp::GtEqInt | BinOp::GtInt | BinOp::GtEqFloat | BinOp::GtFloat | BinOp::AddInt | BinOp::AddFloat | BinOp::SubInt | BinOp::SubFloat | BinOp::MultFloat | BinOp::DivInt | BinOp::DivFloat | BinOp::RemainderInt | BinOp::Concatenate => true, BinOp::MultInt => false, } } } pub fn is_js_scalar(t: Arc) -> bool { t.is_int() || t.is_float() || t.is_bool() || t.is_nil() || t.is_string() } fn requires_semicolon(statement: &TypedStatement) -> bool { match statement { Statement::Expression( TypedExpr::Int { .. } | TypedExpr::Fn { .. } | TypedExpr::Var { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::Echo { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::BinOp { .. } | TypedExpr::Tuple { .. } | TypedExpr::NegateInt { .. } | TypedExpr::BitArray { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::NegateBool { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Block { .. }, ) => true, Statement::Expression( TypedExpr::Todo { .. } | TypedExpr::Case { .. } | TypedExpr::Panic { .. } | TypedExpr::Pipeline { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::Invalid { .. }, ) => false, Statement::Assignment(_) => false, Statement::Use(_) => false, Statement::Assert(_) => false, } } /// Wrap a document in an immediately invoked function expression fn immediately_invoked_function_expression_document(document: Document<'_>) -> Document<'_> { docvec![ docvec!["(() => {", break_("", " "), document].nest(INDENT), break_("", " "), "})()", ] .group() } pub(crate) fn record_constructor<'a>( type_: Arc, qualifier: Option<&'a str>, name: &'a str, arity: u16, tracker: &mut UsageTracker, ) -> Document<'a> { if qualifier.is_none() && type_.is_result_constructor() { if name == "Ok" { tracker.ok_used = true; } else if name == "Error" { tracker.error_used = true; } } if type_.is_bool() && name == "True" { "true".to_doc() } else if type_.is_bool() { "false".to_doc() } else if type_.is_nil() { "undefined".to_doc() } else if arity == 0 { match qualifier { Some(module) => docvec!["new $", module, ".", name, "()"], None => docvec!["new ", name, "()"], } } else { let vars = (0..arity).map(|i| eco_format!("var{i}").to_doc()); let body = docvec![ "return ", construct_record(qualifier, name, vars.clone()), ";" ]; docvec![ docvec![wrap_arguments(vars), " => {", break_("", " "), body] .nest(INDENT) .append(break_("", " ")) .group(), "}", ] } } fn u8_slice<'a>(bytes: &[u8]) -> Document<'a> { let s: EcoString = bytes .iter() .map(u8::to_string) .collect::>() .join(", ") .into(); docvec![s] } ================================================ FILE: compiler-core/src/javascript/import.rs ================================================ use std::collections::{HashMap, HashSet}; use ecow::EcoString; use itertools::Itertools; use crate::{ docvec, javascript::{INDENT, JavaScriptCodegenTarget}, pretty::{Document, Documentable, break_, concat, join, line}, }; /// A collection of JavaScript import statements from Gleam imports and from /// external functions, to be rendered into a JavaScript module. /// #[derive(Debug, Default)] pub(crate) struct Imports<'a> { imports: HashMap>, exports: HashSet, } impl<'a> Imports<'a> { pub fn new() -> Self { Self::default() } pub fn register_export(&mut self, export: EcoString) { let _ = self.exports.insert(export); } pub fn register_module( &mut self, path: EcoString, aliases: impl IntoIterator, unqualified_imports: impl IntoIterator>, ) { let import = self .imports .entry(path.clone()) .or_insert_with(|| Import::new(path.clone())); import.aliases.extend(aliases); import.unqualified.extend(unqualified_imports) } pub fn into_doc(self, codegen_target: JavaScriptCodegenTarget) -> Document<'a> { let imports = concat( self.imports .into_values() .sorted_by(|a, b| a.path.cmp(&b.path)) .map(|import| Import::into_doc(import, codegen_target)), ); if self.exports.is_empty() { imports } else { let names = join( self.exports .into_iter() .sorted() .map(|string| string.to_doc()), break_(",", ", "), ); let names = docvec![ docvec![break_("", " "), names].nest(INDENT), break_(",", " ") ] .group(); let export_keyword = match codegen_target { JavaScriptCodegenTarget::JavaScript => "export {", JavaScriptCodegenTarget::TypeScriptDeclarations => "export type {", }; imports .append(line()) .append(export_keyword) .append(names) .append("};") .append(line()) } } pub fn is_empty(&self) -> bool { self.imports.is_empty() && self.exports.is_empty() } } #[derive(Debug)] struct Import<'a> { path: EcoString, aliases: HashSet, unqualified: Vec>, } impl<'a> Import<'a> { fn new(path: EcoString) -> Self { Self { path, aliases: Default::default(), unqualified: Default::default(), } } pub fn into_doc(self, codegen_target: JavaScriptCodegenTarget) -> Document<'a> { let path = self.path.to_doc(); let import_modifier = if codegen_target == JavaScriptCodegenTarget::TypeScriptDeclarations { "type " } else { "" }; let alias_imports = concat(self.aliases.into_iter().sorted().map(|alias| { docvec![ "import ", import_modifier, "* as ", alias, " from \"", path.clone(), r#"";"#, line() ] })); if self.unqualified.is_empty() { alias_imports } else { let members = self.unqualified.into_iter().map(Member::into_doc); let members = join(members, break_(",", ", ")); let members = docvec![ docvec![break_("", " "), members].nest(INDENT), break_(",", " ") ] .group(); docvec![ alias_imports, "import ", import_modifier, "{", members, "} from \"", path, r#"";"#, line() ] } } } #[derive(Debug)] pub struct Member<'a> { pub name: Document<'a>, pub alias: Option>, } impl<'a> Member<'a> { fn into_doc(self) -> Document<'a> { match self.alias { None => self.name, Some(alias) => docvec![self.name, " as ", alias], } } } #[test] fn into_doc() { let mut imports = Imports::new(); imports.register_module("./gleam/empty".into(), [], []); imports.register_module( "./multiple/times".into(), ["wibble".into(), "wobble".into()], [], ); imports.register_module("./multiple/times".into(), ["wubble".into()], []); imports.register_module( "./multiple/times".into(), [], [Member { name: "one".to_doc(), alias: None, }], ); imports.register_module( "./other".into(), [], [ Member { name: "one".to_doc(), alias: None, }, Member { name: "one".to_doc(), alias: Some("onee".to_doc()), }, Member { name: "two".to_doc(), alias: Some("twoo".to_doc()), }, ], ); imports.register_module( "./other".into(), [], [ Member { name: "three".to_doc(), alias: None, }, Member { name: "four".to_doc(), alias: None, }, ], ); imports.register_module( "./zzz".into(), [], [ Member { name: "one".to_doc(), alias: None, }, Member { name: "two".to_doc(), alias: None, }, ], ); assert_eq!( line() .append(imports.into_doc(JavaScriptCodegenTarget::JavaScript)) .to_pretty_string(40), r#" import * as wibble from "./multiple/times"; import * as wobble from "./multiple/times"; import * as wubble from "./multiple/times"; import { one } from "./multiple/times"; import { one, one as onee, two as twoo, three, four, } from "./other"; import { one, two } from "./zzz"; "# .to_string() ); } ================================================ FILE: compiler-core/src/javascript/tests/assert.rs ================================================ use crate::assert_js; #[test] fn assert_variable() { assert_js!( " pub fn main() { let x = True assert x } " ); } #[test] fn assert_literal() { assert_js!( " pub fn main() { assert False } " ); } #[test] fn assert_binary_operation() { assert_js!( " pub fn main() { let x = True assert x || False } " ); } #[test] fn assert_binary_operation2() { assert_js!( " pub fn eq(a, b) { assert a == b } " ); } #[test] fn assert_binary_operation3() { assert_js!( " pub fn assert_answer(x) { assert x == 42 } " ); } #[test] fn assert_function_call() { assert_js!( " fn bool() { True } pub fn main() { assert bool() } " ); } #[test] fn assert_function_call2() { assert_js!( " fn and(a, b) { a && b } pub fn go(x) { assert and(True, x) } " ); } #[test] fn assert_nested_function_call() { assert_js!( " fn and(x, y) { x && y } pub fn main() { assert and(and(True, False), True) } " ); } #[test] fn assert_binary_operator_with_side_effects() { assert_js!( " fn wibble(a, b) { let result = a + b result == 10 } pub fn go(x) { assert True && wibble(1, 4) } " ); } #[test] fn assert_binary_operator_with_side_effects2() { assert_js!( " fn wibble(a, b) { let result = a + b result == 10 } pub fn go(x) { assert wibble(5, 5) && wibble(4, 6) } " ); } #[test] fn assert_with_message() { assert_js!( r#" pub fn main() { assert True as "This shouldn't fail" } "# ); } #[test] fn assert_with_block_message() { assert_js!( r#" fn identity(a) { a } pub fn main() { assert identity(True) as { let message = identity("This shouldn't fail") message } } "# ); } #[test] fn assert_nil_always_throws() { assert_js!( r#" pub fn go(x: Nil) { let assert Nil = x } "# ); } // https://github.com/gleam-lang/gleam/issues/4643 #[test] fn assert_with_pipe_on_right() { assert_js!( " fn add(a, b) { a + b } pub fn main() { assert 3 == 1 |> add(2) } " ); } #[test] fn prova() { assert_js!( " pub fn main() { assert Ok([]) == Ok([] |> id) } fn id(x) { x } " ) } // https://github.com/gleam-lang/gleam/issues/5251 #[test] fn assert_with_logical_and_binary_rhs_1() { assert_js!( " pub fn main() { assert True && 3 < 4 } " ); } // https://github.com/gleam-lang/gleam/issues/5251 #[test] fn assert_with_logical_and_binary_rhs_2() { assert_js!( " pub fn main() { assert True && \"wibble\" == \"wibble\" } " ); } // https://github.com/gleam-lang/gleam/issues/5251 #[test] fn assert_with_logical_and_binary_rhs_3() { assert_js!( " pub fn main() { assert True && \"wobble\" != \"wobble\" } " ); } // https://github.com/gleam-lang/gleam/issues/5251 #[test] fn assert_with_case_rhs() { assert_js!( " pub fn main() { assert True && case 1 > 2 { True -> True False -> False } } " ); } // https://github.com/gleam-lang/gleam/issues/5251 #[test] fn assert_with_negated_case_rhs() { assert_js!( " pub fn main() { assert True && !case 3 - 2 { 1 -> True _ -> False } } " ); } ================================================ FILE: compiler-core/src/javascript/tests/assignments.rs ================================================ use crate::{assert_js, assert_ts_def}; #[test] fn tuple_matching() { assert_js!( r#" pub fn go(x) { let assert #(1, 2) = x } "#, ) } #[test] fn assert() { assert_js!(r#"pub fn go(x) { let assert 1 = x }"#,); } #[test] fn assert1() { assert_js!(r#"pub fn go(x) { let assert #(1, 2) = x }"#,); } #[test] fn nested_binding() { assert_js!( r#" pub fn go(x) { let assert #(a, #(b, c, 2) as t, _, 1) = x } "#, ) } #[test] fn variable_renaming() { assert_js!( r#" pub fn go(x, wibble) { let a = 1 wibble(a) let a = 2 wibble(a) let assert #(a, 3) = x let b = a wibble(b) let c = { let a = a #(a, b) } wibble(a) // make sure arguments are counted in initial state let x = c x } "#, ) } #[test] fn constant_assignments() { assert_js!( r#" const a = True pub fn go() { a let a = 10 a + 20 } fn second() { let a = 10 a + 20 } "#, ); } #[test] fn returning_literal_subject() { assert_js!(r#"pub fn go(x) { let assert 1 = x + 1 }"#,); } #[test] fn rebound_argument() { assert_js!( r#"pub fn main(x) { let x = False x } "#, ); } #[test] fn rebound_function() { assert_js!( r#"pub fn x() { Nil } pub fn main() { let x = False x } "#, ); } #[test] fn rebound_function_and_arg() { assert_js!( r#"pub fn x() { Nil } pub fn main(x) { let x = False x } "#, ); } #[test] fn variable_used_in_pattern_and_assignment() { assert_js!( r#"pub fn main(x) { let #(x) = #(x) x } "#, ); } // https://github.com/gleam-lang/gleam/issues/1253 #[test] fn correct_variable_renaming_in_assigned_functions() { assert_js!( r#" pub fn debug(x) { let x = x fn(x) { x + 1 } } "#, ); } #[test] fn module_const_var() { assert_js!( r#" pub const int = 42 pub const int_alias = int pub fn use_int_alias() { int_alias } pub const compound: #(Int, Int) = #(int, int_alias) pub fn use_compound() { compound.0 + compound.1 } "# ); } #[test] fn module_const_var1() { assert_ts_def!( r#" pub const int = 42 pub const int_alias = int pub const compound: #(Int, Int) = #(int, int_alias) "# ); } // https://github.com/gleam-lang/gleam/issues/2443 #[test] fn let_assert_string_prefix() { assert_js!( r#" pub fn main() { let assert "Game " <> id = "Game 1" } "# ); } // https://github.com/gleam-lang/gleam/issues/3894 #[test] fn let_assert_nested_string_prefix() { assert_js!( r#" type Wibble { Wibble(wibble: String) } pub fn main() { let assert Wibble(wibble: "w" as prefix <> rest) = Wibble("wibble") prefix <> rest } "# ); } // https://github.com/gleam-lang/gleam/issues/2931 #[test] fn keyword_assignment() { assert_js!( r#" pub fn main() { let class = 10 let debugger = 50 } "# ); } // https://github.com/gleam-lang/gleam/issues/3004 #[test] fn escaped_variables_in_constants() { assert_js!( r#" pub const class = 5 pub const something = class "# ); } #[test] fn message() { assert_js!( r#" pub fn unwrap_or_panic(value) { let assert Ok(inner) = value as "Oops, there was an error" inner } "# ); } #[test] fn variable_message() { assert_js!( r#" pub fn expect(value, message) { let assert Ok(inner) = value as message inner } "# ); } // https://github.com/gleam-lang/gleam/issues/4471 #[test] fn case_message() { assert_js!( r#" pub fn expect(value, message) { let assert Ok(inner) = value as case message { Ok(message) -> message Error(_) -> "No message provided" } inner } "# ); } #[test] fn assert_that_always_succeeds() { assert_js!( r#" type Wibble { Wibble(Int) } pub fn go() { let assert Wibble(n) = Wibble(1) n } "#, ); } #[test] fn assert_that_always_fails() { assert_js!( r#" type Wibble { Wibble(Int) Wobble(Int) } pub fn go() { let assert Wobble(n) = Wibble(1) n } "#, ); } #[test] fn catch_all_assert() { assert_js!( r#" type Wibble { Wibble(Int) Wobble(Int) } pub fn go() { let assert _ = Wibble(1) 1 } "#, ); } #[test] fn assert_with_multiple_variants() { assert_js!( r#" type Wibble { Wibble(Int) Wobble(Int) Woo(Int) } pub fn go() { let assert Wobble(n) = todo n } "#, ); } #[test] fn use_discard_assignment() { assert_js!( r#" type Wibble { Wibble(Int) Wobble(Int) Woo(Int) } fn fun(f) { f(Wibble(1)) } pub fn go() { use _ <- fun 1 } "#, ); } #[test] fn use_matching_assignment() { assert_js!( r#" fn fun(f) { f(#(2, 4)) } pub fn go() { use #(_, n) <- fun n } "#, ); } ================================================ FILE: compiler-core/src/javascript/tests/bit_arrays.rs ================================================ use hexpm::version::Version; use pubgrub::Range; use crate::{ assert_js, assert_js_no_warnings_with_gleam_version, assert_js_warnings_with_gleam_version, assert_ts_def, }; #[test] fn empty() { assert_js!( r#" pub fn go() { <<>> } "#, ); } #[test] fn one() { assert_js!( r#" pub fn go() { <<256>> } "#, ); } #[test] fn two() { assert_js!( r#" pub fn go() { <<256, 4>> } "#, ); } #[test] fn integer() { assert_js!( r#" pub fn go() { <<256:int>> } "#, ); } #[test] fn float() { assert_js!( r#" pub fn go() { <<1.1:float>> } "#, ); } #[test] fn float_big_endian() { assert_js!( r#" pub fn go() { <<1.1:float-big>> } "#, ); } #[test] fn float_little_endian() { assert_js!( r#" pub fn go() { <<1.1:float-little>> } "#, ); } #[test] fn float_sized() { assert_js!( r#" pub fn go() { <<1.1:float-32>> } "#, ); } #[test] fn float_sized_big_endian() { assert_js!( r#" pub fn go() { <<1.1:float-32-big>> } "#, ); } #[test] fn float_sized_little_endian() { assert_js!( r#" pub fn go() { <<1.1:float-32-little>> } "#, ); } #[test] fn sized_constant_value() { assert_js!( r#" pub fn go() { <<256:64>> } "#, ); } #[test] fn sized_dynamic_value() { assert_js!( r#" pub fn go(i: Int) { <> } "#, ); } #[test] fn sized_constant_value_positive_overflow() { assert_js!( r#" pub fn go() { <<80_000:16>> } "#, ); } #[test] fn sized_constant_value_negative_overflow() { assert_js!( r#" pub fn go() { <<-80_000:16>> } "#, ); } #[test] fn sized_constant_value_max_size_for_compile_time_evaluation() { assert_js!( r#" pub fn go() { <<-1:48>> } "#, ); } #[test] fn sized_big_endian_constant_value() { assert_js!( r#" pub fn go() { <<256:16-big>> } "#, ); } #[test] fn sized_big_endian_dynamic_value() { assert_js!( r#" pub fn go(i: Int) { <> } "#, ); } #[test] fn sized_little_endian_constant_value() { assert_js!( r#" pub fn go() { <<256:16-little>> } "#, ); } #[test] fn sized_little_endian_dynamic_value() { assert_js!( r#" pub fn go(i: Int) { <> } "#, ); } #[test] fn explicit_sized_constant_value() { assert_js!( r#" pub fn go() { <<256:size(32)>> } "#, ); } #[test] fn explicit_sized_dynamic_value() { assert_js!( r#" pub fn go(i: Int) { <> } "#, ); } #[test] fn variable_sized() { assert_js!( r#" pub fn go(x, y) { <> } "#, ); } #[test] fn variable() { assert_js!( r#" pub fn go(x) { <<256, 4, x>> } "#, ); } #[test] fn utf8() { assert_js!( r#" pub fn go(x) { <<256, 4, x, "Gleam":utf8>> } "#, ); } #[test] fn match_utf8_with_escape_chars() { assert_js!( r#" pub fn go(x) { let assert <<"\"\\\r\n\t\f\u{1f600}">> = x } "#, ); } #[test] fn match_utf8() { assert_js!( r#" pub fn go(x) { let assert <<"Gleam 👍":utf8>> = x } "#, ); } #[test] fn match_case_utf8_with_escape_chars() { assert_js!( r#" pub fn go(x) { case x { <<"\"\\\r\n\t\f\u{1f600}">> -> 1 _ -> 2 } } "#, ); } #[test] fn match_case_utf8() { assert_js!( r#" pub fn go(x) { case x { <<"Gleam 👍":utf8>> -> 1 _ -> 2 } } "#, ); } #[test] fn utf8_codepoint() { assert_js!( r#" pub fn go(x) { <> } "#, ); } #[test] fn utf8_codepoint_typescript() { assert_ts_def!( r#" pub fn go(x) { <> } "#, ); } #[test] fn bit_string() { assert_js!( r#" pub fn go(x) { <> } "#, ); } #[test] fn bits() { assert_js!( r#" pub fn go(x) { <> } "#, ); } #[test] fn bit_array_sliced() { assert_js!( r#" pub fn go(x) { <<<<0xAB>>:bits-4>> } "#, ); } #[test] fn bit_array_dynamic_slice() { assert_js!( r#" pub fn go(x) { let i = 4 <<<<0xAB>>:bits-size(i)>> } "#, ); } #[test] fn bit_string_typescript() { assert_ts_def!( r#" pub fn go(x) { <> } "#, ); } #[test] fn bits_typescript() { assert_ts_def!( r#" pub fn go(x) { <> } "#, ); } #[test] fn empty_match() { assert_js!( r#" pub fn go(x) { let assert <<>> = x } "#, ); } #[test] fn case_empty_match() { assert_js!( r#" pub fn go(x) { case x { <<>> -> 1 _ -> 2 } } "#, ); } #[test] fn match_bytes() { assert_js!( r#" pub fn go(x) { let assert <<1, y>> = x } "#, ); } #[test] fn case_match_bytes() { assert_js!( r#" pub fn go(x) { case x { <<1, y>> -> y _ -> 1 } } "#, ); } #[test] fn match_sized() { assert_js!( r#" pub fn go(x) { let assert <> = x } "#, ); } #[test] fn case_match_sized() { assert_js!( r#" pub fn go(x) { case x { <> -> a + b _ -> 1 } } "#, ); } #[test] fn match_sized_unaligned() { assert_js!( r#" pub fn go(x) { let assert <> = x } "#, ); } #[test] fn case_match_sized_unaligned() { assert_js!( r#" pub fn go(x) { case x { <> -> b * 2 _ -> 1 } } "#, ); } #[test] fn match_sized_constant_pattern() { assert_js!( r#" pub fn go(x) { let assert <<1234:16, 123:8>> = x } "#, ); } #[test] fn case_match_sized_constant_pattern() { assert_js!( r#" pub fn go(x) { case x { <<1234:16, 123:8>> -> 1 _ -> 2 } } "#, ); } #[test] fn match_unsigned() { assert_js!( r#" pub fn go(x) { let assert <> = x } "#, ); } #[test] fn case_match_unsigned() { assert_js!( r#" pub fn go(x) { case x { <> -> a _ -> 1 } } "#, ); } #[test] fn match_unsigned_constant_pattern() { assert_js!( r#" pub fn go(x) { let assert <<-2:unsigned>> = x } "#, ); } #[test] fn case_match_unsigned_constant_pattern() { assert_js!( r#" pub fn go(x) { case x { <<-2:unsigned>> -> 1 _ -> 2 } } "#, ); } #[test] fn match_signed() { assert_js!( r#" pub fn go(x) { let assert <> = x } "#, ); } #[test] fn case_match_signed() { assert_js!( r#" pub fn go(x) { case x { <> -> a _ -> 1 } } "#, ); } #[test] fn match_signed_constant_pattern() { assert_js!( r#" pub fn go(x) { let assert <<-1:signed>> = x } "#, ); } #[test] fn case_match_signed_constant_pattern() { assert_js!( r#" pub fn go(x) { case x { <<-1:signed>> -> 1 _ -> 2 } } "#, ); } #[test] fn match_sized_big_endian() { assert_js!( r#" pub fn go(x) { let assert <> = x } "#, ); } #[test] fn case_match_sized_big_endian() { assert_js!( r#" pub fn go(x) { case x { <> -> a _ -> 1 } } "#, ); } #[test] fn match_sized_big_endian_constant_pattern() { assert_js!( r#" pub fn go(x) { let assert <<1234:16-big>> = x } "#, ); } #[test] fn case_match_sized_big_endian_constant_pattern() { assert_js!( r#" pub fn go(x) { case x { <<1234:16-big>> -> 1 _ -> 2 } } "#, ); } #[test] fn match_sized_little_endian() { assert_js!( r#" pub fn go(x) { let assert <> = x } "#, ); } #[test] fn case_match_sized_little_endian() { assert_js!( r#" pub fn go(x) { case x { <> -> a _ -> 1 } } "#, ); } #[test] fn match_sized_little_endian_constant_pattern() { assert_js!( r#" pub fn go(x) { let assert <<1234:16-little>> = x } "#, ); } #[test] fn case_match_sized_little_endian_constant_pattern() { assert_js!( r#" pub fn go(x) { case x { <<1234:16-little>> -> 1 _ -> 2 } } "#, ); } #[test] fn match_sized_big_endian_unsigned() { assert_js!( r#" pub fn go(x) { let assert <> = x } "#, ); } #[test] fn case_match_sized_big_endian_unsigned() { assert_js!( r#" pub fn go(x) { case x { <> -> a _ -> 1 } } "#, ); } #[test] fn match_sized_big_endian_unsigned_constant_pattern() { assert_js!( r#" pub fn go(x) { let assert <<1234:16-big-unsigned>> = x } "#, ); } #[test] fn case_match_sized_big_endian_unsigned_constant_pattern() { assert_js!( r#" pub fn go(x) { case x { <<1234:16-big-unsigned>> -> 1 _ -> 2 } } "#, ); } #[test] fn match_sized_big_endian_signed() { assert_js!( r#" pub fn go(x) { let assert <> = x } "#, ); } #[test] fn case_match_sized_big_endian_signed() { assert_js!( r#" pub fn go(x) { case x { <> -> a _ -> 1 } } "#, ); } #[test] fn match_sized_big_endian_signed_constant_pattern() { assert_js!( r#" pub fn go(x) { let assert <<1234:16-big-signed>> = x } "#, ); } #[test] fn case_match_sized_big_endian_signed_constant_pattern() { assert_js!( r#" pub fn go(x) { case x { <<1234:16-big-signed>> -> 1 _ -> 2 } } "#, ); } #[test] fn match_sized_little_endian_unsigned() { assert_js!( r#" pub fn go(x) { let assert <> = x } "#, ); } #[test] fn case_match_sized_little_endian_unsigned() { assert_js!( r#" pub fn go(x) { case x { <> -> a _ -> 1 } } "#, ); } #[test] fn match_sized_little_endian_unsigned_constant_pattern() { assert_js!( r#" pub fn go(x) { let assert <<1234:16-little-unsigned>> = x } "#, ); } #[test] fn case_match_sized_little_endian_unsigned_constant_pattern() { assert_js!( r#" pub fn go(x) { case x { <<1234:16-little-unsigned>> -> 1 _ -> 2 } } "#, ); } #[test] fn match_sized_little_endian_signed() { assert_js!( r#" pub fn go(x) { let assert <> = x } "#, ); } #[test] fn case_match_sized_little_endian_signed() { assert_js!( r#" pub fn go(x) { case x { <> -> a _ -> 1 } } "#, ); } #[test] fn match_sized_little_endian_signed_constant_pattern() { assert_js!( r#" pub fn go(x) { let assert <<1234:16-little-signed>> = x } "#, ); } #[test] fn case_match_sized_little_endian_signed_constant_pattern() { assert_js!( r#" pub fn go(x) { case x { <<1234:16-little-signed>> -> 1 _ -> 2 } } "#, ); } #[test] fn match_dynamic_size() { assert_js!( r#" pub fn go(x) { let n = 16 let assert <> = x } "# ); } #[test] fn case_match_dynamic_size() { assert_js!( r#" pub fn go(x) { let n = 16 case x { <> -> a _ -> 1 } } "# ); } #[test] fn match_dynamic_size_with_other_segments() { assert_js!( r#" pub fn go(x) { let n = 16 let m = 32 let assert <> = x } "# ); } #[test] fn case_match_dynamic_size_with_other_segments() { assert_js!( r#" pub fn go(x) { let n = 16 let m = 32 case x { <> -> first + a + b _ -> 1 } } "# ); } #[test] fn match_dynamic_size_shadowed_variable() { assert_js!( r#" pub fn go(x) { let n = 16 let n = 5 let assert <> = x } "# ); } #[test] fn case_match_dynamic_size_shadowed_variable() { assert_js!( r#" pub fn go(x) { let n = 16 let n = 5 case x { <> -> a _ -> 1 } } "# ); } #[test] fn match_dynamic_size_literal_value() { assert_js!( r#" pub fn go(x) { let n = 8 let assert <> = x } "# ); } #[test] fn case_match_dynamic_size_literal_value() { assert_js!( r#" pub fn go(x) { let n = 8 case x { <> -> a _ -> 1 } } "# ); } #[test] fn match_dynamic_bits_size() { assert_js!( r#" pub fn go(x) { let n = 16 let assert <> = x } "# ); } #[test] fn case_match_dynamic_bits_size() { assert_js!( r#" pub fn go(x) { let n = 16 case x { <> -> a _ -> x } } "# ); } #[test] fn match_dynamic_bytes_size() { assert_js!( r#" pub fn go(x) { let n = 3 let assert <> = x } "# ); } #[test] fn case_match_dynamic_bytes_size() { assert_js!( r#" pub fn go(x) { let n = 3 case x { <> -> a _ -> x } } "# ); } #[test] fn discard_sized() { assert_js!( r#" pub fn go(x) { let assert <<_:16, _:8>> = x let assert <<_:16-little-signed, _:8>> = x } "#, ); } #[test] fn case_discard_sized() { assert_js!( r#" pub fn go(x) { case x { <<_:16, _:8>> -> 1 _ -> 2 } case x { <<_:16-little-signed, _:8>> -> 1 _ -> 2 } } "#, ); } #[test] fn match_sized_value() { assert_js!( r#" pub fn go(x) { let assert <> = x } "#, ); } #[test] fn case_match_sized_value() { assert_js!( r#" pub fn go(x) { case x { <> -> i _ -> 1 } } "#, ); } #[test] fn match_sized_value_constant_pattern() { assert_js!( r#" pub fn go(x) { let assert <<258:16>> = x } "#, ); } #[test] fn case_match_sized_value_constant_pattern() { assert_js!( r#" pub fn go(x) { case x { <<258:16>> -> 1 _ -> 2 } } "#, ); } #[test] fn match_float() { assert_js!( r#" pub fn go(x) { let assert <> = x } "#, ); } #[test] fn case_match_float() { assert_js!( r#" pub fn go(x) { case x { <> -> #(a, b) _ -> #(1.1, 2) } } "#, ); } #[test] fn match_float_big_endian() { assert_js!( r#" pub fn go(x) { let assert <> = x } "#, ); } #[test] fn case_match_float_big_endian() { assert_js!( r#" pub fn go(x) { case x { <> -> #(a, b) _ -> #(1.1, 1) } } "#, ); } #[test] fn match_float_little_endian() { assert_js!( r#" pub fn go(x) { let assert <> = x } "#, ); } #[test] fn case_match_float_little_endian() { assert_js!( r#" pub fn go(x) { case x { <> -> #(a, b) _ -> #(1.1, 2) } } "#, ); } #[test] fn match_float_sized() { assert_js!( r#" pub fn go(x) { let assert <> = x } "#, ); } #[test] fn case_match_float_sized() { assert_js!( r#" pub fn go(x) { case x { <> -> #(a, b) _ -> #(1.1, 2) } } "#, ); } #[test] fn match_float_sized_big_endian() { assert_js!( r#" pub fn go(x) { let assert <> = x } "#, ); } #[test] fn case_match_float_sized_big_endian() { assert_js!( r#" pub fn go(x) { case x { <> -> #(a, b) _ -> #(1.1, 2) } } "#, ); } #[test] fn match_float_sized_little_endian() { assert_js!( r#" pub fn go(x) { let assert <> = x } "#, ); } #[test] fn case_match_float_sized_little_endian() { assert_js!( r#" pub fn go(x) { case x { <> -> #(a, b) _ -> #(1.1, 2) } } "#, ); } #[test] fn match_literal_float() { assert_js!( r#" pub fn go(x) { let assert <<1.4, b:int>> = x } "#, ); } #[test] fn case_match_literal_float() { assert_js!( r#" pub fn go(x) { case x { <<1.4, b:int>> -> 1 _ -> 2 } } "#, ); } #[test] fn match_literal_unaligned_float() { assert_js!( r#" pub fn go(x) { let n = 1 let assert <<_:size(n), 1.1, _:bits>> = x } "#, ); } #[test] fn case_match_literal_unaligned_float() { assert_js!( r#" pub fn go(x) { let n = 1 case x { <<_:size(n), 1.1, _:int>> -> 1 _ -> 2 } } "#, ); } #[test] fn match_literal_aligned_float() { assert_js!( r#" pub fn go(x) { let assert <<_, 1.1, _:bits>> = x } "#, ); } #[test] fn case_match_literal_aligned_float() { assert_js!( r#" pub fn go(x) { case x { <<_, 1.1, _:int>> -> 1 _ -> 2 } } "#, ); } #[test] fn match_float_16_bit() { assert_js!( r#" pub fn go(x) { let assert <> = x } "# ); } #[test] fn case_match_float_16_bit() { assert_js!( r#" pub fn go(x) { case x { <> -> a _ -> 1.1 } } "# ); } #[test] fn match_rest() { assert_js!( r#" pub fn go(x) { let assert <<_, b:bytes>> = <<1,2,3>> } "#, ); } #[test] fn case_match_rest() { assert_js!( r#" pub fn go(x) { case <<1, 2, 3>> { <<_, b:bytes>> -> b _ -> x } } "#, ); } #[test] fn match_bytes_with_size() { assert_js!( r#" pub fn go(x) { let assert <> = <<1, 2>> } "#, ); } #[test] fn case_match_bytes_with_size() { assert_js!( r#" pub fn go(x) { case <<1, 2>> { <> -> f _ -> x } } "#, ); } #[test] fn match_bits_with_size() { assert_js!( r#" pub fn go(x) { let assert <<_:4, f:bits-2, _:1>> = <<0x77:7>> } "#, ); } #[test] fn case_match_bits_with_size() { assert_js!( r#" pub fn go(x) { case <<0x77:7>> { <<_:4, f:bits-2, _:1>> -> f _ -> x } } "#, ); } #[test] fn match_rest_bytes() { assert_js!( r#" pub fn go(x) { let assert <<_, b:bytes>> = <<1,2,3>> } "#, ); } #[test] fn case_match_rest_bytes() { assert_js!( r#" pub fn go(x) { case x { <<_, b:bytes>> -> b _ -> x } } "#, ); } #[test] fn match_rest_bits() { assert_js!( r#" pub fn go(x) { let assert <<_, b:bits>> = <<1,2,3>> } "#, ); } #[test] fn case_match_rest_bits() { assert_js!( r#" pub fn go(x) { case x { <<_, b:bits>> -> b _ -> x } } "#, ); } #[test] fn match_rest_bits_unaligned() { assert_js!( r#" pub fn go(x) { let assert <<_:5, b:bits>> = <<1,2,3>> } "#, ); } #[test] fn case_match_rest_bits_unaligned() { assert_js!( r#" pub fn go(x) { case x { <<_:5, b:bits>> -> b _ -> x } } "#, ); } #[test] fn match_binary_size() { assert_js!( r#" pub fn go(x) { let assert <<_, a:2-bytes>> = x let assert <<_, b:bytes-size(2)>> = x } "#, ); } #[test] fn case_match_binary_size() { assert_js!( r#" pub fn go(x) { case x { <<_, a:2-bytes>> -> a _ -> x } case x { <<_, b:bytes-size(2)>> -> b _ -> x } } "#, ); } #[test] fn unaligned_int_expression_requires_v1_9() { assert_js_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 8, 0)), " pub fn main() { <<0:1>> } ", ); } #[test] fn bits_expression_does_not_require_v1_9() { assert_js_no_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 8, 0)), " pub fn main() { <<<<0>>:bits>> } ", ); } #[test] fn sized_bits_expression_requires_v1_9() { assert_js_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 8, 0)), " pub fn main() { <<<<0>>:bits-5>> } ", ); } #[test] fn unaligned_int_pattern_requires_v1_9() { assert_js_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 8, 0)), " pub fn main() { let assert <<_:3>> = <<0>> } ", ); } #[test] fn bits_pattern_requires_v1_9() { assert_js_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 8, 0)), " pub fn main() { let assert <<_:bits>> = <<0>> } ", ); } #[test] fn bytes_pattern_with_odd_size_does_not_require_v1_9() { assert_js_no_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 8, 0)), " pub fn main() { let assert <<_:bytes-3>> = <<0, 1, 2>> } ", ); } #[test] fn sized_bits_expression_with_javascript_external_does_not_require_v1_9() { assert_js_no_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 8, 0)), r#" @external(javascript, "test.mjs", "go") pub fn go() -> BitArray { <<0:size(5)>> } "#, ); } #[test] fn bits_pattern_with_javascript_external_does_not_require_v1_9() { assert_js_no_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 8, 0)), r#" @external(javascript, "test.mjs", "go") pub fn go() -> BitArray { let <> = <<>> a } "#, ); } #[test] fn as_module_const() { assert_js!( r#" pub const data = << 0x1, 2, 2:size(16), 0x4:size(32), -1:32, "Gleam":utf8, 4.2:float, 4.2:32-float, <<0xFA>>:bits-6, -1:64, << <<1, 2, 3>>:bits, "Gleam":utf8, 1024 >>:bits >> "# ) } #[test] fn bit_array_literal_string_constant_is_treated_as_utf8() { assert_js!(r#"pub const a = <<"hello", " ", "world">>"#); } #[test] fn bit_array_literal_string_is_treated_as_utf8() { assert_js!( r#" pub fn main() { <<"hello", " ", "world">> }"# ); } #[test] fn bit_array_literal_string_pattern_is_treated_as_utf8() { assert_js!( r#" pub fn main() { case <<>> { <<"a", "b", _:bytes>> -> 1 _ -> 2 } }"# ); } #[test] fn with_unit() { assert_js!( r#" pub fn main() { <<1:size(2)-unit(2), 2:size(3)-unit(4)>> } "#, ); } #[test] fn dynamic_size_with_unit() { assert_js!( r#" pub fn main() { let size = 3 <<1:size(size)-unit(2)>> } "#, ); } #[test] fn pattern_with_unit() { assert_js!( r#" pub fn go(x) { let assert <<1:size(2)-unit(2), 2:size(3)-unit(4)>> = x } "#, ); } #[test] fn case_pattern_with_unit() { assert_js!( r#" pub fn go(x) { case x { <<1:size(2)-unit(2), 2:size(3)-unit(4)>> -> 1 _ -> 2 } } "#, ); } #[test] fn dynamic_size_pattern_with_unit() { assert_js!( r#" pub fn go(x) { let size = 3 let assert <<1:size(size)-unit(2)>> = x } "#, ); } #[test] fn case_dynamic_size_pattern_with_unit() { assert_js!( r#" pub fn go(x) { let size = 3 case x { <<1:size(size)-unit(2)>> -> 1 _ -> 2 } } "#, ); } #[test] fn case_dynamic_size_float_pattern_with_unit() { assert_js!( r#" pub fn go(x) { let size = 3 case x { <<1.3:size(size)-unit(2)>> -> 1 _ -> 2 } } "#, ); } #[test] fn case_with_remaining_bytes_after_constant_size() { assert_js!( r#" pub fn go(x) { case x { <<_, _, _:bytes>> -> 1 _ -> 2 } } "#, ); } #[test] fn case_with_remaining_bytes_after_variable_size() { assert_js!( r#" pub fn go(x) { let n = 1 case x { <<_:size(n), _, _:bytes>> -> 1 _ -> 2 } } "#, ); } #[test] fn case_with_remaining_bytes_after_variable_size_2() { assert_js!( r#" pub fn go(x) { let n = 1 case x { <> -> 1 _ -> 2 } } "#, ); } #[test] fn case_is_byte_aligned() { assert_js!( r#" pub fn is_byte_aligned(x) { case x { <<_:bytes>> -> True _ -> False } } "#, ); } #[test] fn alternative_patterns_with_variable_size() { assert_js!( r#" pub fn go(x) { case x { <<_, n, rest:size(n)>> | <> -> True _ -> False } } "#, ); } #[test] fn variable_sized_segment() { assert_js!( r#" pub fn go(x) { case x { <> -> 1 _ -> 2 } } "# ) } #[test] fn segments_shadowing_each_other() { assert_js!( r#" pub fn go(x) { let n = 1 case x { <> -> 1 _ -> 2 } } "# ) } #[test] fn negative_size_pattern() { assert_js!( r#" pub fn go(x) { let n = -10 case x { <> -> int _ -> 2 } } "# ) } #[test] fn negative_size_pattern_2() { assert_js!( r#" pub fn go(x) { case x { <> -> int _ -> 2 } } "# ) } // https://github.com/gleam-lang/gleam/issues/3375 #[test] fn bit_array_assignment_int() { assert_js!( " pub fn main() { let assert <<1 as a>> = <<1>> a } " ); } #[test] fn case_bit_array_assignment_int() { assert_js!( " pub fn go(x) { case x { <<1 as n>> | <<2 as n, _:bytes>> -> n _ -> 1 } } " ); } // https://github.com/gleam-lang/gleam/issues/3375 #[test] fn bit_array_assignment_float() { assert_js!( " pub fn main() { let assert <<3.14 as pi:float>> = <<3.14>> pi } " ); } #[test] fn case_bit_array_assignment_float() { assert_js!( " pub fn go(x) { case x { <<3.14 as pi:float>> | <<1.1 as pi:float, _:bytes>> -> pi _ -> 1.1 } } " ); } // https://github.com/gleam-lang/gleam/issues/3375 #[test] fn bit_array_assignment_string() { assert_js!( r#" pub fn main() { let assert <<"Hello, world!" as message:utf8>> = <<"Hello, world!">> message } "# ); } #[test] fn case_bit_array_assignment_string() { assert_js!( r#" pub fn go(x) { case x { <<"Hello" as message>> | <<"Jak" as message, _:bytes>> -> message _ -> "wibble" } } "# ); } // https://github.com/gleam-lang/gleam/issues/3375 #[test] fn bit_array_assignment_discard() { assert_js!( r#" pub fn main() { let assert <<_ as number>> = <<10>> number } "# ); } #[test] fn case_bit_array_assignment_discard() { assert_js!( r#" pub fn go(x) { case x { <<_ as n>> | <<_ as n, _:bytes>> -> n _ -> 1 } } "# ); } #[test] fn utf16() { assert_js!( r#" pub fn main() { <<"Hello, world!":utf16>> } "# ); } #[test] fn utf16_codepoint() { assert_js!( r#" fn codepoint() -> UtfCodepoint { todo } pub fn main() { let my_codepoint = codepoint() <> } "# ); } #[test] fn utf32() { assert_js!( r#" pub fn main() { <<"Hello, world!":utf32>> } "# ); } #[test] fn utf32_codepoint() { assert_js!( r#" fn codepoint() -> UtfCodepoint { todo } pub fn main() { let my_codepoint = codepoint() <> } "# ); } #[test] fn const_utf16() { assert_js!( r#" pub const message = <<"Hello, world!":utf16>> "# ); } #[test] fn const_utf32() { assert_js!( r#" pub const message = <<"Hello, world!":utf32>> "# ); } #[test] fn pattern_match_utf16() { assert_js!( r#" pub fn go(x) { let assert <<"Hello":utf16, _rest:bytes>> = x } "# ); } #[test] fn pattern_match_utf32() { assert_js!( r#" pub fn go(x) { let assert <<"Hello":utf32, _rest:bytes>> = x } "# ); } #[test] fn utf16_little_endian() { assert_js!( r#" pub fn main() { <<"Hello, world!":utf16-little>> } "# ); } #[test] fn utf32_little_endian() { assert_js!( r#" pub fn main() { <<"Hello, world!":utf32-little>> } "# ); } #[test] fn pattern_match_utf16_little_endian() { assert_js!( r#" pub fn go(x) { let assert <<"Hello":utf16-little, _rest:bytes>> = x } "# ); } #[test] fn pattern_match_utf32_little_endian() { assert_js!( r#" pub fn go(x) { let assert <<"Hello":utf32-little, _rest:bytes>> = x } "# ); } // https://github.com/gleam-lang/gleam/issues/4630 #[test] fn tuple_bit_array() { assert_js!( " pub fn go(x) { let assert #(<<>>) = x } " ); } // https://github.com/gleam-lang/gleam/issues/4630 #[test] fn tuple_bit_array_case() { assert_js!( " pub fn go(x) { case x { #(<<>>) -> 1 _ -> 2 } } " ); } #[test] fn tuple_multiple_bit_arrays() { assert_js!( " pub fn go(x) { let assert #(<<>>, <<1>>, <<2, 3>>) = x } " ); } #[test] fn tuple_multiple_bit_arrays_case() { assert_js!( " pub fn go(x) { case x { #(<<>>, <<1>>, <<2, 3>>) -> True _ -> False } } " ); } // https://github.com/gleam-lang/gleam/issues/4637 #[test] fn pattern_matching_on_32_float_plus_infinity_still_reachable() { assert_js!( r#" pub fn go(x) { case x { <<_:32-float>> -> "Float" <<0x7f800000:32>> -> "+Infinity" _ -> "Other" } } "# ); } #[test] fn pattern_matching_on_32_float_plus_infinity_still_reachable_2() { assert_js!( r#" pub fn go(x) { case x { <<_:32-float>> -> "Float" <<0x7f80:16, 0x0000:16>> -> "+Infinity" _ -> "Other" } } "# ); } #[test] fn pattern_matching_on_32_float_minus_infinity_still_reachable() { assert_js!( r#" pub fn go(x) { case x { <<_:32-float>> -> "Float" <<0xff800000:32>> -> "-Infinity" _ -> "Other" } } "# ); } #[test] fn pattern_matching_on_32_float_minus_infinity_still_reachable_2() { assert_js!( r#" pub fn go(x) { case x { <<_:32-float>> -> "Float" <<0xff80:16, 0x0000:16>> -> "-Infinity" _ -> "Other" } } "# ); } #[test] fn pattern_matching_on_32_float_nan_still_reachable() { assert_js!( r#" pub fn go(x) { case x { <<_:32-float>> -> "Float" <<0x7fc00000:32>> -> "NaN" _ -> "Other" } } "# ); } #[test] fn pattern_matching_on_32_float_nan_still_reachable_2() { assert_js!( r#" pub fn go(x) { case x { <<_:32-float>> -> "Float" <<0x7fc0:16, 0x0000:16>> -> "NaN" _ -> "Other" } } "# ); } #[test] fn pattern_matching_on_64_float_plus_infinity_still_reachable() { assert_js!( r#" pub fn go(x) { case x { <<_:64-float>> -> "Float" <<0x7ff0000000000000:64>> -> "+Infinity" _ -> "Other" } } "# ); } #[test] fn pattern_matching_on_64_float_plus_infinity_still_reachable_2() { assert_js!( r#" pub fn go(x) { case x { <<_:64-float>> -> "Float" <<0x7ff00000:32, 0x00000000:32>> -> "+Infinity" _ -> "Other" } } "# ); } #[test] fn pattern_matching_on_64_float_minus_infinity_still_reachable() { assert_js!( r#" pub fn go(x) { case x { <<_:64-float>> -> "Float" <<0xfff0000000000000:64>> -> "-Infinity" _ -> "Other" } } "# ); } #[test] fn pattern_matching_on_64_float_minus_infinity_still_reachable_2() { assert_js!( r#" pub fn go(x) { case x { <<_:64-float>> -> "Float" <<0xfff00000:32, 0x00000000:32>> -> "-Infinity" _ -> "Other" } } "# ); } #[test] fn pattern_matching_on_64_float_nan_still_reachable() { assert_js!( r#" pub fn go(x) { case x { <<_:64-float>> -> "Float" <<0x7ff8000000000000:64>> -> "NaN" _ -> "Other" } } "# ); } #[test] fn pattern_matching_on_64_float_nan_still_reachable_2() { assert_js!( r#" pub fn go(x) { case x { <<_:64-float>> -> "Float" <<0x7ff80000:32, 0x00000000:32>> -> "NaN" _ -> "Other" } } "# ); } #[test] fn pattern_matching_on_64_float_int_is_still_reachable() { assert_js!( r#" pub fn go(x) { case x { <<_:64-float>> -> "Float" <<_:64-int>> -> "Int" _ -> "Other" } } "# ); } #[test] fn pattern_matching_on_64_float_float_is_unreachable() { assert_js!( r#" pub fn go(x) { case x { <<_:64-float>> -> "Float" <<_:64-float>> -> "unreachable" _ -> "Other" } } "# ); } #[test] fn unit_with_bits_option() { assert_js!( " pub fn go(x) { <> } " ); } #[test] fn unit_with_bits_option_constant() { assert_js!( " pub const bits = <<1, 2, 3>> pub const more_bits = <> " ); } #[test] fn operator_in_size_for_bit_array_segment() { assert_js!( " pub fn go(x) { <> } " ); } // https://github.com/gleam-lang/gleam/issues/4712 #[test] fn multiple_variable_size_segments() { assert_js!( " pub fn main() { let assert <> = <<1, 2, 3, 4>> a + b + c } " ); } #[test] fn utf16_codepoint_little_endian() { assert_js!( " pub fn go(codepoint) { <> } " ); } #[test] fn utf32_codepoint_little_endian() { assert_js!( " pub fn go(codepoint) { <> } " ); } #[test] fn operator_in_pattern_size() { assert_js!( " pub fn main() { let assert <> = <<>> } " ); } #[test] fn operator_in_pattern_size2() { assert_js!( " pub fn main() { let assert <> = <<>> } " ); } #[test] fn operator_in_pattern_size3() { assert_js!( " pub fn main() { let additional = 10 let assert <> = <<>> } " ); } #[test] fn block_in_pattern_size() { assert_js!( " pub fn main() { let assert <> = <<>> } " ); } #[test] fn non_byte_aligned_size_calculation() { assert_js!( " pub fn main() { case <<>> { <> -> c + b _ -> 1 } } " ); } #[test] fn pattern_match_on_negative_size_calculation() { assert_js!( " pub fn main() { let assert <> = <<1, 2, 3, 4, 5>> } " ); } #[test] fn pattern_match_unknown_size_and_literal_string() { assert_js!( r#" pub fn go(x, n) { case x { <<_:size(n), "\r\n">> -> 1 _ -> 2 } } "# ); } #[test] fn pattern_match_size_arithmetic() { assert_js!( r#" pub fn wibble(bits, wobble) { case bits { <<_:size(1), _:size(wobble - 1), _:bits>> -> 0 _ -> 1 } } "# ); } #[test] fn bit_array_pattern_match_all_reachable() { assert_js!( r#" pub fn main(x) { case x { <<_, "==">> -> 1 <<_, _, "=">> -> 2 // ^^^ This should be reachable _ -> 3 } } "# ) } ================================================ FILE: compiler-core/src/javascript/tests/blocks.rs ================================================ use crate::assert_js; #[test] fn block() { assert_js!( r#" pub fn go() { let x = { 1 2 } x } "#, ); } #[test] fn nested_simple_blocks() { assert_js!( r#" pub fn go() { let x = { { 3 } } x } "#, ); } #[test] fn nested_multiexpr_blocks() { assert_js!( r#" pub fn go() { let x = { 1 { 2 3 } } x } "#, ); } #[test] fn nested_multiexpr_blocks_with_pipe() { assert_js!( r#" pub fn add1(a) { a + 1 } pub fn go() { let x = { 1 { 2 3 |> add1 } |> add1 } x } "#, ); } #[test] fn nested_multiexpr_non_ending_blocks() { assert_js!( r#" pub fn go() { let x = { 1 { 2 3 } 4 } x } "#, ); } #[test] fn nested_multiexpr_blocks_with_case() { assert_js!( r#" pub fn go() { let x = { 1 { 2 case True { _ -> 3 } } } x } "#, ); } #[test] fn sequences() { assert_js!( r#" pub fn go() { "one" "two" "three" } "#, ); } #[test] fn left_operator_sequence() { assert_js!( r#" pub fn go() { 1 == { 1 2 } } "#, ); } #[test] fn right_operator_sequence() { assert_js!( r#" pub fn go() { { 1 2 } == 1 } "#, ); } #[test] fn concat_blocks() { assert_js!( r#" pub fn main(f, a, b) { { a |> f } <> { b |> f } } "#, ); } #[test] fn blocks_returning_functions() { assert_js!( r#" pub fn b() { { fn(cb) { cb(1) } } { fn(cb) { cb(2) } } 3 } "# ); } #[test] fn blocks_returning_use() { assert_js!( r#" pub fn b() { { use a <- fn(cb) { cb(1) } a } { use b <- fn(cb) { cb(2) } b } 3 } "# ); } #[test] fn block_with_parenthesised_expression_returning_from_function() { assert_js!( r#" pub fn b() { { 1 + 2 } } "# ); } #[test] fn block_in_tail_position_is_not_an_iife() { assert_js!( r#" pub fn b() { let x = 1 { Nil x + 1 } } "# ); } #[test] fn block_in_tail_position_shadowing_variables() { assert_js!( r#" pub fn b() { let x = 1 { let x = 2 x + 1 } } "# ); } #[test] fn block_in_tail_position_with_just_an_assignment() { assert_js!( r#" pub fn b() { let x = 1 { let x = x } } "# ); } #[test] fn shadowed_variable_in_nested_scope() { assert_js!( " pub fn main() { { let x = 1 let _ = { let x = 2 x } x } } " ) } // https://github.com/gleam-lang/gleam/issues/4393 #[test] fn let_assert_only_statement_in_block() { assert_js!( " pub fn main() { { let assert Ok(1) = Error(Nil) } } " ) } // https://github.com/gleam-lang/gleam/issues/4394 #[test] fn assignment_last_in_block() { assert_js!( " pub fn main() { let a = { let b = 1 let c = b + 1 } a } " ) } // https://github.com/gleam-lang/gleam/issues/4394 #[test] fn pattern_assignment_last_in_block() { assert_js!( " pub fn main() { let a = { let b = #(1, 2) let #(x, y) = b } a } " ) } // https://github.com/gleam-lang/gleam/issues/4395 #[test] fn let_assert_message_no_lifted() { assert_js!( r#" fn side_effects(x) { // Some side effects x } pub fn main() { let assert Error(Nil) = side_effects(Ok(10)) as { let message = side_effects("some message") message } } "# ) } #[test] fn blocks_whose_values_are_unused_do_not_generate_assignments() { // There's no point generating `_block` assignments here, as the values // would be unused. assert_js!( " pub fn main() { { let x = 10 echo x } { let a = 1 let b = 2 a + b } Nil } " ); } ================================================ FILE: compiler-core/src/javascript/tests/bools.rs ================================================ use crate::{assert_js, assert_ts_def}; #[test] fn expressions() { assert_js!( r#" pub fn go() { True False Nil } "# ); } #[test] fn constants() { assert_js!( r#" pub const a = True pub const b = False pub const c = Nil "#, ); } #[test] fn constants_typescript() { assert_ts_def!( r#" pub const a = True pub const b = False pub const c = Nil "#, ); } #[test] fn operators() { assert_js!( r#" pub fn go() { True && True False || False } "#, ); } #[test] fn assigning() { assert_js!( r#" pub fn go(x, y) { let assert True = x let assert False = x let assert Nil = y } "#, ); } // https://github.com/gleam-lang/gleam/issues/1112 // differentiate between prelude constructors and custom type constructors #[test] fn shadowed_bools_and_nil() { assert_js!( r#" pub type True { True False Nil } pub fn go(x, y) { let assert True = x let assert False = x let assert Nil = y } "#, ); } #[test] fn shadowed_bools_and_nil_typescript() { assert_ts_def!( r#" pub type True { True False Nil } pub fn go(x, y) { let assert True = x let assert False = x let assert Nil = y } "#, ); } #[test] fn equality() { assert_js!( r#" pub fn go(a, b) { a == True a != True a == False a != False a == a a != a b == Nil b != Nil b == b } "#, ); } #[test] fn case() { assert_js!( r#" pub fn go(a) { case a { True -> 1 False -> 0 } } "#, ); } #[test] fn nil_case() { assert_js!( r#" pub fn go(a) { case a { Nil -> 0 } } "#, ); } #[test] fn negation() { assert_js!( "pub fn negate(x) { !x }" ); } #[test] fn negation_block() { assert_js!( "pub fn negate(x) { !{ 123 x } }" ); } #[test] fn binop_panic_right() { assert_js!( "pub fn negate(x) { x && panic }" ); } #[test] fn binop_panic_left() { assert_js!( "pub fn negate(x) { panic && x }" ); } #[test] fn binop_todo_right() { assert_js!( "pub fn negate(x) { x && todo }" ); } #[test] fn binop_todo_left() { assert_js!( "pub fn negate(x) { todo && x }" ); } #[test] fn negate_panic() { assert_js!( "pub fn negate(x) { !panic }" ); } #[test] fn negate_todo() { assert_js!( "pub fn negate(x) { !todo }" ); } ================================================ FILE: compiler-core/src/javascript/tests/case.rs ================================================ use crate::assert_js; #[test] fn case_on_error() { assert_js!( r#" fn a_result() { Error(1) } pub fn main() { case a_result() { Error(_) -> 1 _ -> 2 } }"# ); } #[test] fn tuple_and_guard() { assert_js!( r#" pub fn go(x) { case #(1, 2) { #(1, a) if a == 2 -> 1 #(_, _) -> 2 } } "#, ) } #[test] fn guard_variable_only_brought_into_scope_when_needed() { assert_js!( r#" pub fn go(x) { case x { // We want `a` to be defined before the guard check, and // `b` to be defined only if the predicate on a matches! [a, b] if a == 1 -> a + b _ -> 2 } } "# ) } // https://github.com/gleam-lang/gleam/issues/4221 #[test] fn guard_variable_only_brought_into_scope_when_needed_1() { assert_js!( r#" pub fn main() { case 1 { i if i == 1 -> True i if i < 2 -> True _ -> False } } "# ) } // https://github.com/gleam-lang/gleam/issues/1187 #[test] fn pointless() { assert_js!( r#" pub fn go(x) { case x { _ -> x } } "#, ) } // https://github.com/gleam-lang/gleam/issues/1188 #[test] fn following_todo() { assert_js!( r#" pub fn go(x) { case x { True -> todo _ -> 1 } } "#, ) } #[test] fn multi_subject_catch_all() { assert_js!( r#" pub fn go(x, y) { case x, y { True, True -> 1 _, _ -> 0 } } "#, ) } #[test] fn multi_subject_or() { assert_js!( r#" pub fn go(x, y) { case x, y { True, _ | _, True -> 1 _, _ -> 0 } } "#, ) } #[test] fn multi_subject_no_catch_all() { assert_js!( r#" pub fn go(x, y) { case x, y { True, _ -> 1 _, True -> 2 False, False -> 0 } } "#, ) } #[test] fn multi_subject_subject_assignments() { assert_js!( r#" pub fn go() { case True, False { True, True -> 1 _, _ -> 0 } } "#, ) } #[test] fn assignment() { assert_js!( r#" pub fn go(x) { let y = case x { True -> 1 _ -> 0 } y } "#, ) } #[test] fn preassign_assignment() { assert_js!( r#" pub fn go(x) { let y = case x() { True -> 1 _ -> 0 } y } "#, ) } // https://github.com/gleam-lang/gleam/issues/1237 #[test] fn pipe() { assert_js!( r#" pub fn go(x, f) { case x |> f { 0 -> 1 _ -> 2 } } "#, ) } #[test] fn result() { assert_js!( r#" pub fn go(x) { case x { Ok(_) -> 1 Error(_) -> 0 } } "#, ) } // https://github.com/gleam-lang/gleam/issues/1506 #[test] fn called_case() { assert_js!( r#" pub fn go(x, y) { case x { 0 -> y _ -> y }() } "#, ) } // https://github.com/gleam-lang/gleam/issues/1978 #[test] fn case_local_var_in_tuple() { assert_js!( r#" pub fn go(x, y) { let z = False case True { x if #(x, z) == #(True, False) -> x _ -> False } } "#, ) } // https://github.com/gleam-lang/gleam/issues/2665 #[test] fn case_branches_guards_are_wrapped_in_parentheses() { assert_js!( r#" pub fn anything() -> a { case [] { [a] if False || True -> a _ -> anything() } } "#, ) } // https://github.com/gleam-lang/gleam/issues/2759 #[test] fn nested_string_prefix_match() { assert_js!( r#" pub fn main() { case Ok(["a", "b c", "d"]) { Ok(["a", "b " <> _, "d"]) -> 1 _ -> 1 } } "# ); } // https://github.com/gleam-lang/gleam/issues/2759 #[test] fn nested_string_prefix_match_that_would_crash_on_js() { assert_js!( r#" pub fn main() { case Ok(["b c", "d"]) { Ok(["b " <> _, "d"]) -> 1 _ -> 1 } } "# ); } #[test] fn slicing_is_handled_properly_with_multiple_branches() { assert_js!( r#" pub fn main() { case "12345" { "0" <> rest -> rest "123" <> rest -> rest _ -> "" } } "# ) } // https://github.com/gleam-lang/gleam/issues/3379 #[test] fn single_clause_variables() { assert_js!( r#" pub fn main() { let text = "first defined" case "defined again" { text -> Nil } let text = "a third time" } "# ) } // https://github.com/gleam-lang/gleam/issues/3379 #[test] fn single_clause_variables_assigned() { assert_js!( r#" pub fn main() { let text = "first defined" let other = case "defined again" { text -> Nil } let text = "a third time" } "# ) } // https://github.com/gleam-lang/gleam/issues/3894 #[test] fn nested_string_prefix_assignment() { assert_js!( r#" type Wibble { Wibble(wobble: String) } pub fn main() { let tmp = Wibble(wobble: "wibble") case tmp { Wibble(wobble: "w" as wibble <> rest) -> wibble <> rest _ -> panic } } "# ) } #[test] fn deeply_nested_string_prefix_assignment() { assert_js!( r#" type Wibble { Wibble(Wobble) } type Wobble { Wobble(wabble: Wabble) } type Wabble { Wabble(tuple: #(Int, String)) } pub fn main() { let tmp = Wibble(Wobble(Wabble(#(42, "wibble")))) case tmp { Wibble(Wobble(Wabble(#(_int, "w" as wibble <> rest)))) -> wibble <> rest _ -> panic } } "# ) } // https://github.com/gleam-lang/gleam/issues/4383 #[test] fn record_update_in_pipeline_in_case_clause() { assert_js!( " pub type Wibble { Wibble(wibble: Int, wobble: Int) } fn identity(x) { x } pub fn go(x) { case x { Wibble(1, _) -> Wibble(..x, wibble: 4) |> identity Wibble(_, 3) -> Wibble(..x, wobble: 10) |> identity _ -> panic } } " ); } #[test] fn pattern_matching_on_aliased_result_constructor() { assert_js!( " import gleam.{Error as E, Ok as O} pub fn go(x) { case x { E(_) -> 1 O(_) -> 2 } } " ); } #[test] fn list_with_guard() { assert_js!( " pub fn go(x) { case x { [] -> 0 [first, ..] if first < 10 -> first * 2 [first, ..] -> first } } " ); } #[test] fn list_with_guard_no_binding() { assert_js!( " pub fn go(x) { case x { [] -> 0 [first, ..] if 1 < 10 -> first * 2 [first, ..] -> first } } " ); } #[test] fn case_building_simple_value_matched_by_pattern() { assert_js!( "pub fn go(x) { case x { 1 -> 2 n -> n } }" ) } #[test] fn case_building_list_matched_by_pattern() { assert_js!( "pub fn go(x) { case x { [] -> [] [a, b] -> [a, b] [1, ..rest] -> [1, ..rest] _ -> x } }" ) } #[test] fn case_building_record_matched_by_pattern() { assert_js!( "pub fn go(x) { case x { Ok(1) -> Ok(1) Ok(n) -> Ok(n) Error(_) -> Error(Nil) } }" ) } #[test] fn case_building_record_with_select_matched_by_pattern() { assert_js!( " import gleam pub fn go(x) { case x { Ok(1) -> gleam.Ok(1) _ -> Error(Nil) } }" ) } #[test] fn case_building_record_with_select_matched_by_pattern_2() { assert_js!( " import gleam pub fn go(x) { case x { gleam.Ok(1) -> gleam.Ok(1) _ -> Error(Nil) } }" ) } #[test] fn case_building_record_with_select_matched_by_pattern_3() { assert_js!( " import gleam pub fn go(x) { case x { gleam.Ok(1) -> Ok(1) _ -> Error(Nil) } }" ) } #[test] fn case_building_matched_string_1() { assert_js!( r#" import gleam pub fn go(x) { case x { "a" <> rest -> "a" <> rest _ -> "" } }"# ) } #[test] fn case_building_matched_string_2() { assert_js!( r#" import gleam pub fn go(x) { case x { "a" as a <> rest -> a <> rest _ -> "" } }"# ) } #[test] fn case_building_matched_value_wrapped_in_block() { assert_js!( r#" import gleam pub fn go(x) { case x { 1 -> { 1 } _ -> 2 } }"# ) } #[test] fn case_building_matched_value_alias() { assert_js!( r#" import gleam pub fn go(x) { case x { Ok(_) as a -> a Error(Nil) -> Error(Nil) } }"# ) } #[test] fn case_building_matched_value_alias_2() { assert_js!( r#" import gleam pub fn go(x) { case x { Ok(1) as a -> Ok(1) Ok(_) -> Ok(2) Error(Nil) -> Error(Nil) } }"# ) } #[test] fn case_building_matched_value_alias_3() { assert_js!( r#" import gleam pub fn go(x) { case x { Ok(1 as a) -> Ok(a) Ok(_) -> Ok(2) Error(Nil) -> Error(Nil) } }"# ) } #[test] fn case_building_matched_no_variant_record() { assert_js!( r#" pub fn go(x) { case x { Ok(Nil) -> Ok(Nil) _ -> Error(Nil) } }"# ) } #[test] fn case_building_matched_no_variant_record_2() { assert_js!( r#" import gleam pub fn go(x) { case x { Ok(gleam.Nil) -> Ok(Nil) _ -> Error(Nil) } }"# ) } #[test] fn case_building_matched_no_variant_record_3() { assert_js!( r#" import gleam pub fn go(x) { case x { Ok(Nil) -> Ok(gleam.Nil) _ -> Error(Nil) } }"# ) } #[test] fn case_building_matched_no_variant_record_4() { assert_js!( r#" import gleam pub fn go(x) { case x { Ok(gleam.Nil) -> Ok(gleam.Nil) _ -> Error(Nil) } }"# ) } #[test] fn case_building_record_with_labels_matched_by_pattern_1() { assert_js!( " pub type Wibble { Wibble(int: Int, string: String) Wobble(Int) } pub fn go(x) { case x { Wibble(1, s) -> Wibble(1, s) _ -> Wobble(1) } }" ) } #[test] fn case_building_record_with_labels_matched_by_pattern_2() { assert_js!( " pub type Wibble { Wibble(int: Int, string: String) Wobble(Int) } pub fn go(x) { case x { Wibble(string:, int:) -> Wibble(string:, int:) _ -> Wobble(1) } }" ) } #[test] fn case_building_record_with_labels_matched_by_pattern_3() { assert_js!( " pub type Wibble { Wibble(int: Int, string: String) Wobble(Int) } pub fn go(x) { case x { // This should not be optimised away! Wibble(string:, int:) -> Wibble(string:, int: 1) _ -> Wobble(1) } }" ) } #[test] fn case_building_record_with_labels_matched_by_pattern_4() { assert_js!( " pub type Wibble { Wibble(int: Int, string: String) Wobble(Int) } pub fn go(x) { case x { Wibble(string:, int:) -> Wibble(int:, string:) _ -> Wobble(1) } }" ) } #[test] fn case_building_record_with_labels_matched_by_pattern_5() { assert_js!( " pub type Wibble { Wibble(int: Int, string: String) Wobble(Int) } pub fn go(x) { case x { Wibble(string:, int: 1) -> Wibble(1, string:) _ -> Wobble(1) } }" ) } #[test] fn case_building_record_with_labels_matched_by_pattern_6() { assert_js!( " pub type Wibble { Wibble(int: Int, string: String) Wobble(Int) } pub fn go(x) { case x { Wibble(1, string:) -> Wibble(string:, int: 1) _ -> Wobble(1) } }" ) } #[test] fn case_with_multiple_subjects_building_simple_value_matched_by_pattern() { assert_js!( "pub fn go(x) { case x, x + 1 { 1, _ -> 2 _, n -> n } }" ) } #[test] fn case_with_multiple_subjects_building_list_matched_by_pattern() { assert_js!( "pub fn go(n, x) { case n, x { 1, [] -> [] _, [a, b] -> [a, b] 3, [1, ..rest] -> [1, ..rest] _, _ -> x } }" ) } #[test] fn case_with_multiple_subjects_building_record_matched_by_pattern() { assert_js!( "pub fn go(x, y) { case x, y { Ok(1), Error(_) -> Ok(1) Error(_), Ok(n) -> Ok(n) _, _ -> Error(Nil) } }" ) } #[test] fn case_with_multiple_subjects_building_same_value_as_two_subjects_one_is_picked() { assert_js!( " import gleam pub fn go(x, y) { case x, y { gleam.Ok(1), Ok(1) -> Ok(1) _, Error(Nil) -> Error(Nil) _, _ -> Error(Nil) } }" ) } #[test] fn interfering_string_pattern_succeeds_if_succeeding() { assert_js!( r#" pub fn wibble(bits) { case bits { <<"aaa", 0, _:bits>> -> 1 // If the first one succeeds, so will the second check, so it won't be // performed twice inside the first if branch! <<"aaa", 1, _:bits>> -> 2 _ -> 3 } }"# ); } #[test] fn string_concatenation_in_clause_guards() { assert_js!( r#" pub fn main() { let wibble = "wob" case wibble { x if x <> "ble" == "wobble" -> 1 _ -> 0 } }"# ); } #[test] fn var_true() { assert_js!( r#" fn true() { True } pub fn main() { let true_ = true() assert 0 == case Nil { _ if true_ -> 0 _ -> 1 } } "# ) } #[test] // https://github.com/gleam-lang/gleam/issues/5283 fn duplicate_name_for_variables_used_in_guards() { assert_js!( r#" pub fn wibble() { let a = case 1337 { n if n == 1347 -> Nil _ -> Nil } let b = case 1337 { n -> Nil } }"# ) } #[test] // https://github.com/gleam-lang/gleam/issues/5283 fn duplicate_name_for_variables_used_in_guards_shadowing_outer_name() { assert_js!( r#" pub fn wibble() { let n = 1 let a = case 1337 { n if n == 1347 -> n _ -> n } let b = case 1337 { n -> Nil } }"# ) } #[test] fn directly_matching_case_subject() { assert_js!( r#" pub fn go() { let x = "ABC" case True { True -> { let x = 79 0 } False -> { let x = True 0 } } x }"# ) } ================================================ FILE: compiler-core/src/javascript/tests/case_clause_guards.rs ================================================ use crate::assert_js; #[test] fn referencing_pattern_var() { assert_js!( r#"pub fn main(xs) { case xs { #(x) if x -> 1 _ -> 0 } } "#, ); } #[test] fn rebound_var() { assert_js!( r#"pub fn main() { let x = False let x = True case x { _ if x -> 1 _ -> 0 } } "#, ); } #[test] fn bitarray_with_var() { assert_js!( r#"pub fn main() { case 5 { z if <> == <> -> Nil _ -> Nil } } "#, ) } // https://github.com/gleam-lang/gleam/issues/3004 #[test] fn keyword_var() { assert_js!( r#" pub const function = 5 pub const do = 10 pub fn main() { let class = 5 let while = 10 let var = 7 case var { _ if class == while -> True _ if [class] == [5] -> True function if #(function) == #(5) -> False _ if do == function -> True while if while > 5 -> False class -> False } } "#, ); } #[test] fn operator_wrapping_right() { assert_js!( r#"pub fn main(xs, y: Bool, z: Bool) { case xs { #(x) if x == { y == z } -> 1 _ -> 0 } } "#, ); } #[test] fn operator_wrapping_left() { assert_js!( r#"pub fn main(xs, y: Bool, z: Bool) { case xs { #(x) if { x == y } == z -> 1 _ -> 0 } } "#, ); } #[test] fn eq_scalar() { assert_js!( r#"pub fn main(xs, y: Int) { case xs { #(x) if x == y -> 1 _ -> 0 } } "#, ); } #[test] fn not_eq_scalar() { assert_js!( r#"pub fn main(xs, y: Int) { case xs { #(x) if x != y -> 1 _ -> 0 } } "#, ); } #[test] fn tuple_index() { assert_js!( r#"pub fn main(x, xs: #(Bool, Bool, Bool)) { case x { _ if xs.2 -> 1 _ -> 0 } } "#, ); } #[test] fn not_eq_complex() { assert_js!( r#"pub fn main(xs, y) { case xs { #(x) if xs != y -> x _ -> 0 } } "#, ); } #[test] fn eq_complex() { assert_js!( r#"pub fn main(xs, y) { case xs { #(x) if xs == y -> x _ -> 0 } } "#, ); } #[test] fn constant() { assert_js!( r#"pub fn main(xs) { case xs { #(x) if x == 1 -> x _ -> 0 } } "#, ); } #[test] fn alternative_patterns() { assert_js!( r#"pub fn main(xs) { case xs { 1 | 2 -> 0 _ -> 1 } } "#, ); } #[test] fn alternative_patterns_list() { assert_js!( r#"pub fn main(xs) -> Int { case xs { [1] | [1, 2] -> 0 _ -> 1 } } "#, ); } #[test] fn alternative_patterns_assignment() { assert_js!( r#"pub fn main(xs) -> Int { case xs { [x] | [_, x] -> x _ -> 1 } } "#, ); } #[test] fn alternative_patterns_guard() { assert_js!( r#"pub fn main(xs) -> Int { case xs { [x] | [_, x] if x == 1 -> x _ -> 0 } } "#, ); } #[test] fn field_access() { assert_js!( r#" pub type Person { Person(username: String, name: String, age: Int) } pub fn main() { let given_name = "jack" let raiden = Person("raiden", "jack", 31) case given_name { name if name == raiden.name -> "It's jack" _ -> "It's not jack" } } "# ) } #[test] fn nested_record_access() { assert_js!( r#" pub type A { A(b: B) } pub type B { B(c: C) } pub type C { C(d: Bool) } pub fn a(a: A) { case a { _ if a.b.c.d -> 1 _ -> 0 } } "# ); } #[test] fn module_string_access() { assert_js!( ( "package", "hero", r#" pub const ironman = "Tony Stark" "# ), r#" import hero pub fn main() { let name = "Tony Stark" case name { n if n == hero.ironman -> True _ -> False } } "# ); } #[test] fn module_list_access() { assert_js!( ( "package", "hero", r#" pub const heroes = ["Tony Stark", "Bruce Wayne"] "# ), r#" import hero pub fn main() { let names = ["Tony Stark", "Bruce Wayne"] case names { n if n == hero.heroes -> True _ -> False } } "# ); } #[test] fn module_tuple_access() { assert_js!( ( "package", "hero", r#" pub const hero = #("ironman", "Tony Stark") "# ), r#" import hero pub fn main() { let name = "Tony Stark" case name { n if n == hero.hero.1 -> True _ -> False } } "# ); } #[test] fn module_access() { assert_js!( ( "package", "hero", r#" pub type Hero { Hero(name: String) } pub const ironman = Hero("Tony Stark") "# ), r#" import hero pub fn main() { let name = "Tony Stark" case name { n if n == hero.ironman.name -> True _ -> False } } "# ); } #[test] fn module_access_submodule() { assert_js!( ( "package", "hero/submodule", r#" pub type Hero { Hero(name: String) } pub const ironman = Hero("Tony Stark") "# ), r#" import hero/submodule pub fn main() { let name = "Tony Stark" case name { n if n == submodule.ironman.name -> True _ -> False } } "# ); } #[test] fn module_access_aliased() { assert_js!( ( "package", "hero/submodule", r#" pub type Hero { Hero(name: String) } pub const ironman = Hero("Tony Stark") "# ), r#" import hero/submodule as myhero pub fn main() { let name = "Tony Stark" case name { n if n == myhero.ironman.name -> True _ -> False } } "# ); } #[test] fn module_nested_access() { assert_js!( ( "package", "hero", r#" pub type Person { Person(name: String) } pub type Hero { Hero(secret_identity: Person) } const bruce = Person("Bruce Wayne") pub const batman = Hero(bruce) "# ), r#" import hero pub fn main() { let name = "Bruce Wayne" case name { n if n == hero.batman.secret_identity.name -> True _ -> False } } "# ); } #[test] fn not() { assert_js!( r#"pub fn main(x, y) { case x { _ if !y -> 0 _ -> 1 } } "#, ); } #[test] fn not_two() { assert_js!( r#"pub fn main(x, y) { case x { _ if !y && !x -> 0 _ -> 1 } } "#, ); } #[test] fn custom_type_constructor_imported_and_aliased() { assert_js!( ("package", "other_module", "pub type T { A }"), r#"import other_module.{A as B} pub fn func() { case B { x if x == B -> True _ -> False } } "#, ); } #[test] fn imported_aliased_ok() { assert_js!( r#"import gleam.{Ok as Y} pub type X { Ok } pub fn func() { case Y { y if y == Y -> True _ -> False } } "#, ); } #[test] fn imported_ok() { assert_js!( r#"import gleam pub type X { Ok } pub fn func(x) { case gleam.Ok { _ if [] == [ gleam.Ok ] -> True _ -> False } } "#, ); } // Variant of https://github.com/lpil/decode/pull/6 #[test] fn constructor_function_in_guard() { assert_js!( r#"pub fn func(x) { case [] { _ if [] == [ Ok ] -> True _ -> False } } "#, ); } // https://github.com/gleam-lang/gleam/issues/4241 #[test] fn int_division() { assert_js!( r#" pub fn main() { case 5 / 2 { x if x == 5 / 2 -> True _ -> False } } "#, ); } // https://github.com/gleam-lang/gleam/issues/4241 #[test] fn float_division() { assert_js!( r#" pub fn main() { case 5.1 /. 0.0 { x if x == 5.1 /. 0.0 -> True _ -> False } } "#, ); } // https://github.com/gleam-lang/gleam/issues/4241 #[test] fn int_remainder() { assert_js!( r#" pub fn main() { case 4 % 0 { x if x == 4 % 0 -> True _ -> False } } "#, ); } // https://github.com/gleam-lang/gleam/issues/5094 #[test] fn guard_pattern_does_not_shadow_outer_scope() { assert_js!( r#" pub type Option(a) { Some(a) None } pub type Container { Container(x: Option(Int)) } pub fn main() { let x: Option(Int) = Some(42) case Some(1) { Some(x) if x < 0 -> Container(None) _ -> { Container(x:) } } } "#, ); } // https://github.com/gleam-lang/gleam/issues/5214 #[test] fn bit_array_referencing_shadowed_variable() { assert_js!( " pub fn main() { let a = 1 let a = 2 case Nil { _ if <> == <<1>> -> False _ if <> == <<2>> -> True _ -> False } } " ); } ================================================ FILE: compiler-core/src/javascript/tests/consts.rs ================================================ use crate::assert_js; #[test] fn custom_type_constructor_imported_and_aliased() { assert_js!( ("package", "other_module", "pub type T { A }"), r#"import other_module.{A as B} pub const local = B "#, ); } #[test] fn imported_aliased_ok() { assert_js!( r#"import gleam.{Ok as Y} pub type X { Ok } pub const y = Y "#, ); } #[test] fn imported_ok() { assert_js!( r#"import gleam pub type X { Ok } pub const y = gleam.Ok "#, ); } #[test] fn constant_constructor_gets_pure_annotation() { assert_js!( r#" pub type X { X(Int, List(String)) } pub const x = X(1, ["1"]) pub const y = X(1, []) "# ); } #[test] fn constant_list_with_constructors_gets_pure_annotation() { assert_js!( r#" pub type X { X(Int, List(String)) } pub const x = [X(1, ["1"])] pub const y = [X(1, ["1"])] "# ); } #[test] fn constant_tuple_with_constructors_gets_pure_annotation() { assert_js!( r#" pub type X { X(Int, List(String)) } pub const x = #(X(1, ["1"])) pub const y = #(X(1, ["1"])) "# ); } #[test] fn literal_int_does_not_get_constant_annotation() { assert_js!("pub const a = 1"); } #[test] fn literal_float_does_not_get_constant_annotation() { assert_js!("pub const a = 1.1"); } #[test] fn literal_string_does_not_get_constant_annotation() { assert_js!("pub const a = \"1\""); } #[test] fn literal_bool_does_not_get_constant_annotation() { assert_js!( " pub const a = True pub const b = False " ); } #[test] fn literal_list_does_not_get_constant_annotation() { assert_js!("pub const a = [1, 2, 3]"); } #[test] fn literal_tuple_does_not_get_constant_annotation() { assert_js!("pub const a = #(1, 2, 3)"); } #[test] fn literal_nil_does_not_get_constant_annotation() { assert_js!("pub const a = Nil"); } // https://github.com/lpil/decode/pull/6 #[test] fn constructor_function_in_constant() { assert_js!("pub const a = Ok"); } #[test] fn constants_get_their_own_jsdoc_comment() { assert_js!( " /// 11 is clearly the best number! pub const jaks_favourite_number = 11 " ); } #[test] fn list_prepend() { assert_js!( " const wibble = [2, 3, 4] pub const wobble = [0, 1, ..wibble] " ); } #[test] fn list_prepend_from_other_module() { assert_js!( ("mod", "pub const wibble = [2, 3, 4]"), " import mod pub const wobble = [0, 1, ..mod.wibble] " ); } #[test] fn list_prepend_literal() { assert_js!( " pub const wibble = [0, 1, ..[2, 3, 4]] " ); } ================================================ FILE: compiler-core/src/javascript/tests/custom_types.rs ================================================ use crate::javascript::tests::CURRENT_PACKAGE; use crate::{assert_js, assert_ts_def}; #[test] fn zero_arity_literal() { assert_js!( r#" pub type Mine { This ThatOneIsAMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchLongerVariant } pub fn go() { This ThatOneIsAMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchLongerVariant } "#, ); } #[test] fn zero_arity_const() { assert_js!( r#" pub type Mine { This ThatOneIsAMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchLongerVariant } pub const this = This pub const that = ThatOneIsAMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchLongerVariant "#, ); } #[test] fn zero_arity_imported() { assert_js!( ("other", r#"pub type One { Two }"#), r#"import other pub fn main() { other.Two }"#, ); } #[test] fn zero_arity_imported_typscript() { assert_ts_def!( (CURRENT_PACKAGE, "other", r#"pub type One { Two }"#), r#"import other pub fn main() { other.Two }"#, ); } #[test] fn zero_arity_imported_unqualified() { assert_js!( ("other", r#"pub type One { Two }"#), r#"import other.{Two} pub fn main() { Two }"#, ); } #[test] fn zero_arity_imported_unqualified_typescript() { assert_ts_def!( (CURRENT_PACKAGE, "other", r#"pub type One { Two }"#), r#"import other.{Two} pub fn main() { Two }"#, ); } #[test] fn zero_arity_imported_unqualified_aliased() { assert_js!( ("other", r#"pub type One { Two }"#), r#"import other.{Two as Three} pub fn main() { Three }"# ); } #[test] fn zero_arity_imported_unqualified_aliased_typescript() { assert_ts_def!( (CURRENT_PACKAGE, "other", r#"pub type One { Two }"#), r#"import other.{Two as Three} pub fn main() { Three }"# ); } #[test] fn const_zero_arity_imported() { assert_js!( ("other", r#"pub type One { Two }"#), r#"import other pub const x = other.Two "#, ); } #[test] fn const_zero_arity_imported_unqualified() { assert_js!( ("other", r#"pub type One { Two }"#), r#"import other.{Two} pub const a = Two "#, ); } #[test] fn const_with_fields() { assert_js!( r#" pub type Mine { Mine(a: Int, b: Int) } pub const labels = Mine(b: 2, a: 1) pub const no_labels = Mine(3, 4) "#, ); } #[test] fn const_with_fields_typescript() { assert_ts_def!( r#" pub type Mine { Mine(a: Int, b: Int) } pub const labels = Mine(b: 2, a: 1) pub const no_labels = Mine(3, 4) "#, ); } #[test] fn unnamed_fields() { assert_js!( r#" pub type Ip { Ip(String) } pub const local = Ip("0.0.0.0") pub fn build(x) { x("1.2.3.4") } pub fn go() { build(Ip) Ip("5.6.7.8") } pub fn destructure(x) { let Ip(raw) = x raw } "#, ); } #[test] fn unnamed_fields_typescript() { assert_ts_def!( r#" pub type Ip{ Ip(String) } pub const local = Ip("0.0.0.0") "#, ); } #[test] fn long_name_variant_without_labels() { assert_js!( r#" pub type TypeWithALongNameAndSeveralArguments{ TypeWithALongNameAndSeveralArguments(String, String, String, String, String) } pub fn go() { TypeWithALongNameAndSeveralArguments } "#, ); } #[test] fn long_name_variant_mixed_labels_typescript() { assert_ts_def!( r#" pub type TypeWithALongNameAndSeveralArguments{ TypeWithALongNameAndSeveralArguments(String, String, String, a: String, b: String) } pub const local = TypeWithALongNameAndSeveralArguments("one", "two", "three", "four", "five") "#, ); } #[test] fn custom_type_with_named_fields() { assert_js!( r#" pub type Cat { Cat(name: String, cuteness: Int) } pub type Box { Box(occupant: Cat) } pub const felix = Cat("Felix", 12) pub const tom = Cat(cuteness: 1, name: "Tom") pub fn go() { Cat("Nubi", 1) Cat(2, name: "Nubi") Cat(cuteness: 3, name: "Nubi") } pub fn update(cat) { Cat(..cat, name: "Sid") Cat(..cat, name: "Bartholemew Wonder Puss the Fourth !!!!!!!!!!!!!!!!") Cat(..new_cat(), name: "Molly") let box = Box(occupant: cat) Cat(..box.occupant, cuteness: box.occupant.cuteness + 1) } pub fn access(cat: Cat) { cat.cuteness } pub fn new_cat() { Cat(name: "Beau", cuteness: 11) } "#, ); } #[test] fn destructure_custom_type_with_named_fields() { assert_js!( r#" pub type Cat { Cat(name: String, cuteness: Int) } pub fn go(cat) { let Cat(x, y) = cat let Cat(name: x, ..) = cat let assert Cat(cuteness: 4, name: x) = cat x } "#, ) } #[test] fn destructure_custom_type_with_mixed_fields_first_unlabelled() { assert_js!( r#" pub type Cat { Cat(String, cuteness: Int) } pub fn go(cat) { let Cat(x, y) = cat let Cat(cuteness: y, ..) = cat let Cat(x, cuteness: y) = cat x } "#, ) } #[test] fn nested_pattern_with_labels() { assert_js!( r#"pub type Box(x) { Box(a: Int, b: x) } pub fn go(x) { case x { Box(a: _, b: Box(a: a, b: b)) -> a + b _ -> 1 } } "#, ); } #[test] fn imported_no_label() { assert_js!( ("other", r#"pub type One { Two(Int) }"#), r#"import other pub fn main() { other.Two(1) }"#, ); } #[test] fn imported_ignoring_label() { assert_js!( ("other", r#"pub type One { Two(field: Int) }"#), r#"import other pub fn main() { other.Two(1) }"#, ); } #[test] fn imported_using_label() { assert_js!( ("other", r#"pub type One { Two(field: Int) }"#), r#"import other pub fn main() { other.Two(field: 1) }"#, ); } #[test] fn imported_multiple_fields() { assert_js!( ("other", r#"pub type One { Two(a: Int, b: Int, c: Int) }"#), r#"import other pub fn main() { other.Two(b: 2, c: 3, a: 1) }"#, ); } #[test] fn unqualified_imported_no_label() { assert_js!( ("other", r#"pub type One { Two(Int) }"#), r#"import other.{Two} pub fn main() { Two(1) }"#, ); } #[test] fn unqualified_imported_no_label_typescript() { assert_ts_def!( (CURRENT_PACKAGE, "other", r#"pub type One { Two(Int) }"#), r#"import other.{Two} pub fn main() { Two(1) }"#, ); } #[test] fn unqualified_imported_ignoring_label() { assert_js!( ("other", r#"pub type One { Two(field: Int) }"#), r#"import other.{Two} pub fn main() { Two(1) }"#, ); } #[test] fn unqualified_imported_using_label() { assert_js!( ("other", r#"pub type One { Two(field: Int) }"#), r#"import other.{Two} pub fn main() { Two(field: 1) }"#, ); } #[test] fn unqualified_imported_multiple_fields() { assert_js!( ("other", r#"pub type One { Two(a: Int, b: Int, c: Int) }"#), r#"import other.{Two} pub fn main() { Two(b: 2, c: 3, a: 1) }"#, ); } #[test] fn constructor_as_value() { assert_js!( ("other", r#"pub type One { Two(a: Int, b: Int, c: Int) }"#), r#"import other pub fn main() { other.Two }"#, ); } #[test] fn unqualified_constructor_as_value() { assert_js!( ("other", r#"pub type One { Two(a: Int, b: Int, c: Int) }"#), r#"import other.{Two} pub fn main() { Two }"#, ); } #[test] fn const_imported_no_label() { assert_js!( ("other", r#"pub type One { Two(Int) }"#), r#"import other pub const main = other.Two(1) "#, ); } #[test] fn const_imported_ignoring_label() { assert_js!( ("other", r#"pub type One { Two(field: Int) }"#), r#"import other pub const main = other.Two(1) "#, ); } #[test] fn const_imported_using_label() { assert_js!( ("other", r#"pub type One { Two(field: Int) }"#), r#"import other pub const main = other.Two(field: 1) "#, ); } #[test] fn const_imported_multiple_fields() { assert_js!( ("other", r#"pub type One { Two(a: Int, b: Int, c: Int) }"#), r#"import other pub const main = other.Two(b: 2, c: 3, a: 1) "#, ); } #[test] fn const_unqualified_imported_no_label() { assert_js!( ("other", r#"pub type One { Two(Int) }"#), r#"import other.{Two} pub const main = Two(1) "#, ); } #[test] fn const_unqualified_imported_ignoring_label() { assert_js!( ("other", r#"pub type One { Two(field: Int) }"#), r#"import other.{Two} pub const main = Two(1) "#, ); } #[test] fn const_unqualified_imported_using_label() { assert_js!( ("other", r#"pub type One { Two(field: Int) }"#), r#"import other.{Two} pub const main = Two(field: 1) "#, ); } #[test] fn const_unqualified_imported_multiple_fields() { assert_js!( ("other", r#"pub type One { Two(a: Int, b: Int, c: Int) }"#), r#"import other.{Two} pub const main = Two(b: 2, c: 3, a: 1) "#, ); } #[test] fn imported_pattern() { assert_js!( ("other", r#"pub type One { Two(a: Int, b: Int, c: Int) }"#), r#"import other.{Two} pub fn main(x) { case x { Two(a: 1, ..) -> 1 other.Two(b: 2, c: c, ..) -> c _ -> 3 } } "#, ); } #[test] fn keyword_label_name() { assert_js!( r#"pub type Thing { Thing(in: Int, class: Nil) } "#, ); } #[test] fn qualified() { assert_js!( ("other", r#"pub type One { One }"#), r#"import other pub fn main() { other.One } "#, ); } #[test] fn unapplied_record_constructors_typescript() { assert_ts_def!( r#"pub type Cat { Cat(name: String) } pub fn return_unapplied_cat() { Cat } "# ); } #[test] fn opaque_types_typescript() { assert_ts_def!( r#"pub opaque type Animal { Cat(goes_outside: Bool) Dog(plays_fetch: Bool) } "# ); } // https://github.com/gleam-lang/gleam/issues/1650 #[test] fn types_must_be_rendered_before_functions() { assert_js!( r#" pub fn one() { One } pub type One { One } "# ); } // https://github.com/gleam-lang/gleam/issues/2386 #[test] fn new_type_import_syntax() { assert_js!( ("package", "a", r#"pub type A { A }"#), r#" import a.{type A, A} pub fn main() { A } "# ); } // https://github.com/gleam-lang/gleam/issues/3813 #[test] fn record_with_field_named_constructor() { assert_js!( r#" pub type Thing { Thing(constructor: Nil) } pub fn main() { let a = Thing(constructor: Nil) let b = Thing(..a, constructor: Nil) b.constructor } "# ); } #[test] fn record_with_field_named_then() { assert_js!( r#" pub type Thing { Thing(then: Nil) } pub fn main() { let a = Thing(then: Nil) let b = Thing(..a, then: Nil) b.then } "# ); } #[test] fn record_access_in_guard_with_reserved_field_name() { assert_js!( r#" pub type Thing { Thing(constructor: Nil) } pub fn main() { let a = Thing(constructor: Nil) case Nil { Nil if a.constructor == Nil -> a.constructor _ -> Nil } } "# ); } #[test] fn record_access_in_pattern_with_reserved_field_name() { assert_js!( r#" pub type Thing { Thing(constructor: Nil) } pub fn main() { let a = Thing(constructor: Nil) let Thing(constructor: ctor) = a case a { a if a.constructor == ctor -> Nil Thing(constructor:) if ctor == constructor -> Nil _ -> Nil } } "# ); } #[test] fn constructors_get_their_own_jsdoc() { assert_js!( r#" pub type Wibble { /// Wibbling!! Wibble(field: Int) /// Wobbling!! Wobble(field: Int) } "# ); } #[test] fn singleton_record_equality() { assert_js!( r#" pub type Wibble { Wibble Wobble } pub fn is_wibble(w: Wibble) -> Bool { w == Wibble } "#, ); } #[test] fn singleton_record_inequality() { assert_js!( r#" pub type Wibble { Wibble Wobble } pub fn is_not_wibble(w: Wibble) -> Bool { w != Wibble } "#, ); } #[test] fn singleton_record_reverse_order() { assert_js!( r#" pub type Wibble { Wibble Wobble } pub fn is_wibble_reverse(w: Wibble) -> Bool { Wibble == w } "#, ); } #[test] fn non_singleton_record_equality() { assert_js!( r#" pub type Person { Person(name: String, age: Int) } pub fn same_person(p1: Person, p2: Person) -> Bool { p1 == p2 } "#, ); } #[test] fn multiple_singleton_constructors() { assert_js!( r#" pub type Status { Loading Success Error } pub fn is_loading(s: Status) -> Bool { s == Loading } pub fn is_success(s: Status) -> Bool { s == Success } "#, ); } #[test] fn mixed_singleton_and_non_singleton() { assert_js!( r#" pub type Result { Ok(value: Int) Error } pub fn is_error(r: Result) -> Bool { r == Error } "#, ); } #[test] fn singleton_in_case_guard() { assert_js!( r#" pub type State { Active Inactive } pub fn process(s: State) -> String { case s { state if state == Active -> "active" _ -> "inactive" } } "#, ); } #[test] fn equality_with_non_singleton_variant() { assert_js!( r#" pub type Thing { Variant Other(String) } pub fn check_other(x: Thing) -> Bool { x == Other("hello") } "#, ); } #[test] fn guard_equality_with_non_singleton_variant() { assert_js!( r#" pub type Thing { Variant Other(String) } pub fn process(e: Thing) -> String { case e { value if value == Other("hello") -> "match" _ -> "no match" } } "#, ); } #[test] fn variant_defined_in_another_module_qualified_expression() { assert_js!( ( "other_module", r#"pub type Thingy { Variant OtherVariant }"# ), r#" import other_module pub fn check(x) -> Bool { x == other_module.Variant } "#, ); } #[test] fn variant_defined_in_another_module_unqualified_expression() { assert_js!( ("other_module", r#"pub type Thingy { Variant Other(Int) }"#), r#" import other_module.{Variant} pub fn check(x) -> Bool { x == Variant } "#, ); } #[test] fn variant_defined_in_another_module_aliased_expression() { assert_js!( ("other_module", r#"pub type Thingy { Variant Other(Int) }"#), r#" import other_module.{Variant as Aliased} pub fn check(x) -> Bool { x == Aliased } "#, ); } #[test] fn variant_defined_in_another_module_qualified_clause_guard() { assert_js!( ("other_module", r#"pub type Thingy { Variant Other(Int) }"#), r#" import other_module pub fn process(e) -> String { case e { value if value == other_module.Variant -> "match" _ -> "no match" } } "#, ); } #[test] fn variant_defined_in_another_module_unqualified_clause_guard() { assert_js!( ("other_module", r#"pub type Thingy { Variant Other(Int) }"#), r#" import other_module.{Variant} pub fn process(e) -> String { case e { value if value == Variant -> "match" _ -> "no match" } } "#, ); } #[test] fn variant_defined_in_another_module_aliased_clause_guard() { assert_js!( ("other_module", r#"pub type Thingy { Variant Other(Int) }"#), r#" import other_module.{Variant as Aliased} pub fn process(e) -> String { case e { value if value == Aliased -> "match" _ -> "no match" } } "#, ); } #[test] fn external_annotation() { assert_ts_def!( r#" @external(javascript, "./gleam_stdlib.d.ts", "Dict") pub type Dict(key, value) "# ); } #[test] fn external_annotated_type_used_in_function() { assert_ts_def!( r#" @external(javascript, "./gleam_stdlib.d.ts", "Dict") pub type Dict(key, value) @external(javascript, "./gleam_stdlib.mjs", "get") pub fn get(dict: Dict(key, value), key: key) -> Result(value, Nil) "# ); } // https://github.com/gleam-lang/gleam/issues/5127 #[test] fn unused_opaque_constructor_is_generated_correctly() { assert_ts_def!( " type Wibble { Wibble } pub opaque type Wobble { Wobble(Wibble) } " ); } // https://github.com/gleam-lang/gleam/issues/5312 #[test] fn generic_type_parameter_used_in_field() { assert_ts_def!( " pub type Wibble(value, error) { Wibble( wibble: value, wobble: Result(value, error), wubble: error, ) } " ); } ================================================ FILE: compiler-core/src/javascript/tests/echo.rs ================================================ use crate::assert_js; #[test] pub fn echo_with_a_simple_expression() { assert_js!( r#" pub fn main() { echo 1 } "# ); } #[test] pub fn echo_with_a_simple_expression_and_a_message() { assert_js!( r#" pub fn main() { echo 1 as "hello!" } "# ); } #[test] pub fn echo_with_complex_expression_as_a_message() { assert_js!( r#" pub fn main() { echo 1 as case name() { "Giacomo" -> "hello Jak!" _ -> "hello!" } } fn name() { "Giacomo" } "# ); } #[test] pub fn echo_evaluates_printed_value_before_message() { assert_js!( r#" pub fn main() { echo name() as case name() { "Giacomo" -> "hello Jak!" _ -> "hello!" } } fn name() { "Giacomo" } "# ); } #[test] pub fn echo_with_a_block_as_a_message() { assert_js!( r#" pub fn main() { echo 1 as { let name = "Giacomo" "Hello, " <> name } } "# ); } #[test] pub fn multiple_echos_inside_expression() { assert_js!( r#" pub fn main() { echo 1 echo 2 } "# ); } #[test] pub fn echo_with_a_case_expression() { assert_js!( r#" pub fn main() { echo case 1 { _ -> 2 } } "# ); } #[test] pub fn echo_with_a_panic() { assert_js!( r#" pub fn main() { echo panic } "# ); } #[test] pub fn echo_with_a_function_call() { assert_js!( r#" pub fn main() { echo wibble(1, 2) } fn wibble(n: Int, m: Int) { n + m } "# ); } #[test] pub fn echo_with_a_function_call_and_a_message() { assert_js!( r#" pub fn main() { echo wibble(1, 2) as message() } fn wibble(n: Int, m: Int) { n + m } fn message() { "Hello!" } "# ); } #[test] pub fn echo_with_a_block() { assert_js!( r#" pub fn main() { echo { Nil 1 } } "# ); } #[test] pub fn echo_in_a_pipeline() { assert_js!( r#" pub fn main() { [1, 2, 3] |> echo |> wibble } pub fn wibble(n) { n } "# ) } #[test] pub fn echo_in_a_pipeline_with_message() { assert_js!( r#" pub fn main() { [1, 2, 3] |> echo as "message!!" |> wibble } pub fn wibble(n) { n } "# ) } #[test] pub fn multiple_echos_in_a_pipeline() { assert_js!( r#" pub fn main() { [1, 2, 3] |> echo |> wibble |> echo |> wibble |> echo } pub fn wibble(n) { n } "# ) } #[test] pub fn module_named_inspect() { assert_js!( ("other", "other/inspect", "pub const x = Nil"), r#" import other/inspect pub fn main() { echo inspect.x } "# ) } ================================================ FILE: compiler-core/src/javascript/tests/externals.rs ================================================ use crate::{assert_js, assert_module_error, assert_ts_def}; #[test] fn type_() { assert_js!(r#"pub type Thing"#,); } #[test] fn module_fn() { assert_js!( r#" @external(javascript, "utils", "inspect") fn show(x: anything) -> Nil"#, ); } #[test] fn at_namespace_module() { assert_js!( r#" @external(javascript, "@namespace/package", "inspect") fn show(x: anything) -> Nil"#, ); } #[test] fn pub_module_fn() { assert_js!( r#" @external(javascript, "utils", "inspect") pub fn show(x: anything) -> Nil"#, ); } #[test] fn pub_module_fn_typescript() { assert_ts_def!( r#" @external(javascript, "utils", "inspect") pub fn show(x: anything) -> Nil"#, ); } #[test] fn same_name_external() { assert_js!( r#" @external(javascript, "thingy", "fetch") pub fn fetch(request: Nil) -> Nil"#, ); } #[test] fn same_module_multiple_imports() { assert_js!( r#" @external(javascript, "./the/module.mjs", "one") pub fn one() -> Nil @external(javascript, "./the/module.mjs", "two") pub fn two() -> Nil "#, ); } #[test] fn duplicate_import() { assert_js!( r#" @external(javascript, "./the/module.mjs", "dup") pub fn one() -> Nil @external(javascript, "./the/module.mjs", "dup") pub fn two() -> Nil "#, ); } #[test] fn name_to_escape() { assert_js!( r#" @external(javascript, "./the/module.mjs", "one") pub fn class() -> Nil "#, ); } #[test] fn external_type_typescript() { assert_ts_def!( r#"pub type Queue(a) @external(javascript, "queue", "new") pub fn new() -> Queue(a) "#, ); } // https://github.com/gleam-lang/gleam/issues/1636 #[test] fn external_fn_escaping() { assert_js!( r#" @external(javascript, "./ffi.js", "then") pub fn then(a: a) -> b"#, ); } // https://github.com/gleam-lang/gleam/issues/1954 #[test] fn pipe_variable_shadow() { assert_js!( r#" @external(javascript, "module", "string") fn name() -> String pub fn main() { let name = name() name } "# ); } // https://github.com/gleam-lang/gleam/issues/2090 #[test] fn tf_type_name_usage() { assert_ts_def!( r#" pub type TESTitem @external(javascript, "it", "one") pub fn one(a: TESTitem) -> TESTitem "# ); } #[test] fn attribute_erlang() { assert_js!( r#" @external(erlang, "one", "one_erl") pub fn one(x: Int) -> Int { todo } pub fn main() { one(1) } "# ); } #[test] fn attribute_javascript() { assert_js!( r#" @external(javascript, "./one.mjs", "oneJs") pub fn one(x: Int) -> Int { todo } pub fn main() { one(1) } "# ); } #[test] fn erlang_and_javascript() { assert_js!( r#" @external(erlang, "one", "one") @external(javascript, "./one.mjs", "oneJs") pub fn one(x: Int) -> Int { todo } pub fn main() { one(1) } "# ); } #[test] fn private_attribute_erlang() { assert_js!( r#" @external(erlang, "one", "one_erl") fn one(x: Int) -> Int { todo } pub fn main() { one(1) } "# ); } #[test] fn private_attribute_javascript() { assert_js!( r#" @external(javascript, "./one.mjs", "oneJs") fn one(x: Int) -> Int { todo } pub fn main() { one(1) } "# ); } #[test] fn private_erlang_and_javascript() { assert_js!( r#" @external(erlang, "one", "one") @external(javascript, "./one.mjs", "oneJs") fn one(x: Int) -> Int { todo } pub fn main() { one(1) } "# ); } #[test] fn no_body() { assert_js!( r#" @external(javascript, "one", "one") pub fn one(x: Int) -> Int "# ); } #[test] fn no_module() { assert_module_error!( r#" @external(javascript, "", "one") pub fn one(x: Int) -> Int { 1 } "# ); } #[test] fn inline_function() { assert_module_error!( r#" @external(javascript, "blah", "(x => x)") pub fn one(x: Int) -> Int { 1 } "# ); } #[test] fn erlang_only() { assert_js!( r#" pub fn should_be_generated(x: Int) -> Int { x } @external(erlang, "one", "one") pub fn should_not_be_generated(x: Int) -> Int "# ); } #[test] fn both_externals_no_valid_impl() { assert_js!( r#" @external(javascript, "one", "one") pub fn js() -> Nil @external(erlang, "one", "one") pub fn erl() -> Nil pub fn should_not_be_generated() { js() erl() } "# ); } #[test] fn discarded_names_in_external_are_passed_correctly() { assert_js!( r#" @external(javascript, "wibble", "wobble") pub fn woo(_ignored: a) -> Nil "# ); } ================================================ FILE: compiler-core/src/javascript/tests/functions.rs ================================================ use crate::{assert_js, assert_ts_def}; #[test] fn exported_functions() { assert_js!( r#" pub fn add(x, y) { x + y }"#, ); } #[test] fn calling_functions() { assert_js!( r#" pub fn twice(f: fn(t) -> t, x: t) -> t { f(f(x)) } pub fn add_one(x: Int) -> Int { x + 1 } pub fn add_two(x: Int) -> Int { twice(add_one, x) } pub fn take_two(x: Int) -> Int { twice(fn(y) {y - 1}, x) } "#, ); } #[test] fn function_formatting() { assert_js!( r#" pub fn add(the_first_variable_that_should_be_added, the_second_variable_that_should_be_added) { the_first_variable_that_should_be_added + the_second_variable_that_should_be_added }"#, ); } #[test] fn function_formatting1() { assert_js!( r#" pub fn this_function_really_does_have_a_ludicrously_unfeasibly_long_name_for_a_function(x, y) { x + y }"#, ); } #[test] fn function_formatting2() { assert_js!( r#" pub fn add(x, y) { x + y } pub fn long() { add(1, add(1, add(1, add(1, add(1, add(1, add(1, add(1, add(1, add(1, add(1, add(1, add(1, add(1, add(1, 1))))))))))))))) }"#, ); } #[test] fn function_formatting3() { assert_js!( r#" pub fn math(x, y) { fn() { x + y x - y 2 * x } }"#, ); } #[test] fn function_formatting_typescript() { assert_ts_def!( r#" pub fn add(the_first_variable_that_should_be_added, the_second_variable_that_should_be_added) { the_first_variable_that_should_be_added + the_second_variable_that_should_be_added }"#, ); } #[test] fn function_formatting_typescript1() { assert_ts_def!( r#" pub fn this_function_really_does_have_a_ludicrously_unfeasibly_long_name_for_a_function(x, y) { x + y }"#, ); } #[test] fn tail_call() { assert_js!( r#" pub fn count(xs, n) { case xs { [] -> n [_, ..xs] -> count(xs, n + 1) } } "#, ); } #[test] fn tail_call_doesnt_clobber_tail_position_tracking() { assert_js!( r#" pub fn loop(indentation) { case indentation > 0 { True -> loop(indentation - 1) False -> Nil } } "#, ); } #[test] fn pipe_last() { assert_js!( r#"fn id(x) { x } pub fn main() { 1 |> id } "#, ); } #[test] fn calling_fn_literal() { assert_js!( r#"pub fn main() { fn(x) { x }(1) } "#, ); } // Don't mistake calling a function with the same name as the current function // as tail recursion #[test] fn shadowing_current() { assert_js!( r#"pub fn main() { let main = fn() { 0 } main() } "#, ); } #[test] fn recursion_with_discards() { assert_js!( r#"pub fn main(f, _) { f() main(f, 1) } "#, ); } #[test] fn no_recur_in_anon_fn() { assert_js!( r#"pub fn main() { fn() { main() } 1 } "#, ); } #[test] fn case_in_call() { assert_js!( r#"pub fn main(f, x) { f(case x { 1 -> 2 _ -> 0 }) } "#, ); } #[test] fn reserved_word_fn() { assert_js!( r#"pub fn class() { Nil } "#, ); } #[test] fn reserved_word_imported() { assert_js!( ("for", "pub fn class() { 1 }"), r#"import for.{class} pub fn export() { class() } "#, ); } #[test] fn reserved_word_imported_alias() { assert_js!( ("for", "pub fn class() { 1 }"), r#"import for.{class as while} as function pub fn export() { let delete = function.class while() } "#, ); } #[test] fn reserved_word_const() { assert_js!( r#"const in = 1 pub fn export() { in } "#, ); } // https://github.com/gleam-lang/gleam/issues/1208 #[test] fn reserved_word_argument() { assert_js!( r#"pub fn main(with) { with } "#, ); } // https://github.com/gleam-lang/gleam/issues/1186 #[test] fn multiple_discard() { assert_js!( r#"pub fn main(_, _, _) { 1 } "#, ); } #[test] fn keyword_in_recursive_function() { assert_js!( r#"pub fn main(with: Int) -> Nil { main(with - 1) } "#, ); } #[test] fn reserved_word_in_function_arguments() { assert_js!( r#"pub fn main(arguments, eval) { #(arguments, eval) } "#, ); } #[test] fn let_last() { assert_js!( r#"pub fn main() { let x = 1 } "#, ); } #[test] fn assert_last() { assert_js!( r#"pub fn main() { let assert x = 1 } "#, ); } #[test] fn fn_return_fn_typescript() { assert_ts_def!( r#"pub fn main(f: fn(Int) -> Int) { let func = fn(x, y) { f(x) + f(y) } func } "#, ); } // https://github.com/gleam-lang/gleam/issues/1637 #[test] fn variable_rewriting_in_anon_fn_with_matching_parameter() { assert_js!( r#"pub fn bad() { fn(state) { let state = state state } } "#, ); } // https://github.com/gleam-lang/gleam/issues/1637 #[test] fn variable_rewriting_in_anon_fn_with_matching_parameter_in_case() { assert_js!( r#"pub fn bad() { fn(state) { let state = case Nil { _ -> state } state } } "#, ); } // https://github.com/gleam-lang/gleam/issues/1508 #[test] fn pipe_variable_rebinding() { assert_js!( " pub fn main() { let version = 1 |> version() version } pub fn version(n) { Ok(1) }" ) } #[test] fn pipe_shadow_import() { assert_js!( ("wibble", "pub fn println(x: String) { }"), r#" import wibble.{println} pub fn main() { let println = "oh dear" |> println println }"# ); } #[test] fn module_const_fn() { assert_js!( r#" pub fn int_identity(i: Int) -> Int { i } pub const int_identity_alias: fn(Int) -> Int = int_identity pub fn use_int_identity_alias() { int_identity_alias(42) } pub const compound: #(fn(Int) -> Int, fn(Int) -> Int) = #(int_identity, int_identity_alias) pub fn use_compound() { compound.0(compound.1(42)) }"# ); } #[test] fn module_const_fn1() { assert_ts_def!( r#" pub fn int_identity(i: Int) -> Int { i } pub const int_identity_alias: fn(Int) -> Int = int_identity pub const compound: #(fn(Int) -> Int, fn(Int) -> Int) = #(int_identity, int_identity_alias)"# ) } // https://github.com/gleam-lang/gleam/issues/2399 #[test] fn bad_comma() { assert_js!( r#" fn function_with_a_long_name_that_is_intended_to_sit_right_on_the_limit() { Nil } fn identity(x) { x } pub fn main() { function_with_a_long_name_that_is_intended_to_sit_right_on_the_limit() |> identity } "# ) } // https://github.com/gleam-lang/gleam/issues/2518 #[test] fn function_literals_get_properly_wrapped_1() { assert_js!( r#"pub fn main() { fn(n) { n + 1 }(10) } "# ); } // https://github.com/gleam-lang/gleam/issues/2518 #[test] fn function_literals_get_properly_wrapped_2() { assert_js!( r#"pub fn main() { { fn(n) { n + 1 } }(10) } "# ); } // https://github.com/gleam-lang/gleam/issues/2518 #[test] fn function_literals_get_properly_wrapped_3() { assert_js!( r#"pub fn main() { { let a = fn(n) { n + 1 } }(10) } "# ); } #[test] fn labelled_argument_ordering() { // https://github.com/gleam-lang/gleam/issues/3671 assert_js!( " type A { A } type B { B } type C { C } type D { D } fn wibble(a a: A, b b: B, c c: C, d d: D) { Nil } pub fn main() { wibble(A, C, D, b: B) wibble(A, C, D, b: B) wibble(B, C, D, a: A) wibble(B, C, a: A, d: D) wibble(B, C, d: D, a: A) wibble(B, D, a: A, c: C) wibble(B, D, c: C, a: A) wibble(C, D, b: B, a: A) } " ); } // During the implementation of https://github.com/gleam-lang/gleam/pull/4337, // a bug was found where this code would compile incorrectly. #[test] fn two_pipes_in_a_row() { assert_js!( " pub type Function(a) { Function(fn() -> a) } pub fn main() { [fn() { 1 } |> Function, fn() { 2 } |> Function] } " ); } // https://github.com/gleam-lang/gleam/issues/4472 #[test] fn pipe_into_block() { assert_js!( " fn side_effects(x) { x } pub fn main() { 1 |> side_effects |> { side_effects(2) side_effects } } " ); } // https://github.com/gleam-lang/gleam/issues/4472 #[test] fn pipe_with_block_in_the_middle() { assert_js!( " fn side_effects(x) { x } pub fn main() { 1 |> side_effects |> { side_effects(2) side_effects } |> side_effects } " ); } // https://github.com/gleam-lang/gleam/issues/4533 #[test] fn immediately_invoked_function_expressions_include_statement_level() { assert_js!( " fn identity(x) { x } pub type Wibble { Wibble(a: Int, b: Int) } pub fn main() { let w = Wibble(1, 2) identity(Wibble(..w |> identity, b: 4)) |> identity } " ); } #[test] fn public_function_gets_jsdoc() { assert_js!( " /// Hello! This is the documentation of the `main` /// function. /// pub fn main() { 1 } " ); } #[test] fn internal_function_gets_ignored_jsdoc() { assert_js!( " /// Hello! This is the documentation of the `main` /// function, which is internal! /// @internal pub fn main() { 1 } " ); } #[test] fn star_slash_in_jsdoc() { assert_js!( " /// */ /// pub fn main() { 1 } " ); } ================================================ FILE: compiler-core/src/javascript/tests/generics.rs ================================================ use crate::assert_ts_def; #[test] fn fn_generics_typescript() { assert_ts_def!( r#"pub fn identity(a) -> a { a } "#, ); } #[test] fn record_generics_typescript() { assert_ts_def!( r#"pub type Animal(t) { Cat(type_: t) Dog(type_: t) } pub fn main() { Cat(type_: 6) } "#, ); } #[test] fn tuple_generics_typescript() { assert_ts_def!( r#"pub fn make_tuple(x: t) -> #(Int, t, Int) { #(0, x, 1) } "#, ); } #[test] fn result_typescript() { assert_ts_def!( r#"pub fn map(result, fun) { case result { Ok(a) -> Ok(fun(a)) Error(e) -> Error(e) } }"#, ); } #[test] fn task_typescript() { assert_ts_def!( r#"pub type Promise(value) pub type Task(a) = fn() -> Promise(a)"#, ); } ================================================ FILE: compiler-core/src/javascript/tests/inlining.rs ================================================ use crate::assert_js; const BOOL_MODULE: &str = " pub fn guard( when condition: Bool, return value: a, otherwise callback: fn() -> a, ) -> a { case condition { True -> value False -> callback() } } pub fn lazy_guard( when condition: Bool, return consequence: fn() -> a, otherwise alternative: fn() -> a, ) -> a { case condition { True -> consequence() False -> alternative() } } "; const RESULT_MODULE: &str = " pub fn try(result: Result(a, e), apply f: fn(a) -> Result(b, e)) -> Result(b, e) { case result { Ok(value) -> f(value) Error(error) -> Error(error) } } pub fn map(over result: Result(a, e), with f: fn(a) -> b) -> Result(b, e) { case result { Ok(value) -> Ok(f(value)) Error(error) -> Error(error) } } "; #[test] fn inline_higher_order_function() { assert_js!( ("gleam_stdlib", "gleam/result", RESULT_MODULE), " import gleam/result pub fn main() { result.map(over: Ok(10), with: double) } fn double(x) { x + x } " ); } #[test] fn inline_higher_order_function_with_capture() { assert_js!( ("gleam_stdlib", "gleam/result", RESULT_MODULE), " import gleam/result pub fn main() { result.try(Ok(10), divide(_, 2)) } fn divide(a: Int, b: Int) -> Result(Int, Nil) { case a % b { 0 -> Ok(a / b) _ -> Error(Nil) } } " ); } #[test] fn inline_higher_order_function_anonymous() { assert_js!( ("gleam_stdlib", "gleam/result", RESULT_MODULE), " import gleam/result pub fn main() { result.try(Ok(10), fn(value) { Ok({ value + 2 } * 4) }) } " ); } #[test] fn inline_function_which_calls_other_function() { // This function calls `result.try`, meaning this must be inlined twice to // achieve the desired result. assert_js!( ("gleam_stdlib", "gleam/result", RESULT_MODULE), ( "gleam_stdlib", "testing", " import gleam/result.{try} pub fn always_inline(result, f) -> Result(b, e) { try(result, f) } " ), " import testing pub fn main() { testing.always_inline(Ok(10), Error) } " ); } #[test] fn inline_function_with_use() { assert_js!( ("gleam_stdlib", "gleam/bool", BOOL_MODULE), " import gleam/bool pub fn divide(a, b) { use <- bool.guard(when: b == 0, return: 0) a / b } " ); } #[test] fn inline_function_with_use_and_anonymous() { assert_js!( ("gleam_stdlib", "gleam/bool", BOOL_MODULE), r#" import gleam/bool pub fn divide(a, b) { use <- bool.lazy_guard(b == 0, fn() { panic as "Cannot divide by 0" }) a / b } "# ); } #[test] fn inline_function_with_use_becomes_tail_recursive() { assert_js!( ("gleam_stdlib", "gleam/bool", BOOL_MODULE), " import gleam/bool pub fn count(from: Int, to: Int) -> Int { use <- bool.guard(when: from >= to, return: from) echo from count(from + 1, to) } " ); } #[test] fn do_not_inline_parameters_used_more_than_once() { // Since the `something` parameter is used more than once in the body of the // function, it should not be inlined, and should be assigned once at the // beginning of the function. assert_js!( ( "gleam_stdlib", "testing", " pub fn always_inline(something) { case something { True -> something False -> False } } " ), " import testing pub fn main() { testing.always_inline(True) } " ); } #[test] fn do_not_inline_parameters_that_have_side_effects() { assert_js!( ("gleam_stdlib", "gleam/result", RESULT_MODULE), r#" import gleam/result pub fn main() { result.map(Ok(10), do_side_effects()) } fn do_side_effects() { let function = fn(x) { x + 1 } panic as "Side effects" function } "# ); } #[test] fn inline_anonymous_function_call() { assert_js!( " pub fn main() { fn(a, b) { #(a, b) }(42, False) } " ); } #[test] fn inline_anonymous_function_in_pipe() { assert_js!( " pub fn main() { 1 |> fn(x) { x + 1 } |> fn(y) { y * y } } " ); } #[test] fn inline_function_capture_in_pipe() { // The function capture is desugared to an anonymous function, so it should // be turned into a direct call to `add` assert_js!( " pub fn main() { 1 |> add(4, _) } fn add(a, b) { a + b } " ); } #[test] fn inlining_works_through_blocks() { assert_js!( " pub fn main() { { fn(x) { Ok(x + 1) } }(41) } " ); } #[test] fn blocks_get_preserved_when_needed() { assert_js!( " pub fn main() { { 4 |> make_adder }(6) } fn make_adder(a) { fn(b) { a + b } } " ); } #[test] fn blocks_get_preserved_when_needed2() { assert_js!( " pub fn main() { fn(x) { 1 + x }(2) * 3 } " ); } #[test] fn parameters_from_nested_functions_are_correctly_inlined() { assert_js!( ("gleam_stdlib", "gleam/result", RESULT_MODULE), " import gleam/result pub fn halve_all(a, b, c) { use x <- result.try(divide(a, 2)) use y <- result.try(divide(b, 2)) use z <- result.map(divide(c, 2)) #(x, y, z) } fn divide(a, b) { case a % b { 0 -> Ok(a / b) _ -> Error(Nil) } } " ); } // https://github.com/gleam-lang/gleam/issues/4852 #[test] fn inlining_works_properly_with_record_updates() { assert_js!( ("gleam_stdlib", "gleam/result", RESULT_MODULE), " import gleam/result pub type Wibble { Wibble(a: Int, b: Int) } pub fn main() { let w = Wibble(1, 2) use b <- result.map(Ok(3)) Wibble(..w, b:) } " ); } // https://github.com/gleam-lang/gleam/issues/4877 #[test] fn inline_shadowed_variable() { assert_js!( " pub fn main() { let a = 10 let b = 20 fn(x) { let a = 7 x + a }(a + b) a } " ); } #[test] fn inline_variable_shadowing_parameter() { assert_js!( " pub fn sum(a, b) { fn(x) { let a = 7 x + a }(a + b) a } " ); } #[test] fn inline_shadowed_variable_nested() { assert_js!( " pub fn sum(a, b) { fn(x) { let a = 7 fn(y) { let a = 10 y - a }(x + a) a }(a + b) a } " ); } #[test] fn inline_variable_shadowed_in_case_pattern() { assert_js!( " pub fn sum() { let a = 10 let b = 20 fn(x) { case 7, 8 { a, b -> a + b + x } }(a + b) a + b } " ); } #[test] fn inline_variable_shadowing_case_pattern() { assert_js!( " pub fn sum() { case 1, 2 { a, b -> fn(x) { let a = 7 x + a }(a + b) } } " ); } ================================================ FILE: compiler-core/src/javascript/tests/lists.rs ================================================ use crate::{assert_js, assert_ts_def}; #[test] fn list_literals() { assert_js!( r#" pub fn go(x) { [] [1] [1, 2] [1, 2, ..x] } "#, ); } #[test] fn long_list_literals() { assert_js!( r#" pub fn go() { [111111111111111111111111111111111111111111111111111111111111111111111111] [11111111111111111111111111111111111111111111, 1111111111111111111111111111111111111111111] } "#, ); } #[test] fn multi_line_list_literals() { assert_js!( r#" pub fn go(x) { [{True 1}] } "#, ); } #[test] fn list_constants() { assert_js!( r#" pub const a = [] pub const b = [1, 2, 3] "#, ); } #[test] fn list_constants_typescript() { assert_ts_def!( r#" pub const a = [] pub const b = [1, 2, 3] "#, ); } #[test] fn list_destructuring() { assert_js!( r#" pub fn go(x, y) { let assert [] = x let assert [a] = x let assert [1, 2] = x let assert [_, #(3, b)] = y let assert [head, ..tail] = y } "#, ); } #[test] fn equality() { assert_js!( r#" pub fn go() { [] == [1] [] != [1] } "#, ); } #[test] fn case() { assert_js!( r#" pub fn go(xs) { case xs { [] -> 0 [_] -> 1 [_, _] -> 2 _ -> 9999 } } "#, ); } // https://github.com/gleam-lang/gleam/issues/2904 #[test] fn tight_empty_list() { assert_js!( r#" pub fn go(func) { let huuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuge_variable = [] } "#, ); } ================================================ FILE: compiler-core/src/javascript/tests/modules.rs ================================================ use crate::assert_js; use crate::javascript::tests::CURRENT_PACKAGE; #[test] fn empty_module() { // Renders an export statement to ensure it's an ESModule assert_js!("", "export {}\n"); } #[test] fn unqualified_fn_call() { assert_js!( ("rocket_ship", r#"pub fn launch() { 1 }"#), r#"import rocket_ship.{launch} pub fn go() { launch() } "#, ); } #[test] fn aliased_unqualified_fn_call() { assert_js!( ("rocket_ship", r#"pub fn launch() { 1 }"#), r#"import rocket_ship.{launch as boom_time} pub fn go() { boom_time() } "#, ); } #[test] fn multiple_unqualified_fn_call() { assert_js!( ( CURRENT_PACKAGE, "rocket_ship", r#" pub fn a() { 1 } pub fn b() { 2 }"# ), r#"import rocket_ship.{a,b as bb} pub fn go() { a() + bb() } "#, ); } #[test] fn constant() { assert_js!( ("rocket_ship", r#"pub const x = 1"#), r#" import rocket_ship pub fn go() { rocket_ship.x } "#, ); } #[test] fn alias_aliased_constant() { assert_js!( ("rocket_ship", r#"pub const x = 1"#), r#" import rocket_ship.{ x as y } pub const z = y "#, ); } #[test] fn renamed_module() { assert_js!( ("x", r#"pub const v = 1"#), r#" import x as y pub const z = y.v "#, ); } #[test] fn nested_module_constant() { assert_js!( ( CURRENT_PACKAGE, "rocket_ship/launcher", r#"pub const x = 1"# ), r#" import rocket_ship/launcher pub fn go() { launcher.x } "#, ); } #[test] fn alias_constant() { assert_js!( ("rocket_ship", r#"pub const x = 1"#), r#" import rocket_ship as boop pub fn go() { boop.x } "#, ); } #[test] fn alias_fn_call() { assert_js!( ("rocket_ship", r#"pub fn go() { 1 }"#), r#" import rocket_ship as boop pub fn go() { boop.go() } "#, ); } #[test] fn nested_fn_call() { assert_js!( ("one/two", r#"pub fn go() { 1 }"#), r#"import one/two pub fn go() { two.go() }"#, ); } #[test] fn nested_nested_fn_call() { assert_js!( ("one/two/three", r#"pub fn go() { 1 }"#), r#"import one/two/three pub fn go() { three.go() }"#, ); } #[test] fn different_package_import() { assert_js!( ("other_package", "one", r#"pub fn go() { 1 }"#), r#"import one pub fn go() { one.go() } "#, ); } #[test] fn nested_same_package() { assert_js!( ("one/two/three", r#"pub fn go() { 1 }"#), r#"import one/two/three pub fn go() { three.go() } "#, ); } #[test] fn discarded_duplicate_import() { assert_js!( ("esa/rocket_ship", r#"pub fn go() { 1 }"#), ("nasa/rocket_ship", r#"pub fn go() { 1 }"#), r#" import esa/rocket_ship import nasa/rocket_ship as _nasa_rocket pub fn go() { rocket_ship.go() } "# ); } #[test] fn discarded_duplicate_import_with_unqualified() { assert_js!( ("esa/rocket_ship", r#"pub fn go() { 1 }"#), ("nasa/rocket_ship", r#"pub fn go() { 1 }"#), r#" import esa/rocket_ship import nasa/rocket_ship.{go} as _nasa_rocket pub fn esa_go() { rocket_ship.go() } pub fn nasa_go() { go() } "# ); } #[test] fn import_with_keyword() { assert_js!( ( CURRENT_PACKAGE, "rocket_ship", r#" pub const class = 1 pub const in = 2 "# ), r#" import rocket_ship.{class, in as while} pub fn main() { #(class, while) } "# ); } // https://github.com/gleam-lang/gleam/issues/3004 #[test] fn constant_module_access_with_keyword() { assert_js!( ("rocket_ship", r#"pub const class = 1"#), r#" import rocket_ship pub const variable = rocket_ship.class "#, ); } ================================================ FILE: compiler-core/src/javascript/tests/numbers.rs ================================================ use crate::{assert_js, assert_js_module_error}; #[test] fn int_literals() { assert_js!( r#" pub fn go() { 1 2 -3 4001 0b00001111 0o17 0xF 1_000 } "#, ); } #[test] fn float_literals() { assert_js!( r#" pub fn go() { 1.5 2.0 -0.1 1. } "#, ); } #[test] fn float_scientific_literals() { assert_js!( r#" pub fn go() { 0.01e-1 0.01e-0 -10.01e-1 -10.01e-0 -100.001e-523 -100.001e-123_456_789 } "#, ); } #[test] fn int_operators() { assert_js!( r#" pub fn go() { 1 + 1 // => 2 5 - 1 // => 4 5 / 2 // => 2 3 * 3 // => 9 5 % 2 // => 1 2 > 1 // => True 2 < 1 // => False 2 >= 1 // => True 2 <= 1 // => False } "#, ); } #[test] fn int_divide_complex_expr() { assert_js!( r#" pub fn go() { case 1 >= 0 { True -> 2 False -> 4 } / 2 } "#, ); } #[test] fn int_mod_complex_expr() { assert_js!( r#" pub fn go() { case 1 >= 0 { True -> 2 False -> 4 } % 2 } "#, ); } #[test] fn float_operators() { assert_js!( r#" pub fn go() { 1.0 +. 1.4 // => 2.4 5.0 -. 1.5 // => 3.5 5.0 /. 2.0 // => 2.5 3.0 *. 3.1 // => 9.3 2.0 >. 1.0 // => True 2.0 <. 1.0 // => False 2.0 >=. 1.0 // => True 2.0 <=. 1.0 // => False } "#, ); } #[test] fn float_divide_complex_expr() { assert_js!( r#" pub fn go() { case 1.0 >=. 0.0 { True -> 2.0 False -> 4.0 } /. 2.0 } "#, ); } #[test] fn wide_float_div() { assert_js!( r#" pub fn go() { 111111111111111111111111111111. /. 22222222222222222222222222222222222. } "#, ); } #[test] fn int_patterns() { assert_js!( r#" pub fn go(x) { let assert 4 = x } "#, ); } #[test] fn int_equality() { assert_js!( r#" pub fn go() { 1 != 2 1 == 2 } "#, ); } #[test] fn int_equality1() { assert_js!( r#" pub fn go(y) { let x = 1 x == y } "#, ); } #[test] fn float_equality() { assert_js!( r#" pub fn go() { 1.0 != 2.0 1.0 == 2.0 } "#, ); } #[test] fn float_equality1() { assert_js!( r#" pub fn go(y) { let x = 1.0 x == y } "#, ); } #[test] fn operator_precedence() { assert_js!( r#" pub fn go() { 2.4 *. { 3.5 +. 6.0 } } "#, ) } #[test] fn remainder() { assert_js!( r#" pub fn go() { 5 % 0 // => 0 } "#, ); } #[test] fn int_negation() { assert_js!( r#" pub fn go() { let a = 3 let b = -a } "#, ); } #[test] fn repeated_int_negation() { assert_js!( r#" pub fn go() { let a = 3 let b = --a } "# ); } // https://github.com/gleam-lang/gleam/issues/2412 #[test] fn preceeding_zeros_int() { assert_js!( r#" pub fn main() { 09_179 } "# ); } // https://github.com/gleam-lang/gleam/issues/5459 #[test] fn many_preceeding_zeros_int() { assert_js!( r#" pub fn main() { 0000_000_00_9_179 } "# ); } // https://github.com/gleam-lang/gleam/issues/2412 #[test] fn preceeding_zeros_float() { assert_js!( r#" pub fn main() { 09_179.1 } "# ); } // https://github.com/gleam-lang/gleam/issues/5459 #[test] fn many_preceeding_zeros_float() { assert_js!( r#" pub fn main() { 0000_000_00_9_179.1 } "# ); } // https://github.com/gleam-lang/gleam/issues/2412 #[test] fn preceeding_zeros_int_const() { assert_js!( r#" pub const x = 09_179 "# ); } // https://github.com/gleam-lang/gleam/issues/5459 #[test] fn many_preceeding_zeros_int_const() { assert_js!( r#" pub const x = 0000_000_00_9_179 "# ); } // https://github.com/gleam-lang/gleam/issues/2412 #[test] fn preceeding_zeros_float_const() { assert_js!( r#" pub const x = 09_179.1 "# ); } // https://github.com/gleam-lang/gleam/issues/5459 #[test] fn many_preceeding_zeros_float_const() { assert_js!( r#" pub const x = 0000_000_00_9_179.1 "# ); } // https://github.com/gleam-lang/gleam/issues/2412 #[test] fn preceeding_zeros_int_pattern() { assert_js!( r#" pub fn main(x) { let assert 09_179 = x } "# ); } // https://github.com/gleam-lang/gleam/issues/5459 #[test] fn many_preceeding_zeros_int_pattern() { assert_js!( r#" pub fn main(x) { let assert 0000_000_00_9_179 = x } "# ); } // https://github.com/gleam-lang/gleam/issues/2412 #[test] fn preceeding_zeros_float_pattern() { assert_js!( r#" pub fn main(x) { let assert 09_179.1 = x } "# ); } // https://github.com/gleam-lang/gleam/issues/5459 #[test] fn many_preceeding_zeros_float_pattern() { assert_js!( r#" pub fn main(x) { let assert 0000_000_00_9_179.1 = x } "# ); } // https://github.com/gleam-lang/gleam/issues/4459 #[test] fn underscore_after_hexadecimal_prefix() { assert_js!( " pub fn main() { 0x_12_34 } " ); } // https://github.com/gleam-lang/gleam/issues/4459 #[test] fn underscore_after_octal_prefix() { assert_js!( " pub fn main() { 0o_12_34 } " ); } // https://github.com/gleam-lang/gleam/issues/4459 #[test] fn underscore_after_binary_prefix() { assert_js!( " pub fn main() { 0b_10_01 } " ); } // https://github.com/gleam-lang/gleam/issues/4481 #[test] fn underscore_after_zero_after_hex_prefix() { assert_js!( " pub fn main() { 0x0_1_2_3 } " ); } // https://github.com/gleam-lang/gleam/issues/4481 #[test] fn underscore_after_zero_after_octal_prefix() { assert_js!( " pub fn main() { 0o0_1_2_3 } " ); } // https://github.com/gleam-lang/gleam/issues/4481 #[test] fn underscore_after_zero_after_binary_prefix() { assert_js!( " pub fn main() { 0b0_1_0_1 } " ); } // https://github.com/gleam-lang/gleam/issues/4481 #[test] fn underscore_after_zero() { assert_js!( " pub fn main() { 0_1_2_3 } " ); } #[test] fn zero_after_underscore_after_hex_prefix() { assert_js!( " pub fn main() { 0x_0_1_2_3 } " ); } #[test] fn zero_after_underscore_after_octal_prefix() { assert_js!( " pub fn main() { 0o_0_1_2_3 } " ); } #[test] fn zero_after_underscore_after_binary_prefix() { assert_js!( " pub fn main() { 0b_0_1_0_1 } " ); } #[test] fn underscore_after_decimal_point() { assert_js!( " pub fn main() { 0._1 } " ); } #[test] fn underscore_after_decimal_point_case_statement() { assert_js!( " pub fn main(x) { case x { 0._1 -> \"wobble\" _ -> \"wibble\" } } " ); } #[test] fn inf_float_case_statement() { assert_js_module_error!( " pub fn main(x) { case x { 100.001e123_456_789 -> \"wobble\" _ -> \"wibble\" } } " ); } #[test] fn division_inf_by_inf_float() { assert_js_module_error!( " pub fn main(x) { -100.001e123_456_789 /. 100.001e123_456_789 } " ); } #[test] fn division_by_zero_float() { assert_js!( "pub fn main() { 1.1 /. 0.0 }" ) } #[test] fn division_by_non_zero_float() { assert_js!( "pub fn main() { 1.1 /. 2.3 }" ) } #[test] fn complex_division_by_non_zero_float() { assert_js!( "pub fn main() { { 1.1 +. 2.0 } /. 2.3 }" ) } #[test] fn division_by_zero_int() { assert_js!( "pub fn main() { 1 / 0 }" ) } #[test] fn division_by_non_zero_int() { assert_js!( "pub fn main() { 1 / 2 }" ) } #[test] fn complex_division_by_non_zero_int() { assert_js!( "pub fn main() { { 1 + 2 } / 3 }" ) } #[test] fn remainder_by_zero_int() { assert_js!( "pub fn main() { 1 % 0 }" ) } #[test] fn remainder_by_non_zero_int() { assert_js!( "pub fn main() { 1 % 2 }" ) } #[test] fn complex_remainder_by_non_zero_int() { assert_js!( "pub fn main() { { 1 + 2 } % 3 }" ) } ================================================ FILE: compiler-core/src/javascript/tests/panic.rs ================================================ use crate::{assert_js, assert_ts_def}; #[test] fn bare() { assert_js!( r#" pub fn go() { panic } "#, ); } #[test] fn panic_as() { assert_js!( r#" pub fn go() { let x = "wibble" panic as x } "#, ); } #[test] fn bare_typescript() { assert_ts_def!( r#" pub fn go() { panic } "#, ); } #[test] fn as_expression() { assert_js!( r#" pub fn go(f) { let boop = panic f(panic) } "#, ); } #[test] fn pipe() { assert_js!( r#" pub fn go(f) { f |> panic } "#, ); } #[test] fn sequence() { assert_js!( r#" pub fn go(at_the_disco) { panic at_the_disco } "#, ); } #[test] fn case() { assert_js!( r#" pub fn go(x) { case x { _ -> panic } } "#, ); } // https://github.com/gleam-lang/gleam/issues/4471 #[test] fn case_message() { assert_js!( r#" pub fn crash(message) { panic as case message { Ok(message) -> message Error(_) -> "No message provided" } } "# ); } ================================================ FILE: compiler-core/src/javascript/tests/prelude.rs ================================================ use crate::{assert_js, assert_ts_def}; #[test] fn qualified_ok() { assert_js!( r#"import gleam pub fn go() { gleam.Ok(1) } "#, ); } #[test] fn qualified_ok_typescript() { assert_ts_def!( r#"import gleam pub fn go() { gleam.Ok(1) } "#, ); } #[test] fn qualified_error() { assert_js!( r#"import gleam pub fn go() { gleam.Error(1) } "#, ); } #[test] fn qualified_nil() { assert_js!( r#"import gleam pub fn go() { gleam.Nil } "#, ); } #[test] fn qualified_nil_typescript() { assert_ts_def!( r#"import gleam pub fn go() { gleam.Nil } "#, ); } // https://github.com/gleam-lang/gleam/issues/4756 #[test] fn qualified_prelude_value_does_not_conflict_with_local_value() { assert_js!( " import gleam pub type Result(a, e) { Ok(a) Error(e) } pub fn main() { gleam.Ok(10) } ", ); } #[test] fn qualified_prelude_value_does_not_conflict_with_local_value_constant() { assert_js!( r#" import gleam pub type Result(a, e) { Ok(a) Error(e) } pub const error = gleam.Error("Bad") "#, ); } #[test] fn qualified_prelude_value_does_not_conflict_with_local_value_pattern() { assert_js!( r#" import gleam pub type Result(a, e) { Ok(a) Error(e) } pub fn go(x) { case x { gleam.Ok(x) -> x gleam.Error(_) -> 0 } } "#, ); } ================================================ FILE: compiler-core/src/javascript/tests/records.rs ================================================ use crate::assert_js; #[test] fn record_accessors() { // We can use record accessors for types with only one constructor assert_js!( r#" pub type Person { Person(name: String, age: Int) } pub fn get_age(person: Person) { person.age } pub fn get_name(person: Person) { person.name } "# ); } #[test] fn record_accessor_multiple_variants() { // We can access fields on custom types with multiple variants assert_js!( " pub type Person { Teacher(name: String, title: String) Student(name: String, age: Int) } pub fn get_name(person: Person) { person.name }" ); } #[test] fn record_accessor_multiple_variants_positions_other_than_first() { // We can access fields on custom types with multiple variants // In positions other than the 1st field assert_js!( " pub type Person { Teacher(name: String, age: Int, title: String) Student(name: String, age: Int) } pub fn get_name(person: Person) { person.name } pub fn get_age(person: Person) { person.age }" ); } #[test] fn record_accessor_multiple_with_first_position_different_types() { // We can access fields on custom types with multiple variants // In positions other than the 1st field assert_js!( " pub type Person { Teacher(name: Nil, age: Int) Student(name: String, age: Int) } pub fn get_age(person: Person) { person.age }" ); } #[test] fn record_accessor_multiple_variants_parameterised_types() { // We can access fields on custom types with multiple variants // In positions other than the 1st field assert_js!( " pub type Person { Teacher(name: String, age: List(Int), title: String) Student(name: String, age: List(Int)) } pub fn get_name(person: Person) { person.name } pub fn get_age(person: Person) { person.age }" ); } // https://github.com/gleam-lang/gleam/issues/4603 #[test] fn field_named_x0() { assert_js!( " pub type Wibble { Wibble(Int, x0: String) } " ); } #[test] fn field_named_then_is_escaped() { assert_js!( " pub type Wibble { Wibble(then: fn() -> Int) } " ); } #[test] fn field_named_constructor_is_escaped() { assert_js!( " pub type Wibble { Wibble(constructor: fn() -> Wibble) } " ); } #[test] fn field_named_prototype_is_escaped() { assert_js!( " pub type Wibble { Wibble(prototype: String) } " ); } #[test] fn const_record_update_generic_respecialization() { assert_js!( " pub type Box(a) { Box(name: String, value: a) } pub const base = Box(\"score\", 50) pub const updated = Box(..base, value: \"Hello\") pub fn main() { #(base, updated) } ", ); } #[test] fn record_update_with_unlabelled_fields() { assert_js!( r#" pub type Wibble { Wibble(Int, Float, b: Bool, s: String) } pub fn main() { let record = Wibble(1, 3.14, True, "Hello") Wibble(..record, b: False) } "# ); } #[test] fn constant_record_update_with_unlabelled_fields() { assert_js!( r#" pub type Wibble { Wibble(Int, Float, b: Bool, s: String) } pub const record = Wibble(1, 3.14, True, "Hello") pub const updated = Wibble(..record, b: False) pub fn main() { updated } "# ); } ================================================ FILE: compiler-core/src/javascript/tests/recursion.rs ================================================ use crate::assert_js; #[test] fn tco() { assert_js!( r#" pub fn main(x) { case x { 0 -> Nil _ -> main(x - 1) } } "# ); } #[test] fn tco_case_block() { assert_js!( r#" pub fn main(x) { case x { 0 -> Nil _ -> { let y = x main(y - 1) } } } "# ); } // https://github.com/gleam-lang/gleam/issues/1779 #[test] fn not_tco_due_to_assignment() { assert_js!( r#" pub fn main(x) { let z = { let y = x main(y - 1) } z } "# ); } // https://github.com/gleam-lang/gleam/issues/2400 #[test] fn shadowing_so_not_recursive() { // This funtion is calling an argument with the same name as itself, so it is not recursive assert_js!( r#" pub fn map(map) { map() } "# ); } ================================================ FILE: compiler-core/src/javascript/tests/results.rs ================================================ use crate::assert_js; #[test] fn ok() { assert_js!(r#"pub fn main() { Ok(1) }"#); } #[test] fn error() { assert_js!(r#"pub fn main() { Error(1) }"#); } #[test] fn ok_fn() { assert_js!(r#"pub fn main() { Ok }"#); } #[test] fn error_fn() { assert_js!(r#"pub fn main() { Error }"#); } #[test] fn qualified_ok() { assert_js!( r#"import gleam pub fn main() { gleam.Ok(1) }"# ); } #[test] fn qualified_error() { assert_js!( r#"import gleam pub fn main() { gleam.Error(1) }"# ); } #[test] fn qualified_ok_fn() { assert_js!( r#"import gleam pub fn main() { gleam.Ok }"# ); } #[test] fn qualified_error_fn() { assert_js!( r#"import gleam pub fn main() { gleam.Error }"# ); } #[test] fn aliased_ok() { assert_js!( r#"import gleam.{Ok as Thing} pub fn main() { Thing(1) }"# ); } #[test] fn aliased_error() { assert_js!( r#"import gleam.{Error as Thing} pub fn main() { Thing(1) }"# ); } #[test] fn aliased_ok_fn() { assert_js!( r#"import gleam.{Ok as Thing} pub fn main() { Thing }"# ); } #[test] fn aliased_error_fn() { assert_js!( r#"import gleam.{Error as Thing} pub fn main() { Thing }"# ); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assert__assert_binary_operation.snap ================================================ --- source: compiler-core/src/javascript/tests/assert.rs assertion_line: 28 expression: "\npub fn main() {\n let x = True\n assert x || False\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn main() { let x = True assert x || False } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main() { let x = true; if (!(x || false)) { throw makeError( "assert", FILEPATH, "my/mod", 4, "main", "Assertion failed.", { kind: "binary_operator", operator: "||", left: { kind: "expression", value: false, start: 41, end: 42 }, right: { kind: "literal", value: false, start: 46, end: 51 }, start: 34, end: 51, expression_start: 41 } ) } return undefined; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assert__assert_binary_operation2.snap ================================================ --- source: compiler-core/src/javascript/tests/assert.rs assertion_line: 40 expression: "\npub fn eq(a, b) {\n assert a == b\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn eq(a, b) { assert a == b } ----- COMPILED JAVASCRIPT import { makeError, isEqual } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function eq(a, b) { if (!(isEqual(a, b))) { throw makeError( "assert", FILEPATH, "my/mod", 3, "eq", "Assertion failed.", { kind: "binary_operator", operator: "==", left: { kind: "expression", value: a, start: 28, end: 29 }, right: { kind: "expression", value: b, start: 33, end: 34 }, start: 21, end: 34, expression_start: 28 } ) } return undefined; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assert__assert_binary_operation3.snap ================================================ --- source: compiler-core/src/javascript/tests/assert.rs assertion_line: 51 expression: "\npub fn assert_answer(x) {\n assert x == 42\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn assert_answer(x) { assert x == 42 } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function assert_answer(x) { let $ = 42; if (!(x === $)) { throw makeError( "assert", FILEPATH, "my/mod", 3, "assert_answer", "Assertion failed.", { kind: "binary_operator", operator: "==", left: { kind: "expression", value: x, start: 36, end: 37 }, right: { kind: "literal", value: $, start: 41, end: 43 }, start: 29, end: 43, expression_start: 36 } ) } return undefined; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assert__assert_binary_operator_with_side_effects.snap ================================================ --- source: compiler-core/src/javascript/tests/assert.rs expression: "\nfn wibble(a, b) {\n let result = a + b\n result == 10\n}\n\npub fn go(x) {\n assert True && wibble(1, 4)\n}\n" --- ----- SOURCE CODE fn wibble(a, b) { let result = a + b result == 10 } pub fn go(x) { assert True && wibble(1, 4) } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; function wibble(a, b) { let result = a + b; return result === 10; } export function go(x) { if (true) { if (!wibble(1, 4)) { throw makeError( "assert", FILEPATH, "my/mod", 8, "go", "Assertion failed.", { kind: "binary_operator", operator: "&&", left: { kind: "literal", value: true, start: 82, end: 86 }, right: { kind: "expression", value: false, start: 90, end: 102 }, start: 75, end: 102, expression_start: 82 } ) } } else { throw makeError( "assert", FILEPATH, "my/mod", 8, "go", "Assertion failed.", { kind: "binary_operator", operator: "&&", left: { kind: "literal", value: false, start: 82, end: 86 }, right: { kind: "unevaluated", start: 90, end: 102 }, start: 75, end: 102, expression_start: 82 } ) } return undefined; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assert__assert_binary_operator_with_side_effects2.snap ================================================ --- source: compiler-core/src/javascript/tests/assert.rs expression: "\nfn wibble(a, b) {\n let result = a + b\n result == 10\n}\n\npub fn go(x) {\n assert wibble(5, 5) && wibble(4, 6)\n}\n" --- ----- SOURCE CODE fn wibble(a, b) { let result = a + b result == 10 } pub fn go(x) { assert wibble(5, 5) && wibble(4, 6) } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; function wibble(a, b) { let result = a + b; return result === 10; } export function go(x) { if (wibble(5, 5)) { if (!wibble(4, 6)) { throw makeError( "assert", FILEPATH, "my/mod", 8, "go", "Assertion failed.", { kind: "binary_operator", operator: "&&", left: { kind: "expression", value: true, start: 82, end: 94 }, right: { kind: "expression", value: false, start: 98, end: 110 }, start: 75, end: 110, expression_start: 82 } ) } } else { throw makeError( "assert", FILEPATH, "my/mod", 8, "go", "Assertion failed.", { kind: "binary_operator", operator: "&&", left: { kind: "expression", value: false, start: 82, end: 94 }, right: { kind: "unevaluated", start: 98, end: 110 }, start: 75, end: 110, expression_start: 82 } ) } return undefined; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assert__assert_function_call.snap ================================================ --- source: compiler-core/src/javascript/tests/assert.rs assertion_line: 62 expression: "\nfn bool() {\n True\n}\n\npub fn main() {\n assert bool()\n}\n" snapshot_kind: text --- ----- SOURCE CODE fn bool() { True } pub fn main() { assert bool() } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; function bool() { return true; } export function main() { if (!bool()) { throw makeError( "assert", FILEPATH, "my/mod", 7, "main", "Assertion failed.", { kind: "function_call", arguments: [], start: 41, end: 54, expression_start: 48 } ) } return undefined; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assert__assert_function_call2.snap ================================================ --- source: compiler-core/src/javascript/tests/assert.rs assertion_line: 77 expression: "\nfn and(a, b) {\n a && b\n}\n\npub fn go(x) {\n assert and(True, x)\n}\n" snapshot_kind: text --- ----- SOURCE CODE fn and(a, b) { a && b } pub fn go(x) { assert and(True, x) } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; function and(a, b) { return a && b; } export function go(x) { if (!and(true, x)) { throw makeError( "assert", FILEPATH, "my/mod", 7, "go", "Assertion failed.", { kind: "function_call", arguments: [ { kind: "literal", value: true, start: 56, end: 60 }, { kind: "expression", value: x, start: 62, end: 63 }, ], start: 45, end: 64, expression_start: 52 } ) } return undefined; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assert__assert_literal.snap ================================================ --- source: compiler-core/src/javascript/tests/assert.rs assertion_line: 17 expression: "\npub fn main() {\n assert False\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn main() { assert False } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main() { if (!false) { throw makeError( "assert", FILEPATH, "my/mod", 3, "main", "Assertion failed.", { kind: "expression", expression: { kind: "literal", value: false, start: 26, end: 31 }, start: 19, end: 31, expression_start: 26 } ) } return undefined; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assert__assert_nested_function_call.snap ================================================ --- source: compiler-core/src/javascript/tests/assert.rs assertion_line: 92 expression: "\nfn and(x, y) {\n x && y\n}\n\npub fn main() {\n assert and(and(True, False), True)\n}\n" snapshot_kind: text --- ----- SOURCE CODE fn and(x, y) { x && y } pub fn main() { assert and(and(True, False), True) } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; function and(x, y) { return x && y; } export function main() { let $ = and(true, false); if (!and($, true)) { throw makeError( "assert", FILEPATH, "my/mod", 7, "main", "Assertion failed.", { kind: "function_call", arguments: [ { kind: "expression", value: $, start: 57, end: 73 }, { kind: "literal", value: true, start: 75, end: 79 }, ], start: 46, end: 80, expression_start: 53 } ) } return undefined; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assert__assert_nil_always_throws.snap ================================================ --- source: compiler-core/src/javascript/tests/assert.rs expression: "\npub fn go(x: Nil) {\n let assert Nil = x\n}\n" --- ----- SOURCE CODE pub fn go(x: Nil) { let assert Nil = x } ----- COMPILED JAVASCRIPT export function go(x) { return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assert__assert_variable.snap ================================================ --- source: compiler-core/src/javascript/tests/assert.rs assertion_line: 5 expression: "\npub fn main() {\n let x = True\n assert x\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn main() { let x = True assert x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main() { let x = true; if (!x) { throw makeError( "assert", FILEPATH, "my/mod", 4, "main", "Assertion failed.", { kind: "expression", expression: { kind: "expression", value: false, start: 41, end: 42 }, start: 34, end: 42, expression_start: 41 } ) } return undefined; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assert__assert_with_block_message.snap ================================================ --- source: compiler-core/src/javascript/tests/assert.rs assertion_line: 150 expression: "\nfn identity(a) {\n a\n}\n\npub fn main() {\n assert identity(True) as {\n let message = identity(\"This shouldn't fail\")\n message\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE fn identity(a) { a } pub fn main() { assert identity(True) as { let message = identity("This shouldn't fail") message } } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; function identity(a) { return a; } export function main() { if (!identity(true)) { throw makeError( "assert", FILEPATH, "my/mod", 7, "main", (() => { let message = identity("This shouldn't fail"); return message; })(), { kind: "function_call", arguments: [{ kind: "literal", value: true, start: 59, end: 63 }], start: 43, end: 64, expression_start: 50 } ) } return undefined; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assert__assert_with_case_rhs.snap ================================================ --- source: compiler-core/src/javascript/tests/assert.rs expression: "\npub fn main() {\n assert True && case 1 > 2 {\n True -> True\n False -> False\n }\n}\n" --- ----- SOURCE CODE pub fn main() { assert True && case 1 > 2 { True -> True False -> False } } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main() { if (true) { if (!(() => { let $ = 1 > 2; if ($) { return $; } else { return $; } })()) { throw makeError( "assert", FILEPATH, "my/mod", 3, "main", "Assertion failed.", { kind: "binary_operator", operator: "&&", left: { kind: "literal", value: true, start: 26, end: 30 }, right: { kind: "expression", value: false, start: 34, end: 86 }, start: 19, end: 86, expression_start: 26 } ) } } else { throw makeError( "assert", FILEPATH, "my/mod", 3, "main", "Assertion failed.", { kind: "binary_operator", operator: "&&", left: { kind: "literal", value: false, start: 26, end: 30 }, right: { kind: "unevaluated", start: 34, end: 86 }, start: 19, end: 86, expression_start: 26 } ) } return undefined; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assert__assert_with_logical_and_binary_rhs_1.snap ================================================ --- source: compiler-core/src/javascript/tests/assert.rs expression: "\npub fn main() {\n assert True && 3 < 4\n}\n" --- ----- SOURCE CODE pub fn main() { assert True && 3 < 4 } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main() { if (true) { if (3 >= 4) { throw makeError( "assert", FILEPATH, "my/mod", 3, "main", "Assertion failed.", { kind: "binary_operator", operator: "&&", left: { kind: "literal", value: true, start: 26, end: 30 }, right: { kind: "expression", value: false, start: 34, end: 39 }, start: 19, end: 39, expression_start: 26 } ) } } else { throw makeError( "assert", FILEPATH, "my/mod", 3, "main", "Assertion failed.", { kind: "binary_operator", operator: "&&", left: { kind: "literal", value: false, start: 26, end: 30 }, right: { kind: "unevaluated", start: 34, end: 39 }, start: 19, end: 39, expression_start: 26 } ) } return undefined; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assert__assert_with_logical_and_binary_rhs_2.snap ================================================ --- source: compiler-core/src/javascript/tests/assert.rs expression: "\npub fn main() {\n assert True && \"wibble\" == \"wibble\"\n}\n" --- ----- SOURCE CODE pub fn main() { assert True && "wibble" == "wibble" } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main() { if (true) { if ("wibble" !== "wibble") { throw makeError( "assert", FILEPATH, "my/mod", 3, "main", "Assertion failed.", { kind: "binary_operator", operator: "&&", left: { kind: "literal", value: true, start: 26, end: 30 }, right: { kind: "expression", value: false, start: 34, end: 54 }, start: 19, end: 54, expression_start: 26 } ) } } else { throw makeError( "assert", FILEPATH, "my/mod", 3, "main", "Assertion failed.", { kind: "binary_operator", operator: "&&", left: { kind: "literal", value: false, start: 26, end: 30 }, right: { kind: "unevaluated", start: 34, end: 54 }, start: 19, end: 54, expression_start: 26 } ) } return undefined; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assert__assert_with_logical_and_binary_rhs_3.snap ================================================ --- source: compiler-core/src/javascript/tests/assert.rs expression: "\npub fn main() {\n assert True && \"wobble\" != \"wobble\"\n}\n" --- ----- SOURCE CODE pub fn main() { assert True && "wobble" != "wobble" } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main() { if (true) { if ("wobble" === "wobble") { throw makeError( "assert", FILEPATH, "my/mod", 3, "main", "Assertion failed.", { kind: "binary_operator", operator: "&&", left: { kind: "literal", value: true, start: 26, end: 30 }, right: { kind: "expression", value: false, start: 34, end: 54 }, start: 19, end: 54, expression_start: 26 } ) } } else { throw makeError( "assert", FILEPATH, "my/mod", 3, "main", "Assertion failed.", { kind: "binary_operator", operator: "&&", left: { kind: "literal", value: false, start: 26, end: 30 }, right: { kind: "unevaluated", start: 34, end: 54 }, start: 19, end: 54, expression_start: 26 } ) } return undefined; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assert__assert_with_message.snap ================================================ --- source: compiler-core/src/javascript/tests/assert.rs assertion_line: 139 expression: "\npub fn main() {\n assert True as \"This shouldn't fail\"\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn main() { assert True as "This shouldn't fail" } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main() { if (!true) { throw makeError( "assert", FILEPATH, "my/mod", 3, "main", "This shouldn't fail", { kind: "expression", expression: { kind: "literal", value: false, start: 26, end: 30 }, start: 19, end: 30, expression_start: 26 } ) } return undefined; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assert__assert_with_negated_case_rhs.snap ================================================ --- source: compiler-core/src/javascript/tests/assert.rs expression: "\npub fn main() {\n assert True && !case 3 - 2 {\n 1 -> True\n _ -> False\n }\n}\n" --- ----- SOURCE CODE pub fn main() { assert True && !case 3 - 2 { 1 -> True _ -> False } } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main() { if (true) { if ((() => { let $ = 3 - 2; if ($ === 1) { return true; } else { return false; } })()) { throw makeError( "assert", FILEPATH, "my/mod", 3, "main", "Assertion failed.", { kind: "binary_operator", operator: "&&", left: { kind: "literal", value: true, start: 26, end: 30 }, right: { kind: "expression", value: false, start: 34, end: 80 }, start: 19, end: 80, expression_start: 26 } ) } } else { throw makeError( "assert", FILEPATH, "my/mod", 3, "main", "Assertion failed.", { kind: "binary_operator", operator: "&&", left: { kind: "literal", value: false, start: 26, end: 30 }, right: { kind: "unevaluated", start: 34, end: 80 }, start: 19, end: 80, expression_start: 26 } ) } return undefined; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assert__assert_with_pipe_on_right.snap ================================================ --- source: compiler-core/src/javascript/tests/assert.rs expression: "\nfn add(a, b) { a + b }\n\npub fn main() {\n assert 3 == 1 |> add(2)\n}\n" --- ----- SOURCE CODE fn add(a, b) { a + b } pub fn main() { assert 3 == 1 |> add(2) } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; function add(a, b) { return a + b; } export function main() { let _block; let $ = 3; let _pipe = 1; _block = add(_pipe, 2); let $1 = _block; if (!($ === $1)) { throw makeError( "assert", FILEPATH, "my/mod", 5, "main", "Assertion failed.", { kind: "binary_operator", operator: "==", left: { kind: "literal", value: $, start: 50, end: 51 }, right: { kind: "expression", value: $1, start: 55, end: 66 }, start: 43, end: 66, expression_start: 50 } ) } return undefined; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assert__prova.snap ================================================ --- source: compiler-core/src/javascript/tests/assert.rs expression: "\npub fn main() {\n assert Ok([]) == Ok([] |> id)\n}\n\nfn id(x) { x }\n" --- ----- SOURCE CODE pub fn main() { assert Ok([]) == Ok([] |> id) } fn id(x) { x } ----- COMPILED JAVASCRIPT import { Ok, toList, makeError, isEqual } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; function id(x) { return x; } export function main() { let $ = new Ok(toList([])); let $1 = new Ok( (() => { let _pipe = toList([]); return id(_pipe); })(), ); if (!(isEqual($, $1))) { throw makeError( "assert", FILEPATH, "my/mod", 3, "main", "Assertion failed.", { kind: "binary_operator", operator: "==", left: { kind: "literal", value: $, start: 26, end: 32 }, right: { kind: "expression", value: $1, start: 36, end: 48 }, start: 19, end: 48, expression_start: 26 } ) } return undefined; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__assert.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "pub fn go(x) { let assert 1 = x }" --- ----- SOURCE CODE pub fn go(x) { let assert 1 = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { if (!(x === 1)) { throw makeError( "let_assert", FILEPATH, "my/mod", 1, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 15, end: 31, pattern_start: 26, pattern_end: 27 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__assert1.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "pub fn go(x) { let assert #(1, 2) = x }" --- ----- SOURCE CODE pub fn go(x) { let assert #(1, 2) = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let $ = x[0]; if ($ === 1) { let $1 = x[1]; if (!($1 === 2)) { throw makeError( "let_assert", FILEPATH, "my/mod", 1, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 15, end: 37, pattern_start: 26, pattern_end: 33 } ) } } else { throw makeError( "let_assert", FILEPATH, "my/mod", 1, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 15, end: 37, pattern_start: 26, pattern_end: 33 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__assert_that_always_fails.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "\ntype Wibble {\n Wibble(Int)\n Wobble(Int)\n}\n\npub fn go() {\n let assert Wobble(n) = Wibble(1)\n n\n}\n" --- ----- SOURCE CODE type Wibble { Wibble(Int) Wobble(Int) } pub fn go() { let assert Wobble(n) = Wibble(1) n } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType, makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; class Wibble extends $CustomType { constructor($0) { super(); this[0] = $0; } } class Wobble extends $CustomType { constructor($0) { super(); this[0] = $0; } } export function go() { let $ = new Wibble(1); let n; throw makeError( "let_assert", FILEPATH, "my/mod", 8, "go", "Pattern match failed, no pattern matched the value.", { value: $, start: 66, end: 98, pattern_start: 77, pattern_end: 86 } ) return n; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__assert_that_always_succeeds.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "\ntype Wibble {\n Wibble(Int)\n}\n\npub fn go() {\n let assert Wibble(n) = Wibble(1)\n n\n}\n" --- ----- SOURCE CODE type Wibble { Wibble(Int) } pub fn go() { let assert Wibble(n) = Wibble(1) n } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; class Wibble extends $CustomType { constructor($0) { super(); this[0] = $0; } } export function go() { let $ = new Wibble(1); let n; n = $[0]; return n; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__assert_with_multiple_variants.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "\ntype Wibble {\n Wibble(Int)\n Wobble(Int)\n Woo(Int)\n}\n\npub fn go() {\n let assert Wobble(n) = todo\n n\n}\n" --- ----- SOURCE CODE type Wibble { Wibble(Int) Wobble(Int) Woo(Int) } pub fn go() { let assert Wobble(n) = todo n } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType, makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; class Wibble extends $CustomType { constructor($0) { super(); this[0] = $0; } } class Wobble extends $CustomType { constructor($0) { super(); this[0] = $0; } } class Woo extends $CustomType { constructor($0) { super(); this[0] = $0; } } export function go() { let _block; throw makeError( "todo", FILEPATH, "my/mod", 9, "go", "`todo` expression evaluated. This code has not yet been implemented.", {} ) let $ = _block; let n; if ($ instanceof Wobble) { n = $[0]; } else { throw makeError( "let_assert", FILEPATH, "my/mod", 9, "go", "Pattern match failed, no pattern matched the value.", { value: $, start: 79, end: 106, pattern_start: 90, pattern_end: 99 } ) } return n; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__case_message.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "\npub fn expect(value, message) {\n let assert Ok(inner) = value as case message {\n Ok(message) -> message\n Error(_) -> \"No message provided\"\n }\n inner\n}\n" --- ----- SOURCE CODE pub fn expect(value, message) { let assert Ok(inner) = value as case message { Ok(message) -> message Error(_) -> "No message provided" } inner } ----- COMPILED JAVASCRIPT import { Ok, makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function expect(value, message) { let inner; if (value instanceof Ok) { inner = value[0]; } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "expect", (() => { if (message instanceof Ok) { let message$1 = message[0]; return message$1; } else { return "No message provided"; } })(), { value: value, start: 35, end: 63, pattern_start: 46, pattern_end: 55 } ) } return inner; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__catch_all_assert.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "\ntype Wibble {\n Wibble(Int)\n Wobble(Int)\n}\n\npub fn go() {\n let assert _ = Wibble(1)\n 1\n}\n" --- ----- SOURCE CODE type Wibble { Wibble(Int) Wobble(Int) } pub fn go() { let assert _ = Wibble(1) 1 } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; class Wibble extends $CustomType { constructor($0) { super(); this[0] = $0; } } class Wobble extends $CustomType { constructor($0) { super(); this[0] = $0; } } export function go() { let $ = new Wibble(1); return 1; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__constant_assignments.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs assertion_line: 63 expression: "\nconst a = True\n\npub fn go() {\n a\n let a = 10\n a + 20\n}\n\nfn second() {\n let a = 10\n a + 20\n}\n" snapshot_kind: text --- ----- SOURCE CODE const a = True pub fn go() { a let a = 10 a + 20 } fn second() { let a = 10 a + 20 } ----- COMPILED JAVASCRIPT const a = true; export function go() { a; let a$1 = 10; return a$1 + 20; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__correct_variable_renaming_in_assigned_functions.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "\npub fn debug(x) {\n let x = x\n fn(x) { x + 1 }\n}\n" --- ----- SOURCE CODE pub fn debug(x) { let x = x fn(x) { x + 1 } } ----- COMPILED JAVASCRIPT export function debug(x) { let x$1 = x; return (x) => { return x + 1; }; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__escaped_variables_in_constants.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "\npub const class = 5\npub const something = class\n" --- ----- SOURCE CODE pub const class = 5 pub const something = class ----- COMPILED JAVASCRIPT export const class$ = 5; export const something = class$; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__keyword_assignment.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "\npub fn main() {\n let class = 10\n let debugger = 50\n}\n" --- ----- SOURCE CODE pub fn main() { let class = 10 let debugger = 50 } ----- COMPILED JAVASCRIPT export function main() { let class$ = 10; let debugger$ = 50; return debugger$; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__let_assert_nested_string_prefix.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "\ntype Wibble {\n Wibble(wibble: String)\n}\n\npub fn main() {\n let assert Wibble(wibble: \"w\" as prefix <> rest) = Wibble(\"wibble\")\n prefix <> rest\n}\n" --- ----- SOURCE CODE type Wibble { Wibble(wibble: String) } pub fn main() { let assert Wibble(wibble: "w" as prefix <> rest) = Wibble("wibble") prefix <> rest } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType, makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; class Wibble extends $CustomType { constructor(wibble) { super(); this.wibble = wibble; } } export function main() { let $ = new Wibble("wibble"); let prefix; let rest; let $1 = $.wibble; if ($1.startsWith("w")) { prefix = "w"; rest = $1.slice(1); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 7, "main", "Pattern match failed, no pattern matched the value.", { value: $, start: 61, end: 128, pattern_start: 72, pattern_end: 109 } ) } return prefix + rest; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__let_assert_string_prefix.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "\npub fn main() {\n let assert \"Game \" <> id = \"Game 1\"\n}\n" --- ----- SOURCE CODE pub fn main() { let assert "Game " <> id = "Game 1" } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main() { let $ = "Game 1"; let id; if ($.startsWith("Game ")) { id = $.slice(5); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "main", "Pattern match failed, no pattern matched the value.", { value: $, start: 19, end: 54, pattern_start: 30, pattern_end: 43 } ) } return $; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__message.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "\npub fn unwrap_or_panic(value) {\n let assert Ok(inner) = value as \"Oops, there was an error\"\n inner\n}\n" --- ----- SOURCE CODE pub fn unwrap_or_panic(value) { let assert Ok(inner) = value as "Oops, there was an error" inner } ----- COMPILED JAVASCRIPT import { Ok, makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function unwrap_or_panic(value) { let inner; if (value instanceof Ok) { inner = value[0]; } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "unwrap_or_panic", "Oops, there was an error", { value: value, start: 35, end: 63, pattern_start: 46, pattern_end: 55 } ) } return inner; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__module_const_var.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "\npub const int = 42\npub const int_alias = int\npub fn use_int_alias() { int_alias }\n\npub const compound: #(Int, Int) = #(int, int_alias)\npub fn use_compound() { compound.0 + compound.1 }\n" --- ----- SOURCE CODE pub const int = 42 pub const int_alias = int pub fn use_int_alias() { int_alias } pub const compound: #(Int, Int) = #(int, int_alias) pub fn use_compound() { compound.0 + compound.1 } ----- COMPILED JAVASCRIPT export const int = 42; export const int_alias = int; export const compound = [int, int_alias]; export function use_int_alias() { return int_alias; } export function use_compound() { return compound[0] + compound[1]; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__module_const_var1.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "\npub const int = 42\npub const int_alias = int\npub const compound: #(Int, Int) = #(int, int_alias)\n" --- ----- SOURCE CODE pub const int = 42 pub const int_alias = int pub const compound: #(Int, Int) = #(int, int_alias) ----- TYPESCRIPT DEFINITIONS export const int: number; export const int_alias: number; export const compound: [number, number]; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__nested_binding.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "\npub fn go(x) {\n let assert #(a, #(b, c, 2) as t, _, 1) = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert #(a, #(b, c, 2) as t, _, 1) = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let a; let t; let b; let c; let $ = x[3]; if ($ === 1) { let $1 = x[1][2]; if ($1 === 2) { a = x[0]; t = x[1]; b = x[1][0]; c = x[1][1]; } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 60, pattern_start: 29, pattern_end: 56 } ) } } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 60, pattern_start: 29, pattern_end: 56 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__rebound_argument.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "pub fn main(x) {\n let x = False\n x\n}\n" --- ----- SOURCE CODE pub fn main(x) { let x = False x } ----- COMPILED JAVASCRIPT export function main(x) { let x$1 = false; return x$1; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__rebound_function.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "pub fn x() {\n Nil\n}\n\npub fn main() {\n let x = False\n x\n}\n" --- ----- SOURCE CODE pub fn x() { Nil } pub fn main() { let x = False x } ----- COMPILED JAVASCRIPT export function x() { return undefined; } export function main() { let x$1 = false; return x$1; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__rebound_function_and_arg.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "pub fn x() {\n Nil\n}\n\npub fn main(x) {\n let x = False\n x\n}\n" --- ----- SOURCE CODE pub fn x() { Nil } pub fn main(x) { let x = False x } ----- COMPILED JAVASCRIPT export function x() { return undefined; } export function main(x) { let x$1 = false; return x$1; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__returning_literal_subject.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "pub fn go(x) { let assert 1 = x + 1 }" --- ----- SOURCE CODE pub fn go(x) { let assert 1 = x + 1 } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let $ = x + 1; if (!($ === 1)) { throw makeError( "let_assert", FILEPATH, "my/mod", 1, "go", "Pattern match failed, no pattern matched the value.", { value: $, start: 15, end: 35, pattern_start: 26, pattern_end: 27 } ) } return $; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__tuple_matching.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "\npub fn go(x) {\n let assert #(1, 2) = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert #(1, 2) = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let $ = x[0]; if ($ === 1) { let $1 = x[1]; if (!($1 === 2)) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 40, pattern_start: 29, pattern_end: 36 } ) } } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 40, pattern_start: 29, pattern_end: 36 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__use_discard_assignment.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "\ntype Wibble {\n Wibble(Int)\n Wobble(Int)\n Woo(Int)\n}\n\nfn fun(f) { f(Wibble(1)) }\n\npub fn go() {\n use _ <- fun\n 1\n}\n" --- ----- SOURCE CODE type Wibble { Wibble(Int) Wobble(Int) Woo(Int) } fn fun(f) { f(Wibble(1)) } pub fn go() { use _ <- fun 1 } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; class Wibble extends $CustomType { constructor($0) { super(); this[0] = $0; } } class Wobble extends $CustomType { constructor($0) { super(); this[0] = $0; } } class Woo extends $CustomType { constructor($0) { super(); this[0] = $0; } } function fun(f) { return f(new Wibble(1)); } export function go() { return fun((_) => { return 1; }); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__use_matching_assignment.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "\nfn fun(f) { f(#(2, 4)) }\n\npub fn go() {\n use #(_, n) <- fun\n n\n}\n" --- ----- SOURCE CODE fn fun(f) { f(#(2, 4)) } pub fn go() { use #(_, n) <- fun n } ----- COMPILED JAVASCRIPT function fun(f) { return f([2, 4]); } export function go() { return fun( (_use0) => { let n; n = _use0[1]; return n; }, ); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__variable_message.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "\npub fn expect(value, message) {\n let assert Ok(inner) = value as message\n inner\n}\n" --- ----- SOURCE CODE pub fn expect(value, message) { let assert Ok(inner) = value as message inner } ----- COMPILED JAVASCRIPT import { Ok, makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function expect(value, message) { let inner; if (value instanceof Ok) { inner = value[0]; } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "expect", message, { value: value, start: 35, end: 63, pattern_start: 46, pattern_end: 55 } ) } return inner; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__variable_renaming.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "\n\npub fn go(x, wibble) {\n let a = 1\n wibble(a)\n let a = 2\n wibble(a)\n let assert #(a, 3) = x\n let b = a\n wibble(b)\n let c = {\n let a = a\n #(a, b)\n }\n wibble(a)\n // make sure arguments are counted in initial state\n let x = c\n x\n}\n" --- ----- SOURCE CODE pub fn go(x, wibble) { let a = 1 wibble(a) let a = 2 wibble(a) let assert #(a, 3) = x let b = a wibble(b) let c = { let a = a #(a, b) } wibble(a) // make sure arguments are counted in initial state let x = c x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x, wibble) { let a = 1; wibble(a); let a$1 = 2; wibble(a$1); let a$2; let $ = x[1]; if ($ === 3) { a$2 = x[0]; } else { throw makeError( "let_assert", FILEPATH, "my/mod", 8, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 75, end: 97, pattern_start: 86, pattern_end: 93 } ) } let b = a$2; wibble(b); let _block; { let a$3 = a$2; _block = [a$3, b]; } let c = _block; wibble(a$2); let x$1 = c; return x$1; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__variable_used_in_pattern_and_assignment.snap ================================================ --- source: compiler-core/src/javascript/tests/assignments.rs expression: "pub fn main(x) {\n let #(x) = #(x)\n x\n}\n" --- ----- SOURCE CODE pub fn main(x) { let #(x) = #(x) x } ----- COMPILED JAVASCRIPT export function main(x) { let $ = [x]; let x$1; x$1 = $[0]; return x$1; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__alternative_patterns_with_variable_size.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1918 expression: "\npub fn go(x) {\n case x {\n <<_, n, rest:size(n)>> |\n <> -> True\n _ -> False\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <<_, n, rest:size(n)>> | <> -> True _ -> False } } ----- COMPILED JAVASCRIPT import { bitArraySliceToInt } from "../gleam.mjs"; export function go(x) { if (x.bitSize >= 8 && x.bitSize >= 16) { let n = x.byteAt(1); if (x.bitSize === 16 + n) { let n$1 = n; let rest = bitArraySliceToInt(x, 16, 16 + n$1, true, false); return true; } else { let n = x.byteAt(0); if (x.bitSize === 16 + n) { let n$1 = n; let rest = bitArraySliceToInt(x, 16, 16 + n$1, true, false); return true; } else { return false; } } } else { return false; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__as_module_const.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\n pub const data = <<\n 0x1,\n 2,\n 2:size(16),\n 0x4:size(32),\n -1:32,\n \"Gleam\":utf8,\n 4.2:float,\n 4.2:32-float,\n <<0xFA>>:bits-6,\n -1:64,\n <<\n <<1, 2, 3>>:bits,\n \"Gleam\":utf8,\n 1024\n >>:bits\n >>\n " --- ----- SOURCE CODE pub const data = << 0x1, 2, 2:size(16), 0x4:size(32), -1:32, "Gleam":utf8, 4.2:float, 4.2:32-float, <<0xFA>>:bits-6, -1:64, << <<1, 2, 3>>:bits, "Gleam":utf8, 1024 >>:bits >> ----- COMPILED JAVASCRIPT import { toBitArray, bitArraySlice, sizedInt, stringBits, sizedFloat } from "../gleam.mjs"; export const data = /* @__PURE__ */ toBitArray([ 1, 2, 0, 2, 0, 0, 0, 4, 255, 255, 255, 255, stringBits("Gleam"), sizedFloat(4.2, 64, true), sizedFloat(4.2, 32, true), bitArraySlice(/* @__PURE__ */ toBitArray([250]), 0, 6), sizedInt(-1, 64, true), /* @__PURE__ */ toBitArray([ /* @__PURE__ */ toBitArray([1, 2, 3]), stringBits("Gleam"), 0, ]), ]); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__bit_array_assignment_discard.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn main() {\n let assert <<_ as number>> = <<10>>\n number\n}\n" --- ----- SOURCE CODE pub fn main() { let assert <<_ as number>> = <<10>> number } ----- COMPILED JAVASCRIPT import { makeError, toBitArray } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main() { let $ = toBitArray([10]); let number; if ($.bitSize === 8) { number = $.byteAt(0); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "main", "Pattern match failed, no pattern matched the value.", { value: $, start: 18, end: 53, pattern_start: 29, pattern_end: 44 } ) } return number; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__bit_array_assignment_float.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn main() {\n let assert <<3.14 as pi:float>> = <<3.14>>\n pi\n}\n" --- ----- SOURCE CODE pub fn main() { let assert <<3.14 as pi:float>> = <<3.14>> pi } ----- COMPILED JAVASCRIPT import { makeError, toBitArray, bitArraySliceToFloat, sizedFloat } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main() { let $ = toBitArray([sizedFloat(3.14, 64, true)]); let pi; if ($.bitSize === 64 && bitArraySliceToFloat($, 0, 64, true) === 3.14) { pi = 3.14; } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "main", "Pattern match failed, no pattern matched the value.", { value: $, start: 18, end: 60, pattern_start: 29, pattern_end: 49 } ) } return pi; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__bit_array_assignment_int.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn main() {\n let assert <<1 as a>> = <<1>>\n a\n}\n" --- ----- SOURCE CODE pub fn main() { let assert <<1 as a>> = <<1>> a } ----- COMPILED JAVASCRIPT import { makeError, toBitArray } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main() { let $ = toBitArray([1]); let a; if ($.bitSize === 8 && $.byteAt(0) === 1) { a = 1; } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "main", "Pattern match failed, no pattern matched the value.", { value: $, start: 18, end: 47, pattern_start: 29, pattern_end: 39 } ) } return a; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__bit_array_assignment_string.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn main() {\n let assert <<\"Hello, world!\" as message:utf8>> = <<\"Hello, world!\">>\n message\n}\n" --- ----- SOURCE CODE pub fn main() { let assert <<"Hello, world!" as message:utf8>> = <<"Hello, world!">> message } ----- COMPILED JAVASCRIPT import { makeError, toBitArray, stringBits } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main() { let $ = toBitArray([stringBits("Hello, world!")]); let message; if ( $.bitSize === 104 && $.byteAt(0) === 72 && $.byteAt(1) === 101 && $.byteAt(2) === 108 && $.byteAt(3) === 108 && $.byteAt(4) === 111 && $.byteAt(5) === 44 && $.byteAt(6) === 32 && $.byteAt(7) === 119 && $.byteAt(8) === 111 && $.byteAt(9) === 114 && $.byteAt(10) === 108 && $.byteAt(11) === 100 && $.byteAt(12) === 33 ) { message = "Hello, world!"; } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "main", "Pattern match failed, no pattern matched the value.", { value: $, start: 18, end: 86, pattern_start: 29, pattern_end: 64 } ) } return message; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__bit_array_dynamic_slice.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 380 expression: "\npub fn go(x) {\n let i = 4\n <<<<0xAB>>:bits-size(i)>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { let i = 4 <<<<0xAB>>:bits-size(i)>> } ----- COMPILED JAVASCRIPT import { toBitArray, bitArraySlice } from "../gleam.mjs"; export function go(x) { let i = 4; return toBitArray([bitArraySlice(toBitArray([171]), 0, i)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__bit_array_literal_string_constant_is_treated_as_utf8.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1742 expression: "pub const a = <<\"hello\", \" \", \"world\">>" snapshot_kind: text --- ----- SOURCE CODE pub const a = <<"hello", " ", "world">> ----- COMPILED JAVASCRIPT import { toBitArray, stringBits } from "../gleam.mjs"; export const a = /* @__PURE__ */ toBitArray([ stringBits("hello"), stringBits(" "), stringBits("world"), ]); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__bit_array_literal_string_is_treated_as_utf8.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn main() {\n <<\"hello\", \" \", \"world\">>\n}" --- ----- SOURCE CODE pub fn main() { <<"hello", " ", "world">> } ----- COMPILED JAVASCRIPT import { toBitArray, stringBits } from "../gleam.mjs"; export function main() { return toBitArray([stringBits("hello"), stringBits(" "), stringBits("world")]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__bit_array_literal_string_pattern_is_treated_as_utf8.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn main() {\n case <<>> {\n <<\"a\", \"b\", _:bytes>> -> 1\n _ -> 2\n }\n}" --- ----- SOURCE CODE pub fn main() { case <<>> { <<"a", "b", _:bytes>> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT import { toBitArray } from "../gleam.mjs"; export function main() { let $ = toBitArray([]); if ( $.bitSize >= 8 && $.byteAt(0) === 97 && $.bitSize >= 16 && $.byteAt(1) === 98 && ($.bitSize - 16) % 8 === 0 ) { return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__bit_array_pattern_match_all_reachable.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn main(x) {\n case x {\n <<_, \"==\">> -> 1\n <<_, _, \"=\">> -> 2\n // ^^^ This should be reachable\n _ -> 3\n }\n}\n " --- ----- SOURCE CODE pub fn main(x) { case x { <<_, "==">> -> 1 <<_, _, "=">> -> 2 // ^^^ This should be reachable _ -> 3 } } ----- COMPILED JAVASCRIPT export function main(x) { if (x.bitSize >= 8 && x.bitSize === 24) { if (x.byteAt(1) === 61 && x.byteAt(2) === 61) { return 1; } else if (x.byteAt(2) === 61) { return 2; } else { return 3; } } else { return 3; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__bit_array_sliced.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 369 expression: "\npub fn go(x) {\n <<<<0xAB>>:bits-4>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { <<<<0xAB>>:bits-4>> } ----- COMPILED JAVASCRIPT import { toBitArray, bitArraySlice } from "../gleam.mjs"; export function go(x) { return toBitArray([bitArraySlice(toBitArray([171]), 0, 4)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__bit_string.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 347 expression: "\npub fn go(x) {\n <>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { <> } ----- COMPILED JAVASCRIPT import { toBitArray } from "../gleam.mjs"; export function go(x) { return toBitArray([x]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__bit_string_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n <>\n}\n" --- ----- SOURCE CODE pub fn go(x) { <> } ----- TYPESCRIPT DEFINITIONS import type * as _ from "../gleam.d.mts"; export function go(x: _.BitArray): _.BitArray; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__bits.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 358 expression: "\npub fn go(x) {\n <>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { <> } ----- COMPILED JAVASCRIPT import { toBitArray } from "../gleam.mjs"; export function go(x) { return toBitArray([x]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__bits_pattern_requires_v1_9.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn main() {\n let assert <<_:bits>> = <<0>>\n}\n " --- ----- SOURCE CODE pub fn main() { let assert <<_:bits>> = <<0>> } ----- WARNING warning: Redundant assertion ┌─ /src/warning/wrn.gleam:3:7 │ 3 │ let assert <<_:bits>> = <<0>> │ ^^^^^^ You can remove this This assertion is redundant since the pattern covers all possibilities. warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:3:18 │ 3 │ let assert <<_:bits>> = <<0>> │ ^^^^ This requires a Gleam version >= 1.9.0 Use of unaligned bit arrays on the JavaScript target was introduced in version v1.9.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.8.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.9.0" ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__bits_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n <>\n}\n" --- ----- SOURCE CODE pub fn go(x) { <> } ----- TYPESCRIPT DEFINITIONS import type * as _ from "../gleam.d.mts"; export function go(x: _.BitArray): _.BitArray; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__block_in_pattern_size.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn main() {\n let assert <> = <<>>\n}\n" --- ----- SOURCE CODE pub fn main() { let assert <> = <<>> } ----- COMPILED JAVASCRIPT import { makeError, toBitArray, bitArraySliceToInt } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main() { let $ = toBitArray([]); let len$1; let payload; if ($.bitSize >= 8) { let len = $.byteAt(0); if ((len - 1) * 8 >= 0 && $.bitSize === 8 + (len - 1) * 8) { len$1 = len; payload = bitArraySliceToInt($, 8, 8 + (len$1 - 1) * 8, true, false); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "main", "Pattern match failed, no pattern matched the value.", { value: $, start: 19, end: 75, pattern_start: 30, pattern_end: 68 } ) } } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "main", "Pattern match failed, no pattern matched the value.", { value: $, start: 19, end: 75, pattern_start: 30, pattern_end: 68 } ) } return $; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_bit_array_assignment_discard.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n case x {\n <<_ as n>>\n | <<_ as n, _:bytes>> -> n\n _ -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { <<_ as n>> | <<_ as n, _:bytes>> -> n _ -> 1 } } ----- COMPILED JAVASCRIPT export function go(x) { if (x.bitSize === 8) { let n = x.byteAt(0); return n; } else if (x.bitSize >= 8 && (x.bitSize - 8) % 8 === 0) { let n = x.byteAt(0); return n; } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_bit_array_assignment_float.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n case x {\n <<3.14 as pi:float>>\n | <<1.1 as pi:float, _:bytes>> -> pi\n _ -> 1.1\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { <<3.14 as pi:float>> | <<1.1 as pi:float, _:bytes>> -> pi _ -> 1.1 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { if (x.bitSize === 64) { if (bitArraySliceToFloat(x, 0, 64, true) === 3.14) { let pi = 3.14; return pi; } else if ( bitArraySliceToFloat(x, 0, 64, true) === 1.1 && (x.bitSize - 64) % 8 === 0 ) { let pi = 1.1; return pi; } else { return 1.1; } } else if ( x.bitSize >= 64 && bitArraySliceToFloat(x, 0, 64, true) === 1.1 && (x.bitSize - 64) % 8 === 0 ) { let pi = 1.1; return pi; } else { return 1.1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_bit_array_assignment_int.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n case x {\n <<1 as n>>\n | <<2 as n, _:bytes>> -> n\n _ -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { <<1 as n>> | <<2 as n, _:bytes>> -> n _ -> 1 } } ----- COMPILED JAVASCRIPT export function go(x) { if (x.bitSize === 8) { if (x.byteAt(0) === 1) { let n = 1; return n; } else if (x.byteAt(0) === 2 && (x.bitSize - 8) % 8 === 0) { let n = 2; return n; } else { return 1; } } else if (x.bitSize >= 8 && x.byteAt(0) === 2 && (x.bitSize - 8) % 8 === 0) { let n = 2; return n; } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_bit_array_assignment_string.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n case x {\n <<\"Hello\" as message>>\n | <<\"Jak\" as message, _:bytes>> -> message\n _ -> \"wibble\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { <<"Hello" as message>> | <<"Jak" as message, _:bytes>> -> message _ -> "wibble" } } ----- COMPILED JAVASCRIPT export function go(x) { if (x.bitSize === 40) { if ( x.byteAt(0) === 72 && x.byteAt(1) === 101 && x.byteAt(2) === 108 && x.byteAt(3) === 108 && x.byteAt(4) === 111 ) { let message = "Hello"; return message; } else if ( x.byteAt(0) === 74 && x.byteAt(1) === 97 && x.byteAt(2) === 107 && (x.bitSize - 24) % 8 === 0 ) { let message = "Jak"; return message; } else { return "wibble"; } } else if ( x.bitSize >= 24 && x.byteAt(0) === 74 && x.byteAt(1) === 97 && x.byteAt(2) === 107 && (x.bitSize - 24) % 8 === 0 ) { let message = "Jak"; return message; } else { return "wibble"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_discard_sized.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1117 expression: "\npub fn go(x) {\n case x {\n <<_:16, _:8>> -> 1\n _ -> 2\n }\n case x {\n <<_:16-little-signed, _:8>> -> 1\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <<_:16, _:8>> -> 1 _ -> 2 } case x { <<_:16-little-signed, _:8>> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT export function go(x) { if (x.bitSize >= 16 && x.bitSize === 24) { 1 } else { 2 } if (x.bitSize >= 16 && x.bitSize === 24) { return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_dynamic_size_float_pattern_with_unit.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let size = 3\n case x {\n <<1.3:size(size)-unit(2)>> -> 1\n _ -> 2\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { let size = 3 case x { <<1.3:size(size)-unit(2)>> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { let size = 3; if ( size * 2 >= 0 && x.bitSize === size * 2 && bitArraySliceToFloat(x, 0, size * 2, true) === 1.3 ) { return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_dynamic_size_pattern_with_unit.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let size = 3\n case x {\n <<1:size(size)-unit(2)>> -> 1\n _ -> 2\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { let size = 3 case x { <<1:size(size)-unit(2)>> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToInt } from "../gleam.mjs"; export function go(x) { let size = 3; if ( size * 2 >= 0 && x.bitSize === size * 2 && bitArraySliceToInt(x, 0, size * 2, true, false) === 1 ) { return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_empty_match.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 425 expression: "\npub fn go(x) {\n case x {\n <<>> -> 1\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <<>> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT export function go(x) { if (x.bitSize === 0) { return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_is_byte_aligned.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1904 expression: "\npub fn is_byte_aligned(x) {\n case x {\n <<_:bytes>> -> True\n _ -> False\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn is_byte_aligned(x) { case x { <<_:bytes>> -> True _ -> False } } ----- COMPILED JAVASCRIPT export function is_byte_aligned(x) { if (x.bitSize % 8 === 0) { return true; } else { return false; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_binary_size.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1599 expression: "\npub fn go(x) {\n case x {\n <<_, a:2-bytes>> -> a\n _ -> x\n }\n\n case x {\n <<_, b:bytes-size(2)>> -> b\n _ -> x\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <<_, a:2-bytes>> -> a _ -> x } case x { <<_, b:bytes-size(2)>> -> b _ -> x } } ----- COMPILED JAVASCRIPT import { bitArraySlice } from "../gleam.mjs"; export function go(x) { if (x.bitSize >= 8 && x.bitSize === 24) { let a = bitArraySlice(x, 8, 24); a } else { x } if (x.bitSize >= 8 && x.bitSize === 24) { let b = bitArraySlice(x, 8, 24); return b; } else { return x; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_bits_with_size.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1498 expression: "\npub fn go(x) {\n case <<0x77:7>> {\n <<_:4, f:bits-2, _:1>> -> f\n _ -> x\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case <<0x77:7>> { <<_:4, f:bits-2, _:1>> -> f _ -> x } } ----- COMPILED JAVASCRIPT import { toBitArray, bitArraySlice, sizedInt } from "../gleam.mjs"; export function go(x) { let $ = toBitArray([sizedInt(0x77, 7, true)]); if ($.bitSize >= 4 && $.bitSize >= 6 && $.bitSize === 7) { let f = bitArraySlice($, 4, 6); return f; } else { return x; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_bytes.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 450 expression: "\npub fn go(x) {\n case x {\n <<1, y>> -> y\n _ -> 1\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <<1, y>> -> y _ -> 1 } } ----- COMPILED JAVASCRIPT export function go(x) { if (x.bitSize >= 8 && x.byteAt(0) === 1 && x.bitSize === 16) { let y = x.byteAt(1); return y; } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_bytes_with_size.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1473 expression: "\npub fn go(x) {\n case <<1, 2>> {\n <> -> f\n _ -> x\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case <<1, 2>> { <> -> f _ -> x } } ----- COMPILED JAVASCRIPT import { toBitArray, bitArraySlice } from "../gleam.mjs"; export function go(x) { let $ = toBitArray([1, 2]); if ($.bitSize === 16) { let f = bitArraySlice($, 0, 16); return f; } else { return x; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_dynamic_bits_size.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1063 expression: "\npub fn go(x) {\n let n = 16\n case x {\n <> -> a\n _ -> x\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { let n = 16 case x { <> -> a _ -> x } } ----- COMPILED JAVASCRIPT import { bitArraySlice } from "../gleam.mjs"; export function go(x) { let n = 16; if (n >= 0 && x.bitSize === n) { let a = bitArraySlice(x, 0, n); return a; } else { return x; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_dynamic_bytes_size.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let n = 3\n case x {\n <> -> a\n _ -> x\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { let n = 3 case x { <> -> a _ -> x } } ----- COMPILED JAVASCRIPT import { bitArraySlice } from "../gleam.mjs"; export function go(x) { let n = 3; if (n * 8 >= 0 && x.bitSize === n * 8) { let a = bitArraySlice(x, 0, n * 8); return a; } else { return x; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_dynamic_size.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 951 expression: "\npub fn go(x) {\n let n = 16\n case x {\n <> -> a\n _ -> 1\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { let n = 16 case x { <> -> a _ -> 1 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToInt } from "../gleam.mjs"; export function go(x) { let n = 16; if (n >= 0 && x.bitSize === n) { let a = bitArraySliceToInt(x, 0, n, true, false); return a; } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_dynamic_size_literal_value.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1036 expression: "\npub fn go(x) {\n let n = 8\n case x {\n <> -> a\n _ -> 1\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { let n = 8 case x { <> -> a _ -> 1 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToInt } from "../gleam.mjs"; export function go(x) { let n = 8; if ( n >= 0 && x.bitSize >= n && x.bitSize === 8 + n && bitArraySliceToInt(x, n, n + 8, true, false) === 21 ) { let a = bitArraySliceToInt(x, 0, n, true, false); return a; } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_dynamic_size_shadowed_variable.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1008 expression: "\npub fn go(x) {\n let n = 16\n let n = 5\n case x {\n <> -> a\n _ -> 1\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { let n = 16 let n = 5 case x { <> -> a _ -> 1 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToInt } from "../gleam.mjs"; export function go(x) { let n = 16; let n$1 = 5; if (n$1 >= 0 && x.bitSize === n$1) { let a = bitArraySliceToInt(x, 0, n$1, true, false); return a; } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_dynamic_size_with_other_segments.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 979 expression: "\npub fn go(x) {\n let n = 16\n let m = 32\n case x {\n <> -> first + a + b\n _ -> 1\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { let n = 16 let m = 32 case x { <> -> first + a + b _ -> 1 } } ----- COMPILED JAVASCRIPT import { bitArraySlice, bitArraySliceToInt } from "../gleam.mjs"; export function go(x) { let n = 16; let m = 32; if ( x.bitSize >= 8 && n >= 0 && x.bitSize >= 8 + n && m >= 0 && x.bitSize >= 8 + m + n ) { let first = x.byteAt(0); let a = bitArraySliceToInt(x, 8, 8 + n, true, false); let b = bitArraySliceToInt(x, 8 + n, 8 + n + m, true, false); let rest = bitArraySlice(x, 8 + m + n); return (first + a) + b; } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_float.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1196 expression: "\npub fn go(x) {\n case x {\n <> -> #(a, b)\n _ -> #(1.1, 2)\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <> -> #(a, b) _ -> #(1.1, 2) } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { if ( x.bitSize >= 64 && Number.isFinite(bitArraySliceToFloat(x, 0, 64, true)) && x.bitSize === 72 ) { let a = bitArraySliceToFloat(x, 0, 64, true); let b = x.byteAt(8); return [a, b]; } else { return [1.1, 2]; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_float_16_bit.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1423 expression: "\npub fn go(x) {\n case x {\n <> -> a\n _ -> 1.1\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <> -> a _ -> 1.1 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { if (x.bitSize === 16 && Number.isFinite(bitArraySliceToFloat(x, 0, 16, true))) { let a = bitArraySliceToFloat(x, 0, 16, true); return a; } else { return 1.1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_float_big_endian.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1221 expression: "\npub fn go(x) {\n case x {\n <> -> #(a, b)\n _ -> #(1.1, 1)\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <> -> #(a, b) _ -> #(1.1, 1) } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { if ( x.bitSize >= 64 && Number.isFinite(bitArraySliceToFloat(x, 0, 64, true)) && x.bitSize === 72 ) { let a = bitArraySliceToFloat(x, 0, 64, true); let b = x.byteAt(8); return [a, b]; } else { return [1.1, 1]; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_float_little_endian.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1246 expression: "\npub fn go(x) {\n case x {\n <> -> #(a, b)\n _ -> #(1.1, 2)\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <> -> #(a, b) _ -> #(1.1, 2) } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { if ( x.bitSize >= 64 && Number.isFinite(bitArraySliceToFloat(x, 0, 64, false)) && x.bitSize === 72 ) { let a = bitArraySliceToFloat(x, 0, 64, false); let b = x.byteAt(8); return [a, b]; } else { return [1.1, 2]; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_float_sized.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1271 expression: "\npub fn go(x) {\n case x {\n <> -> #(a, b)\n _ -> #(1.1, 2)\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <> -> #(a, b) _ -> #(1.1, 2) } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { if ( x.bitSize >= 32 && Number.isFinite(bitArraySliceToFloat(x, 0, 32, true)) && x.bitSize === 40 ) { let a = bitArraySliceToFloat(x, 0, 32, true); let b = x.byteAt(4); return [a, b]; } else { return [1.1, 2]; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_float_sized_big_endian.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1296 expression: "\npub fn go(x) {\n case x {\n <> -> #(a, b)\n _ -> #(1.1, 2)\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <> -> #(a, b) _ -> #(1.1, 2) } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { if ( x.bitSize >= 32 && Number.isFinite(bitArraySliceToFloat(x, 0, 32, true)) && x.bitSize === 40 ) { let a = bitArraySliceToFloat(x, 0, 32, true); let b = x.byteAt(4); return [a, b]; } else { return [1.1, 2]; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_float_sized_little_endian.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1321 expression: "\npub fn go(x) {\n case x {\n <> -> #(a, b)\n _ -> #(1.1, 2)\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <> -> #(a, b) _ -> #(1.1, 2) } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { if ( x.bitSize >= 32 && Number.isFinite(bitArraySliceToFloat(x, 0, 32, false)) && x.bitSize === 40 ) { let a = bitArraySliceToFloat(x, 0, 32, false); let b = x.byteAt(4); return [a, b]; } else { return [1.1, 2]; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_literal_aligned_float.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1398 expression: "\npub fn go(x) {\n case x {\n <<_, 1.1, _:int>> -> 1\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <<_, 1.1, _:int>> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { if ( x.bitSize >= 8 && x.bitSize >= 72 && bitArraySliceToFloat(x, 8, 72, true) === 1.1 && x.bitSize === 80 ) { return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_literal_float.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1346 expression: "\npub fn go(x) {\n case x {\n <<1.4, b:int>> -> 1\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <<1.4, b:int>> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { if ( x.bitSize >= 64 && bitArraySliceToFloat(x, 0, 64, true) === 1.4 && x.bitSize === 72 ) { let b = x.byteAt(8); return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_literal_unaligned_float.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1372 expression: "\npub fn go(x) {\n let n = 1\n case x {\n <<_:size(n), 1.1, _:int>> -> 1\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { let n = 1 case x { <<_:size(n), 1.1, _:int>> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { let n = 1; if ( n >= 0 && x.bitSize >= n && x.bitSize >= 64 + n && bitArraySliceToFloat(x, n, n + 64, true) === 1.1 && x.bitSize === 72 + n ) { return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_rest.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1448 expression: "\npub fn go(x) {\n case <<1, 2, 3>> {\n <<_, b:bytes>> -> b\n _ -> x\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case <<1, 2, 3>> { <<_, b:bytes>> -> b _ -> x } } ----- COMPILED JAVASCRIPT import { toBitArray, bitArraySlice } from "../gleam.mjs"; export function go(x) { let $ = toBitArray([1, 2, 3]); if ($.bitSize >= 8 && ($.bitSize - 8) % 8 === 0) { let b = bitArraySlice($, 8); return b; } else { return x; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_rest_bits.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1548 expression: "\npub fn go(x) {\n case x {\n <<_, b:bits>> -> b\n _ -> x\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <<_, b:bits>> -> b _ -> x } } ----- COMPILED JAVASCRIPT import { bitArraySlice } from "../gleam.mjs"; export function go(x) { if (x.bitSize >= 8) { let b = bitArraySlice(x, 8); return b; } else { return x; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_rest_bits_unaligned.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1573 expression: "\npub fn go(x) {\n case x {\n <<_:5, b:bits>> -> b\n _ -> x\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <<_:5, b:bits>> -> b _ -> x } } ----- COMPILED JAVASCRIPT import { bitArraySlice } from "../gleam.mjs"; export function go(x) { if (x.bitSize >= 5) { let b = bitArraySlice(x, 5); return b; } else { return x; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_rest_bytes.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1523 expression: "\npub fn go(x) {\n case x {\n <<_, b:bytes>> -> b\n _ -> x\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <<_, b:bytes>> -> b _ -> x } } ----- COMPILED JAVASCRIPT import { bitArraySlice } from "../gleam.mjs"; export function go(x) { if (x.bitSize >= 8 && (x.bitSize - 8) % 8 === 0) { let b = bitArraySlice(x, 8); return b; } else { return x; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_signed.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 600 expression: "\npub fn go(x) {\n case x {\n <> -> a\n _ -> 1\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <> -> a _ -> 1 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToInt } from "../gleam.mjs"; export function go(x) { if (x.bitSize === 8) { let a = bitArraySliceToInt(x, 0, 8, true, true); return a; } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_signed_constant_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 625 expression: "\npub fn go(x) {\n case x {\n <<-1:signed>> -> 1\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <<-1:signed>> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT export function go(x) { if (x.bitSize === 8 && x.byteAt(0) === 255) { return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_sized.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 475 expression: "\npub fn go(x) {\n case x {\n <> -> a + b\n _ -> 1\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <> -> a + b _ -> 1 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToInt } from "../gleam.mjs"; export function go(x) { if (x.bitSize >= 16 && x.bitSize === 24) { let a = bitArraySliceToInt(x, 0, 16, true, false); let b = x.byteAt(2); return a + b; } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_sized_big_endian.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 650 expression: "\npub fn go(x) {\n case x {\n <> -> a\n _ -> 1\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <> -> a _ -> 1 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToInt } from "../gleam.mjs"; export function go(x) { if (x.bitSize === 16) { let a = bitArraySliceToInt(x, 0, 16, true, false); return a; } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_sized_big_endian_constant_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 675 expression: "\npub fn go(x) {\n case x {\n <<1234:16-big>> -> 1\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <<1234:16-big>> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT export function go(x) { if (x.bitSize === 16 && x.byteAt(0) === 4 && x.byteAt(1) === 210) { return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_sized_big_endian_signed.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 800 expression: "\npub fn go(x) {\n case x {\n <> -> a\n _ -> 1\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <> -> a _ -> 1 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToInt } from "../gleam.mjs"; export function go(x) { if (x.bitSize === 16) { let a = bitArraySliceToInt(x, 0, 16, true, true); return a; } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_sized_big_endian_signed_constant_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 825 expression: "\npub fn go(x) {\n case x {\n <<1234:16-big-signed>> -> 1\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <<1234:16-big-signed>> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT export function go(x) { if (x.bitSize === 16 && x.byteAt(0) === 4 && x.byteAt(1) === 210) { return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_sized_big_endian_unsigned.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 750 expression: "\npub fn go(x) {\n case x {\n <> -> a\n _ -> 1\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <> -> a _ -> 1 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToInt } from "../gleam.mjs"; export function go(x) { if (x.bitSize === 16) { let a = bitArraySliceToInt(x, 0, 16, true, false); return a; } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_sized_big_endian_unsigned_constant_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 775 expression: "\npub fn go(x) {\n case x {\n <<1234:16-big-unsigned>> -> 1\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <<1234:16-big-unsigned>> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT export function go(x) { if (x.bitSize === 16 && x.byteAt(0) === 4 && x.byteAt(1) === 210) { return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_sized_constant_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 525 expression: "\npub fn go(x) {\n case x {\n <<1234:16, 123:8>> -> 1\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <<1234:16, 123:8>> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT export function go(x) { if ( x.bitSize >= 16 && x.byteAt(0) === 4 && x.byteAt(1) === 210 && x.bitSize === 24 && x.byteAt(2) === 123 ) { return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_sized_little_endian.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 700 expression: "\npub fn go(x) {\n case x {\n <> -> a\n _ -> 1\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <> -> a _ -> 1 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToInt } from "../gleam.mjs"; export function go(x) { if (x.bitSize === 16) { let a = bitArraySliceToInt(x, 0, 16, false, false); return a; } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_sized_little_endian_constant_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 725 expression: "\npub fn go(x) {\n case x {\n <<1234:16-little>> -> 1\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <<1234:16-little>> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT export function go(x) { if (x.bitSize === 16 && x.byteAt(0) === 210 && x.byteAt(1) === 4) { return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_sized_little_endian_signed.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 900 expression: "\npub fn go(x) {\n case x {\n <> -> a\n _ -> 1\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <> -> a _ -> 1 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToInt } from "../gleam.mjs"; export function go(x) { if (x.bitSize === 16) { let a = bitArraySliceToInt(x, 0, 16, false, true); return a; } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_sized_little_endian_signed_constant_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 925 expression: "\npub fn go(x) {\n case x {\n <<1234:16-little-signed>> -> 1\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <<1234:16-little-signed>> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT export function go(x) { if (x.bitSize === 16 && x.byteAt(0) === 210 && x.byteAt(1) === 4) { return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_sized_little_endian_unsigned.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 850 expression: "\npub fn go(x) {\n case x {\n <> -> a\n _ -> 1\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <> -> a _ -> 1 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToInt } from "../gleam.mjs"; export function go(x) { if (x.bitSize === 16) { let a = bitArraySliceToInt(x, 0, 16, false, false); return a; } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_sized_little_endian_unsigned_constant_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 875 expression: "\npub fn go(x) {\n case x {\n <<1234:16-little-unsigned>> -> 1\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <<1234:16-little-unsigned>> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT export function go(x) { if (x.bitSize === 16 && x.byteAt(0) === 210 && x.byteAt(1) === 4) { return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_sized_unaligned.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 500 expression: "\npub fn go(x) {\n case x {\n <> -> b * 2\n _ -> 1\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <> -> b * 2 _ -> 1 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToInt } from "../gleam.mjs"; export function go(x) { if (x.bitSize >= 17 && x.bitSize === 24) { let a = bitArraySliceToInt(x, 0, 17, true, false); let b = bitArraySliceToInt(x, 17, 24, true, false); return b * 2; } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_sized_value.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1146 expression: "\npub fn go(x) {\n case x {\n <> -> i\n _ -> 1\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <> -> i _ -> 1 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToInt } from "../gleam.mjs"; export function go(x) { if (x.bitSize === 16) { let i = bitArraySliceToInt(x, 0, 16, true, false); return i; } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_sized_value_constant_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1171 expression: "\npub fn go(x) {\n case x {\n <<258:16>> -> 1\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <<258:16>> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT export function go(x) { if (x.bitSize === 16 && x.byteAt(0) === 1 && x.byteAt(1) === 2) { return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_unsigned.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 550 expression: "\npub fn go(x) {\n case x {\n <> -> a\n _ -> 1\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <> -> a _ -> 1 } } ----- COMPILED JAVASCRIPT export function go(x) { if (x.bitSize === 8) { let a = x.byteAt(0); return a; } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_match_unsigned_constant_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n case x {\n <<-2:unsigned>> -> 1\n _ -> 2\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { <<-2:unsigned>> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT export function go(x) { return 2; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_pattern_with_unit.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1804 expression: "\npub fn go(x) {\n case x {\n <<1:size(2)-unit(2), 2:size(3)-unit(4)>> -> 1\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <<1:size(2)-unit(2), 2:size(3)-unit(4)>> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToInt } from "../gleam.mjs"; export function go(x) { if ( x.bitSize >= 4 && bitArraySliceToInt(x, 0, 4, true, false) === 1 && x.bitSize === 16 && bitArraySliceToInt(x, 4, 16, true, false) === 2 ) { return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_with_remaining_bytes_after_constant_size.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1860 expression: "\npub fn go(x) {\n case x {\n <<_, _, _:bytes>> -> 1\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <<_, _, _:bytes>> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT export function go(x) { if (x.bitSize >= 8 && x.bitSize >= 16 && (x.bitSize - 16) % 8 === 0) { return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_with_remaining_bytes_after_variable_size.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1874 expression: "\npub fn go(x) {\n let n = 1\n case x {\n <<_:size(n), _, _:bytes>> -> 1\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { let n = 1 case x { <<_:size(n), _, _:bytes>> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT export function go(x) { let n = 1; if ( n >= 0 && x.bitSize >= n && x.bitSize >= 8 + n && (x.bitSize - (8 + n)) % 8 === 0 ) { return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__case_with_remaining_bytes_after_variable_size_2.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1889 expression: "\npub fn go(x) {\n let n = 1\n case x {\n <> -> 1\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { let n = 1 case x { <> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToInt } from "../gleam.mjs"; export function go(x) { let n = 1; if (n >= 0 && x.bitSize >= n) { let m = bitArraySliceToInt(x, 0, n, true, false); if (x.bitSize >= m + n && (x.bitSize - (m + n)) % 8 === 0) { let m$1 = m; return 1; } else { return 2; } } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__const_utf16.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub const message = <<\"Hello, world!\":utf16>>\n" --- ----- SOURCE CODE pub const message = <<"Hello, world!":utf16>> ----- COMPILED JAVASCRIPT import { toBitArray, stringToUtf16 } from "../gleam.mjs"; export const message = /* @__PURE__ */ toBitArray([ stringToUtf16("Hello, world!", true), ]); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__const_utf32.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub const message = <<\"Hello, world!\":utf32>>\n" --- ----- SOURCE CODE pub const message = <<"Hello, world!":utf32>> ----- COMPILED JAVASCRIPT import { toBitArray, stringToUtf32 } from "../gleam.mjs"; export const message = /* @__PURE__ */ toBitArray([ stringToUtf32("Hello, world!", true), ]); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__discard_sized.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<_:16, _:8>> = x\n let assert <<_:16-little-signed, _:8>> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<_:16, _:8>> = x let assert <<_:16-little-signed, _:8>> = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { if (!(x.bitSize >= 16 && x.bitSize === 24)) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 46, pattern_start: 29, pattern_end: 42 } ) } if (!(x.bitSize >= 16 && x.bitSize === 24)) { throw makeError( "let_assert", FILEPATH, "my/mod", 4, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 49, end: 91, pattern_start: 60, pattern_end: 87 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__dynamic_size_pattern_with_unit.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let size = 3\n let assert <<1:size(size)-unit(2)>> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let size = 3 let assert <<1:size(size)-unit(2)>> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySliceToInt } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let size = 3; if (!( size * 2 >= 0 && x.bitSize === size * 2 && bitArraySliceToInt(x, 0, size * 2, true, false) === 1 )) { throw makeError( "let_assert", FILEPATH, "my/mod", 4, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 33, end: 72, pattern_start: 44, pattern_end: 68 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__dynamic_size_with_unit.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1781 expression: "\npub fn main() {\n let size = 3\n <<1:size(size)-unit(2)>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn main() { let size = 3 <<1:size(size)-unit(2)>> } ----- COMPILED JAVASCRIPT import { toBitArray, sizedInt } from "../gleam.mjs"; export function main() { let size = 3; return toBitArray([sizedInt(1, size * 2, true)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__empty.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 11 expression: "\npub fn go() {\n <<>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { <<>> } ----- COMPILED JAVASCRIPT import { toBitArray } from "../gleam.mjs"; export function go() { return toBitArray([]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__empty_match.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<>> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<>> = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { if (!(x.bitSize === 0)) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 37, pattern_start: 29, pattern_end: 33 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__explicit_sized_constant_value.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 220 expression: "\npub fn go() {\n <<256:size(32)>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { <<256:size(32)>> } ----- COMPILED JAVASCRIPT import { toBitArray } from "../gleam.mjs"; export function go() { return toBitArray([0, 0, 1, 0]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__explicit_sized_dynamic_value.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 231 expression: "\npub fn go(i: Int) {\n <>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(i: Int) { <> } ----- COMPILED JAVASCRIPT import { toBitArray, sizedInt } from "../gleam.mjs"; export function go(i) { return toBitArray([sizedInt(i, 32, true)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__float.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 55 expression: "\npub fn go() {\n <<1.1:float>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { <<1.1:float>> } ----- COMPILED JAVASCRIPT import { toBitArray, sizedFloat } from "../gleam.mjs"; export function go() { return toBitArray([sizedFloat(1.1, 64, true)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__float_big_endian.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 66 expression: "\npub fn go() {\n <<1.1:float-big>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { <<1.1:float-big>> } ----- COMPILED JAVASCRIPT import { toBitArray, sizedFloat } from "../gleam.mjs"; export function go() { return toBitArray([sizedFloat(1.1, 64, true)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__float_little_endian.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 77 expression: "\npub fn go() {\n <<1.1:float-little>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { <<1.1:float-little>> } ----- COMPILED JAVASCRIPT import { toBitArray, sizedFloat } from "../gleam.mjs"; export function go() { return toBitArray([sizedFloat(1.1, 64, false)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__float_sized.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 88 expression: "\npub fn go() {\n <<1.1:float-32>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { <<1.1:float-32>> } ----- COMPILED JAVASCRIPT import { toBitArray, sizedFloat } from "../gleam.mjs"; export function go() { return toBitArray([sizedFloat(1.1, 32, true)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__float_sized_big_endian.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 99 expression: "\npub fn go() {\n <<1.1:float-32-big>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { <<1.1:float-32-big>> } ----- COMPILED JAVASCRIPT import { toBitArray, sizedFloat } from "../gleam.mjs"; export function go() { return toBitArray([sizedFloat(1.1, 32, true)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__float_sized_little_endian.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 110 expression: "\npub fn go() {\n <<1.1:float-32-little>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { <<1.1:float-32-little>> } ----- COMPILED JAVASCRIPT import { toBitArray, sizedFloat } from "../gleam.mjs"; export function go() { return toBitArray([sizedFloat(1.1, 32, false)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__integer.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 44 expression: "\npub fn go() {\n <<256:int>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { <<256:int>> } ----- COMPILED JAVASCRIPT import { toBitArray } from "../gleam.mjs"; export function go() { return toBitArray([0]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_binary_size.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<_, a:2-bytes>> = x\n let assert <<_, b:bytes-size(2)>> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<_, a:2-bytes>> = x let assert <<_, b:bytes-size(2)>> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySlice } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let a; if (x.bitSize >= 8 && x.bitSize === 24) { a = bitArraySlice(x, 8, 24); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 49, pattern_start: 29, pattern_end: 45 } ) } let b; if (x.bitSize >= 8 && x.bitSize === 24) { b = bitArraySlice(x, 8, 24); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 4, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 52, end: 89, pattern_start: 63, pattern_end: 85 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_bits_with_size.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<_:4, f:bits-2, _:1>> = <<0x77:7>>\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<_:4, f:bits-2, _:1>> = <<0x77:7>> } ----- COMPILED JAVASCRIPT import { makeError, toBitArray, bitArraySlice, sizedInt } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let $ = toBitArray([sizedInt(0x77, 7, true)]); let f; if ($.bitSize >= 4 && $.bitSize >= 6 && $.bitSize === 7) { f = bitArraySlice($, 4, 6); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: $, start: 18, end: 64, pattern_start: 29, pattern_end: 51 } ) } return $; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_bytes.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<1, y>> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<1, y>> = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let y; if (x.bitSize >= 8 && x.byteAt(0) === 1 && x.bitSize === 16) { y = x.byteAt(1); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 41, pattern_start: 29, pattern_end: 37 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_bytes_with_size.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <> = <<1, 2>>\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <> = <<1, 2>> } ----- COMPILED JAVASCRIPT import { makeError, toBitArray, bitArraySlice } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let $ = toBitArray([1, 2]); let f; if ($.bitSize === 16) { f = bitArraySlice($, 0, 16); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: $, start: 18, end: 53, pattern_start: 29, pattern_end: 42 } ) } return $; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_case_utf8.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 311 expression: "\npub fn go(x) {\n case x {\n <<\"Gleam 👍\":utf8>> -> 1\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <<"Gleam 👍":utf8>> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT export function go(x) { if ( x.bitSize === 80 && x.byteAt(0) === 71 && x.byteAt(1) === 108 && x.byteAt(2) === 101 && x.byteAt(3) === 97 && x.byteAt(4) === 109 && x.byteAt(5) === 32 && x.byteAt(6) === 240 && x.byteAt(7) === 159 && x.byteAt(8) === 145 && x.byteAt(9) === 141 ) { return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_case_utf8_with_escape_chars.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 297 expression: "\npub fn go(x) {\n case x {\n <<\"\\\"\\\\\\r\\n\\t\\f\\u{1f600}\">> -> 1\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <<"\"\\\r\n\t\f\u{1f600}">> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT export function go(x) { if ( x.bitSize === 80 && x.byteAt(0) === 34 && x.byteAt(1) === 92 && x.byteAt(2) === 13 && x.byteAt(3) === 10 && x.byteAt(4) === 9 && x.byteAt(5) === 12 && x.byteAt(6) === 240 && x.byteAt(7) === 159 && x.byteAt(8) === 152 && x.byteAt(9) === 128 ) { return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_dynamic_bits_size.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let n = 16\n let assert <> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let n = 16 let assert <> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySlice } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let n = 16; let a; if (n >= 0 && x.bitSize === n) { a = bitArraySlice(x, 0, n); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 4, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 31, end: 64, pattern_start: 42, pattern_end: 60 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_dynamic_bytes_size.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let n = 3\n let assert <> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let n = 3 let assert <> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySlice } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let n = 3; let a; if (n * 8 >= 0 && x.bitSize === n * 8) { a = bitArraySlice(x, 0, n * 8); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 4, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 30, end: 64, pattern_start: 41, pattern_end: 60 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_dynamic_size.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let n = 16\n let assert <> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let n = 16 let assert <> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySliceToInt } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let n = 16; let a; if (n >= 0 && x.bitSize === n) { a = bitArraySliceToInt(x, 0, n, true, false); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 4, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 31, end: 59, pattern_start: 42, pattern_end: 55 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_dynamic_size_literal_value.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let n = 8\n let assert <> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let n = 8 let assert <> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySliceToInt } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let n = 8; let a; if ( n >= 0 && x.bitSize >= n && x.bitSize === 8 + n && bitArraySliceToInt(x, n, n + 8, true, false) === 21 ) { a = bitArraySliceToInt(x, 0, n, true, false); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 4, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 30, end: 76, pattern_start: 41, pattern_end: 72 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_dynamic_size_shadowed_variable.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let n = 16\n let n = 5\n let assert <> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let n = 16 let n = 5 let assert <> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySliceToInt } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let n = 16; let n$1 = 5; let a; if (n$1 >= 0 && x.bitSize === n$1) { a = bitArraySliceToInt(x, 0, n$1, true, false); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 5, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 43, end: 71, pattern_start: 54, pattern_end: 67 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_dynamic_size_with_other_segments.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let n = 16\n let m = 32\n let assert <> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let n = 16 let m = 32 let assert <> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySlice, bitArraySliceToInt } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let n = 16; let m = 32; let first; let a; let b; let rest; if ( x.bitSize >= 8 && n >= 0 && x.bitSize >= 8 + n && m >= 0 && x.bitSize >= 8 + m + n ) { first = x.byteAt(0); a = bitArraySliceToInt(x, 8, 8 + n, true, false); b = bitArraySliceToInt(x, 8 + n, 8 + n + m, true, false); rest = bitArraySlice(x, 8 + m + n); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 5, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 44, end: 109, pattern_start: 55, pattern_end: 105 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_float.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySliceToFloat } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let a; let b; if ( x.bitSize >= 64 && Number.isFinite(bitArraySliceToFloat(x, 0, 64, true)) && x.bitSize === 72 ) { a = bitArraySliceToFloat(x, 0, 64, true); b = x.byteAt(8); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 51, pattern_start: 29, pattern_end: 47 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_float_16_bit.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySliceToFloat } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let a; if (x.bitSize === 16 && Number.isFinite(bitArraySliceToFloat(x, 0, 16, true))) { a = bitArraySliceToFloat(x, 0, 16, true); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 53, pattern_start: 29, pattern_end: 49 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_float_big_endian.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySliceToFloat } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let a; let b; if ( x.bitSize >= 64 && Number.isFinite(bitArraySliceToFloat(x, 0, 64, true)) && x.bitSize === 72 ) { a = bitArraySliceToFloat(x, 0, 64, true); b = x.byteAt(8); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 55, pattern_start: 29, pattern_end: 51 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_float_little_endian.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySliceToFloat } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let a; let b; if ( x.bitSize >= 64 && Number.isFinite(bitArraySliceToFloat(x, 0, 64, false)) && x.bitSize === 72 ) { a = bitArraySliceToFloat(x, 0, 64, false); b = x.byteAt(8); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 58, pattern_start: 29, pattern_end: 54 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_float_sized.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySliceToFloat } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let a; let b; if ( x.bitSize >= 32 && Number.isFinite(bitArraySliceToFloat(x, 0, 32, true)) && x.bitSize === 40 ) { a = bitArraySliceToFloat(x, 0, 32, true); b = x.byteAt(4); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 54, pattern_start: 29, pattern_end: 50 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_float_sized_big_endian.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySliceToFloat } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let a; let b; if ( x.bitSize >= 32 && Number.isFinite(bitArraySliceToFloat(x, 0, 32, true)) && x.bitSize === 40 ) { a = bitArraySliceToFloat(x, 0, 32, true); b = x.byteAt(4); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 58, pattern_start: 29, pattern_end: 54 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_float_sized_little_endian.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySliceToFloat } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let a; let b; if ( x.bitSize >= 32 && Number.isFinite(bitArraySliceToFloat(x, 0, 32, false)) && x.bitSize === 40 ) { a = bitArraySliceToFloat(x, 0, 32, false); b = x.byteAt(4); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 61, pattern_start: 29, pattern_end: 57 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_literal_aligned_float.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<_, 1.1, _:bits>> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<_, 1.1, _:bits>> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySliceToFloat } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { if (!( x.bitSize >= 8 && x.bitSize >= 72 && bitArraySliceToFloat(x, 8, 72, true) === 1.1 )) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 51, pattern_start: 29, pattern_end: 47 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_literal_float.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<1.4, b:int>> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<1.4, b:int>> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySliceToFloat } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let b; if ( x.bitSize >= 64 && bitArraySliceToFloat(x, 0, 64, true) === 1.4 && x.bitSize === 72 ) { b = x.byteAt(8); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 47, pattern_start: 29, pattern_end: 43 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_literal_unaligned_float.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let n = 1\n let assert <<_:size(n), 1.1, _:bits>> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let n = 1 let assert <<_:size(n), 1.1, _:bits>> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySliceToFloat } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let n = 1; if (!( n >= 0 && x.bitSize >= n && x.bitSize >= 64 + n && bitArraySliceToFloat(x, n, n + 64, true) === 1.1 )) { throw makeError( "let_assert", FILEPATH, "my/mod", 4, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 30, end: 71, pattern_start: 41, pattern_end: 67 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_rest.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<_, b:bytes>> = <<1,2,3>>\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<_, b:bytes>> = <<1,2,3>> } ----- COMPILED JAVASCRIPT import { makeError, toBitArray, bitArraySlice } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let $ = toBitArray([1, 2, 3]); let b; if ($.bitSize >= 8 && ($.bitSize - 8) % 8 === 0) { b = bitArraySlice($, 8); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: $, start: 18, end: 55, pattern_start: 29, pattern_end: 43 } ) } return $; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_rest_bits.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<_, b:bits>> = <<1,2,3>>\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<_, b:bits>> = <<1,2,3>> } ----- COMPILED JAVASCRIPT import { makeError, toBitArray, bitArraySlice } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let $ = toBitArray([1, 2, 3]); let b; if ($.bitSize >= 8) { b = bitArraySlice($, 8); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: $, start: 18, end: 54, pattern_start: 29, pattern_end: 42 } ) } return $; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_rest_bits_unaligned.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<_:5, b:bits>> = <<1,2,3>>\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<_:5, b:bits>> = <<1,2,3>> } ----- COMPILED JAVASCRIPT import { makeError, toBitArray, bitArraySlice } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let $ = toBitArray([1, 2, 3]); let b; if ($.bitSize >= 5) { b = bitArraySlice($, 5); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: $, start: 18, end: 56, pattern_start: 29, pattern_end: 44 } ) } return $; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_rest_bytes.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<_, b:bytes>> = <<1,2,3>>\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<_, b:bytes>> = <<1,2,3>> } ----- COMPILED JAVASCRIPT import { makeError, toBitArray, bitArraySlice } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let $ = toBitArray([1, 2, 3]); let b; if ($.bitSize >= 8 && ($.bitSize - 8) % 8 === 0) { b = bitArraySlice($, 8); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: $, start: 18, end: 55, pattern_start: 29, pattern_end: 43 } ) } return $; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_signed.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySliceToInt } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let a; if (x.bitSize === 8) { a = bitArraySliceToInt(x, 0, 8, true, true); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 45, pattern_start: 29, pattern_end: 41 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_signed_constant_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<-1:signed>> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<-1:signed>> = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { if (!(x.bitSize === 8 && x.byteAt(0) === 255)) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 46, pattern_start: 29, pattern_end: 42 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_sized.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySliceToInt } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let a; let b; if (x.bitSize >= 16 && x.bitSize === 24) { a = bitArraySliceToInt(x, 0, 16, true, false); b = x.byteAt(2); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 46, pattern_start: 29, pattern_end: 42 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_sized_big_endian.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySliceToInt } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let a; if (x.bitSize === 16) { a = bitArraySliceToInt(x, 0, 16, true, false); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 45, pattern_start: 29, pattern_end: 41 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_sized_big_endian_constant_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<1234:16-big>> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<1234:16-big>> = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { if (!(x.bitSize === 16 && x.byteAt(0) === 4 && x.byteAt(1) === 210)) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 48, pattern_start: 29, pattern_end: 44 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_sized_big_endian_signed.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySliceToInt } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let a; if (x.bitSize === 16) { a = bitArraySliceToInt(x, 0, 16, true, true); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 52, pattern_start: 29, pattern_end: 48 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_sized_big_endian_signed_constant_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<1234:16-big-signed>> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<1234:16-big-signed>> = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { if (!(x.bitSize === 16 && x.byteAt(0) === 4 && x.byteAt(1) === 210)) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 55, pattern_start: 29, pattern_end: 51 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_sized_big_endian_unsigned.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySliceToInt } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let a; if (x.bitSize === 16) { a = bitArraySliceToInt(x, 0, 16, true, false); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 54, pattern_start: 29, pattern_end: 50 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_sized_big_endian_unsigned_constant_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<1234:16-big-unsigned>> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<1234:16-big-unsigned>> = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { if (!(x.bitSize === 16 && x.byteAt(0) === 4 && x.byteAt(1) === 210)) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 57, pattern_start: 29, pattern_end: 53 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_sized_constant_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<1234:16, 123:8>> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<1234:16, 123:8>> = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { if (!( x.bitSize >= 16 && x.byteAt(0) === 4 && x.byteAt(1) === 210 && x.bitSize === 24 && x.byteAt(2) === 123 )) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 51, pattern_start: 29, pattern_end: 47 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_sized_little_endian.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySliceToInt } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let a; if (x.bitSize === 16) { a = bitArraySliceToInt(x, 0, 16, false, false); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 48, pattern_start: 29, pattern_end: 44 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_sized_little_endian_constant_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<1234:16-little>> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<1234:16-little>> = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { if (!(x.bitSize === 16 && x.byteAt(0) === 210 && x.byteAt(1) === 4)) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 51, pattern_start: 29, pattern_end: 47 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_sized_little_endian_signed.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySliceToInt } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let a; if (x.bitSize === 16) { a = bitArraySliceToInt(x, 0, 16, false, true); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 55, pattern_start: 29, pattern_end: 51 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_sized_little_endian_signed_constant_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<1234:16-little-signed>> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<1234:16-little-signed>> = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { if (!(x.bitSize === 16 && x.byteAt(0) === 210 && x.byteAt(1) === 4)) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 58, pattern_start: 29, pattern_end: 54 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_sized_little_endian_unsigned.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySliceToInt } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let a; if (x.bitSize === 16) { a = bitArraySliceToInt(x, 0, 16, false, false); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 57, pattern_start: 29, pattern_end: 53 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_sized_little_endian_unsigned_constant_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<1234:16-little-unsigned>> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<1234:16-little-unsigned>> = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { if (!(x.bitSize === 16 && x.byteAt(0) === 210 && x.byteAt(1) === 4)) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 60, pattern_start: 29, pattern_end: 56 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_sized_unaligned.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySliceToInt } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let a; let b; if (x.bitSize >= 17 && x.bitSize === 24) { a = bitArraySliceToInt(x, 0, 17, true, false); b = bitArraySliceToInt(x, 17, 24, true, false); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 46, pattern_start: 29, pattern_end: 42 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_sized_value.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySliceToInt } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let i; if (x.bitSize === 16) { i = bitArraySliceToInt(x, 0, 16, true, false); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 41, pattern_start: 29, pattern_end: 37 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_sized_value_constant_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<258:16>> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<258:16>> = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { if (!(x.bitSize === 16 && x.byteAt(0) === 1 && x.byteAt(1) === 2)) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 43, pattern_start: 29, pattern_end: 39 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_unsigned.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <> = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let a; if (x.bitSize === 8) { a = x.byteAt(0); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 47, pattern_start: 29, pattern_end: 43 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_unsigned_constant_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<-2:unsigned>> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<-2:unsigned>> = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 48, pattern_start: 29, pattern_end: 44 } ) return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_utf8.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<\"Gleam 👍\":utf8>> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<"Gleam 👍":utf8>> = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { if (!( x.bitSize === 80 && x.byteAt(0) === 71 && x.byteAt(1) === 108 && x.byteAt(2) === 101 && x.byteAt(3) === 97 && x.byteAt(4) === 109 && x.byteAt(5) === 32 && x.byteAt(6) === 240 && x.byteAt(7) === 159 && x.byteAt(8) === 145 && x.byteAt(9) === 141 )) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 54, pattern_start: 29, pattern_end: 50 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__match_utf8_with_escape_chars.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<\"\\\"\\\\\\r\\n\\t\\f\\u{1f600}\">> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<"\"\\\r\n\t\f\u{1f600}">> = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { if (!( x.bitSize === 80 && x.byteAt(0) === 34 && x.byteAt(1) === 92 && x.byteAt(2) === 13 && x.byteAt(3) === 10 && x.byteAt(4) === 9 && x.byteAt(5) === 12 && x.byteAt(6) === 240 && x.byteAt(7) === 159 && x.byteAt(8) === 152 && x.byteAt(9) === 128 )) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 60, pattern_start: 29, pattern_end: 56 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__multiple_variable_size_segments.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn main() {\n let assert <> = <<1, 2, 3, 4>>\n a + b + c\n}\n" --- ----- SOURCE CODE pub fn main() { let assert <> = <<1, 2, 3, 4>> a + b + c } ----- COMPILED JAVASCRIPT import { makeError, toBitArray, bitArraySliceToInt } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main() { let $ = toBitArray([1, 2, 3, 4]); let a$1; let b$1; let c; if ($.bitSize >= 8) { let a = $.byteAt(0); if ($.bitSize >= 8 + a) { let b = bitArraySliceToInt($, 8, 8 + a, true, false); if ($.bitSize === 8 + a + b) { a$1 = a; b$1 = b; c = bitArraySliceToInt($, 8 + a, 8 + a + b$1, true, false); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "main", "Pattern match failed, no pattern matched the value.", { value: $, start: 19, end: 74, pattern_start: 30, pattern_end: 57 } ) } } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "main", "Pattern match failed, no pattern matched the value.", { value: $, start: 19, end: 74, pattern_start: 30, pattern_end: 57 } ) } } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "main", "Pattern match failed, no pattern matched the value.", { value: $, start: 19, end: 74, pattern_start: 30, pattern_end: 57 } ) } return (a$1 + b$1) + c; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__negative_size_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1962 expression: "\npub fn go(x) {\n let n = -10\n case x {\n <> -> int\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { let n = -10 case x { <> -> int _ -> 2 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToInt } from "../gleam.mjs"; export function go(x) { let n = -10; if (n >= 0 && x.bitSize === n) { let int = bitArraySliceToInt(x, 0, n, true, false); return int; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__negative_size_pattern_2.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1977 expression: "\npub fn go(x) {\n case x {\n <> -> int\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <> -> int _ -> 2 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToInt } from "../gleam.mjs"; export function go(x) { if (x.bitSize >= 8) { let n = bitArraySliceToInt(x, 0, 8, true, true); if (n >= 0 && x.bitSize === 8 + n) { let n$1 = n; let int = bitArraySliceToInt(x, 8, 8 + n$1, true, false); return int; } else { return 2; } } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__non_byte_aligned_size_calculation.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn main() {\n case <<>> {\n <> -> c + b\n _ -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case <<>> { <> -> c + b _ -> 1 } } ----- COMPILED JAVASCRIPT import { toBitArray, bitArraySliceToInt } from "../gleam.mjs"; export function main() { let $ = toBitArray([]); if ($.bitSize >= 1 && $.bitSize >= 4) { let b = bitArraySliceToInt($, 1, 4, true, false); if (b - 2 >= 0 && $.bitSize === 4 + b - 2) { let a = bitArraySliceToInt($, 0, 1, true, false); let b$1 = b; let c = bitArraySliceToInt($, 4, 4 + b$1 - 2, true, false); return c + b$1; } else { return 1; } } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__one.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 22 expression: "\npub fn go() {\n <<256>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { <<256>> } ----- COMPILED JAVASCRIPT import { toBitArray } from "../gleam.mjs"; export function go() { return toBitArray([0]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__operator_in_pattern_size.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn main() {\n let assert <> = <<>>\n}\n" --- ----- SOURCE CODE pub fn main() { let assert <> = <<>> } ----- COMPILED JAVASCRIPT import { makeError, toBitArray, bitArraySliceToInt } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main() { let $ = toBitArray([]); let len$1; let payload; if ($.bitSize >= 8) { let len = $.byteAt(0); if ($.bitSize === 8 + len * 8) { len$1 = len; payload = bitArraySliceToInt($, 8, 8 + len$1 * 8, true, false); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "main", "Pattern match failed, no pattern matched the value.", { value: $, start: 19, end: 67, pattern_start: 30, pattern_end: 60 } ) } } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "main", "Pattern match failed, no pattern matched the value.", { value: $, start: 19, end: 67, pattern_start: 30, pattern_end: 60 } ) } return $; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__operator_in_pattern_size2.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn main() {\n let assert <> = <<>>\n}\n" --- ----- SOURCE CODE pub fn main() { let assert <> = <<>> } ----- COMPILED JAVASCRIPT import { makeError, divideInt, toBitArray, bitArraySliceToInt } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main() { let $ = toBitArray([]); let len$1; let payload; if ($.bitSize >= 8) { let len = $.byteAt(0); if ( (divideInt(len, 8)) - 1 >= 0 && $.bitSize === 8 + (divideInt(len, 8)) - 1 ) { len$1 = len; payload = bitArraySliceToInt($, 8, 8 + (divideInt(len$1, 8)) - 1, true, false); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "main", "Pattern match failed, no pattern matched the value.", { value: $, start: 19, end: 71, pattern_start: 30, pattern_end: 64 } ) } } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "main", "Pattern match failed, no pattern matched the value.", { value: $, start: 19, end: 71, pattern_start: 30, pattern_end: 64 } ) } return $; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__operator_in_pattern_size3.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn main() {\n let additional = 10\n let assert <> = <<>>\n}\n" --- ----- SOURCE CODE pub fn main() { let additional = 10 let assert <> = <<>> } ----- COMPILED JAVASCRIPT import { makeError, toBitArray, bitArraySliceToInt } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main() { let additional = 10; let $ = toBitArray([]); let len$1; let payload; if ($.bitSize >= 8) { let len = $.byteAt(0); if (len + (additional * 8) >= 0 && $.bitSize === 8 + len + additional * 8) { len$1 = len; payload = bitArraySliceToInt($, 8, 8 + len$1 + (additional * 8), true, false); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 4, "main", "Pattern match failed, no pattern matched the value.", { value: $, start: 41, end: 102, pattern_start: 52, pattern_end: 95 } ) } } else { throw makeError( "let_assert", FILEPATH, "my/mod", 4, "main", "Pattern match failed, no pattern matched the value.", { value: $, start: 41, end: 102, pattern_start: 52, pattern_end: 95 } ) } return $; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__operator_in_size_for_bit_array_segment.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n <>\n}\n" --- ----- SOURCE CODE pub fn go(x) { <> } ----- COMPILED JAVASCRIPT import { toBitArray, bitArraySlice } from "../gleam.mjs"; export function go(x) { return toBitArray([bitArraySlice(x, 0, 4 + 1)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__pattern_match_on_negative_size_calculation.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn main() {\n let assert <> = <<1, 2, 3, 4, 5>>\n}\n" --- ----- SOURCE CODE pub fn main() { let assert <> = <<1, 2, 3, 4, 5>> } ----- COMPILED JAVASCRIPT import { makeError, toBitArray, bitArraySliceToInt } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main() { let $ = toBitArray([1, 2, 3, 4, 5]); let a$1; let b$1; let c; if ($.bitSize >= 8) { let a = $.byteAt(0); if (a - 100000 >= 0 && $.bitSize >= 8 + a - 100000) { let b = bitArraySliceToInt($, 8, 8 + a - 100000, true, false); if ($.bitSize === 8 + b + a - 100000) { a$1 = a; b$1 = b; c = bitArraySliceToInt($, 8 + a - 100000, 8 + a - 100000 + b$1, true, false); } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "main", "Pattern match failed, no pattern matched the value.", { value: $, start: 19, end: 86, pattern_start: 30, pattern_end: 66 } ) } } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "main", "Pattern match failed, no pattern matched the value.", { value: $, start: 19, end: 86, pattern_start: 30, pattern_end: 66 } ) } } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "main", "Pattern match failed, no pattern matched the value.", { value: $, start: 19, end: 86, pattern_start: 30, pattern_end: 66 } ) } return $; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__pattern_match_size_arithmetic.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn wibble(bits, wobble) {\n case bits {\n <<_:size(1), _:size(wobble - 1), _:bits>> -> 0\n _ -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn wibble(bits, wobble) { case bits { <<_:size(1), _:size(wobble - 1), _:bits>> -> 0 _ -> 1 } } ----- COMPILED JAVASCRIPT export function wibble(bits, wobble) { if (bits.bitSize >= 1 && wobble - 1 >= 0 && bits.bitSize >= 1 + wobble - 1) { return 0; } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__pattern_match_unknown_size_and_literal_string.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x, n) {\n case x {\n <<_:size(n), \"\\r\\n\">> -> 1\n _ -> 2\n }\n}\n" --- ----- SOURCE CODE pub fn go(x, n) { case x { <<_:size(n), "\r\n">> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToInt } from "../gleam.mjs"; export function go(x, n) { if ( n >= 0 && x.bitSize >= n && x.bitSize === 16 + n && bitArraySliceToInt(x, n, 8 + n, true, false) === 13 && bitArraySliceToInt(x, 8 + n, 16 + n, true, false) === 10 ) { return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__pattern_match_utf16.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<\"Hello\":utf16, _rest:bytes>> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<"Hello":utf16, _rest:bytes>> = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { if (!( x.bitSize >= 80 && x.byteAt(0) === 0 && x.byteAt(1) === 72 && x.byteAt(2) === 0 && x.byteAt(3) === 101 && x.byteAt(4) === 0 && x.byteAt(5) === 108 && x.byteAt(6) === 0 && x.byteAt(7) === 108 && x.byteAt(8) === 0 && x.byteAt(9) === 111 && (x.bitSize - 80) % 8 === 0 )) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 63, pattern_start: 29, pattern_end: 59 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__pattern_match_utf16_little_endian.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<\"Hello\":utf16-little, _rest:bytes>> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<"Hello":utf16-little, _rest:bytes>> = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { if (!( x.bitSize >= 80 && x.byteAt(0) === 72 && x.byteAt(1) === 0 && x.byteAt(2) === 101 && x.byteAt(3) === 0 && x.byteAt(4) === 108 && x.byteAt(5) === 0 && x.byteAt(6) === 108 && x.byteAt(7) === 0 && x.byteAt(8) === 111 && x.byteAt(9) === 0 && (x.bitSize - 80) % 8 === 0 )) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 70, pattern_start: 29, pattern_end: 66 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__pattern_match_utf32.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<\"Hello\":utf32, _rest:bytes>> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<"Hello":utf32, _rest:bytes>> = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { if (!( x.bitSize >= 160 && x.byteAt(0) === 0 && x.byteAt(1) === 0 && x.byteAt(2) === 0 && x.byteAt(3) === 72 && x.byteAt(4) === 0 && x.byteAt(5) === 0 && x.byteAt(6) === 0 && x.byteAt(7) === 101 && x.byteAt(8) === 0 && x.byteAt(9) === 0 && x.byteAt(10) === 0 && x.byteAt(11) === 108 && x.byteAt(12) === 0 && x.byteAt(13) === 0 && x.byteAt(14) === 0 && x.byteAt(15) === 108 && x.byteAt(16) === 0 && x.byteAt(17) === 0 && x.byteAt(18) === 0 && x.byteAt(19) === 111 && (x.bitSize - 160) % 8 === 0 )) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 63, pattern_start: 29, pattern_end: 59 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__pattern_match_utf32_little_endian.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<\"Hello\":utf32-little, _rest:bytes>> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<"Hello":utf32-little, _rest:bytes>> = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { if (!( x.bitSize >= 160 && x.byteAt(0) === 72 && x.byteAt(1) === 0 && x.byteAt(2) === 0 && x.byteAt(3) === 0 && x.byteAt(4) === 101 && x.byteAt(5) === 0 && x.byteAt(6) === 0 && x.byteAt(7) === 0 && x.byteAt(8) === 108 && x.byteAt(9) === 0 && x.byteAt(10) === 0 && x.byteAt(11) === 0 && x.byteAt(12) === 108 && x.byteAt(13) === 0 && x.byteAt(14) === 0 && x.byteAt(15) === 0 && x.byteAt(16) === 111 && x.byteAt(17) === 0 && x.byteAt(18) === 0 && x.byteAt(19) === 0 && (x.bitSize - 160) % 8 === 0 )) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 70, pattern_start: 29, pattern_end: 66 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__pattern_matching_on_32_float_minus_infinity_still_reachable.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n case x {\n <<_:32-float>> -> \"Float\"\n <<0xff800000:32>> -> \"-Infinity\"\n _ -> \"Other\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { <<_:32-float>> -> "Float" <<0xff800000:32>> -> "-Infinity" _ -> "Other" } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { if (x.bitSize === 32) { if (Number.isFinite(bitArraySliceToFloat(x, 0, 32, true))) { return "Float"; } else if ( x.byteAt(0) === 255 && x.byteAt(1) === 128 && x.byteAt(2) === 0 && x.byteAt(3) === 0 ) { return "-Infinity"; } else { return "Other"; } } else { return "Other"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__pattern_matching_on_32_float_minus_infinity_still_reachable_2.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n case x {\n <<_:32-float>> -> \"Float\"\n <<0xff80:16, 0x0000:16>> -> \"-Infinity\"\n _ -> \"Other\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { <<_:32-float>> -> "Float" <<0xff80:16, 0x0000:16>> -> "-Infinity" _ -> "Other" } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { if (x.bitSize === 32) { if (Number.isFinite(bitArraySliceToFloat(x, 0, 32, true))) { return "Float"; } else if ( x.byteAt(0) === 255 && x.byteAt(1) === 128 && x.byteAt(2) === 0 && x.byteAt(3) === 0 ) { return "-Infinity"; } else { return "Other"; } } else { return "Other"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__pattern_matching_on_32_float_nan_still_reachable.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n case x {\n <<_:32-float>> -> \"Float\"\n <<0x7fc00000:32>> -> \"NaN\"\n _ -> \"Other\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { <<_:32-float>> -> "Float" <<0x7fc00000:32>> -> "NaN" _ -> "Other" } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { if (x.bitSize === 32) { if (Number.isFinite(bitArraySliceToFloat(x, 0, 32, true))) { return "Float"; } else if ( x.byteAt(0) === 127 && x.byteAt(1) === 192 && x.byteAt(2) === 0 && x.byteAt(3) === 0 ) { return "NaN"; } else { return "Other"; } } else { return "Other"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__pattern_matching_on_32_float_nan_still_reachable_2.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n case x {\n <<_:32-float>> -> \"Float\"\n <<0x7fc0:16, 0x0000:16>> -> \"NaN\"\n _ -> \"Other\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { <<_:32-float>> -> "Float" <<0x7fc0:16, 0x0000:16>> -> "NaN" _ -> "Other" } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { if (x.bitSize === 32) { if (Number.isFinite(bitArraySliceToFloat(x, 0, 32, true))) { return "Float"; } else if ( x.byteAt(0) === 127 && x.byteAt(1) === 192 && x.byteAt(2) === 0 && x.byteAt(3) === 0 ) { return "NaN"; } else { return "Other"; } } else { return "Other"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__pattern_matching_on_32_float_plus_infinity_still_reachable.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n case x {\n <<_:32-float>> -> \"Float\"\n <<0x7f800000:32>> -> \"+Infinity\"\n _ -> \"Other\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { <<_:32-float>> -> "Float" <<0x7f800000:32>> -> "+Infinity" _ -> "Other" } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { if (x.bitSize === 32) { if (Number.isFinite(bitArraySliceToFloat(x, 0, 32, true))) { return "Float"; } else if ( x.byteAt(0) === 127 && x.byteAt(1) === 128 && x.byteAt(2) === 0 && x.byteAt(3) === 0 ) { return "+Infinity"; } else { return "Other"; } } else { return "Other"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__pattern_matching_on_32_float_plus_infinity_still_reachable_2.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n case x {\n <<_:32-float>> -> \"Float\"\n <<0x7f80:16, 0x0000:16>> -> \"+Infinity\"\n _ -> \"Other\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { <<_:32-float>> -> "Float" <<0x7f80:16, 0x0000:16>> -> "+Infinity" _ -> "Other" } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { if (x.bitSize === 32) { if (Number.isFinite(bitArraySliceToFloat(x, 0, 32, true))) { return "Float"; } else if ( x.byteAt(0) === 127 && x.byteAt(1) === 128 && x.byteAt(2) === 0 && x.byteAt(3) === 0 ) { return "+Infinity"; } else { return "Other"; } } else { return "Other"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__pattern_matching_on_64_float_float_is_unreachable.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n case x {\n <<_:64-float>> -> \"Float\"\n <<_:64-float>> -> \"unreachable\"\n _ -> \"Other\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { <<_:64-float>> -> "Float" <<_:64-float>> -> "unreachable" _ -> "Other" } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { if (x.bitSize === 64 && Number.isFinite(bitArraySliceToFloat(x, 0, 64, true))) { return "Float"; } else { return "Other"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__pattern_matching_on_64_float_int_is_still_reachable.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n case x {\n <<_:64-float>> -> \"Float\"\n <<_:64-int>> -> \"Int\"\n _ -> \"Other\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { <<_:64-float>> -> "Float" <<_:64-int>> -> "Int" _ -> "Other" } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { if (x.bitSize === 64) { if (Number.isFinite(bitArraySliceToFloat(x, 0, 64, true))) { return "Float"; } else { return "Int"; } } else { return "Other"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__pattern_matching_on_64_float_minus_infinity_still_reachable.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n case x {\n <<_:64-float>> -> \"Float\"\n <<0xfff0000000000000:64>> -> \"-Infinity\"\n _ -> \"Other\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { <<_:64-float>> -> "Float" <<0xfff0000000000000:64>> -> "-Infinity" _ -> "Other" } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { if (x.bitSize === 64) { if (Number.isFinite(bitArraySliceToFloat(x, 0, 64, true))) { return "Float"; } else if ( x.byteAt(0) === 255 && x.byteAt(1) === 240 && x.byteAt(2) === 0 && x.byteAt(3) === 0 && x.byteAt(4) === 0 && x.byteAt(5) === 0 && x.byteAt(6) === 0 && x.byteAt(7) === 0 ) { return "-Infinity"; } else { return "Other"; } } else { return "Other"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__pattern_matching_on_64_float_minus_infinity_still_reachable_2.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n case x {\n <<_:64-float>> -> \"Float\"\n <<0xfff00000:32, 0x00000000:32>> -> \"-Infinity\"\n _ -> \"Other\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { <<_:64-float>> -> "Float" <<0xfff00000:32, 0x00000000:32>> -> "-Infinity" _ -> "Other" } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { if (x.bitSize === 64) { if (Number.isFinite(bitArraySliceToFloat(x, 0, 64, true))) { return "Float"; } else if ( x.byteAt(0) === 255 && x.byteAt(1) === 240 && x.byteAt(2) === 0 && x.byteAt(3) === 0 && x.byteAt(4) === 0 && x.byteAt(5) === 0 && x.byteAt(6) === 0 && x.byteAt(7) === 0 ) { return "-Infinity"; } else { return "Other"; } } else { return "Other"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__pattern_matching_on_64_float_nan_still_reachable.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n case x {\n <<_:64-float>> -> \"Float\"\n <<0x7ff8000000000000:64>> -> \"NaN\"\n _ -> \"Other\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { <<_:64-float>> -> "Float" <<0x7ff8000000000000:64>> -> "NaN" _ -> "Other" } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { if (x.bitSize === 64) { if (Number.isFinite(bitArraySliceToFloat(x, 0, 64, true))) { return "Float"; } else if ( x.byteAt(0) === 127 && x.byteAt(1) === 248 && x.byteAt(2) === 0 && x.byteAt(3) === 0 && x.byteAt(4) === 0 && x.byteAt(5) === 0 && x.byteAt(6) === 0 && x.byteAt(7) === 0 ) { return "NaN"; } else { return "Other"; } } else { return "Other"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__pattern_matching_on_64_float_nan_still_reachable_2.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n case x {\n <<_:64-float>> -> \"Float\"\n <<0x7ff80000:32, 0x00000000:32>> -> \"NaN\"\n _ -> \"Other\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { <<_:64-float>> -> "Float" <<0x7ff80000:32, 0x00000000:32>> -> "NaN" _ -> "Other" } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { if (x.bitSize === 64) { if (Number.isFinite(bitArraySliceToFloat(x, 0, 64, true))) { return "Float"; } else if ( x.byteAt(0) === 127 && x.byteAt(1) === 248 && x.byteAt(2) === 0 && x.byteAt(3) === 0 && x.byteAt(4) === 0 && x.byteAt(5) === 0 && x.byteAt(6) === 0 && x.byteAt(7) === 0 ) { return "NaN"; } else { return "Other"; } } else { return "Other"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__pattern_matching_on_64_float_plus_infinity_still_reachable.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n case x {\n <<_:64-float>> -> \"Float\"\n <<0x7ff0000000000000:64>> -> \"+Infinity\"\n _ -> \"Other\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { <<_:64-float>> -> "Float" <<0x7ff0000000000000:64>> -> "+Infinity" _ -> "Other" } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { if (x.bitSize === 64) { if (Number.isFinite(bitArraySliceToFloat(x, 0, 64, true))) { return "Float"; } else if ( x.byteAt(0) === 127 && x.byteAt(1) === 240 && x.byteAt(2) === 0 && x.byteAt(3) === 0 && x.byteAt(4) === 0 && x.byteAt(5) === 0 && x.byteAt(6) === 0 && x.byteAt(7) === 0 ) { return "+Infinity"; } else { return "Other"; } } else { return "Other"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__pattern_matching_on_64_float_plus_infinity_still_reachable_2.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n case x {\n <<_:64-float>> -> \"Float\"\n <<0x7ff00000:32, 0x00000000:32>> -> \"+Infinity\"\n _ -> \"Other\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { <<_:64-float>> -> "Float" <<0x7ff00000:32, 0x00000000:32>> -> "+Infinity" _ -> "Other" } } ----- COMPILED JAVASCRIPT import { bitArraySliceToFloat } from "../gleam.mjs"; export function go(x) { if (x.bitSize === 64) { if (Number.isFinite(bitArraySliceToFloat(x, 0, 64, true))) { return "Float"; } else if ( x.byteAt(0) === 127 && x.byteAt(1) === 240 && x.byteAt(2) === 0 && x.byteAt(3) === 0 && x.byteAt(4) === 0 && x.byteAt(5) === 0 && x.byteAt(6) === 0 && x.byteAt(7) === 0 ) { return "+Infinity"; } else { return "Other"; } } else { return "Other"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__pattern_with_unit.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert <<1:size(2)-unit(2), 2:size(3)-unit(4)>> = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert <<1:size(2)-unit(2), 2:size(3)-unit(4)>> = x } ----- COMPILED JAVASCRIPT import { makeError, bitArraySliceToInt } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { if (!( x.bitSize >= 4 && bitArraySliceToInt(x, 0, 4, true, false) === 1 && x.bitSize === 16 && bitArraySliceToInt(x, 4, 16, true, false) === 2 )) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 73, pattern_start: 29, pattern_end: 69 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__segments_shadowing_each_other.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1947 expression: "\npub fn go(x) {\n let n = 1\n case x {\n <> -> 1\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { let n = 1 case x { <> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToInt } from "../gleam.mjs"; export function go(x) { let n = 1; if (x.bitSize >= 8) { let n$1 = x.byteAt(0); if (x.bitSize === 8 + n$1) { let n$2 = n$1; let rest = bitArraySliceToInt(x, 8, 8 + n$2, true, false); return 1; } else { return 2; } } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__sized_big_endian_constant_value.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 176 expression: "\npub fn go() {\n <<256:16-big>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { <<256:16-big>> } ----- COMPILED JAVASCRIPT import { toBitArray } from "../gleam.mjs"; export function go() { return toBitArray([1, 0]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__sized_big_endian_dynamic_value.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 187 expression: "\npub fn go(i: Int) {\n <>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(i: Int) { <> } ----- COMPILED JAVASCRIPT import { toBitArray, sizedInt } from "../gleam.mjs"; export function go(i) { return toBitArray([sizedInt(i, 16, true)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__sized_bits_expression_requires_v1_9.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn main() {\n <<<<0>>:bits-5>>\n}\n " --- ----- SOURCE CODE pub fn main() { <<<<0>>:bits-5>> } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:3:16 │ 3 │ <<<<0>>:bits-5>> │ ^ This requires a Gleam version >= 1.9.0 Use of unaligned bit arrays on the JavaScript target was introduced in version v1.9.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.8.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.9.0" ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__sized_constant_value.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 121 expression: "\npub fn go() {\n <<256:64>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { <<256:64>> } ----- COMPILED JAVASCRIPT import { toBitArray, sizedInt } from "../gleam.mjs"; export function go() { return toBitArray([sizedInt(256, 64, true)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__sized_constant_value_max_size_for_compile_time_evaluation.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 165 expression: "\npub fn go() {\n <<-1:48>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { <<-1:48>> } ----- COMPILED JAVASCRIPT import { toBitArray } from "../gleam.mjs"; export function go() { return toBitArray([255, 255, 255, 255, 255, 255]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__sized_constant_value_negative_overflow.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 154 expression: "\npub fn go() {\n <<-80_000:16>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { <<-80_000:16>> } ----- COMPILED JAVASCRIPT import { toBitArray } from "../gleam.mjs"; export function go() { return toBitArray([199, 128]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__sized_constant_value_positive_overflow.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 143 expression: "\npub fn go() {\n <<80_000:16>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { <<80_000:16>> } ----- COMPILED JAVASCRIPT import { toBitArray } from "../gleam.mjs"; export function go() { return toBitArray([56, 128]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__sized_dynamic_value.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 132 expression: "\npub fn go(i: Int) {\n <>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(i: Int) { <> } ----- COMPILED JAVASCRIPT import { toBitArray, sizedInt } from "../gleam.mjs"; export function go(i) { return toBitArray([sizedInt(i, 64, true)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__sized_little_endian_constant_value.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 198 expression: "\npub fn go() {\n <<256:16-little>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { <<256:16-little>> } ----- COMPILED JAVASCRIPT import { toBitArray } from "../gleam.mjs"; export function go() { return toBitArray([0, 1]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__sized_little_endian_dynamic_value.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 209 expression: "\npub fn go(i: Int) {\n <>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(i: Int) { <> } ----- COMPILED JAVASCRIPT import { toBitArray, sizedInt } from "../gleam.mjs"; export function go(i) { return toBitArray([sizedInt(i, 16, false)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__tuple_bit_array.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert #(<<>>) = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert #(<<>>) = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let $ = x[0]; if (!($.bitSize === 0)) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 40, pattern_start: 29, pattern_end: 36 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__tuple_bit_array_case.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n case x {\n #(<<>>) -> 1\n _ -> 2\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { #(<<>>) -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT export function go(x) { let $ = x[0]; if ($.bitSize === 0) { return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__tuple_multiple_bit_arrays.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n let assert #(<<>>, <<1>>, <<2, 3>>) = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert #(<<>>, <<1>>, <<2, 3>>) = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { let $ = x[0]; if ($.bitSize === 0) { let $1 = x[1]; if ($1.bitSize === 8) { let $2 = x[2]; if (!( $2.bitSize >= 8 && $1.byteAt(0) === 1 && $2.byteAt(0) === 2 && $2.bitSize === 16 && $2.byteAt(1) === 3 )) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 57, pattern_start: 29, pattern_end: 53 } ) } } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 57, pattern_start: 29, pattern_end: 53 } ) } } else { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 57, pattern_start: 29, pattern_end: 53 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__tuple_multiple_bit_arrays_case.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n case x {\n #(<<>>, <<1>>, <<2, 3>>) -> True\n _ -> False\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { #(<<>>, <<1>>, <<2, 3>>) -> True _ -> False } } ----- COMPILED JAVASCRIPT export function go(x) { let $ = x[0]; if ($.bitSize === 0) { let $1 = x[1]; if ($1.bitSize === 8) { let $2 = x[2]; if ( $2.bitSize >= 8 && $1.byteAt(0) === 1 && $2.byteAt(0) === 2 && $2.bitSize === 16 && $2.byteAt(1) === 3 ) { return true; } else { return false; } } else { return false; } } else { return false; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__two.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 33 expression: "\npub fn go() {\n <<256, 4>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { <<256, 4>> } ----- COMPILED JAVASCRIPT import { toBitArray } from "../gleam.mjs"; export function go() { return toBitArray([0, 4]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__unaligned_int_expression_requires_v1_9.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn main() {\n <<0:1>>\n}\n " --- ----- SOURCE CODE pub fn main() { <<0:1>> } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:3:7 │ 3 │ <<0:1>> │ ^ This requires a Gleam version >= 1.9.0 Use of unaligned bit arrays on the JavaScript target was introduced in version v1.9.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.8.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.9.0" ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__unaligned_int_pattern_requires_v1_9.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn main() {\n let assert <<_:3>> = <<0>>\n}\n " --- ----- SOURCE CODE pub fn main() { let assert <<_:3>> = <<0>> } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:3:18 │ 3 │ let assert <<_:3>> = <<0>> │ ^ This requires a Gleam version >= 1.9.0 Use of unaligned bit arrays on the JavaScript target was introduced in version v1.9.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.8.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.9.0" ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__unit_with_bits_option.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n <>\n}\n" --- ----- SOURCE CODE pub fn go(x) { <> } ----- COMPILED JAVASCRIPT import { toBitArray, bitArraySlice } from "../gleam.mjs"; export function go(x) { return toBitArray([bitArraySlice(x, 0, 32)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__unit_with_bits_option_constant.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub const bits = <<1, 2, 3>>\npub const more_bits = <>\n" --- ----- SOURCE CODE pub const bits = <<1, 2, 3>> pub const more_bits = <> ----- COMPILED JAVASCRIPT import { toBitArray, bitArraySlice } from "../gleam.mjs"; export const bits = /* @__PURE__ */ toBitArray([1, 2, 3]); export const more_bits = /* @__PURE__ */ toBitArray([bitArraySlice(bits, 0, 24)]); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__utf16.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn main() {\n <<\"Hello, world!\":utf16>>\n}\n" --- ----- SOURCE CODE pub fn main() { <<"Hello, world!":utf16>> } ----- COMPILED JAVASCRIPT import { toBitArray, stringToUtf16 } from "../gleam.mjs"; export function main() { return toBitArray([stringToUtf16("Hello, world!", true)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__utf16_codepoint.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 2114 expression: "\nfn codepoint() -> UtfCodepoint { todo }\n\npub fn main() {\n let my_codepoint = codepoint()\n <>\n}\n" snapshot_kind: text --- ----- SOURCE CODE fn codepoint() -> UtfCodepoint { todo } pub fn main() { let my_codepoint = codepoint() <> } ----- COMPILED JAVASCRIPT import { makeError, toBitArray, codepointToUtf16 } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; function codepoint() { throw makeError( "todo", FILEPATH, "my/mod", 2, "codepoint", "`todo` expression evaluated. This code has not yet been implemented.", {} ) } export function main() { let my_codepoint = codepoint(); return toBitArray([codepointToUtf16(my_codepoint, true)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__utf16_codepoint_little_endian.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(codepoint) {\n <>\n}\n" --- ----- SOURCE CODE pub fn go(codepoint) { <> } ----- COMPILED JAVASCRIPT import { toBitArray, codepointToUtf16 } from "../gleam.mjs"; export function go(codepoint) { return toBitArray([codepointToUtf16(codepoint, false)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__utf16_little_endian.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn main() {\n <<\"Hello, world!\":utf16-little>>\n}\n" --- ----- SOURCE CODE pub fn main() { <<"Hello, world!":utf16-little>> } ----- COMPILED JAVASCRIPT import { toBitArray, stringToUtf16 } from "../gleam.mjs"; export function main() { return toBitArray([stringToUtf16("Hello, world!", false)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__utf32.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn main() {\n <<\"Hello, world!\":utf32>>\n}\n" --- ----- SOURCE CODE pub fn main() { <<"Hello, world!":utf32>> } ----- COMPILED JAVASCRIPT import { toBitArray, stringToUtf32 } from "../gleam.mjs"; export function main() { return toBitArray([stringToUtf32("Hello, world!", true)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__utf32_codepoint.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 2139 expression: "\nfn codepoint() -> UtfCodepoint { todo }\n\npub fn main() {\n let my_codepoint = codepoint()\n <>\n}\n" snapshot_kind: text --- ----- SOURCE CODE fn codepoint() -> UtfCodepoint { todo } pub fn main() { let my_codepoint = codepoint() <> } ----- COMPILED JAVASCRIPT import { makeError, toBitArray, codepointToUtf32 } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; function codepoint() { throw makeError( "todo", FILEPATH, "my/mod", 2, "codepoint", "`todo` expression evaluated. This code has not yet been implemented.", {} ) } export function main() { let my_codepoint = codepoint(); return toBitArray([codepointToUtf32(my_codepoint, true)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__utf32_codepoint_little_endian.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(codepoint) {\n <>\n}\n" --- ----- SOURCE CODE pub fn go(codepoint) { <> } ----- COMPILED JAVASCRIPT import { toBitArray, codepointToUtf32 } from "../gleam.mjs"; export function go(codepoint) { return toBitArray([codepointToUtf32(codepoint, false)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__utf32_little_endian.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn main() {\n <<\"Hello, world!\":utf32-little>>\n}\n" --- ----- SOURCE CODE pub fn main() { <<"Hello, world!":utf32-little>> } ----- COMPILED JAVASCRIPT import { toBitArray, stringToUtf32 } from "../gleam.mjs"; export function main() { return toBitArray([stringToUtf32("Hello, world!", false)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__utf8.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 264 expression: "\npub fn go(x) {\n <<256, 4, x, \"Gleam\":utf8>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { <<256, 4, x, "Gleam":utf8>> } ----- COMPILED JAVASCRIPT import { toBitArray, stringBits } from "../gleam.mjs"; export function go(x) { return toBitArray([0, 4, x, stringBits("Gleam")]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__utf8_codepoint.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 325 expression: "\npub fn go(x) {\n <>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { <> } ----- COMPILED JAVASCRIPT import { toBitArray, stringBits, codepointBits } from "../gleam.mjs"; export function go(x) { return toBitArray([codepointBits(x), stringBits("Gleam")]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__utf8_codepoint_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs expression: "\npub fn go(x) {\n <>\n}\n" --- ----- SOURCE CODE pub fn go(x) { <> } ----- TYPESCRIPT DEFINITIONS import type * as _ from "../gleam.d.mts"; export function go(x: _.UtfCodepoint): _.BitArray; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__variable.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 253 expression: "\npub fn go(x) {\n <<256, 4, x>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { <<256, 4, x>> } ----- COMPILED JAVASCRIPT import { toBitArray } from "../gleam.mjs"; export function go(x) { return toBitArray([0, 4, x]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__variable_sized.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 242 expression: "\npub fn go(x, y) {\n <>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x, y) { <> } ----- COMPILED JAVASCRIPT import { toBitArray, sizedInt } from "../gleam.mjs"; export function go(x, y) { return toBitArray([sizedInt(x, y, true)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__variable_sized_segment.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1933 expression: "\npub fn go(x) {\n case x {\n <> -> 1\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { <> -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT import { bitArraySliceToInt } from "../gleam.mjs"; export function go(x) { if (x.bitSize >= 8) { let n = x.byteAt(0); if (x.bitSize === 8 + n) { let n$1 = n; let rest = bitArraySliceToInt(x, 8, 8 + n$1, true, false); return 1; } else { return 2; } } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bit_arrays__with_unit.snap ================================================ --- source: compiler-core/src/javascript/tests/bit_arrays.rs assertion_line: 1770 expression: "\npub fn main() {\n <<1:size(2)-unit(2), 2:size(3)-unit(4)>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn main() { <<1:size(2)-unit(2), 2:size(3)-unit(4)>> } ----- COMPILED JAVASCRIPT import { toBitArray, sizedInt } from "../gleam.mjs"; export function main() { return toBitArray([sizedInt(1, 4, true), sizedInt(2, 12, true)]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__blocks__assignment_last_in_block.snap ================================================ --- source: compiler-core/src/javascript/tests/blocks.rs expression: "\npub fn main() {\n let a = {\n let b = 1\n let c = b + 1\n }\n a\n}\n" --- ----- SOURCE CODE pub fn main() { let a = { let b = 1 let c = b + 1 } a } ----- COMPILED JAVASCRIPT export function main() { let _block; { let b = 1; let c = b + 1; _block = c; } let a = _block; return a; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__blocks__block.snap ================================================ --- source: compiler-core/src/javascript/tests/blocks.rs assertion_line: 5 expression: "\npub fn go() {\n let x = {\n 1\n 2\n }\n x\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { let x = { 1 2 } x } ----- COMPILED JAVASCRIPT export function go() { let _block; { 1; _block = 2; } let x = _block; return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__blocks__block_in_tail_position_is_not_an_iife.snap ================================================ --- source: compiler-core/src/javascript/tests/blocks.rs assertion_line: 221 expression: "\npub fn b() {\n let x = 1\n {\n Nil\n x + 1\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn b() { let x = 1 { Nil x + 1 } } ----- COMPILED JAVASCRIPT export function b() { let x = 1; { undefined; return x + 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__blocks__block_in_tail_position_shadowing_variables.snap ================================================ --- source: compiler-core/src/javascript/tests/blocks.rs assertion_line: 236 expression: "\npub fn b() {\n let x = 1\n {\n let x = 2\n x + 1\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn b() { let x = 1 { let x = 2 x + 1 } } ----- COMPILED JAVASCRIPT export function b() { let x = 1; { let x$1 = 2; return x$1 + 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__blocks__block_in_tail_position_with_just_an_assignment.snap ================================================ --- source: compiler-core/src/javascript/tests/blocks.rs assertion_line: 251 expression: "\npub fn b() {\n let x = 1\n {\n let x = x\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn b() { let x = 1 { let x = x } } ----- COMPILED JAVASCRIPT export function b() { let x = 1; return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__blocks__block_with_parenthesised_expression_returning_from_function.snap ================================================ --- source: compiler-core/src/javascript/tests/blocks.rs assertion_line: 208 expression: "\npub fn b() {\n {\n 1 + 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn b() { { 1 + 2 } } ----- COMPILED JAVASCRIPT export function b() { return 1 + 2; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__blocks__blocks_returning_functions.snap ================================================ --- source: compiler-core/src/javascript/tests/blocks.rs expression: "\npub fn b() {\n {\n fn(cb) { cb(1) }\n }\n {\n fn(cb) { cb(2) }\n }\n 3\n}\n" --- ----- SOURCE CODE pub fn b() { { fn(cb) { cb(1) } } { fn(cb) { cb(2) } } 3 } ----- COMPILED JAVASCRIPT export function b() { (cb) => { return cb(1); }; (cb) => { return cb(2); }; return 3; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__blocks__blocks_returning_use.snap ================================================ --- source: compiler-core/src/javascript/tests/blocks.rs expression: "\npub fn b() {\n {\n use a <- fn(cb) { cb(1) }\n a\n }\n {\n use b <- fn(cb) { cb(2) }\n b\n }\n 3\n}\n " --- ----- SOURCE CODE pub fn b() { { use a <- fn(cb) { cb(1) } a } { use b <- fn(cb) { cb(2) } b } 3 } ----- COMPILED JAVASCRIPT export function b() { 1; 2; return 3; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__blocks__blocks_whose_values_are_unused_do_not_generate_assignments.snap ================================================ --- source: compiler-core/src/javascript/tests/blocks.rs expression: "\npub fn main() {\n {\n let x = 10\n echo x\n }\n\n {\n let a = 1\n let b = 2\n a + b\n }\n\n Nil\n}\n" --- ----- SOURCE CODE pub fn main() { { let x = 10 echo x } { let a = 1 let b = 2 a + b } Nil } ----- COMPILED JAVASCRIPT import * as $stdlib$dict from "../../gleam_stdlib/gleam/dict.mjs"; import { Empty as $Empty, NonEmpty as $NonEmpty, CustomType as $CustomType, bitArraySlice, bitArraySliceToInt, BitArray as $BitArray, List as $List, UtfCodepoint as $UtfCodepoint, } from "../gleam.mjs"; export function main() { { let x = 10; echo(x, undefined, "src/module.gleam", 5) }; { let a = 1; let b = 2; a + b }; return undefined; } // ...omitted code from `templates/echo.mjs`... ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__blocks__concat_blocks.snap ================================================ --- source: compiler-core/src/javascript/tests/blocks.rs assertion_line: 155 expression: "\npub fn main(f, a, b) {\n {\n a\n |> f\n } <> {\n b\n |> f\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn main(f, a, b) { { a |> f } <> { b |> f } } ----- COMPILED JAVASCRIPT export function main(f, a, b) { return (() => { let _pipe = a; return f(_pipe); })() + (() => { let _pipe = b; return f(_pipe); })(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__blocks__left_operator_sequence.snap ================================================ --- source: compiler-core/src/javascript/tests/blocks.rs assertion_line: 127 expression: "\npub fn go() {\n 1 == {\n 1\n 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { 1 == { 1 2 } } ----- COMPILED JAVASCRIPT export function go() { return 1 === (() => { 1; return 2; })(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__blocks__let_assert_message_no_lifted.snap ================================================ --- source: compiler-core/src/javascript/tests/blocks.rs expression: "\nfn side_effects(x) {\n // Some side effects\n x\n}\n\npub fn main() {\n let assert Error(Nil) = side_effects(Ok(10))\n as {\n let message = side_effects(\"some message\")\n message\n }\n}\n" --- ----- SOURCE CODE fn side_effects(x) { // Some side effects x } pub fn main() { let assert Error(Nil) = side_effects(Ok(10)) as { let message = side_effects("some message") message } } ----- COMPILED JAVASCRIPT import { Ok, Error, makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; function side_effects(x) { return x; } export function main() { let $ = side_effects(new Ok(10)); if (!($ instanceof Error)) { throw makeError( "let_assert", FILEPATH, "my/mod", 8, "main", (() => { let message = side_effects("some message"); return message; })(), { value: $, start: 70, end: 114, pattern_start: 81, pattern_end: 91 } ) } return $; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__blocks__let_assert_only_statement_in_block.snap ================================================ --- source: compiler-core/src/javascript/tests/blocks.rs assertion_line: 284 expression: "\npub fn main() {\n {\n let assert Ok(1) = Error(Nil)\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn main() { { let assert Ok(1) = Error(Nil) } } ----- COMPILED JAVASCRIPT import { Error, makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main() { { let $ = new Error(undefined); throw makeError( "let_assert", FILEPATH, "my/mod", 4, "main", "Pattern match failed, no pattern matched the value.", { value: $, start: 25, end: 54, pattern_start: 36, pattern_end: 41 } ) return $; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__blocks__nested_multiexpr_blocks.snap ================================================ --- source: compiler-core/src/javascript/tests/blocks.rs assertion_line: 36 expression: "\npub fn go() {\n let x = {\n 1\n {\n 2\n 3\n }\n }\n x\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { let x = { 1 { 2 3 } } x } ----- COMPILED JAVASCRIPT export function go() { let _block; { 1; { 2; _block = 3; } } let x = _block; return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__blocks__nested_multiexpr_blocks_with_case.snap ================================================ --- source: compiler-core/src/javascript/tests/blocks.rs assertion_line: 94 expression: "\npub fn go() {\n let x = {\n 1\n {\n 2\n case True {\n _ -> 3\n }\n }\n }\n x\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { let x = { 1 { 2 case True { _ -> 3 } } } x } ----- COMPILED JAVASCRIPT export function go() { let _block; { 1; { 2; let $ = true; _block = 3; } } let x = _block; return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__blocks__nested_multiexpr_blocks_with_pipe.snap ================================================ --- source: compiler-core/src/javascript/tests/blocks.rs assertion_line: 54 expression: "\npub fn add1(a) {\n a + 1\n}\npub fn go() {\n let x = {\n 1\n {\n 2\n 3 |> add1\n } |> add1\n }\n x\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn add1(a) { a + 1 } pub fn go() { let x = { 1 { 2 3 |> add1 } |> add1 } x } ----- COMPILED JAVASCRIPT export function add1(a) { return a + 1; } export function go() { let _block; { 1; let _block$1; { 2; let _pipe = 3; _block$1 = add1(_pipe); } let _pipe = _block$1; _block = add1(_pipe); } let x = _block; return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__blocks__nested_multiexpr_non_ending_blocks.snap ================================================ --- source: compiler-core/src/javascript/tests/blocks.rs expression: "\npub fn go() {\n let x = {\n 1\n {\n 2\n 3\n }\n 4\n }\n x\n}\n" --- ----- SOURCE CODE pub fn go() { let x = { 1 { 2 3 } 4 } x } ----- COMPILED JAVASCRIPT export function go() { let _block; { 1; { 2; 3 }; _block = 4; } let x = _block; return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__blocks__nested_simple_blocks.snap ================================================ --- source: compiler-core/src/javascript/tests/blocks.rs assertion_line: 20 expression: "\npub fn go() {\n let x = {\n {\n 3\n }\n }\n x\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { let x = { { 3 } } x } ----- COMPILED JAVASCRIPT export function go() { let x = 3; return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__blocks__pattern_assignment_last_in_block.snap ================================================ --- source: compiler-core/src/javascript/tests/blocks.rs expression: "\npub fn main() {\n let a = {\n let b = #(1, 2)\n let #(x, y) = b\n }\n a\n}\n" --- ----- SOURCE CODE pub fn main() { let a = { let b = #(1, 2) let #(x, y) = b } a } ----- COMPILED JAVASCRIPT export function main() { let _block; { let b = [1, 2]; let x; let y; x = b[0]; y = b[1]; _block = b; } let a = _block; return a; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__blocks__right_operator_sequence.snap ================================================ --- source: compiler-core/src/javascript/tests/blocks.rs assertion_line: 141 expression: "\npub fn go() {\n {\n 1\n 2\n } == 1\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { { 1 2 } == 1 } ----- COMPILED JAVASCRIPT export function go() { return (() => { 1; return 2; })() === 1; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__blocks__sequences.snap ================================================ --- source: compiler-core/src/javascript/tests/blocks.rs assertion_line: 114 expression: "\npub fn go() {\n \"one\"\n \"two\"\n \"three\"\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { "one" "two" "three" } ----- COMPILED JAVASCRIPT export function go() { "one"; "two"; return "three"; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__blocks__shadowed_variable_in_nested_scope.snap ================================================ --- source: compiler-core/src/javascript/tests/blocks.rs expression: "\npub fn main() {\n {\n let x = 1\n let _ = {\n let x = 2\n x\n }\n x\n }\n}\n" --- ----- SOURCE CODE pub fn main() { { let x = 1 let _ = { let x = 2 x } x } } ----- COMPILED JAVASCRIPT export function main() { { let x = 1; let _block; { let x$1 = 2; _block = x$1; } let $ = _block; return x; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bools__assigning.snap ================================================ --- source: compiler-core/src/javascript/tests/bools.rs expression: "\npub fn go(x, y) {\n let assert True = x\n let assert False = x\n let assert Nil = y\n}\n" --- ----- SOURCE CODE pub fn go(x, y) { let assert True = x let assert False = x let assert Nil = y } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x, y) { if (!(x)) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 21, end: 40, pattern_start: 32, pattern_end: 36 } ) } throw makeError( "let_assert", FILEPATH, "my/mod", 4, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 43, end: 63, pattern_start: 54, pattern_end: 59 } ) return y; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bools__binop_panic_left.snap ================================================ --- source: compiler-core/src/javascript/tests/bools.rs assertion_line: 171 expression: "pub fn negate(x) {\n panic && x\n}" snapshot_kind: text --- ----- SOURCE CODE pub fn negate(x) { panic && x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function negate(x) { return (() => { throw makeError( "panic", FILEPATH, "my/mod", 2, "negate", "`panic` expression evaluated.", {} ) })() && x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bools__binop_panic_right.snap ================================================ --- source: compiler-core/src/javascript/tests/bools.rs assertion_line: 162 expression: "pub fn negate(x) {\n x && panic\n}" snapshot_kind: text --- ----- SOURCE CODE pub fn negate(x) { x && panic } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function negate(x) { return x && (() => { throw makeError( "panic", FILEPATH, "my/mod", 2, "negate", "`panic` expression evaluated.", {} ) })(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bools__binop_todo_left.snap ================================================ --- source: compiler-core/src/javascript/tests/bools.rs assertion_line: 189 expression: "pub fn negate(x) {\n todo && x\n}" snapshot_kind: text --- ----- SOURCE CODE pub fn negate(x) { todo && x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function negate(x) { return (() => { throw makeError( "todo", FILEPATH, "my/mod", 2, "negate", "`todo` expression evaluated. This code has not yet been implemented.", {} ) })() && x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bools__binop_todo_right.snap ================================================ --- source: compiler-core/src/javascript/tests/bools.rs assertion_line: 180 expression: "pub fn negate(x) {\n x && todo\n}" snapshot_kind: text --- ----- SOURCE CODE pub fn negate(x) { x && todo } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function negate(x) { return x && (() => { throw makeError( "todo", FILEPATH, "my/mod", 2, "negate", "`todo` expression evaluated. This code has not yet been implemented.", {} ) })(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bools__case.snap ================================================ --- source: compiler-core/src/javascript/tests/bools.rs assertion_line: 114 expression: "\npub fn go(a) {\n case a {\n True -> 1\n False -> 0\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(a) { case a { True -> 1 False -> 0 } } ----- COMPILED JAVASCRIPT export function go(a) { if (a) { return 1; } else { return 0; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bools__constants.snap ================================================ --- source: compiler-core/src/javascript/tests/bools.rs assertion_line: 18 expression: "\npub const a = True\npub const b = False\npub const c = Nil\n" snapshot_kind: text --- ----- SOURCE CODE pub const a = True pub const b = False pub const c = Nil ----- COMPILED JAVASCRIPT export const a = true; export const b = false; export const c = undefined; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bools__constants_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/bools.rs expression: "\npub const a = True\npub const b = False\npub const c = Nil\n" --- ----- SOURCE CODE pub const a = True pub const b = False pub const c = Nil ----- TYPESCRIPT DEFINITIONS export const a: boolean; export const b: boolean; export const c: undefined; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bools__equality.snap ================================================ --- source: compiler-core/src/javascript/tests/bools.rs assertion_line: 95 expression: "\npub fn go(a, b) {\n a == True\n a != True\n a == False\n a != False\n a == a\n a != a\n b == Nil\n b != Nil\n b == b\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(a, b) { a == True a != True a == False a != False a == a a != a b == Nil b != Nil b == b } ----- COMPILED JAVASCRIPT export function go(a, b) { a === true; a !== true; a === false; a !== false; a === a; a !== a; b === undefined; b !== undefined; return b === b; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bools__expressions.snap ================================================ --- source: compiler-core/src/javascript/tests/bools.rs assertion_line: 5 expression: "\npub fn go() {\n True\n False\n Nil\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { True False Nil } ----- COMPILED JAVASCRIPT export function go() { true; false; return undefined; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bools__negate_panic.snap ================================================ --- source: compiler-core/src/javascript/tests/bools.rs assertion_line: 198 expression: "pub fn negate(x) {\n !panic\n}" snapshot_kind: text --- ----- SOURCE CODE pub fn negate(x) { !panic } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function negate(x) { let _block; throw makeError( "panic", FILEPATH, "my/mod", 2, "negate", "`panic` expression evaluated.", {} ) return !_block; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bools__negate_todo.snap ================================================ --- source: compiler-core/src/javascript/tests/bools.rs assertion_line: 207 expression: "pub fn negate(x) {\n !todo\n}" snapshot_kind: text --- ----- SOURCE CODE pub fn negate(x) { !todo } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function negate(x) { let _block; throw makeError( "todo", FILEPATH, "my/mod", 2, "negate", "`todo` expression evaluated. This code has not yet been implemented.", {} ) return !_block; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bools__negation.snap ================================================ --- source: compiler-core/src/javascript/tests/bools.rs expression: "pub fn negate(x) {\n !x\n}" --- ----- SOURCE CODE pub fn negate(x) { !x } ----- COMPILED JAVASCRIPT export function negate(x) { return !x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bools__negation_block.snap ================================================ --- source: compiler-core/src/javascript/tests/bools.rs expression: "pub fn negate(x) {\n !{\n 123\n x\n }\n}" --- ----- SOURCE CODE pub fn negate(x) { !{ 123 x } } ----- COMPILED JAVASCRIPT export function negate(x) { let _block; { 123; _block = x; } return !_block; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bools__nil_case.snap ================================================ --- source: compiler-core/src/javascript/tests/bools.rs assertion_line: 128 expression: "\npub fn go(a) {\n case a {\n Nil -> 0\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(a) { case a { Nil -> 0 } } ----- COMPILED JAVASCRIPT export function go(a) { return 0; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bools__operators.snap ================================================ --- source: compiler-core/src/javascript/tests/bools.rs assertion_line: 40 expression: "\npub fn go() {\n True && True\n False || False\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { True && True False || False } ----- COMPILED JAVASCRIPT export function go() { true && true; return false || false; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bools__shadowed_bools_and_nil.snap ================================================ --- source: compiler-core/src/javascript/tests/bools.rs expression: "\npub type True { True False Nil }\npub fn go(x, y) {\n let assert True = x\n let assert False = x\n let assert Nil = y\n}\n" --- ----- SOURCE CODE pub type True { True False Nil } pub fn go(x, y) { let assert True = x let assert False = x let assert Nil = y } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType, makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export class True extends $CustomType {} export const True$True = () => new True(); export const True$isTrue = (value) => value instanceof True; export class False extends $CustomType {} export const True$False = () => new False(); export const True$isFalse = (value) => value instanceof False; export class Nil extends $CustomType {} export const True$Nil = () => new Nil(); export const True$isNil = (value) => value instanceof Nil; export function go(x, y) { if (!(x instanceof True)) { throw makeError( "let_assert", FILEPATH, "my/mod", 4, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 54, end: 73, pattern_start: 65, pattern_end: 69 } ) } throw makeError( "let_assert", FILEPATH, "my/mod", 5, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 76, end: 96, pattern_start: 87, pattern_end: 92 } ) if (!(y instanceof Nil)) { throw makeError( "let_assert", FILEPATH, "my/mod", 6, "go", "Pattern match failed, no pattern matched the value.", { value: y, start: 99, end: 117, pattern_start: 110, pattern_end: 113 } ) } return y; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bools__shadowed_bools_and_nil_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/bools.rs assertion_line: 81 expression: "\npub type True { True False Nil }\npub fn go(x, y) {\n let assert True = x\n let assert False = x\n let assert Nil = y\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub type True { True False Nil } pub fn go(x, y) { let assert True = x let assert False = x let assert Nil = y } ----- TYPESCRIPT DEFINITIONS import type * as _ from "../gleam.d.mts"; export class True extends _.CustomType {} export function True$True(): True$; export function True$isTrue(value: any): value is True$; export class False extends _.CustomType {} export function True$False(): True$; export function True$isFalse(value: any): value is True$; export class Nil extends _.CustomType {} export function True$Nil(): True$; export function True$isNil(value: any): value is True$; export type True$ = True | False | Nil; export function go(x: True$, y: True$): True$; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__assignment.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs assertion_line: 152 expression: "\npub fn go(x) {\n let y = case x {\n True -> 1\n _ -> 0\n }\n y\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { let y = case x { True -> 1 _ -> 0 } y } ----- COMPILED JAVASCRIPT export function go(x) { let _block; if (x) { _block = 1; } else { _block = 0; } let y = _block; return y; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__called_case.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs assertion_line: 212 expression: "\npub fn go(x, y) {\n case x {\n 0 -> y\n _ -> y\n }()\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x, y) { case x { 0 -> y _ -> y }() } ----- COMPILED JAVASCRIPT export function go(x, y) { let _block; if (x === 0) { _block = y; } else { _block = y; } return _block(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_branches_guards_are_wrapped_in_parentheses.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\npub fn anything() -> a {\n case [] {\n [a] if False || True -> a\n _ -> anything()\n }\n}\n" --- ----- SOURCE CODE pub fn anything() -> a { case [] { [a] if False || True -> a _ -> anything() } } ----- COMPILED JAVASCRIPT import { toList, Empty as $Empty } from "../gleam.mjs"; export function anything() { while (true) { let $ = toList([]); if (!($ instanceof $Empty)) { let $1 = $.tail; if ($1 instanceof $Empty && false || true) { let a = $.head; return a; } } } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_building_list_matched_by_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "pub fn go(x) {\n case x {\n [] -> []\n [a, b] -> [a, b]\n [1, ..rest] -> [1, ..rest]\n _ -> x\n }\n}" --- ----- SOURCE CODE pub fn go(x) { case x { [] -> [] [a, b] -> [a, b] [1, ..rest] -> [1, ..rest] _ -> x } } ----- COMPILED JAVASCRIPT import { Empty as $Empty } from "../gleam.mjs"; export function go(x) { if (x instanceof $Empty) { return x; } else { let $ = x.tail; if ($ instanceof $Empty) { let $1 = x.head; if ($1 === 1) { return x; } else { return x; } } else { let $1 = $.tail; if ($1 instanceof $Empty) { return x; } else { let $2 = x.head; if ($2 === 1) { return x; } else { return x; } } } } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_building_matched_no_variant_record.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\npub fn go(x) {\n case x {\n Ok(Nil) -> Ok(Nil)\n _ -> Error(Nil)\n }\n}" --- ----- SOURCE CODE pub fn go(x) { case x { Ok(Nil) -> Ok(Nil) _ -> Error(Nil) } } ----- COMPILED JAVASCRIPT import { Ok, Error } from "../gleam.mjs"; export function go(x) { if (x instanceof Ok) { return x; } else { return new Error(undefined); } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_building_matched_no_variant_record_2.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\nimport gleam\n\npub fn go(x) {\n case x {\n Ok(gleam.Nil) -> Ok(Nil)\n _ -> Error(Nil)\n }\n}" --- ----- SOURCE CODE import gleam pub fn go(x) { case x { Ok(gleam.Nil) -> Ok(Nil) _ -> Error(Nil) } } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; import { Ok, Error } from "../gleam.mjs"; export function go(x) { if (x instanceof Ok) { return x; } else { return new Error(undefined); } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_building_matched_no_variant_record_3.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\nimport gleam\n\npub fn go(x) {\n case x {\n Ok(Nil) -> Ok(gleam.Nil)\n _ -> Error(Nil)\n }\n}" --- ----- SOURCE CODE import gleam pub fn go(x) { case x { Ok(Nil) -> Ok(gleam.Nil) _ -> Error(Nil) } } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; import { Ok, Error } from "../gleam.mjs"; export function go(x) { if (x instanceof Ok) { return x; } else { return new Error(undefined); } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_building_matched_no_variant_record_4.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\nimport gleam\n\npub fn go(x) {\n case x {\n Ok(gleam.Nil) -> Ok(gleam.Nil)\n _ -> Error(Nil)\n }\n}" --- ----- SOURCE CODE import gleam pub fn go(x) { case x { Ok(gleam.Nil) -> Ok(gleam.Nil) _ -> Error(Nil) } } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; import { Ok, Error } from "../gleam.mjs"; export function go(x) { if (x instanceof Ok) { return x; } else { return new Error(undefined); } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_building_matched_string_1.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\nimport gleam\n\npub fn go(x) {\n case x {\n \"a\" <> rest -> \"a\" <> rest\n _ -> \"\"\n }\n}" --- ----- SOURCE CODE import gleam pub fn go(x) { case x { "a" <> rest -> "a" <> rest _ -> "" } } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; export function go(x) { if (x.startsWith("a")) { return x; } else { return ""; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_building_matched_string_2.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\nimport gleam\n\npub fn go(x) {\n case x {\n \"a\" as a <> rest -> a <> rest\n _ -> \"\"\n }\n}" --- ----- SOURCE CODE import gleam pub fn go(x) { case x { "a" as a <> rest -> a <> rest _ -> "" } } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; export function go(x) { if (x.startsWith("a")) { return x; } else { return ""; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_building_matched_value_alias.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\nimport gleam\n\npub fn go(x) {\n case x {\n Ok(_) as a -> a\n Error(Nil) -> Error(Nil)\n }\n}" --- ----- SOURCE CODE import gleam pub fn go(x) { case x { Ok(_) as a -> a Error(Nil) -> Error(Nil) } } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; import { Ok } from "../gleam.mjs"; export function go(x) { if (x instanceof Ok) { return x; } else { return x; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_building_matched_value_alias_2.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\nimport gleam\n\npub fn go(x) {\n case x {\n Ok(1) as a -> Ok(1)\n Ok(_) -> Ok(2)\n Error(Nil) -> Error(Nil)\n }\n}" --- ----- SOURCE CODE import gleam pub fn go(x) { case x { Ok(1) as a -> Ok(1) Ok(_) -> Ok(2) Error(Nil) -> Error(Nil) } } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; import { Ok } from "../gleam.mjs"; export function go(x) { if (x instanceof Ok) { let $ = x[0]; if ($ === 1) { return x; } else { return new Ok(2); } } else { return x; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_building_matched_value_alias_3.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\nimport gleam\n\npub fn go(x) {\n case x {\n Ok(1 as a) -> Ok(a)\n Ok(_) -> Ok(2)\n Error(Nil) -> Error(Nil)\n }\n}" --- ----- SOURCE CODE import gleam pub fn go(x) { case x { Ok(1 as a) -> Ok(a) Ok(_) -> Ok(2) Error(Nil) -> Error(Nil) } } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; import { Ok } from "../gleam.mjs"; export function go(x) { if (x instanceof Ok) { let $ = x[0]; if ($ === 1) { return x; } else { return new Ok(2); } } else { return x; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_building_matched_value_wrapped_in_block.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\nimport gleam\n\npub fn go(x) {\n case x {\n 1 -> { 1 }\n _ -> 2\n }\n}" --- ----- SOURCE CODE import gleam pub fn go(x) { case x { 1 -> { 1 } _ -> 2 } } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; export function go(x) { if (x === 1) { return x; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_building_record_matched_by_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "pub fn go(x) {\n case x {\n Ok(1) -> Ok(1)\n Ok(n) -> Ok(n)\n Error(_) -> Error(Nil)\n }\n}" --- ----- SOURCE CODE pub fn go(x) { case x { Ok(1) -> Ok(1) Ok(n) -> Ok(n) Error(_) -> Error(Nil) } } ----- COMPILED JAVASCRIPT import { Ok, Error } from "../gleam.mjs"; export function go(x) { if (x instanceof Ok) { let $ = x[0]; if ($ === 1) { return x; } else { return x; } } else { return new Error(undefined); } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_building_record_with_labels_matched_by_pattern_1.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\npub type Wibble {\n Wibble(int: Int, string: String)\n Wobble(Int)\n}\n\npub fn go(x) {\n case x {\n Wibble(1, s) -> Wibble(1, s)\n _ -> Wobble(1)\n }\n}" --- ----- SOURCE CODE pub type Wibble { Wibble(int: Int, string: String) Wobble(Int) } pub fn go(x) { case x { Wibble(1, s) -> Wibble(1, s) _ -> Wobble(1) } } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Wibble extends $CustomType { constructor(int, string) { super(); this.int = int; this.string = string; } } export const Wibble$Wibble = (int, string) => new Wibble(int, string); export const Wibble$isWibble = (value) => value instanceof Wibble; export const Wibble$Wibble$int = (value) => value.int; export const Wibble$Wibble$0 = (value) => value.int; export const Wibble$Wibble$string = (value) => value.string; export const Wibble$Wibble$1 = (value) => value.string; export class Wobble extends $CustomType { constructor($0) { super(); this[0] = $0; } } export const Wibble$Wobble = ($0) => new Wobble($0); export const Wibble$isWobble = (value) => value instanceof Wobble; export const Wibble$Wobble$0 = (value) => value[0]; export function go(x) { if (x instanceof Wibble) { let $ = x.int; if ($ === 1) { return x; } else { return new Wobble(1); } } else { return new Wobble(1); } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_building_record_with_labels_matched_by_pattern_2.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\npub type Wibble {\n Wibble(int: Int, string: String)\n Wobble(Int)\n}\n\npub fn go(x) {\n case x {\n Wibble(string:, int:) -> Wibble(string:, int:)\n _ -> Wobble(1)\n }\n}" --- ----- SOURCE CODE pub type Wibble { Wibble(int: Int, string: String) Wobble(Int) } pub fn go(x) { case x { Wibble(string:, int:) -> Wibble(string:, int:) _ -> Wobble(1) } } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Wibble extends $CustomType { constructor(int, string) { super(); this.int = int; this.string = string; } } export const Wibble$Wibble = (int, string) => new Wibble(int, string); export const Wibble$isWibble = (value) => value instanceof Wibble; export const Wibble$Wibble$int = (value) => value.int; export const Wibble$Wibble$0 = (value) => value.int; export const Wibble$Wibble$string = (value) => value.string; export const Wibble$Wibble$1 = (value) => value.string; export class Wobble extends $CustomType { constructor($0) { super(); this[0] = $0; } } export const Wibble$Wobble = ($0) => new Wobble($0); export const Wibble$isWobble = (value) => value instanceof Wobble; export const Wibble$Wobble$0 = (value) => value[0]; export function go(x) { if (x instanceof Wibble) { return x; } else { return new Wobble(1); } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_building_record_with_labels_matched_by_pattern_3.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\npub type Wibble {\n Wibble(int: Int, string: String)\n Wobble(Int)\n}\n\npub fn go(x) {\n case x {\n // This should not be optimised away!\n Wibble(string:, int:) -> Wibble(string:, int: 1)\n _ -> Wobble(1)\n }\n}" --- ----- SOURCE CODE pub type Wibble { Wibble(int: Int, string: String) Wobble(Int) } pub fn go(x) { case x { // This should not be optimised away! Wibble(string:, int:) -> Wibble(string:, int: 1) _ -> Wobble(1) } } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Wibble extends $CustomType { constructor(int, string) { super(); this.int = int; this.string = string; } } export const Wibble$Wibble = (int, string) => new Wibble(int, string); export const Wibble$isWibble = (value) => value instanceof Wibble; export const Wibble$Wibble$int = (value) => value.int; export const Wibble$Wibble$0 = (value) => value.int; export const Wibble$Wibble$string = (value) => value.string; export const Wibble$Wibble$1 = (value) => value.string; export class Wobble extends $CustomType { constructor($0) { super(); this[0] = $0; } } export const Wibble$Wobble = ($0) => new Wobble($0); export const Wibble$isWobble = (value) => value instanceof Wobble; export const Wibble$Wobble$0 = (value) => value[0]; export function go(x) { if (x instanceof Wibble) { let int = x.int; let string = x.string; return new Wibble(1, string); } else { return new Wobble(1); } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_building_record_with_labels_matched_by_pattern_4.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\npub type Wibble {\n Wibble(int: Int, string: String)\n Wobble(Int)\n}\n\npub fn go(x) {\n case x {\n Wibble(string:, int:) -> Wibble(int:, string:)\n _ -> Wobble(1)\n }\n}" --- ----- SOURCE CODE pub type Wibble { Wibble(int: Int, string: String) Wobble(Int) } pub fn go(x) { case x { Wibble(string:, int:) -> Wibble(int:, string:) _ -> Wobble(1) } } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Wibble extends $CustomType { constructor(int, string) { super(); this.int = int; this.string = string; } } export const Wibble$Wibble = (int, string) => new Wibble(int, string); export const Wibble$isWibble = (value) => value instanceof Wibble; export const Wibble$Wibble$int = (value) => value.int; export const Wibble$Wibble$0 = (value) => value.int; export const Wibble$Wibble$string = (value) => value.string; export const Wibble$Wibble$1 = (value) => value.string; export class Wobble extends $CustomType { constructor($0) { super(); this[0] = $0; } } export const Wibble$Wobble = ($0) => new Wobble($0); export const Wibble$isWobble = (value) => value instanceof Wobble; export const Wibble$Wobble$0 = (value) => value[0]; export function go(x) { if (x instanceof Wibble) { return x; } else { return new Wobble(1); } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_building_record_with_labels_matched_by_pattern_5.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\npub type Wibble {\n Wibble(int: Int, string: String)\n Wobble(Int)\n}\n\npub fn go(x) {\n case x {\n Wibble(string:, int: 1) -> Wibble(1, string:)\n _ -> Wobble(1)\n }\n}" --- ----- SOURCE CODE pub type Wibble { Wibble(int: Int, string: String) Wobble(Int) } pub fn go(x) { case x { Wibble(string:, int: 1) -> Wibble(1, string:) _ -> Wobble(1) } } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Wibble extends $CustomType { constructor(int, string) { super(); this.int = int; this.string = string; } } export const Wibble$Wibble = (int, string) => new Wibble(int, string); export const Wibble$isWibble = (value) => value instanceof Wibble; export const Wibble$Wibble$int = (value) => value.int; export const Wibble$Wibble$0 = (value) => value.int; export const Wibble$Wibble$string = (value) => value.string; export const Wibble$Wibble$1 = (value) => value.string; export class Wobble extends $CustomType { constructor($0) { super(); this[0] = $0; } } export const Wibble$Wobble = ($0) => new Wobble($0); export const Wibble$isWobble = (value) => value instanceof Wobble; export const Wibble$Wobble$0 = (value) => value[0]; export function go(x) { if (x instanceof Wibble) { let $ = x.int; if ($ === 1) { return x; } else { return new Wobble(1); } } else { return new Wobble(1); } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_building_record_with_labels_matched_by_pattern_6.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\npub type Wibble {\n Wibble(int: Int, string: String)\n Wobble(Int)\n}\n\npub fn go(x) {\n case x {\n Wibble(1, string:) -> Wibble(string:, int: 1)\n _ -> Wobble(1)\n }\n}" --- ----- SOURCE CODE pub type Wibble { Wibble(int: Int, string: String) Wobble(Int) } pub fn go(x) { case x { Wibble(1, string:) -> Wibble(string:, int: 1) _ -> Wobble(1) } } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Wibble extends $CustomType { constructor(int, string) { super(); this.int = int; this.string = string; } } export const Wibble$Wibble = (int, string) => new Wibble(int, string); export const Wibble$isWibble = (value) => value instanceof Wibble; export const Wibble$Wibble$int = (value) => value.int; export const Wibble$Wibble$0 = (value) => value.int; export const Wibble$Wibble$string = (value) => value.string; export const Wibble$Wibble$1 = (value) => value.string; export class Wobble extends $CustomType { constructor($0) { super(); this[0] = $0; } } export const Wibble$Wobble = ($0) => new Wobble($0); export const Wibble$isWobble = (value) => value instanceof Wobble; export const Wibble$Wobble$0 = (value) => value[0]; export function go(x) { if (x instanceof Wibble) { let $ = x.int; if ($ === 1) { return x; } else { return new Wobble(1); } } else { return new Wobble(1); } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_building_record_with_select_matched_by_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\nimport gleam\n\npub fn go(x) {\n case x {\n Ok(1) -> gleam.Ok(1)\n _ -> Error(Nil)\n }\n}" --- ----- SOURCE CODE import gleam pub fn go(x) { case x { Ok(1) -> gleam.Ok(1) _ -> Error(Nil) } } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; import { Ok, Error } from "../gleam.mjs"; export function go(x) { if (x instanceof Ok) { let $ = x[0]; if ($ === 1) { return x; } else { return new Error(undefined); } } else { return new Error(undefined); } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_building_record_with_select_matched_by_pattern_2.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\nimport gleam\n\npub fn go(x) {\n case x {\n gleam.Ok(1) -> gleam.Ok(1)\n _ -> Error(Nil)\n }\n}" --- ----- SOURCE CODE import gleam pub fn go(x) { case x { gleam.Ok(1) -> gleam.Ok(1) _ -> Error(Nil) } } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; import { Error } from "../gleam.mjs"; export function go(x) { if (x instanceof $gleam.Ok) { let $ = x[0]; if ($ === 1) { return x; } else { return new Error(undefined); } } else { return new Error(undefined); } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_building_record_with_select_matched_by_pattern_3.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\nimport gleam\n\npub fn go(x) {\n case x {\n gleam.Ok(1) -> Ok(1)\n _ -> Error(Nil)\n }\n}" --- ----- SOURCE CODE import gleam pub fn go(x) { case x { gleam.Ok(1) -> Ok(1) _ -> Error(Nil) } } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; import { Error } from "../gleam.mjs"; export function go(x) { if (x instanceof $gleam.Ok) { let $ = x[0]; if ($ === 1) { return x; } else { return new Error(undefined); } } else { return new Error(undefined); } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_building_simple_value_matched_by_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "pub fn go(x) {\n case x {\n 1 -> 2\n n -> n\n }\n}" --- ----- SOURCE CODE pub fn go(x) { case x { 1 -> 2 n -> n } } ----- COMPILED JAVASCRIPT export function go(x) { if (x === 1) { return 2; } else { return x; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_local_var_in_tuple.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\npub fn go(x, y) {\n let z = False\n case True {\n x if #(x, z) == #(True, False) -> x\n _ -> False\n }\n}\n" --- ----- SOURCE CODE pub fn go(x, y) { let z = False case True { x if #(x, z) == #(True, False) -> x _ -> False } } ----- COMPILED JAVASCRIPT import { isEqual } from "../gleam.mjs"; export function go(x, y) { let z = false; let $ = true; let x$1 = $; if (isEqual([x$1, z], [true, false])) { return $; } else { return false; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_on_error.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\nfn a_result() { Error(1) }\n\npub fn main() {\n case a_result() {\n Error(_) -> 1\n _ -> 2\n }\n}" --- ----- SOURCE CODE fn a_result() { Error(1) } pub fn main() { case a_result() { Error(_) -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT import { Error } from "../gleam.mjs"; function a_result() { return new Error(1); } export function main() { let $ = a_result(); if ($ instanceof Error) { return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_with_multiple_subjects_building_list_matched_by_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "pub fn go(n, x) {\n case n, x {\n 1, [] -> []\n _, [a, b] -> [a, b]\n 3, [1, ..rest] -> [1, ..rest]\n _, _ -> x\n }\n}" --- ----- SOURCE CODE pub fn go(n, x) { case n, x { 1, [] -> [] _, [a, b] -> [a, b] 3, [1, ..rest] -> [1, ..rest] _, _ -> x } } ----- COMPILED JAVASCRIPT import { Empty as $Empty } from "../gleam.mjs"; export function go(n, x) { if (x instanceof $Empty) { if (n === 1) { return x; } else { return x; } } else { let $ = x.tail; if ($ instanceof $Empty) { if (n === 3) { let $1 = x.head; if ($1 === 1) { return x; } else { return x; } } else { return x; } } else { let $1 = $.tail; if ($1 instanceof $Empty) { return x; } else if (n === 3) { let $2 = x.head; if ($2 === 1) { return x; } else { return x; } } else { return x; } } } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_with_multiple_subjects_building_record_matched_by_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "pub fn go(x, y) {\n case x, y {\n Ok(1), Error(_) -> Ok(1)\n Error(_), Ok(n) -> Ok(n)\n _, _ -> Error(Nil)\n }\n}" --- ----- SOURCE CODE pub fn go(x, y) { case x, y { Ok(1), Error(_) -> Ok(1) Error(_), Ok(n) -> Ok(n) _, _ -> Error(Nil) } } ----- COMPILED JAVASCRIPT import { Ok, Error } from "../gleam.mjs"; export function go(x, y) { if (x instanceof Ok) { if (y instanceof Error) { let $ = x[0]; if ($ === 1) { return x; } else { return new Error(undefined); } } else { return new Error(undefined); } } else if (y instanceof Ok) { return y; } else { return new Error(undefined); } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_with_multiple_subjects_building_same_value_as_two_subjects_one_is_picked.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\nimport gleam\n\npub fn go(x, y) {\n case x, y {\n gleam.Ok(1), Ok(1) -> Ok(1)\n _, Error(Nil) -> Error(Nil)\n _, _ -> Error(Nil)\n }\n}" --- ----- SOURCE CODE import gleam pub fn go(x, y) { case x, y { gleam.Ok(1), Ok(1) -> Ok(1) _, Error(Nil) -> Error(Nil) _, _ -> Error(Nil) } } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; import { Ok, Error } from "../gleam.mjs"; export function go(x, y) { if (y instanceof Ok) { if (x instanceof $gleam.Ok) { let $ = y[0]; if ($ === 1) { let $1 = x[0]; if ($1 === 1) { return x; } else { return new Error(undefined); } } else { return new Error(undefined); } } else { return new Error(undefined); } } else { return y; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_with_multiple_subjects_building_simple_value_matched_by_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "pub fn go(x) {\n case x, x + 1 {\n 1, _ -> 2\n _, n -> n\n }\n}" --- ----- SOURCE CODE pub fn go(x) { case x, x + 1 { 1, _ -> 2 _, n -> n } } ----- COMPILED JAVASCRIPT export function go(x) { let $ = x + 1; if (x === 1) { return 2; } else { return $; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__deeply_nested_string_prefix_assignment.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\ntype Wibble {\n Wibble(Wobble)\n}\ntype Wobble {\n Wobble(wabble: Wabble)\n}\ntype Wabble {\n Wabble(tuple: #(Int, String))\n}\n\npub fn main() {\n let tmp = Wibble(Wobble(Wabble(#(42, \"wibble\"))))\n case tmp {\n Wibble(Wobble(Wabble(#(_int, \"w\" as wibble <> rest)))) -> wibble <> rest\n _ -> panic\n }\n}\n" --- ----- SOURCE CODE type Wibble { Wibble(Wobble) } type Wobble { Wobble(wabble: Wabble) } type Wabble { Wabble(tuple: #(Int, String)) } pub fn main() { let tmp = Wibble(Wobble(Wabble(#(42, "wibble")))) case tmp { Wibble(Wobble(Wabble(#(_int, "w" as wibble <> rest)))) -> wibble <> rest _ -> panic } } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType, makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; class Wibble extends $CustomType { constructor($0) { super(); this[0] = $0; } } class Wobble extends $CustomType { constructor(wabble) { super(); this.wabble = wabble; } } class Wabble extends $CustomType { constructor(tuple) { super(); this.tuple = tuple; } } export function main() { let tmp = new Wibble(new Wobble(new Wabble([42, "wibble"]))); let $ = tmp[0].wabble.tuple[1]; if ($.startsWith("w")) { let wibble = "w"; let rest = $.slice(1); return wibble + rest; } else { throw makeError( "panic", FILEPATH, "my/mod", 16, "main", "`panic` expression evaluated.", {} ) } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__directly_matching_case_subject.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\npub fn go() {\n let x = \"ABC\"\n case True {\n True -> {\n let x = 79\n 0\n }\n False -> {\n let x = True\n 0\n }\n }\n x\n}" --- ----- SOURCE CODE pub fn go() { let x = "ABC" case True { True -> { let x = 79 0 } False -> { let x = True 0 } } x } ----- COMPILED JAVASCRIPT export function go() { let x = "ABC"; let $ = true; let x$1 = 79; 0 return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__duplicate_name_for_variables_used_in_guards.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\npub fn wibble() {\n let a = case 1337 {\n n if n == 1347 -> Nil\n _ -> Nil\n }\n let b = case 1337 {\n n -> Nil\n }\n}" --- ----- SOURCE CODE pub fn wibble() { let a = case 1337 { n if n == 1347 -> Nil _ -> Nil } let b = case 1337 { n -> Nil } } ----- COMPILED JAVASCRIPT export function wibble() { let _block; let $ = 1337; { let n = $; if (n === 1347) { _block = undefined; } else { _block = undefined; } } let a = _block; let _block$1; let $1 = 1337; let n = $1; _block$1 = undefined; let b = _block$1; return b; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__duplicate_name_for_variables_used_in_guards_shadowing_outer_name.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\npub fn wibble() {\n let n = 1\n let a = case 1337 {\n n if n == 1347 -> n\n _ -> n\n }\n let b = case 1337 {\n n -> Nil\n }\n}" --- ----- SOURCE CODE pub fn wibble() { let n = 1 let a = case 1337 { n if n == 1347 -> n _ -> n } let b = case 1337 { n -> Nil } } ----- COMPILED JAVASCRIPT export function wibble() { let n = 1; let _block; let $ = 1337; { let n$1 = $; if (n$1 === 1347) { _block = $; } else { _block = n; } } let a = _block; let _block$1; let $1 = 1337; let n$1 = $1; _block$1 = undefined; let b = _block$1; return b; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__following_todo.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs assertion_line: 81 expression: "\npub fn go(x) {\n case x {\n True -> todo\n _ -> 1\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { True -> todo _ -> 1 } } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { if (x) { throw makeError( "todo", FILEPATH, "my/mod", 4, "go", "`todo` expression evaluated. This code has not yet been implemented.", {} ) } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__guard_variable_only_brought_into_scope_when_needed.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs assertion_line: 34 expression: "\npub fn go(x) {\n case x {\n // We want `a` to be defined before the guard check, and\n // `b` to be defined only if the predicate on a matches!\n [a, b] if a == 1 -> a + b\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { // We want `a` to be defined before the guard check, and // `b` to be defined only if the predicate on a matches! [a, b] if a == 1 -> a + b _ -> 2 } } ----- COMPILED JAVASCRIPT import { Empty as $Empty } from "../gleam.mjs"; export function go(x) { if (x instanceof $Empty) { return 2; } else { let $ = x.tail; if ($ instanceof $Empty) { return 2; } else { let $1 = $.tail; if ($1 instanceof $Empty) { let a = x.head; if (a === 1) { let b = $.head; return a + b; } else { return 2; } } else { return 2; } } } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__guard_variable_only_brought_into_scope_when_needed_1.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\npub fn main() {\n case 1 {\n i if i == 1 -> True\n i if i < 2 -> True\n _ -> False\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case 1 { i if i == 1 -> True i if i < 2 -> True _ -> False } } ----- COMPILED JAVASCRIPT export function main() { let $ = 1; let i = $; if (i === 1) { return true; } else { let i = $; if (i < 2) { return true; } else { return false; } } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__interfering_string_pattern_succeeds_if_succeeding.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\npub fn wibble(bits) {\n case bits {\n <<\"aaa\", 0, _:bits>> -> 1\n // If the first one succeeds, so will the second check, so it won't be\n // performed twice inside the first if branch!\n <<\"aaa\", 1, _:bits>> -> 2\n _ -> 3\n }\n}" --- ----- SOURCE CODE pub fn wibble(bits) { case bits { <<"aaa", 0, _:bits>> -> 1 // If the first one succeeds, so will the second check, so it won't be // performed twice inside the first if branch! <<"aaa", 1, _:bits>> -> 2 _ -> 3 } } ----- COMPILED JAVASCRIPT export function wibble(bits) { if ( bits.bitSize >= 24 && bits.byteAt(0) === 97 && bits.byteAt(1) === 97 && bits.byteAt(2) === 97 && bits.bitSize >= 32 ) { if (bits.byteAt(3) === 0) { return 1; } else if (bits.byteAt(3) === 1) { return 2; } else { return 3; } } else { return 3; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__list_with_guard.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\npub fn go(x) {\n case x {\n [] -> 0\n [first, ..] if first < 10 -> first * 2\n [first, ..] -> first\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { [] -> 0 [first, ..] if first < 10 -> first * 2 [first, ..] -> first } } ----- COMPILED JAVASCRIPT import { Empty as $Empty } from "../gleam.mjs"; export function go(x) { if (x instanceof $Empty) { return 0; } else { let first = x.head; if (first < 10) { return first * 2; } else { let first = x.head; return first; } } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__list_with_guard_no_binding.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\npub fn go(x) {\n case x {\n [] -> 0\n [first, ..] if 1 < 10 -> first * 2\n [first, ..] -> first\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { [] -> 0 [first, ..] if 1 < 10 -> first * 2 [first, ..] -> first } } ----- COMPILED JAVASCRIPT import { Empty as $Empty } from "../gleam.mjs"; export function go(x) { if (x instanceof $Empty) { return 0; } else if (1 < 10) { let first = x.head; return first * 2; } else { let first = x.head; return first; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__multi_subject_catch_all.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\npub fn go(x, y) {\n case x, y {\n True, True -> 1\n _, _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn go(x, y) { case x, y { True, True -> 1 _, _ -> 0 } } ----- COMPILED JAVASCRIPT export function go(x, y) { if (x && y) { return 1; } else { return 0; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__multi_subject_no_catch_all.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs assertion_line: 123 expression: "\npub fn go(x, y) {\n case x, y {\n True, _ -> 1\n _, True -> 2\n False, False -> 0\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x, y) { case x, y { True, _ -> 1 _, True -> 2 False, False -> 0 } } ----- COMPILED JAVASCRIPT export function go(x, y) { if (x) { return 1; } else if (y) { return 2; } else { return 0; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__multi_subject_or.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs assertion_line: 109 expression: "\npub fn go(x, y) {\n case x, y {\n True, _ | _, True -> 1\n _, _ -> 0\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x, y) { case x, y { True, _ | _, True -> 1 _, _ -> 0 } } ----- COMPILED JAVASCRIPT export function go(x, y) { if (x) { return 1; } else if (y) { return 1; } else { return 0; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__multi_subject_subject_assignments.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs assertion_line: 138 expression: "\npub fn go() {\n case True, False {\n True, True -> 1\n _, _ -> 0\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { case True, False { True, True -> 1 _, _ -> 0 } } ----- COMPILED JAVASCRIPT export function go() { let $ = true; let $1 = false; return 0; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__nested_string_prefix_assignment.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\ntype Wibble {\n Wibble(wobble: String)\n}\n\npub fn main() {\n let tmp = Wibble(wobble: \"wibble\")\n case tmp {\n Wibble(wobble: \"w\" as wibble <> rest) -> wibble <> rest\n _ -> panic\n }\n}\n" --- ----- SOURCE CODE type Wibble { Wibble(wobble: String) } pub fn main() { let tmp = Wibble(wobble: "wibble") case tmp { Wibble(wobble: "w" as wibble <> rest) -> wibble <> rest _ -> panic } } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType, makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; class Wibble extends $CustomType { constructor(wobble) { super(); this.wobble = wobble; } } export function main() { let tmp = new Wibble("wibble"); let $ = tmp.wobble; if ($.startsWith("w")) { let wibble = "w"; let rest = $.slice(1); return wibble + rest; } else { throw makeError( "panic", FILEPATH, "my/mod", 10, "main", "`panic` expression evaluated.", {} ) } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__nested_string_prefix_match.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\npub fn main() {\n case Ok([\"a\", \"b c\", \"d\"]) {\n Ok([\"a\", \"b \" <> _, \"d\"]) -> 1\n _ -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case Ok(["a", "b c", "d"]) { Ok(["a", "b " <> _, "d"]) -> 1 _ -> 1 } } ----- COMPILED JAVASCRIPT import { Ok, toList, Empty as $Empty } from "../gleam.mjs"; export function main() { let $ = new Ok(toList(["a", "b c", "d"])); let $1 = $[0]; if ($1 instanceof $Empty) { return 1; } else { let $2 = $1.tail; if ($2 instanceof $Empty) { return 1; } else { let $3 = $2.tail; if ($3 instanceof $Empty) { return 1; } else { let $4 = $3.tail; if ($4 instanceof $Empty) { let $5 = $1.head; if ($5 === "a") { let $6 = $2.head; if ($6.startsWith("b ")) { let $7 = $3.head; if ($7 === "d") { return 1; } else { return 1; } } else { return 1; } } else { return 1; } } else { return 1; } } } } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__nested_string_prefix_match_that_would_crash_on_js.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\npub fn main() {\n case Ok([\"b c\", \"d\"]) {\n Ok([\"b \" <> _, \"d\"]) -> 1\n _ -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case Ok(["b c", "d"]) { Ok(["b " <> _, "d"]) -> 1 _ -> 1 } } ----- COMPILED JAVASCRIPT import { Ok, toList, Empty as $Empty } from "../gleam.mjs"; export function main() { let $ = new Ok(toList(["b c", "d"])); let $1 = $[0]; if ($1 instanceof $Empty) { return 1; } else { let $2 = $1.tail; if ($2 instanceof $Empty) { return 1; } else { let $3 = $2.tail; if ($3 instanceof $Empty) { let $4 = $1.head; if ($4.startsWith("b ")) { let $5 = $2.head; if ($5 === "d") { return 1; } else { return 1; } } else { return 1; } } else { return 1; } } } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__pattern_matching_on_aliased_result_constructor.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\nimport gleam.{Error as E, Ok as O}\n\npub fn go(x) {\n case x {\n E(_) -> 1\n O(_) -> 2\n }\n}\n" --- ----- SOURCE CODE import gleam.{Error as E, Ok as O} pub fn go(x) { case x { E(_) -> 1 O(_) -> 2 } } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; import { Error as E, Ok as O, Ok } from "../gleam.mjs"; export function go(x) { if (x instanceof O) { return 2; } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__pipe.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs assertion_line: 183 expression: "\npub fn go(x, f) {\n case x |> f {\n 0 -> 1\n _ -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x, f) { case x |> f { 0 -> 1 _ -> 2 } } ----- COMPILED JAVASCRIPT export function go(x, f) { let $ = (() => { let _pipe = x; return f(_pipe); })(); if ($ === 0) { return 1; } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__pointless.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs assertion_line: 67 expression: "\npub fn go(x) {\n case x {\n _ -> x\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { _ -> x } } ----- COMPILED JAVASCRIPT export function go(x) { return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__preassign_assignment.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs assertion_line: 167 expression: "\npub fn go(x) {\n let y = case x() {\n True -> 1\n _ -> 0\n }\n y\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { let y = case x() { True -> 1 _ -> 0 } y } ----- COMPILED JAVASCRIPT export function go(x) { let _block; let $ = x(); if ($) { _block = 1; } else { _block = 0; } let y = _block; return y; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__record_update_in_pipeline_in_case_clause.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\npub type Wibble {\n Wibble(wibble: Int, wobble: Int)\n}\n\nfn identity(x) {\n x\n}\n\npub fn go(x) {\n case x {\n Wibble(1, _) -> Wibble(..x, wibble: 4) |> identity\n Wibble(_, 3) -> Wibble(..x, wobble: 10) |> identity\n _ -> panic\n }\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(wibble: Int, wobble: Int) } fn identity(x) { x } pub fn go(x) { case x { Wibble(1, _) -> Wibble(..x, wibble: 4) |> identity Wibble(_, 3) -> Wibble(..x, wobble: 10) |> identity _ -> panic } } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType, makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export class Wibble extends $CustomType { constructor(wibble, wobble) { super(); this.wibble = wibble; this.wobble = wobble; } } export const Wibble$Wibble = (wibble, wobble) => new Wibble(wibble, wobble); export const Wibble$isWibble = (value) => value instanceof Wibble; export const Wibble$Wibble$wibble = (value) => value.wibble; export const Wibble$Wibble$0 = (value) => value.wibble; export const Wibble$Wibble$wobble = (value) => value.wobble; export const Wibble$Wibble$1 = (value) => value.wobble; function identity(x) { return x; } export function go(x) { let $ = x.wibble; if ($ === 1) { let _pipe = new Wibble(4, x.wobble); return identity(_pipe); } else { let $1 = x.wobble; if ($1 === 3) { let _pipe = new Wibble(x.wibble, 10); return identity(_pipe); } else { throw makeError( "panic", FILEPATH, "my/mod", 14, "go", "`panic` expression evaluated.", {} ) } } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__result.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs assertion_line: 197 expression: "\npub fn go(x) {\n case x {\n Ok(_) -> 1\n Error(_) -> 0\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { Ok(_) -> 1 Error(_) -> 0 } } ----- COMPILED JAVASCRIPT import { Ok } from "../gleam.mjs"; export function go(x) { if (x instanceof Ok) { return 1; } else { return 0; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__single_clause_variables.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\npub fn main() {\n let text = \"first defined\"\n case \"defined again\" {\n text -> Nil\n }\n let text = \"a third time\"\n}\n" --- ----- SOURCE CODE pub fn main() { let text = "first defined" case "defined again" { text -> Nil } let text = "a third time" } ----- COMPILED JAVASCRIPT export function main() { let text = "first defined"; let $ = "defined again"; let text$1 = $; undefined let text$2 = "a third time"; return text$2; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__single_clause_variables_assigned.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\npub fn main() {\n let text = \"first defined\"\n let other = case \"defined again\" {\n text -> Nil\n }\n let text = \"a third time\"\n}\n" --- ----- SOURCE CODE pub fn main() { let text = "first defined" let other = case "defined again" { text -> Nil } let text = "a third time" } ----- COMPILED JAVASCRIPT export function main() { let text = "first defined"; let _block; let $ = "defined again"; let text$1 = $; _block = undefined; let other = _block; let text$2 = "a third time"; return text$2; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__slicing_is_handled_properly_with_multiple_branches.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\npub fn main() {\n case \"12345\" {\n \"0\" <> rest -> rest\n \"123\" <> rest -> rest\n _ -> \"\"\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case "12345" { "0" <> rest -> rest "123" <> rest -> rest _ -> "" } } ----- COMPILED JAVASCRIPT export function main() { let $ = "12345"; if ($.startsWith("0")) { let rest = $.slice(1); return rest; } else if ($.startsWith("123")) { let rest = $.slice(3); return rest; } else { return ""; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__string_concatenation_in_clause_guards.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\npub fn main() {\n let wibble = \"wob\"\n case wibble {\n x if x <> \"ble\" == \"wobble\" -> 1\n _ -> 0\n }\n}" --- ----- SOURCE CODE pub fn main() { let wibble = "wob" case wibble { x if x <> "ble" == "wobble" -> 1 _ -> 0 } } ----- COMPILED JAVASCRIPT export function main() { let wibble = "wob"; let x = wibble; if ((x + "ble") === "wobble") { return 1; } else { return 0; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__tuple_and_guard.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs assertion_line: 20 expression: "\npub fn go(x) {\n case #(1, 2) {\n #(1, a) if a == 2 -> 1\n #(_, _) -> 2\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case #(1, 2) { #(1, a) if a == 2 -> 1 #(_, _) -> 2 } } ----- COMPILED JAVASCRIPT export function go(x) { let $ = [1, 2]; let $1 = $[0]; if ($1 === 1) { let a = $[1]; if (a === 2) { return 1; } else { return 2; } } else { return 2; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__var_true.snap ================================================ --- source: compiler-core/src/javascript/tests/case.rs expression: "\nfn true() { True }\npub fn main() {\n let true_ = true()\n assert 0 == case Nil {\n _ if true_ -> 0\n _ -> 1\n }\n}\n" --- ----- SOURCE CODE fn true() { True } pub fn main() { let true_ = true() assert 0 == case Nil { _ if true_ -> 0 _ -> 1 } } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; function true$() { return true; } export function main() { let true_ = true$(); let $ = 0; let _block; let $1 = undefined; { if (true_) { _block = 0; } else { _block = 1; } } let $2 = _block; if (!($ === $2)) { throw makeError( "assert", FILEPATH, "my/mod", 5, "main", "Assertion failed.", { kind: "binary_operator", operator: "==", left: { kind: "literal", value: $, start: 70, end: 71 }, right: { kind: "expression", value: $2, start: 75, end: 130 }, start: 63, end: 130, expression_start: 70 } ) } return undefined; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__alternative_patterns.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "pub fn main(xs) {\n case xs {\n 1 | 2 -> 0\n _ -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(xs) { case xs { 1 | 2 -> 0 _ -> 1 } } ----- COMPILED JAVASCRIPT export function main(xs) { if (xs === 1) { return 0; } else if (xs === 2) { return 0; } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__alternative_patterns_assignment.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "pub fn main(xs) -> Int {\n case xs {\n [x] | [_, x] -> x\n _ -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(xs) -> Int { case xs { [x] | [_, x] -> x _ -> 1 } } ----- COMPILED JAVASCRIPT import { Empty as $Empty } from "../gleam.mjs"; export function main(xs) { if (xs instanceof $Empty) { return 1; } else { let $ = xs.tail; if ($ instanceof $Empty) { let x = xs.head; return x; } else { let $1 = $.tail; if ($1 instanceof $Empty) { let x = $.head; return x; } else { return 1; } } } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__alternative_patterns_guard.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "pub fn main(xs) -> Int {\n case xs {\n [x] | [_, x] if x == 1 -> x\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main(xs) -> Int { case xs { [x] | [_, x] if x == 1 -> x _ -> 0 } } ----- COMPILED JAVASCRIPT import { Empty as $Empty } from "../gleam.mjs"; export function main(xs) { if (xs instanceof $Empty) { return 0; } else { let $ = xs.tail; if ($ instanceof $Empty) { let x = xs.head; if (x === 1) { return x; } else { return 0; } } else { let $1 = $.tail; if ($1 instanceof $Empty) { let x = $.head; if (x === 1) { return x; } else { return 0; } } else { return 0; } } } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__alternative_patterns_list.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "pub fn main(xs) -> Int {\n case xs {\n [1] | [1, 2] -> 0\n _ -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(xs) -> Int { case xs { [1] | [1, 2] -> 0 _ -> 1 } } ----- COMPILED JAVASCRIPT import { Empty as $Empty } from "../gleam.mjs"; export function main(xs) { if (xs instanceof $Empty) { return 1; } else { let $ = xs.tail; if ($ instanceof $Empty) { let $1 = xs.head; if ($1 === 1) { return 0; } else { return 1; } } else { let $1 = $.tail; if ($1 instanceof $Empty) { let $2 = xs.head; if ($2 === 1) { let $3 = $.head; if ($3 === 2) { return 0; } else { return 1; } } else { return 1; } } else { return 1; } } } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__bit_array_referencing_shadowed_variable.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "\npub fn main() {\n let a = 1\n let a = 2\n\n case Nil {\n _ if <> == <<1>> -> False\n _ if <> == <<2>> -> True\n _ -> False\n }\n}\n" --- ----- SOURCE CODE pub fn main() { let a = 1 let a = 2 case Nil { _ if <> == <<1>> -> False _ if <> == <<2>> -> True _ -> False } } ----- COMPILED JAVASCRIPT import { isEqual, toBitArray } from "../gleam.mjs"; export function main() { let a = 1; let a$1 = 2; let $ = undefined; if (isEqual(toBitArray([a$1]), toBitArray([1]))) { return false; } else if (isEqual(toBitArray([a$1]), toBitArray([2]))) { return true; } else { return false; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__bitarray_with_var.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "pub fn main() {\n case 5 {\n z if <> == <> -> Nil\n _ -> Nil\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case 5 { z if <> == <> -> Nil _ -> Nil } } ----- COMPILED JAVASCRIPT import { isEqual, toBitArray } from "../gleam.mjs"; export function main() { let $ = 5; let z = $; if (isEqual(toBitArray([z]), toBitArray([z]))) { return undefined; } else { return undefined; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__constant.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "pub fn main(xs) {\n case xs {\n #(x) if x == 1 -> x\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main(xs) { case xs { #(x) if x == 1 -> x _ -> 0 } } ----- COMPILED JAVASCRIPT export function main(xs) { let x = xs[0]; if (x === 1) { return x; } else { return 0; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__constructor_function_in_guard.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs assertion_line: 524 expression: "pub fn func(x) {\n case [] {\n _ if [] == [ Ok ] -> True\n _ -> False\n }\n}\n " snapshot_kind: text --- ----- SOURCE CODE pub fn func(x) { case [] { _ if [] == [ Ok ] -> True _ -> False } } ----- COMPILED JAVASCRIPT import { Ok, toList, isEqual } from "../gleam.mjs"; export function func(x) { let $ = toList([]); if (isEqual(toList([]), toList([(var0) => { return new Ok(var0); }]))) { return true; } else { return false; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__custom_type_constructor_imported_and_aliased.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "import other_module.{A as B}\npub fn func() {\n case B {\n x if x == B -> True\n _ -> False\n }\n}\n" --- ----- SOURCE CODE import other_module.{A as B} pub fn func() { case B { x if x == B -> True _ -> False } } ----- COMPILED JAVASCRIPT import * as $other_module from "../../package/other_module.mjs"; import { A as B } from "../../package/other_module.mjs"; export function func() { let $ = new B(); let x = $; if (x instanceof B) { return true; } else { return false; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__eq_complex.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "pub fn main(xs, y) {\n case xs {\n #(x) if xs == y -> x\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main(xs, y) { case xs { #(x) if xs == y -> x _ -> 0 } } ----- COMPILED JAVASCRIPT import { isEqual } from "../gleam.mjs"; export function main(xs, y) { if (isEqual(xs, y)) { let x = xs[0]; return x; } else { return 0; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__eq_scalar.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "pub fn main(xs, y: Int) {\n case xs {\n #(x) if x == y -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main(xs, y: Int) { case xs { #(x) if x == y -> 1 _ -> 0 } } ----- COMPILED JAVASCRIPT export function main(xs, y) { let x = xs[0]; if (x === y) { return 1; } else { return 0; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__field_access.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "\n pub type Person {\n Person(username: String, name: String, age: Int)\n }\n pub fn main() {\n let given_name = \"jack\"\n let raiden = Person(\"raiden\", \"jack\", 31)\n case given_name {\n name if name == raiden.name -> \"It's jack\"\n _ -> \"It's not jack\"\n }\n }\n " --- ----- SOURCE CODE pub type Person { Person(username: String, name: String, age: Int) } pub fn main() { let given_name = "jack" let raiden = Person("raiden", "jack", 31) case given_name { name if name == raiden.name -> "It's jack" _ -> "It's not jack" } } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Person extends $CustomType { constructor(username, name, age) { super(); this.username = username; this.name = name; this.age = age; } } export const Person$Person = (username, name, age) => new Person(username, name, age); export const Person$isPerson = (value) => value instanceof Person; export const Person$Person$username = (value) => value.username; export const Person$Person$0 = (value) => value.username; export const Person$Person$name = (value) => value.name; export const Person$Person$1 = (value) => value.name; export const Person$Person$age = (value) => value.age; export const Person$Person$2 = (value) => value.age; export function main() { let given_name = "jack"; let raiden = new Person("raiden", "jack", 31); let name = given_name; if (name === raiden.name) { return "It's jack"; } else { return "It's not jack"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__float_division.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "\npub fn main() {\n case 5.1 /. 0.0 {\n x if x == 5.1 /. 0.0 -> True\n _ -> False\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case 5.1 /. 0.0 { x if x == 5.1 /. 0.0 -> True _ -> False } } ----- COMPILED JAVASCRIPT import { divideFloat } from "../gleam.mjs"; export function main() { let $ = 0.0; let x = $; if (x === (divideFloat(5.1, 0.0))) { return true; } else { return false; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__guard_pattern_does_not_shadow_outer_scope.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "\npub type Option(a) {\n Some(a)\n None\n}\n\npub type Container {\n Container(x: Option(Int))\n}\n\npub fn main() {\n let x: Option(Int) = Some(42)\n case Some(1) {\n Some(x) if x < 0 -> Container(None)\n _ -> {\n Container(x:)\n }\n }\n}\n" --- ----- SOURCE CODE pub type Option(a) { Some(a) None } pub type Container { Container(x: Option(Int)) } pub fn main() { let x: Option(Int) = Some(42) case Some(1) { Some(x) if x < 0 -> Container(None) _ -> { Container(x:) } } } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Some extends $CustomType { constructor($0) { super(); this[0] = $0; } } export const Option$Some = ($0) => new Some($0); export const Option$isSome = (value) => value instanceof Some; export const Option$Some$0 = (value) => value[0]; export class None extends $CustomType {} export const Option$None = () => new None(); export const Option$isNone = (value) => value instanceof None; export class Container extends $CustomType { constructor(x) { super(); this.x = x; } } export const Container$Container = (x) => new Container(x); export const Container$isContainer = (value) => value instanceof Container; export const Container$Container$x = (value) => value.x; export const Container$Container$0 = (value) => value.x; export function main() { let x = new Some(42); let $ = new Some(1); let x$1 = $[0]; if (x$1 < 0) { return new Container(new None()); } else { return new Container(x); } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__imported_aliased_ok.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "import gleam.{Ok as Y}\npub type X {\n Ok\n}\npub fn func() {\n case Y {\n y if y == Y -> True\n _ -> False\n }\n}\n" --- ----- SOURCE CODE import gleam.{Ok as Y} pub type X { Ok } pub fn func() { case Y { y if y == Y -> True _ -> False } } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; import { Ok as Y, CustomType as $CustomType, isEqual } from "../gleam.mjs"; export class Ok extends $CustomType {} export const X$Ok = () => new Ok(); export const X$isOk = (value) => value instanceof Ok; export function func() { let $ = (var0) => { return new Y(var0); }; let y = $; if (isEqual(y, (var0) => { return new Y(var0); })) { return true; } else { return false; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__imported_ok.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "import gleam\npub type X {\n Ok\n}\npub fn func(x) {\n case gleam.Ok {\n _ if [] == [ gleam.Ok ] -> True\n _ -> False\n }\n}\n" --- ----- SOURCE CODE import gleam pub type X { Ok } pub fn func(x) { case gleam.Ok { _ if [] == [ gleam.Ok ] -> True _ -> False } } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; import { Ok, toList, CustomType as $CustomType, isEqual } from "../gleam.mjs"; export class Ok extends $CustomType {} export const X$Ok = () => new Ok(); export const X$isOk = (value) => value instanceof Ok; export function func(x) { let $ = (var0) => { return new $gleam.Ok(var0); }; if (isEqual(toList([]), toList([(var0) => { return new Ok(var0); }]))) { return true; } else { return false; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__int_division.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "\npub fn main() {\n case 5 / 2 {\n x if x == 5 / 2 -> True\n _ -> False\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case 5 / 2 { x if x == 5 / 2 -> True _ -> False } } ----- COMPILED JAVASCRIPT import { divideInt } from "../gleam.mjs"; export function main() { let $ = globalThis.Math.trunc(5 / 2); let x = $; if (x === (divideInt(5, 2))) { return true; } else { return false; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__int_remainder.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "\npub fn main() {\n case 4 % 0 {\n x if x == 4 % 0 -> True\n _ -> False\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case 4 % 0 { x if x == 4 % 0 -> True _ -> False } } ----- COMPILED JAVASCRIPT import { remainderInt } from "../gleam.mjs"; export function main() { let $ = 0; let x = $; if (x === (remainderInt(4, 0))) { return true; } else { return false; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__keyword_var.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "\npub const function = 5\npub const do = 10\npub fn main() {\n let class = 5\n let while = 10\n let var = 7\n case var {\n _ if class == while -> True\n _ if [class] == [5] -> True\n function if #(function) == #(5) -> False\n _ if do == function -> True\n while if while > 5 -> False\n class -> False\n }\n}\n" --- ----- SOURCE CODE pub const function = 5 pub const do = 10 pub fn main() { let class = 5 let while = 10 let var = 7 case var { _ if class == while -> True _ if [class] == [5] -> True function if #(function) == #(5) -> False _ if do == function -> True while if while > 5 -> False class -> False } } ----- COMPILED JAVASCRIPT import { toList, isEqual } from "../gleam.mjs"; export const function$ = 5; export const do$ = 10; export function main() { let class$ = 5; let while$ = 10; let var$ = 7; if (class$ === while$) { return true; } else if (isEqual(toList([class$]), toList([5]))) { return true; } else { let function$1 = var$; if (isEqual([function$1], [5])) { return false; } else if (10 === 5) { return true; } else { let while$1 = var$; if (while$1 > 5) { return false; } else { let class$1 = var$; return false; } } } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__module_access.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "\n import hero\n pub fn main() {\n let name = \"Tony Stark\"\n case name {\n n if n == hero.ironman.name -> True\n _ -> False\n }\n }\n " --- ----- SOURCE CODE import hero pub fn main() { let name = "Tony Stark" case name { n if n == hero.ironman.name -> True _ -> False } } ----- COMPILED JAVASCRIPT import * as $hero from "../../package/hero.mjs"; export function main() { let name = "Tony Stark"; let n = name; if (n === $hero.ironman.name) { return true; } else { return false; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__module_access_aliased.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "\n import hero/submodule as myhero\n pub fn main() {\n let name = \"Tony Stark\"\n case name {\n n if n == myhero.ironman.name -> True\n _ -> False\n }\n }\n " --- ----- SOURCE CODE import hero/submodule as myhero pub fn main() { let name = "Tony Stark" case name { n if n == myhero.ironman.name -> True _ -> False } } ----- COMPILED JAVASCRIPT import * as $myhero from "../../package/hero/submodule.mjs"; export function main() { let name = "Tony Stark"; let n = name; if (n === $myhero.ironman.name) { return true; } else { return false; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__module_access_submodule.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "\n import hero/submodule\n pub fn main() {\n let name = \"Tony Stark\"\n case name {\n n if n == submodule.ironman.name -> True\n _ -> False\n }\n }\n " --- ----- SOURCE CODE import hero/submodule pub fn main() { let name = "Tony Stark" case name { n if n == submodule.ironman.name -> True _ -> False } } ----- COMPILED JAVASCRIPT import * as $submodule from "../../package/hero/submodule.mjs"; export function main() { let name = "Tony Stark"; let n = name; if (n === $submodule.ironman.name) { return true; } else { return false; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__module_list_access.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "\n import hero\n pub fn main() {\n let names = [\"Tony Stark\", \"Bruce Wayne\"]\n case names {\n n if n == hero.heroes -> True\n _ -> False\n }\n }\n " --- ----- SOURCE CODE import hero pub fn main() { let names = ["Tony Stark", "Bruce Wayne"] case names { n if n == hero.heroes -> True _ -> False } } ----- COMPILED JAVASCRIPT import * as $hero from "../../package/hero.mjs"; import { toList, isEqual } from "../gleam.mjs"; export function main() { let names = toList(["Tony Stark", "Bruce Wayne"]); let n = names; if (isEqual(n, $hero.heroes)) { return true; } else { return false; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__module_nested_access.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "\n import hero\n pub fn main() {\n let name = \"Bruce Wayne\"\n case name {\n n if n == hero.batman.secret_identity.name -> True\n _ -> False\n }\n }\n " --- ----- SOURCE CODE import hero pub fn main() { let name = "Bruce Wayne" case name { n if n == hero.batman.secret_identity.name -> True _ -> False } } ----- COMPILED JAVASCRIPT import * as $hero from "../../package/hero.mjs"; export function main() { let name = "Bruce Wayne"; let n = name; if (n === $hero.batman.secret_identity.name) { return true; } else { return false; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__module_string_access.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "\n import hero\n pub fn main() {\n let name = \"Tony Stark\"\n case name {\n n if n == hero.ironman -> True\n _ -> False\n }\n }\n " --- ----- SOURCE CODE import hero pub fn main() { let name = "Tony Stark" case name { n if n == hero.ironman -> True _ -> False } } ----- COMPILED JAVASCRIPT import * as $hero from "../../package/hero.mjs"; export function main() { let name = "Tony Stark"; let n = name; if (n === ($hero.ironman)) { return true; } else { return false; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__module_tuple_access.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "\n import hero\n pub fn main() {\n let name = \"Tony Stark\"\n case name {\n n if n == hero.hero.1 -> True\n _ -> False\n }\n }\n " --- ----- SOURCE CODE import hero pub fn main() { let name = "Tony Stark" case name { n if n == hero.hero.1 -> True _ -> False } } ----- COMPILED JAVASCRIPT import * as $hero from "../../package/hero.mjs"; export function main() { let name = "Tony Stark"; let n = name; if (n === $hero.hero[1]) { return true; } else { return false; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__nested_record_access.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "\npub type A {\n A(b: B)\n}\n\npub type B {\n B(c: C)\n}\n\npub type C {\n C(d: Bool)\n}\n\npub fn a(a: A) {\n case a {\n _ if a.b.c.d -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub type A { A(b: B) } pub type B { B(c: C) } pub type C { C(d: Bool) } pub fn a(a: A) { case a { _ if a.b.c.d -> 1 _ -> 0 } } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class A extends $CustomType { constructor(b) { super(); this.b = b; } } export const A$A = (b) => new A(b); export const A$isA = (value) => value instanceof A; export const A$A$b = (value) => value.b; export const A$A$0 = (value) => value.b; export class B extends $CustomType { constructor(c) { super(); this.c = c; } } export const B$B = (c) => new B(c); export const B$isB = (value) => value instanceof B; export const B$B$c = (value) => value.c; export const B$B$0 = (value) => value.c; export class C extends $CustomType { constructor(d) { super(); this.d = d; } } export const C$C = (d) => new C(d); export const C$isC = (value) => value instanceof C; export const C$C$d = (value) => value.d; export const C$C$0 = (value) => value.d; export function a(a) { if (a.b.c.d) { return 1; } else { return 0; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__not.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "pub fn main(x, y) {\n case x {\n _ if !y -> 0\n _ -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x, y) { case x { _ if !y -> 0 _ -> 1 } } ----- COMPILED JAVASCRIPT export function main(x, y) { if (!y) { return 0; } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__not_eq_complex.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "pub fn main(xs, y) {\n case xs {\n #(x) if xs != y -> x\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main(xs, y) { case xs { #(x) if xs != y -> x _ -> 0 } } ----- COMPILED JAVASCRIPT import { isEqual } from "../gleam.mjs"; export function main(xs, y) { if (!isEqual(xs, y)) { let x = xs[0]; return x; } else { return 0; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__not_eq_scalar.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "pub fn main(xs, y: Int) {\n case xs {\n #(x) if x != y -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main(xs, y: Int) { case xs { #(x) if x != y -> 1 _ -> 0 } } ----- COMPILED JAVASCRIPT export function main(xs, y) { let x = xs[0]; if (x !== y) { return 1; } else { return 0; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__not_two.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "pub fn main(x, y) {\n case x {\n _ if !y && !x -> 0\n _ -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x, y) { case x { _ if !y && !x -> 0 _ -> 1 } } ----- COMPILED JAVASCRIPT export function main(x, y) { if (!y && !x) { return 0; } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__operator_wrapping_left.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "pub fn main(xs, y: Bool, z: Bool) {\n case xs {\n #(x) if { x == y } == z -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main(xs, y: Bool, z: Bool) { case xs { #(x) if { x == y } == z -> 1 _ -> 0 } } ----- COMPILED JAVASCRIPT export function main(xs, y, z) { let x = xs[0]; if ((x === y) === z) { return 1; } else { return 0; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__operator_wrapping_right.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "pub fn main(xs, y: Bool, z: Bool) {\n case xs {\n #(x) if x == { y == z } -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main(xs, y: Bool, z: Bool) { case xs { #(x) if x == { y == z } -> 1 _ -> 0 } } ----- COMPILED JAVASCRIPT export function main(xs, y, z) { let x = xs[0]; if (x === (y === z)) { return 1; } else { return 0; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__rebound_var.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "pub fn main() {\n let x = False\n let x = True\n case x {\n _ if x -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main() { let x = False let x = True case x { _ if x -> 1 _ -> 0 } } ----- COMPILED JAVASCRIPT export function main() { let x = false; let x$1 = true; if (x$1) { return 1; } else { return 0; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__referencing_pattern_var.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "pub fn main(xs) {\n case xs {\n #(x) if x -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main(xs) { case xs { #(x) if x -> 1 _ -> 0 } } ----- COMPILED JAVASCRIPT export function main(xs) { let x = xs[0]; if (x) { return 1; } else { return 0; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__tuple_index.snap ================================================ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs expression: "pub fn main(x, xs: #(Bool, Bool, Bool)) {\n case x {\n _ if xs.2 -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn main(x, xs: #(Bool, Bool, Bool)) { case x { _ if xs.2 -> 1 _ -> 0 } } ----- COMPILED JAVASCRIPT export function main(x, xs) { if (xs[2]) { return 1; } else { return 0; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__constant_constructor_gets_pure_annotation.snap ================================================ --- source: compiler-core/src/javascript/tests/consts.rs expression: "\npub type X {\n X(Int, List(String))\n}\n\npub const x = X(1, [\"1\"])\npub const y = X(1, [])\n " --- ----- SOURCE CODE pub type X { X(Int, List(String)) } pub const x = X(1, ["1"]) pub const y = X(1, []) ----- COMPILED JAVASCRIPT import { toList, CustomType as $CustomType } from "../gleam.mjs"; export class X extends $CustomType { constructor($0, $1) { super(); this[0] = $0; this[1] = $1; } } export const X$X = ($0, $1) => new X($0, $1); export const X$isX = (value) => value instanceof X; export const X$X$0 = (value) => value[0]; export const X$X$1 = (value) => value[1]; export const x = /* @__PURE__ */ new X(1, /* @__PURE__ */ toList(["1"])); export const y = /* @__PURE__ */ new X(1, /* @__PURE__ */ toList([])); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__constant_list_with_constructors_gets_pure_annotation.snap ================================================ --- source: compiler-core/src/javascript/tests/consts.rs expression: "\npub type X {\n X(Int, List(String))\n}\n\npub const x = [X(1, [\"1\"])]\npub const y = [X(1, [\"1\"])]\n " --- ----- SOURCE CODE pub type X { X(Int, List(String)) } pub const x = [X(1, ["1"])] pub const y = [X(1, ["1"])] ----- COMPILED JAVASCRIPT import { toList, CustomType as $CustomType } from "../gleam.mjs"; export class X extends $CustomType { constructor($0, $1) { super(); this[0] = $0; this[1] = $1; } } export const X$X = ($0, $1) => new X($0, $1); export const X$isX = (value) => value instanceof X; export const X$X$0 = (value) => value[0]; export const X$X$1 = (value) => value[1]; export const x = /* @__PURE__ */ toList([ /* @__PURE__ */ new X(1, /* @__PURE__ */ toList(["1"])), ]); export const y = /* @__PURE__ */ toList([ /* @__PURE__ */ new X(1, /* @__PURE__ */ toList(["1"])), ]); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__constant_tuple_with_constructors_gets_pure_annotation.snap ================================================ --- source: compiler-core/src/javascript/tests/consts.rs expression: "\npub type X {\n X(Int, List(String))\n}\n\npub const x = #(X(1, [\"1\"]))\npub const y = #(X(1, [\"1\"]))\n " --- ----- SOURCE CODE pub type X { X(Int, List(String)) } pub const x = #(X(1, ["1"])) pub const y = #(X(1, ["1"])) ----- COMPILED JAVASCRIPT import { toList, CustomType as $CustomType } from "../gleam.mjs"; export class X extends $CustomType { constructor($0, $1) { super(); this[0] = $0; this[1] = $1; } } export const X$X = ($0, $1) => new X($0, $1); export const X$isX = (value) => value instanceof X; export const X$X$0 = (value) => value[0]; export const X$X$1 = (value) => value[1]; export const x = [/* @__PURE__ */ new X(1, /* @__PURE__ */ toList(["1"]))]; export const y = [/* @__PURE__ */ new X(1, /* @__PURE__ */ toList(["1"]))]; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__constants_get_their_own_jsdoc_comment.snap ================================================ --- source: compiler-core/src/javascript/tests/consts.rs expression: "\n/// 11 is clearly the best number!\npub const jaks_favourite_number = 11\n" --- ----- SOURCE CODE /// 11 is clearly the best number! pub const jaks_favourite_number = 11 ----- COMPILED JAVASCRIPT /** * 11 is clearly the best number! */ export const jaks_favourite_number = 11; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__constructor_function_in_constant.snap ================================================ --- source: compiler-core/src/javascript/tests/consts.rs expression: pub const a = Ok --- ----- SOURCE CODE pub const a = Ok ----- COMPILED JAVASCRIPT import { Ok } from "../gleam.mjs"; export const a = (var0) => { return new Ok(var0); }; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__custom_type_constructor_imported_and_aliased.snap ================================================ --- source: compiler-core/src/javascript/tests/consts.rs expression: "import other_module.{A as B}\n\npub const local = B\n" --- ----- SOURCE CODE import other_module.{A as B} pub const local = B ----- COMPILED JAVASCRIPT import * as $other_module from "../../package/other_module.mjs"; import { A as B } from "../../package/other_module.mjs"; export const local = /* @__PURE__ */ new B(); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__imported_aliased_ok.snap ================================================ --- source: compiler-core/src/javascript/tests/consts.rs expression: "import gleam.{Ok as Y}\n\npub type X {\n Ok\n}\n\npub const y = Y\n" --- ----- SOURCE CODE import gleam.{Ok as Y} pub type X { Ok } pub const y = Y ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; import { Ok as Y, CustomType as $CustomType } from "../gleam.mjs"; export class Ok extends $CustomType {} export const X$Ok = () => new Ok(); export const X$isOk = (value) => value instanceof Ok; export const y = (var0) => { return new Y(var0); }; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__imported_ok.snap ================================================ --- source: compiler-core/src/javascript/tests/consts.rs expression: "import gleam\n\npub type X {\n Ok\n}\n\npub const y = gleam.Ok\n" --- ----- SOURCE CODE import gleam pub type X { Ok } pub const y = gleam.Ok ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; import { Ok, CustomType as $CustomType } from "../gleam.mjs"; export class Ok extends $CustomType {} export const X$Ok = () => new Ok(); export const X$isOk = (value) => value instanceof Ok; export const y = (var0) => { return new Ok(var0); }; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__list_prepend.snap ================================================ --- source: compiler-core/src/javascript/tests/consts.rs expression: "\nconst wibble = [2, 3, 4]\npub const wobble = [0, 1, ..wibble]\n" --- ----- SOURCE CODE const wibble = [2, 3, 4] pub const wobble = [0, 1, ..wibble] ----- COMPILED JAVASCRIPT import { toList } from "../gleam.mjs"; const wibble = /* @__PURE__ */ toList([2, 3, 4]); export const wobble = /* @__PURE__ */ toList([0, 1, 2, 3, 4]); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__list_prepend_from_other_module.snap ================================================ --- source: compiler-core/src/javascript/tests/consts.rs expression: "\nimport mod\n\npub const wobble = [0, 1, ..mod.wibble]\n" --- ----- SOURCE CODE -- mod.gleam pub const wibble = [2, 3, 4] -- main.gleam import mod pub const wobble = [0, 1, ..mod.wibble] ----- COMPILED JAVASCRIPT import { toList } from "../gleam.mjs"; import * as $mod from "../mod.mjs"; export const wobble = /* @__PURE__ */ toList([0, 1, 2, 3, 4]); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__list_prepend_literal.snap ================================================ --- source: compiler-core/src/javascript/tests/consts.rs expression: "\npub const wibble = [0, 1, ..[2, 3, 4]]\n" --- ----- SOURCE CODE pub const wibble = [0, 1, ..[2, 3, 4]] ----- COMPILED JAVASCRIPT import { toList } from "../gleam.mjs"; export const wibble = /* @__PURE__ */ toList([0, 1, 2, 3, 4]); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__literal_bool_does_not_get_constant_annotation.snap ================================================ --- source: compiler-core/src/javascript/tests/consts.rs expression: "\n pub const a = True\n pub const b = False\n " --- ----- SOURCE CODE pub const a = True pub const b = False ----- COMPILED JAVASCRIPT export const a = true; export const b = false; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__literal_float_does_not_get_constant_annotation.snap ================================================ --- source: compiler-core/src/javascript/tests/consts.rs expression: pub const a = 1.1 --- ----- SOURCE CODE pub const a = 1.1 ----- COMPILED JAVASCRIPT export const a = 1.1; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__literal_int_does_not_get_constant_annotation.snap ================================================ --- source: compiler-core/src/javascript/tests/consts.rs expression: pub const a = 1 --- ----- SOURCE CODE pub const a = 1 ----- COMPILED JAVASCRIPT export const a = 1; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__literal_list_does_not_get_constant_annotation.snap ================================================ --- source: compiler-core/src/javascript/tests/consts.rs expression: "pub const a = [1, 2, 3]" --- ----- SOURCE CODE pub const a = [1, 2, 3] ----- COMPILED JAVASCRIPT import { toList } from "../gleam.mjs"; export const a = /* @__PURE__ */ toList([1, 2, 3]); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__literal_nil_does_not_get_constant_annotation.snap ================================================ --- source: compiler-core/src/javascript/tests/consts.rs expression: pub const a = Nil --- ----- SOURCE CODE pub const a = Nil ----- COMPILED JAVASCRIPT export const a = undefined; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__literal_string_does_not_get_constant_annotation.snap ================================================ --- source: compiler-core/src/javascript/tests/consts.rs expression: "pub const a = \"1\"" --- ----- SOURCE CODE pub const a = "1" ----- COMPILED JAVASCRIPT export const a = "1"; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__consts__literal_tuple_does_not_get_constant_annotation.snap ================================================ --- source: compiler-core/src/javascript/tests/consts.rs expression: "pub const a = #(1, 2, 3)" --- ----- SOURCE CODE pub const a = #(1, 2, 3) ----- COMPILED JAVASCRIPT export const a = [1, 2, 3]; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__const_imported_ignoring_label.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other\npub const main = other.Two(1)\n" --- ----- SOURCE CODE -- other.gleam pub type One { Two(field: Int) } -- main.gleam import other pub const main = other.Two(1) ----- COMPILED JAVASCRIPT import * as $other from "../other.mjs"; export const main = /* @__PURE__ */ new $other.Two(1); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__const_imported_multiple_fields.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other\npub const main = other.Two(b: 2, c: 3, a: 1)\n" --- ----- SOURCE CODE -- other.gleam pub type One { Two(a: Int, b: Int, c: Int) } -- main.gleam import other pub const main = other.Two(b: 2, c: 3, a: 1) ----- COMPILED JAVASCRIPT import * as $other from "../other.mjs"; export const main = /* @__PURE__ */ new $other.Two(1, 2, 3); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__const_imported_no_label.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other\npub const main = other.Two(1)\n" --- ----- SOURCE CODE -- other.gleam pub type One { Two(Int) } -- main.gleam import other pub const main = other.Two(1) ----- COMPILED JAVASCRIPT import * as $other from "../other.mjs"; export const main = /* @__PURE__ */ new $other.Two(1); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__const_imported_using_label.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other\npub const main = other.Two(field: 1)\n" --- ----- SOURCE CODE -- other.gleam pub type One { Two(field: Int) } -- main.gleam import other pub const main = other.Two(field: 1) ----- COMPILED JAVASCRIPT import * as $other from "../other.mjs"; export const main = /* @__PURE__ */ new $other.Two(1); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__const_unqualified_imported_ignoring_label.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other.{Two}\npub const main = Two(1)\n" --- ----- SOURCE CODE -- other.gleam pub type One { Two(field: Int) } -- main.gleam import other.{Two} pub const main = Two(1) ----- COMPILED JAVASCRIPT import * as $other from "../other.mjs"; import { Two } from "../other.mjs"; export const main = /* @__PURE__ */ new Two(1); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__const_unqualified_imported_multiple_fields.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other.{Two}\npub const main = Two(b: 2, c: 3, a: 1)\n" --- ----- SOURCE CODE -- other.gleam pub type One { Two(a: Int, b: Int, c: Int) } -- main.gleam import other.{Two} pub const main = Two(b: 2, c: 3, a: 1) ----- COMPILED JAVASCRIPT import * as $other from "../other.mjs"; import { Two } from "../other.mjs"; export const main = /* @__PURE__ */ new Two(1, 2, 3); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__const_unqualified_imported_no_label.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other.{Two}\npub const main = Two(1)\n" --- ----- SOURCE CODE -- other.gleam pub type One { Two(Int) } -- main.gleam import other.{Two} pub const main = Two(1) ----- COMPILED JAVASCRIPT import * as $other from "../other.mjs"; import { Two } from "../other.mjs"; export const main = /* @__PURE__ */ new Two(1); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__const_unqualified_imported_using_label.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other.{Two}\npub const main = Two(field: 1)\n" --- ----- SOURCE CODE -- other.gleam pub type One { Two(field: Int) } -- main.gleam import other.{Two} pub const main = Two(field: 1) ----- COMPILED JAVASCRIPT import * as $other from "../other.mjs"; import { Two } from "../other.mjs"; export const main = /* @__PURE__ */ new Two(1); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__const_with_fields.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\npub type Mine {\n Mine(a: Int, b: Int)\n}\n\npub const labels = Mine(b: 2, a: 1)\npub const no_labels = Mine(3, 4)\n" --- ----- SOURCE CODE pub type Mine { Mine(a: Int, b: Int) } pub const labels = Mine(b: 2, a: 1) pub const no_labels = Mine(3, 4) ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Mine extends $CustomType { constructor(a, b) { super(); this.a = a; this.b = b; } } export const Mine$Mine = (a, b) => new Mine(a, b); export const Mine$isMine = (value) => value instanceof Mine; export const Mine$Mine$a = (value) => value.a; export const Mine$Mine$0 = (value) => value.a; export const Mine$Mine$b = (value) => value.b; export const Mine$Mine$1 = (value) => value.b; export const labels = /* @__PURE__ */ new Mine(1, 2); export const no_labels = /* @__PURE__ */ new Mine(3, 4); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__const_with_fields_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs assertion_line: 138 expression: "\npub type Mine {\n Mine(a: Int, b: Int)\n}\n\npub const labels = Mine(b: 2, a: 1)\npub const no_labels = Mine(3, 4)\n" snapshot_kind: text --- ----- SOURCE CODE pub type Mine { Mine(a: Int, b: Int) } pub const labels = Mine(b: 2, a: 1) pub const no_labels = Mine(3, 4) ----- TYPESCRIPT DEFINITIONS import type * as _ from "../gleam.d.mts"; export class Mine extends _.CustomType { /** @deprecated */ constructor(a: number, b: number); /** @deprecated */ a: number; /** @deprecated */ b: number; } export function Mine$Mine(a: number, b: number): Mine$; export function Mine$isMine(value: any): value is Mine$; export function Mine$Mine$0(value: Mine$): number; export function Mine$Mine$a(value: Mine$): number; export function Mine$Mine$1(value: Mine$): number; export function Mine$Mine$b(value: Mine$): number; export type Mine$ = Mine; export const labels: Mine$; export const no_labels: Mine$; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__const_zero_arity_imported.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs assertion_line: 104 expression: "import other\npub const x = other.Two\n" snapshot_kind: text --- ----- SOURCE CODE -- other.gleam pub type One { Two } -- main.gleam import other pub const x = other.Two ----- COMPILED JAVASCRIPT import * as $other from "../other.mjs"; export const x = /* @__PURE__ */ new $other.Two(); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__const_zero_arity_imported_unqualified.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs assertion_line: 114 expression: "import other.{Two}\npub const a = Two\n" snapshot_kind: text --- ----- SOURCE CODE -- other.gleam pub type One { Two } -- main.gleam import other.{Two} pub const a = Two ----- COMPILED JAVASCRIPT import * as $other from "../other.mjs"; import { Two } from "../other.mjs"; export const a = /* @__PURE__ */ new Two(); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__constructor_as_value.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other\npub fn main() {\n other.Two\n}" --- ----- SOURCE CODE -- other.gleam pub type One { Two(a: Int, b: Int, c: Int) } -- main.gleam import other pub fn main() { other.Two } ----- COMPILED JAVASCRIPT import * as $other from "../other.mjs"; export function main() { return (var0, var1, var2) => { return new $other.Two(var0, var1, var2); }; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__constructors_get_their_own_jsdoc.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs assertion_line: 678 expression: "\npub type Wibble {\n /// Wibbling!!\n Wibble(field: Int)\n\n /// Wobbling!!\n Wobble(field: Int)\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub type Wibble { /// Wibbling!! Wibble(field: Int) /// Wobbling!! Wobble(field: Int) } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; /** * Wibbling!! */ export class Wibble extends $CustomType { constructor(field) { super(); this.field = field; } } export const Wibble$Wibble = (field) => new Wibble(field); export const Wibble$isWibble = (value) => value instanceof Wibble; export const Wibble$Wibble$field = (value) => value.field; export const Wibble$Wibble$0 = (value) => value.field; /** * Wobbling!! */ export class Wobble extends $CustomType { constructor(field) { super(); this.field = field; } } export const Wibble$Wobble = (field) => new Wobble(field); export const Wibble$isWobble = (value) => value instanceof Wobble; export const Wibble$Wobble$field = (value) => value.field; export const Wibble$Wobble$0 = (value) => value.field; export const Wibble$field = (value) => value.field; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__custom_type_with_named_fields.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\npub type Cat {\n Cat(name: String, cuteness: Int)\n}\n\npub type Box {\n Box(occupant: Cat)\n}\n\npub const felix = Cat(\"Felix\", 12)\npub const tom = Cat(cuteness: 1, name: \"Tom\")\n\npub fn go() {\n Cat(\"Nubi\", 1)\n Cat(2, name: \"Nubi\")\n Cat(cuteness: 3, name: \"Nubi\")\n}\n\npub fn update(cat) {\n Cat(..cat, name: \"Sid\")\n Cat(..cat, name: \"Bartholemew Wonder Puss the Fourth !!!!!!!!!!!!!!!!\")\n Cat(..new_cat(), name: \"Molly\")\n let box = Box(occupant: cat)\n Cat(..box.occupant, cuteness: box.occupant.cuteness + 1)\n}\n\npub fn access(cat: Cat) {\n cat.cuteness\n}\n\npub fn new_cat() {\n Cat(name: \"Beau\", cuteness: 11)\n}\n" --- ----- SOURCE CODE pub type Cat { Cat(name: String, cuteness: Int) } pub type Box { Box(occupant: Cat) } pub const felix = Cat("Felix", 12) pub const tom = Cat(cuteness: 1, name: "Tom") pub fn go() { Cat("Nubi", 1) Cat(2, name: "Nubi") Cat(cuteness: 3, name: "Nubi") } pub fn update(cat) { Cat(..cat, name: "Sid") Cat(..cat, name: "Bartholemew Wonder Puss the Fourth !!!!!!!!!!!!!!!!") Cat(..new_cat(), name: "Molly") let box = Box(occupant: cat) Cat(..box.occupant, cuteness: box.occupant.cuteness + 1) } pub fn access(cat: Cat) { cat.cuteness } pub fn new_cat() { Cat(name: "Beau", cuteness: 11) } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Cat extends $CustomType { constructor(name, cuteness) { super(); this.name = name; this.cuteness = cuteness; } } export const Cat$Cat = (name, cuteness) => new Cat(name, cuteness); export const Cat$isCat = (value) => value instanceof Cat; export const Cat$Cat$name = (value) => value.name; export const Cat$Cat$0 = (value) => value.name; export const Cat$Cat$cuteness = (value) => value.cuteness; export const Cat$Cat$1 = (value) => value.cuteness; export class Box extends $CustomType { constructor(occupant) { super(); this.occupant = occupant; } } export const Box$Box = (occupant) => new Box(occupant); export const Box$isBox = (value) => value instanceof Box; export const Box$Box$occupant = (value) => value.occupant; export const Box$Box$0 = (value) => value.occupant; export const felix = /* @__PURE__ */ new Cat("Felix", 12); export const tom = /* @__PURE__ */ new Cat("Tom", 1); export function go() { new Cat("Nubi", 1); new Cat("Nubi", 2); return new Cat("Nubi", 3); } export function access(cat) { return cat.cuteness; } export function new_cat() { return new Cat("Beau", 11); } export function update(cat) { new Cat("Sid", cat.cuteness) new Cat("Bartholemew Wonder Puss the Fourth !!!!!!!!!!!!!!!!", cat.cuteness) let _record = new_cat(); new Cat("Molly", _record.cuteness) let box = new Box(cat); let _record$1 = box.occupant; return new Cat(_record$1.name, box.occupant.cuteness + 1); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__destructure_custom_type_with_mixed_fields_first_unlabelled.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\npub type Cat {\n Cat(String, cuteness: Int)\n}\n\npub fn go(cat) {\n let Cat(x, y) = cat\n let Cat(cuteness: y, ..) = cat\n let Cat(x, cuteness: y) = cat\n x\n}\n\n" --- ----- SOURCE CODE pub type Cat { Cat(String, cuteness: Int) } pub fn go(cat) { let Cat(x, y) = cat let Cat(cuteness: y, ..) = cat let Cat(x, cuteness: y) = cat x } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Cat extends $CustomType { constructor($0, cuteness) { super(); this[0] = $0; this.cuteness = cuteness; } } export const Cat$Cat = ($0, cuteness) => new Cat($0, cuteness); export const Cat$isCat = (value) => value instanceof Cat; export const Cat$Cat$0 = (value) => value[0]; export const Cat$Cat$cuteness = (value) => value.cuteness; export const Cat$Cat$1 = (value) => value.cuteness; export function go(cat) { let x; let y; x = cat[0]; y = cat.cuteness; let y$1; y$1 = cat.cuteness; let x$1; let y$2; x$1 = cat[0]; y$2 = cat.cuteness; return x$1; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__destructure_custom_type_with_named_fields.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\npub type Cat {\n Cat(name: String, cuteness: Int)\n}\n\npub fn go(cat) {\n let Cat(x, y) = cat\n let Cat(name: x, ..) = cat\n let assert Cat(cuteness: 4, name: x) = cat\n x\n}\n\n" --- ----- SOURCE CODE pub type Cat { Cat(name: String, cuteness: Int) } pub fn go(cat) { let Cat(x, y) = cat let Cat(name: x, ..) = cat let assert Cat(cuteness: 4, name: x) = cat x } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType, makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export class Cat extends $CustomType { constructor(name, cuteness) { super(); this.name = name; this.cuteness = cuteness; } } export const Cat$Cat = (name, cuteness) => new Cat(name, cuteness); export const Cat$isCat = (value) => value instanceof Cat; export const Cat$Cat$name = (value) => value.name; export const Cat$Cat$0 = (value) => value.name; export const Cat$Cat$cuteness = (value) => value.cuteness; export const Cat$Cat$1 = (value) => value.cuteness; export function go(cat) { let x; let y; x = cat.name; y = cat.cuteness; let x$1; x$1 = cat.name; let x$2; let $ = cat.cuteness; if ($ === 4) { x$2 = cat.name; } else { throw makeError( "let_assert", FILEPATH, "my/mod", 9, "go", "Pattern match failed, no pattern matched the value.", { value: cat, start: 124, end: 166, pattern_start: 135, pattern_end: 160 } ) } return x$2; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__equality_with_non_singleton_variant.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\npub type Thing {\n Variant\n Other(String)\n}\n\npub fn check_other(x: Thing) -> Bool {\n x == Other(\"hello\")\n}\n" --- ----- SOURCE CODE pub type Thing { Variant Other(String) } pub fn check_other(x: Thing) -> Bool { x == Other("hello") } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType, isEqual } from "../gleam.mjs"; export class Variant extends $CustomType {} export const Thing$Variant = () => new Variant(); export const Thing$isVariant = (value) => value instanceof Variant; export class Other extends $CustomType { constructor($0) { super(); this[0] = $0; } } export const Thing$Other = ($0) => new Other($0); export const Thing$isOther = (value) => value instanceof Other; export const Thing$Other$0 = (value) => value[0]; export function check_other(x) { return isEqual(x, new Other("hello")); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__external_annotated_type_used_in_function.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\n@external(javascript, \"./gleam_stdlib.d.ts\", \"Dict\")\npub type Dict(key, value)\n\n@external(javascript, \"./gleam_stdlib.mjs\", \"get\")\npub fn get(dict: Dict(key, value), key: key) -> Result(value, Nil)\n" --- ----- SOURCE CODE @external(javascript, "./gleam_stdlib.d.ts", "Dict") pub type Dict(key, value) @external(javascript, "./gleam_stdlib.mjs", "get") pub fn get(dict: Dict(key, value), key: key) -> Result(value, Nil) ----- TYPESCRIPT DEFINITIONS import type * as _ from "../gleam.d.mts"; import type { Dict as Dict$ } from "./gleam_stdlib.d.ts"; export type { Dict$ }; export function get(dict: Dict$, key: K): _.Result; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__external_annotation.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\n@external(javascript, \"./gleam_stdlib.d.ts\", \"Dict\")\npub type Dict(key, value)\n" --- ----- SOURCE CODE @external(javascript, "./gleam_stdlib.d.ts", "Dict") pub type Dict(key, value) ----- TYPESCRIPT DEFINITIONS import type { Dict as Dict$ } from "./gleam_stdlib.d.ts"; export type { Dict$ }; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__generic_type_parameter_used_in_field.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs assertion_line: 983 expression: "\npub type Wibble(value, error) {\n Wibble(\n wibble: value,\n wobble: Result(value, error),\n wubble: error,\n )\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub type Wibble(value, error) { Wibble( wibble: value, wobble: Result(value, error), wubble: error, ) } ----- TYPESCRIPT DEFINITIONS import type * as _ from "../gleam.d.mts"; export class Wibble extends _.CustomType { /** @deprecated */ constructor(wibble: I, wobble: _.Result, wubble: J); /** @deprecated */ wibble: I; /** @deprecated */ wobble: _.Result; /** @deprecated */ wubble: J; } export function Wibble$Wibble( wibble: I, wobble: _.Result, wubble: J, ): Wibble$; export function Wibble$isWibble( value: any, ): value is Wibble$; export function Wibble$Wibble$0(value: Wibble$): I; export function Wibble$Wibble$wibble(value: Wibble$): I; export function Wibble$Wibble$1(value: Wibble$): _.Result; export function Wibble$Wibble$wobble(value: Wibble$): _.Result; export function Wibble$Wibble$2( value: Wibble$, ): J; export function Wibble$Wibble$wubble(value: Wibble$): J; export type Wibble$ = Wibble; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__guard_equality_with_non_singleton_variant.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\npub type Thing {\n Variant\n Other(String)\n}\n\npub fn process(e: Thing) -> String {\n case e {\n value if value == Other(\"hello\") -> \"match\"\n _ -> \"no match\"\n }\n}\n" --- ----- SOURCE CODE pub type Thing { Variant Other(String) } pub fn process(e: Thing) -> String { case e { value if value == Other("hello") -> "match" _ -> "no match" } } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType, isEqual } from "../gleam.mjs"; export class Variant extends $CustomType {} export const Thing$Variant = () => new Variant(); export const Thing$isVariant = (value) => value instanceof Variant; export class Other extends $CustomType { constructor($0) { super(); this[0] = $0; } } export const Thing$Other = ($0) => new Other($0); export const Thing$isOther = (value) => value instanceof Other; export const Thing$Other$0 = (value) => value[0]; export function process(e) { let value = e; if (isEqual(value, new Other("hello"))) { return "match"; } else { return "no match"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__imported_ignoring_label.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other\npub fn main() {\n other.Two(1)\n}" --- ----- SOURCE CODE -- other.gleam pub type One { Two(field: Int) } -- main.gleam import other pub fn main() { other.Two(1) } ----- COMPILED JAVASCRIPT import * as $other from "../other.mjs"; export function main() { return new $other.Two(1); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__imported_multiple_fields.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other\npub fn main() {\n other.Two(b: 2, c: 3, a: 1)\n}" --- ----- SOURCE CODE -- other.gleam pub type One { Two(a: Int, b: Int, c: Int) } -- main.gleam import other pub fn main() { other.Two(b: 2, c: 3, a: 1) } ----- COMPILED JAVASCRIPT import * as $other from "../other.mjs"; export function main() { return new $other.Two(1, 2, 3); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__imported_no_label.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other\npub fn main() {\n other.Two(1)\n}" --- ----- SOURCE CODE -- other.gleam pub type One { Two(Int) } -- main.gleam import other pub fn main() { other.Two(1) } ----- COMPILED JAVASCRIPT import * as $other from "../other.mjs"; export function main() { return new $other.Two(1); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__imported_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other.{Two}\n\npub fn main(x) {\n case x {\n Two(a: 1, ..) -> 1\n other.Two(b: 2, c: c, ..) -> c\n _ -> 3\n }\n}\n" --- ----- SOURCE CODE -- other.gleam pub type One { Two(a: Int, b: Int, c: Int) } -- main.gleam import other.{Two} pub fn main(x) { case x { Two(a: 1, ..) -> 1 other.Two(b: 2, c: c, ..) -> c _ -> 3 } } ----- COMPILED JAVASCRIPT import * as $other from "../other.mjs"; import { Two } from "../other.mjs"; export function main(x) { let $ = x.a; if ($ === 1) { return 1; } else { let $1 = x.b; if ($1 === 2) { let c = x.c; return c; } else { return 3; } } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__imported_using_label.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other\npub fn main() {\n other.Two(field: 1)\n}" --- ----- SOURCE CODE -- other.gleam pub type One { Two(field: Int) } -- main.gleam import other pub fn main() { other.Two(field: 1) } ----- COMPILED JAVASCRIPT import * as $other from "../other.mjs"; export function main() { return new $other.Two(1); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__keyword_label_name.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "pub type Thing {\n Thing(in: Int, class: Nil)\n}\n" --- ----- SOURCE CODE pub type Thing { Thing(in: Int, class: Nil) } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Thing extends $CustomType { constructor(in$, class$) { super(); this.in = in$; this.class = class$; } } export const Thing$Thing = (in$, class$) => new Thing(in$, class$); export const Thing$isThing = (value) => value instanceof Thing; export const Thing$Thing$in = (value) => value.in; export const Thing$Thing$0 = (value) => value.in; export const Thing$Thing$class = (value) => value.class; export const Thing$Thing$1 = (value) => value.class; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__long_name_variant_mixed_labels_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs assertion_line: 209 expression: "\npub type TypeWithALongNameAndSeveralArguments{\n TypeWithALongNameAndSeveralArguments(String, String, String, a: String, b: String)\n}\n\npub const local = TypeWithALongNameAndSeveralArguments(\"one\", \"two\", \"three\", \"four\", \"five\")\n" snapshot_kind: text --- ----- SOURCE CODE pub type TypeWithALongNameAndSeveralArguments{ TypeWithALongNameAndSeveralArguments(String, String, String, a: String, b: String) } pub const local = TypeWithALongNameAndSeveralArguments("one", "two", "three", "four", "five") ----- TYPESCRIPT DEFINITIONS import type * as _ from "../gleam.d.mts"; export class TypeWithALongNameAndSeveralArguments extends _.CustomType { /** @deprecated */ constructor( argument$0: string, argument$1: string, argument$2: string, a: string, b: string ); /** @deprecated */ 0: string; /** @deprecated */ 1: string; /** @deprecated */ 2: string; /** @deprecated */ a: string; /** @deprecated */ b: string; } export function TypeWithALongNameAndSeveralArguments$TypeWithALongNameAndSeveralArguments( $0: string, $1: string, $2: string, a: string, b: string, ): TypeWithALongNameAndSeveralArguments$; export function TypeWithALongNameAndSeveralArguments$isTypeWithALongNameAndSeveralArguments( value: any, ): value is TypeWithALongNameAndSeveralArguments$; export function TypeWithALongNameAndSeveralArguments$TypeWithALongNameAndSeveralArguments$0(value: TypeWithALongNameAndSeveralArguments$): string; export function TypeWithALongNameAndSeveralArguments$TypeWithALongNameAndSeveralArguments$1( value: TypeWithALongNameAndSeveralArguments$, ): string; export function TypeWithALongNameAndSeveralArguments$TypeWithALongNameAndSeveralArguments$2(value: TypeWithALongNameAndSeveralArguments$): string; export function TypeWithALongNameAndSeveralArguments$TypeWithALongNameAndSeveralArguments$3( value: TypeWithALongNameAndSeveralArguments$, ): string; export function TypeWithALongNameAndSeveralArguments$TypeWithALongNameAndSeveralArguments$a(value: TypeWithALongNameAndSeveralArguments$): string; export function TypeWithALongNameAndSeveralArguments$TypeWithALongNameAndSeveralArguments$4( value: TypeWithALongNameAndSeveralArguments$, ): string; export function TypeWithALongNameAndSeveralArguments$TypeWithALongNameAndSeveralArguments$b(value: TypeWithALongNameAndSeveralArguments$): string; export type TypeWithALongNameAndSeveralArguments$ = TypeWithALongNameAndSeveralArguments; export const local: TypeWithALongNameAndSeveralArguments$; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__long_name_variant_without_labels.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\npub type TypeWithALongNameAndSeveralArguments{\n TypeWithALongNameAndSeveralArguments(String, String, String, String, String)\n}\n\n\npub fn go() {\n TypeWithALongNameAndSeveralArguments\n}\n" --- ----- SOURCE CODE pub type TypeWithALongNameAndSeveralArguments{ TypeWithALongNameAndSeveralArguments(String, String, String, String, String) } pub fn go() { TypeWithALongNameAndSeveralArguments } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class TypeWithALongNameAndSeveralArguments extends $CustomType { constructor($0, $1, $2, $3, $4) { super(); this[0] = $0; this[1] = $1; this[2] = $2; this[3] = $3; this[4] = $4; } } export const TypeWithALongNameAndSeveralArguments$TypeWithALongNameAndSeveralArguments = ($0, $1, $2, $3, $4) => new TypeWithALongNameAndSeveralArguments($0, $1, $2, $3, $4); export const TypeWithALongNameAndSeveralArguments$isTypeWithALongNameAndSeveralArguments = (value) => value instanceof TypeWithALongNameAndSeveralArguments; export const TypeWithALongNameAndSeveralArguments$TypeWithALongNameAndSeveralArguments$0 = (value) => value[0]; export const TypeWithALongNameAndSeveralArguments$TypeWithALongNameAndSeveralArguments$1 = (value) => value[1]; export const TypeWithALongNameAndSeveralArguments$TypeWithALongNameAndSeveralArguments$2 = (value) => value[2]; export const TypeWithALongNameAndSeveralArguments$TypeWithALongNameAndSeveralArguments$3 = (value) => value[3]; export const TypeWithALongNameAndSeveralArguments$TypeWithALongNameAndSeveralArguments$4 = (value) => value[4]; export function go() { return (var0, var1, var2, var3, var4) => { return new TypeWithALongNameAndSeveralArguments( var0, var1, var2, var3, var4, ); }; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__mixed_singleton_and_non_singleton.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\npub type Result {\n Ok(value: Int)\n Error\n}\n\npub fn is_error(r: Result) -> Bool {\n r == Error\n}\n" --- ----- SOURCE CODE pub type Result { Ok(value: Int) Error } pub fn is_error(r: Result) -> Bool { r == Error } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Ok extends $CustomType { constructor(value) { super(); this.value = value; } } export const Result$Ok = (value) => new Ok(value); export const Result$isOk = (value) => value instanceof Ok; export const Result$Ok$value = (value) => value.value; export const Result$Ok$0 = (value) => value.value; export class Error extends $CustomType {} export const Result$Error = () => new Error(); export const Result$isError = (value) => value instanceof Error; export function is_error(r) { return r instanceof Error; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__multiple_singleton_constructors.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\npub type Status {\n Loading\n Success\n Error\n}\n\npub fn is_loading(s: Status) -> Bool {\n s == Loading\n}\n\npub fn is_success(s: Status) -> Bool {\n s == Success\n}\n" --- ----- SOURCE CODE pub type Status { Loading Success Error } pub fn is_loading(s: Status) -> Bool { s == Loading } pub fn is_success(s: Status) -> Bool { s == Success } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Loading extends $CustomType {} export const Status$Loading = () => new Loading(); export const Status$isLoading = (value) => value instanceof Loading; export class Success extends $CustomType {} export const Status$Success = () => new Success(); export const Status$isSuccess = (value) => value instanceof Success; export class Error extends $CustomType {} export const Status$Error = () => new Error(); export const Status$isError = (value) => value instanceof Error; export function is_loading(s) { return s instanceof Loading; } export function is_success(s) { return s instanceof Success; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__nested_pattern_with_labels.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "pub type Box(x) { Box(a: Int, b: x) }\npub fn go(x) {\n case x {\n Box(a: _, b: Box(a: a, b: b)) -> a + b\n _ -> 1\n }\n}\n" --- ----- SOURCE CODE pub type Box(x) { Box(a: Int, b: x) } pub fn go(x) { case x { Box(a: _, b: Box(a: a, b: b)) -> a + b _ -> 1 } } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Box extends $CustomType { constructor(a, b) { super(); this.a = a; this.b = b; } } export const Box$Box = (a, b) => new Box(a, b); export const Box$isBox = (value) => value instanceof Box; export const Box$Box$a = (value) => value.a; export const Box$Box$0 = (value) => value.a; export const Box$Box$b = (value) => value.b; export const Box$Box$1 = (value) => value.b; export function go(x) { let a = x.b.a; let b = x.b.b; return a + b; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__new_type_import_syntax.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\nimport a.{type A, A}\n\npub fn main() {\n A\n}\n" --- ----- SOURCE CODE import a.{type A, A} pub fn main() { A } ----- COMPILED JAVASCRIPT import * as $a from "../../package/a.mjs"; import { A } from "../../package/a.mjs"; export function main() { return new A(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__non_singleton_record_equality.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\npub type Person {\n Person(name: String, age: Int)\n}\n\npub fn same_person(p1: Person, p2: Person) -> Bool {\n p1 == p2\n}\n" --- ----- SOURCE CODE pub type Person { Person(name: String, age: Int) } pub fn same_person(p1: Person, p2: Person) -> Bool { p1 == p2 } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType, isEqual } from "../gleam.mjs"; export class Person extends $CustomType { constructor(name, age) { super(); this.name = name; this.age = age; } } export const Person$Person = (name, age) => new Person(name, age); export const Person$isPerson = (value) => value instanceof Person; export const Person$Person$name = (value) => value.name; export const Person$Person$0 = (value) => value.name; export const Person$Person$age = (value) => value.age; export const Person$Person$1 = (value) => value.age; export function same_person(p1, p2) { return isEqual(p1, p2); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__opaque_types_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs assertion_line: 567 expression: "pub opaque type Animal {\n Cat(goes_outside: Bool)\n Dog(plays_fetch: Bool)\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub opaque type Animal { Cat(goes_outside: Bool) Dog(plays_fetch: Bool) } ----- TYPESCRIPT DEFINITIONS import type * as _ from "../gleam.d.mts"; declare class Cat extends _.CustomType { /** @deprecated */ constructor(goes_outside: boolean); /** @deprecated */ goes_outside: boolean; } declare class Dog extends _.CustomType { /** @deprecated */ constructor(plays_fetch: boolean); /** @deprecated */ plays_fetch: boolean; } export type Animal$ = Cat | Dog; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__qualified.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other\n\npub fn main() {\n other.One\n}\n" --- ----- SOURCE CODE -- other.gleam pub type One { One } -- main.gleam import other pub fn main() { other.One } ----- COMPILED JAVASCRIPT import * as $other from "../other.mjs"; export function main() { return new $other.One(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__record_access_in_guard_with_reserved_field_name.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\npub type Thing {\n Thing(constructor: Nil)\n}\n\npub fn main() {\n let a = Thing(constructor: Nil)\n case Nil {\n Nil if a.constructor == Nil -> a.constructor\n _ -> Nil\n }\n}\n" --- ----- SOURCE CODE pub type Thing { Thing(constructor: Nil) } pub fn main() { let a = Thing(constructor: Nil) case Nil { Nil if a.constructor == Nil -> a.constructor _ -> Nil } } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Thing extends $CustomType { constructor(constructor) { super(); this.constructor$ = constructor; } } export const Thing$Thing = (constructor) => new Thing(constructor); export const Thing$isThing = (value) => value instanceof Thing; export const Thing$Thing$constructor = (value) => value.constructor$; export const Thing$Thing$0 = (value) => value.constructor$; export function main() { let a = new Thing(undefined); let $ = undefined; if (a.constructor$ === undefined) { return a.constructor$; } else { return undefined; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__record_access_in_pattern_with_reserved_field_name.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\npub type Thing {\n Thing(constructor: Nil)\n}\n\npub fn main() {\n let a = Thing(constructor: Nil)\n let Thing(constructor: ctor) = a\n case a {\n a if a.constructor == ctor -> Nil\n Thing(constructor:) if ctor == constructor -> Nil\n _ -> Nil\n }\n}\n" --- ----- SOURCE CODE pub type Thing { Thing(constructor: Nil) } pub fn main() { let a = Thing(constructor: Nil) let Thing(constructor: ctor) = a case a { a if a.constructor == ctor -> Nil Thing(constructor:) if ctor == constructor -> Nil _ -> Nil } } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Thing extends $CustomType { constructor(constructor) { super(); this.constructor$ = constructor; } } export const Thing$Thing = (constructor) => new Thing(constructor); export const Thing$isThing = (value) => value instanceof Thing; export const Thing$Thing$constructor = (value) => value.constructor$; export const Thing$Thing$0 = (value) => value.constructor$; export function main() { let a = new Thing(undefined); let ctor; ctor = a.constructor$; let a$1 = a; if (a$1.constructor$ === ctor) { return undefined; } else { let constructor = a.constructor$; if (ctor === constructor) { return undefined; } else { return undefined; } } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__record_with_field_named_constructor.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\npub type Thing {\n Thing(constructor: Nil)\n}\n\npub fn main() {\n let a = Thing(constructor: Nil)\n let b = Thing(..a, constructor: Nil)\n b.constructor\n}\n" --- ----- SOURCE CODE pub type Thing { Thing(constructor: Nil) } pub fn main() { let a = Thing(constructor: Nil) let b = Thing(..a, constructor: Nil) b.constructor } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Thing extends $CustomType { constructor(constructor) { super(); this.constructor$ = constructor; } } export const Thing$Thing = (constructor) => new Thing(constructor); export const Thing$isThing = (value) => value instanceof Thing; export const Thing$Thing$constructor = (value) => value.constructor$; export const Thing$Thing$0 = (value) => value.constructor$; export function main() { let a = new Thing(undefined); let b = new Thing(undefined); return b.constructor$; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__record_with_field_named_then.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\npub type Thing {\n Thing(then: Nil)\n}\n\npub fn main() {\n let a = Thing(then: Nil)\n let b = Thing(..a, then: Nil)\n b.then\n}\n" --- ----- SOURCE CODE pub type Thing { Thing(then: Nil) } pub fn main() { let a = Thing(then: Nil) let b = Thing(..a, then: Nil) b.then } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Thing extends $CustomType { constructor(then$) { super(); this.then$ = then$; } } export const Thing$Thing = (then$) => new Thing(then$); export const Thing$isThing = (value) => value instanceof Thing; export const Thing$Thing$then = (value) => value.then$; export const Thing$Thing$0 = (value) => value.then$; export function main() { let a = new Thing(undefined); let b = new Thing(undefined); return b.then$; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_in_case_guard.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\npub type State {\n Active\n Inactive\n}\n\npub fn process(s: State) -> String {\n case s {\n state if state == Active -> \"active\"\n _ -> \"inactive\"\n }\n}\n" --- ----- SOURCE CODE pub type State { Active Inactive } pub fn process(s: State) -> String { case s { state if state == Active -> "active" _ -> "inactive" } } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Active extends $CustomType {} export const State$Active = () => new Active(); export const State$isActive = (value) => value instanceof Active; export class Inactive extends $CustomType {} export const State$Inactive = () => new Inactive(); export const State$isInactive = (value) => value instanceof Inactive; export function process(s) { let state = s; if (state instanceof Active) { return "active"; } else { return "inactive"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_equality.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\npub type Wibble {\n Wibble\n Wobble\n}\n\npub fn is_wibble(w: Wibble) -> Bool {\n w == Wibble\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble Wobble } pub fn is_wibble(w: Wibble) -> Bool { w == Wibble } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Wibble extends $CustomType {} export const Wibble$Wibble = () => new Wibble(); export const Wibble$isWibble = (value) => value instanceof Wibble; export class Wobble extends $CustomType {} export const Wibble$Wobble = () => new Wobble(); export const Wibble$isWobble = (value) => value instanceof Wobble; export function is_wibble(w) { return w instanceof Wibble; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_inequality.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\npub type Wibble {\n Wibble\n Wobble\n}\n\npub fn is_not_wibble(w: Wibble) -> Bool {\n w != Wibble\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble Wobble } pub fn is_not_wibble(w: Wibble) -> Bool { w != Wibble } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Wibble extends $CustomType {} export const Wibble$Wibble = () => new Wibble(); export const Wibble$isWibble = (value) => value instanceof Wibble; export class Wobble extends $CustomType {} export const Wibble$Wobble = () => new Wobble(); export const Wibble$isWobble = (value) => value instanceof Wobble; export function is_not_wibble(w) { return !(w instanceof Wibble); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_reverse_order.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\npub type Wibble {\n Wibble\n Wobble\n}\n\npub fn is_wibble_reverse(w: Wibble) -> Bool {\n Wibble == w\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble Wobble } pub fn is_wibble_reverse(w: Wibble) -> Bool { Wibble == w } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Wibble extends $CustomType {} export const Wibble$Wibble = () => new Wibble(); export const Wibble$isWibble = (value) => value instanceof Wibble; export class Wobble extends $CustomType {} export const Wibble$Wobble = () => new Wobble(); export const Wibble$isWobble = (value) => value instanceof Wobble; export function is_wibble_reverse(w) { return w instanceof Wibble; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__types_must_be_rendered_before_functions.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\npub fn one() { One }\npub type One { One }\n" --- ----- SOURCE CODE pub fn one() { One } pub type One { One } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class One extends $CustomType {} export const One$One = () => new One(); export const One$isOne = (value) => value instanceof One; export function one() { return new One(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__unapplied_record_constructors_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs assertion_line: 555 expression: "pub type Cat { Cat(name: String) }\n\npub fn return_unapplied_cat() {\n Cat\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub type Cat { Cat(name: String) } pub fn return_unapplied_cat() { Cat } ----- TYPESCRIPT DEFINITIONS import type * as _ from "../gleam.d.mts"; export class Cat extends _.CustomType { /** @deprecated */ constructor(name: string); /** @deprecated */ name: string; } export function Cat$Cat(name: string): Cat$; export function Cat$isCat(value: any): value is Cat$; export function Cat$Cat$0(value: Cat$): string; export function Cat$Cat$name(value: Cat$): string; export type Cat$ = Cat; export function return_unapplied_cat(): (x0: string) => Cat$; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__unnamed_fields.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\npub type Ip {\n Ip(String)\n}\n\npub const local = Ip(\"0.0.0.0\")\n\npub fn build(x) {\n x(\"1.2.3.4\")\n}\n\npub fn go() {\n build(Ip)\n Ip(\"5.6.7.8\")\n}\n\npub fn destructure(x) {\n let Ip(raw) = x\n raw\n}\n" --- ----- SOURCE CODE pub type Ip { Ip(String) } pub const local = Ip("0.0.0.0") pub fn build(x) { x("1.2.3.4") } pub fn go() { build(Ip) Ip("5.6.7.8") } pub fn destructure(x) { let Ip(raw) = x raw } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Ip extends $CustomType { constructor($0) { super(); this[0] = $0; } } export const Ip$Ip = ($0) => new Ip($0); export const Ip$isIp = (value) => value instanceof Ip; export const Ip$Ip$0 = (value) => value[0]; export const local = /* @__PURE__ */ new Ip("0.0.0.0"); export function build(x) { return x("1.2.3.4"); } export function go() { build((var0) => { return new Ip(var0); }); return new Ip("5.6.7.8"); } export function destructure(x) { let raw; raw = x[0]; return raw; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__unnamed_fields_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs assertion_line: 179 expression: "\npub type Ip{\n Ip(String)\n}\n\npub const local = Ip(\"0.0.0.0\")\n\n" snapshot_kind: text --- ----- SOURCE CODE pub type Ip{ Ip(String) } pub const local = Ip("0.0.0.0") ----- TYPESCRIPT DEFINITIONS import type * as _ from "../gleam.d.mts"; export class Ip extends _.CustomType { /** @deprecated */ constructor(argument$0: string); /** @deprecated */ 0: string; } export function Ip$Ip($0: string): Ip$; export function Ip$isIp(value: any): value is Ip$; export function Ip$Ip$0(value: Ip$): string; export type Ip$ = Ip; export const local: Ip$; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__unqualified_constructor_as_value.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other.{Two}\npub fn main() {\n Two\n}" --- ----- SOURCE CODE -- other.gleam pub type One { Two(a: Int, b: Int, c: Int) } -- main.gleam import other.{Two} pub fn main() { Two } ----- COMPILED JAVASCRIPT import * as $other from "../other.mjs"; import { Two } from "../other.mjs"; export function main() { return (var0, var1, var2) => { return new Two(var0, var1, var2); }; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__unqualified_imported_ignoring_label.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other.{Two}\npub fn main() {\n Two(1)\n}" --- ----- SOURCE CODE -- other.gleam pub type One { Two(field: Int) } -- main.gleam import other.{Two} pub fn main() { Two(1) } ----- COMPILED JAVASCRIPT import * as $other from "../other.mjs"; import { Two } from "../other.mjs"; export function main() { return new Two(1); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__unqualified_imported_multiple_fields.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other.{Two}\npub fn main() {\n Two(b: 2, c: 3, a: 1)\n}" --- ----- SOURCE CODE -- other.gleam pub type One { Two(a: Int, b: Int, c: Int) } -- main.gleam import other.{Two} pub fn main() { Two(b: 2, c: 3, a: 1) } ----- COMPILED JAVASCRIPT import * as $other from "../other.mjs"; import { Two } from "../other.mjs"; export function main() { return new Two(1, 2, 3); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__unqualified_imported_no_label.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other.{Two}\npub fn main() {\n Two(1)\n}" --- ----- SOURCE CODE -- other.gleam pub type One { Two(Int) } -- main.gleam import other.{Two} pub fn main() { Two(1) } ----- COMPILED JAVASCRIPT import * as $other from "../other.mjs"; import { Two } from "../other.mjs"; export function main() { return new Two(1); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__unqualified_imported_no_label_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other.{Two}\npub fn main() {\n Two(1)\n}" --- ----- SOURCE CODE import other.{Two} pub fn main() { Two(1) } ----- TYPESCRIPT DEFINITIONS import type * as $other from "../other.d.mts"; export function main(): $other.One$; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__unqualified_imported_using_label.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other.{Two}\npub fn main() {\n Two(field: 1)\n}" --- ----- SOURCE CODE -- other.gleam pub type One { Two(field: Int) } -- main.gleam import other.{Two} pub fn main() { Two(field: 1) } ----- COMPILED JAVASCRIPT import * as $other from "../other.mjs"; import { Two } from "../other.mjs"; export function main() { return new Two(1); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__unused_opaque_constructor_is_generated_correctly.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\ntype Wibble {\n Wibble\n}\n\npub opaque type Wobble {\n Wobble(Wibble)\n}\n" --- ----- SOURCE CODE type Wibble { Wibble } pub opaque type Wobble { Wobble(Wibble) } ----- TYPESCRIPT DEFINITIONS import type * as _ from "../gleam.d.mts"; declare class Wibble extends _.CustomType {} type Wibble$ = Wibble; declare class Wobble extends _.CustomType { /** @deprecated */ constructor(argument$0: Wibble$); /** @deprecated */ 0: Wibble$; } export type Wobble$ = Wobble; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_aliased_clause_guard.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\nimport other_module.{Variant as Aliased}\n\npub fn process(e) -> String {\n case e {\n value if value == Aliased -> \"match\"\n _ -> \"no match\"\n }\n}\n" --- ----- SOURCE CODE -- other_module.gleam pub type Thingy { Variant Other(Int) } -- main.gleam import other_module.{Variant as Aliased} pub fn process(e) -> String { case e { value if value == Aliased -> "match" _ -> "no match" } } ----- COMPILED JAVASCRIPT import * as $other_module from "../other_module.mjs"; import { Variant as Aliased } from "../other_module.mjs"; export function process(e) { let value = e; if (value instanceof Aliased) { return "match"; } else { return "no match"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_aliased_expression.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\nimport other_module.{Variant as Aliased}\n\npub fn check(x) -> Bool {\n x == Aliased\n}\n" --- ----- SOURCE CODE -- other_module.gleam pub type Thingy { Variant Other(Int) } -- main.gleam import other_module.{Variant as Aliased} pub fn check(x) -> Bool { x == Aliased } ----- COMPILED JAVASCRIPT import * as $other_module from "../other_module.mjs"; import { Variant as Aliased } from "../other_module.mjs"; export function check(x) { return x instanceof Variant; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_qualified_clause_guard.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\nimport other_module\n\npub fn process(e) -> String {\n case e {\n value if value == other_module.Variant -> \"match\"\n _ -> \"no match\"\n }\n}\n" --- ----- SOURCE CODE -- other_module.gleam pub type Thingy { Variant Other(Int) } -- main.gleam import other_module pub fn process(e) -> String { case e { value if value == other_module.Variant -> "match" _ -> "no match" } } ----- COMPILED JAVASCRIPT import * as $other_module from "../other_module.mjs"; export function process(e) { let value = e; if (value instanceof $other_module.Variant) { return "match"; } else { return "no match"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_qualified_expression.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\nimport other_module\n\npub fn check(x) -> Bool {\n x == other_module.Variant\n}\n" --- ----- SOURCE CODE -- other_module.gleam pub type Thingy { Variant OtherVariant } -- main.gleam import other_module pub fn check(x) -> Bool { x == other_module.Variant } ----- COMPILED JAVASCRIPT import * as $other_module from "../other_module.mjs"; export function check(x) { return x instanceof $other_module.Variant; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_unqualified_clause_guard.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\nimport other_module.{Variant}\n\npub fn process(e) -> String {\n case e {\n value if value == Variant -> \"match\"\n _ -> \"no match\"\n }\n}\n" --- ----- SOURCE CODE -- other_module.gleam pub type Thingy { Variant Other(Int) } -- main.gleam import other_module.{Variant} pub fn process(e) -> String { case e { value if value == Variant -> "match" _ -> "no match" } } ----- COMPILED JAVASCRIPT import * as $other_module from "../other_module.mjs"; import { Variant } from "../other_module.mjs"; export function process(e) { let value = e; if (value instanceof Variant) { return "match"; } else { return "no match"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_unqualified_expression.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\nimport other_module.{Variant}\n\npub fn check(x) -> Bool {\n x == Variant\n}\n" --- ----- SOURCE CODE -- other_module.gleam pub type Thingy { Variant Other(Int) } -- main.gleam import other_module.{Variant} pub fn check(x) -> Bool { x == Variant } ----- COMPILED JAVASCRIPT import * as $other_module from "../other_module.mjs"; import { Variant } from "../other_module.mjs"; export function check(x) { return x instanceof Variant; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__zero_arity_const.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\npub type Mine {\n This\n ThatOneIsAMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchLongerVariant\n}\n\npub const this = This\npub const that = ThatOneIsAMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchLongerVariant\n" --- ----- SOURCE CODE pub type Mine { This ThatOneIsAMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchLongerVariant } pub const this = This pub const that = ThatOneIsAMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchLongerVariant ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class This extends $CustomType {} export const Mine$This = () => new This(); export const Mine$isThis = (value) => value instanceof This; export class ThatOneIsAMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchLongerVariant extends $CustomType {} export const Mine$ThatOneIsAMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchLongerVariant = () => new ThatOneIsAMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchLongerVariant(); export const Mine$isThatOneIsAMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchLongerVariant = (value) => value instanceof ThatOneIsAMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchLongerVariant; export const this$ = /* @__PURE__ */ new This(); export const that = /* @__PURE__ */ new ThatOneIsAMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchLongerVariant(); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__zero_arity_imported.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other\npub fn main() {\n other.Two\n}" --- ----- SOURCE CODE -- other.gleam pub type One { Two } -- main.gleam import other pub fn main() { other.Two } ----- COMPILED JAVASCRIPT import * as $other from "../other.mjs"; export function main() { return new $other.Two(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__zero_arity_imported_typscript.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other\npub fn main() {\n other.Two\n}" --- ----- SOURCE CODE import other pub fn main() { other.Two } ----- TYPESCRIPT DEFINITIONS import type * as $other from "../other.d.mts"; export function main(): $other.One$; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__zero_arity_imported_unqualified.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other.{Two}\npub fn main() {\n Two\n}" --- ----- SOURCE CODE -- other.gleam pub type One { Two } -- main.gleam import other.{Two} pub fn main() { Two } ----- COMPILED JAVASCRIPT import * as $other from "../other.mjs"; import { Two } from "../other.mjs"; export function main() { return new Two(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__zero_arity_imported_unqualified_aliased.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other.{Two as Three}\npub fn main() {\n Three\n}" --- ----- SOURCE CODE -- other.gleam pub type One { Two } -- main.gleam import other.{Two as Three} pub fn main() { Three } ----- COMPILED JAVASCRIPT import * as $other from "../other.mjs"; import { Two as Three } from "../other.mjs"; export function main() { return new Three(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__zero_arity_imported_unqualified_aliased_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other.{Two as Three}\npub fn main() {\n Three\n}" --- ----- SOURCE CODE import other.{Two as Three} pub fn main() { Three } ----- TYPESCRIPT DEFINITIONS import type * as $other from "../other.d.mts"; export function main(): $other.One$; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__zero_arity_imported_unqualified_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "import other.{Two}\npub fn main() {\n Two\n}" --- ----- SOURCE CODE import other.{Two} pub fn main() { Two } ----- TYPESCRIPT DEFINITIONS import type * as $other from "../other.d.mts"; export function main(): $other.One$; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__zero_arity_literal.snap ================================================ --- source: compiler-core/src/javascript/tests/custom_types.rs expression: "\npub type Mine {\n This\n ThatOneIsAMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchLongerVariant\n}\n\npub fn go() {\n This\n ThatOneIsAMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchLongerVariant\n}\n" --- ----- SOURCE CODE pub type Mine { This ThatOneIsAMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchLongerVariant } pub fn go() { This ThatOneIsAMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchLongerVariant } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class This extends $CustomType {} export const Mine$This = () => new This(); export const Mine$isThis = (value) => value instanceof This; export class ThatOneIsAMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchLongerVariant extends $CustomType {} export const Mine$ThatOneIsAMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchLongerVariant = () => new ThatOneIsAMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchLongerVariant(); export const Mine$isThatOneIsAMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchLongerVariant = (value) => value instanceof ThatOneIsAMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchLongerVariant; export function go() { new This(); return new ThatOneIsAMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchMuchLongerVariant(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__echo__echo_evaluates_printed_value_before_message.snap ================================================ --- source: compiler-core/src/javascript/tests/echo.rs expression: "\npub fn main() {\n echo name() as case name() {\n \"Giacomo\" -> \"hello Jak!\"\n _ -> \"hello!\"\n }\n}\n\nfn name() { \"Giacomo\" }\n" --- ----- SOURCE CODE pub fn main() { echo name() as case name() { "Giacomo" -> "hello Jak!" _ -> "hello!" } } fn name() { "Giacomo" } ----- COMPILED JAVASCRIPT import * as $stdlib$dict from "../../gleam_stdlib/gleam/dict.mjs"; import { Empty as $Empty, NonEmpty as $NonEmpty, CustomType as $CustomType, bitArraySlice, bitArraySliceToInt, BitArray as $BitArray, List as $List, UtfCodepoint as $UtfCodepoint, } from "../gleam.mjs"; function name() { return "Giacomo"; } export function main() { return echo( name(), (() => { let $ = name(); if ($ === "Giacomo") { return "hello Jak!"; } else { return "hello!"; } })(), "src/module.gleam", 3, ); } // ...omitted code from `templates/echo.mjs`... ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__echo__echo_in_a_pipeline.snap ================================================ --- source: compiler-core/src/javascript/tests/echo.rs expression: "\npub fn main() {\n [1, 2, 3]\n |> echo\n |> wibble\n}\n\npub fn wibble(n) { n }\n" --- ----- SOURCE CODE pub fn main() { [1, 2, 3] |> echo |> wibble } pub fn wibble(n) { n } ----- COMPILED JAVASCRIPT import * as $stdlib$dict from "../../gleam_stdlib/gleam/dict.mjs"; import { toList, Empty as $Empty, NonEmpty as $NonEmpty, CustomType as $CustomType, bitArraySlice, bitArraySliceToInt, BitArray as $BitArray, List as $List, UtfCodepoint as $UtfCodepoint, } from "../gleam.mjs"; export function wibble(n) { return n; } export function main() { let _pipe = toList([1, 2, 3]); echo(_pipe, undefined, "src/module.gleam", 4) return wibble(_pipe); } // ...omitted code from `templates/echo.mjs`... ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__echo__echo_in_a_pipeline_with_message.snap ================================================ --- source: compiler-core/src/javascript/tests/echo.rs expression: "\npub fn main() {\n [1, 2, 3]\n |> echo as \"message!!\"\n |> wibble\n}\n\npub fn wibble(n) { n }\n" --- ----- SOURCE CODE pub fn main() { [1, 2, 3] |> echo as "message!!" |> wibble } pub fn wibble(n) { n } ----- COMPILED JAVASCRIPT import * as $stdlib$dict from "../../gleam_stdlib/gleam/dict.mjs"; import { toList, Empty as $Empty, NonEmpty as $NonEmpty, CustomType as $CustomType, bitArraySlice, bitArraySliceToInt, BitArray as $BitArray, List as $List, UtfCodepoint as $UtfCodepoint, } from "../gleam.mjs"; export function wibble(n) { return n; } export function main() { let _pipe = toList([1, 2, 3]); echo(_pipe, "message!!", "src/module.gleam", 4) return wibble(_pipe); } // ...omitted code from `templates/echo.mjs`... ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__echo__echo_with_a_block.snap ================================================ --- source: compiler-core/src/javascript/tests/echo.rs expression: "\npub fn main() {\n echo {\n Nil\n 1\n }\n}\n" --- ----- SOURCE CODE pub fn main() { echo { Nil 1 } } ----- COMPILED JAVASCRIPT import * as $stdlib$dict from "../../gleam_stdlib/gleam/dict.mjs"; import { Empty as $Empty, NonEmpty as $NonEmpty, CustomType as $CustomType, bitArraySlice, bitArraySliceToInt, BitArray as $BitArray, List as $List, UtfCodepoint as $UtfCodepoint, } from "../gleam.mjs"; export function main() { let _block; { undefined; _block = 1; } return echo(_block, undefined, "src/module.gleam", 3); } // ...omitted code from `templates/echo.mjs`... ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__echo__echo_with_a_block_as_a_message.snap ================================================ --- source: compiler-core/src/javascript/tests/echo.rs expression: "\npub fn main() {\n echo 1 as {\n let name = \"Giacomo\"\n \"Hello, \" <> name\n }\n}\n" --- ----- SOURCE CODE pub fn main() { echo 1 as { let name = "Giacomo" "Hello, " <> name } } ----- COMPILED JAVASCRIPT import * as $stdlib$dict from "../../gleam_stdlib/gleam/dict.mjs"; import { Empty as $Empty, NonEmpty as $NonEmpty, CustomType as $CustomType, bitArraySlice, bitArraySliceToInt, BitArray as $BitArray, List as $List, UtfCodepoint as $UtfCodepoint, } from "../gleam.mjs"; export function main() { return echo( 1, (() => { let name = "Giacomo"; return "Hello, " + name; })(), "src/module.gleam", 3, ); } // ...omitted code from `templates/echo.mjs`... ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__echo__echo_with_a_case_expression.snap ================================================ --- source: compiler-core/src/javascript/tests/echo.rs expression: "\npub fn main() {\n echo case 1 {\n _ -> 2\n }\n}\n" --- ----- SOURCE CODE pub fn main() { echo case 1 { _ -> 2 } } ----- COMPILED JAVASCRIPT import * as $stdlib$dict from "../../gleam_stdlib/gleam/dict.mjs"; import { Empty as $Empty, NonEmpty as $NonEmpty, CustomType as $CustomType, bitArraySlice, bitArraySliceToInt, BitArray as $BitArray, List as $List, UtfCodepoint as $UtfCodepoint, } from "../gleam.mjs"; export function main() { let _block; let $ = 1; _block = 2; return echo(_block, undefined, "src/module.gleam", 3); } // ...omitted code from `templates/echo.mjs`... ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__echo__echo_with_a_function_call.snap ================================================ --- source: compiler-core/src/javascript/tests/echo.rs expression: "\npub fn main() {\n echo wibble(1, 2)\n}\n\nfn wibble(n: Int, m: Int) { n + m }\n" --- ----- SOURCE CODE pub fn main() { echo wibble(1, 2) } fn wibble(n: Int, m: Int) { n + m } ----- COMPILED JAVASCRIPT import * as $stdlib$dict from "../../gleam_stdlib/gleam/dict.mjs"; import { Empty as $Empty, NonEmpty as $NonEmpty, CustomType as $CustomType, bitArraySlice, bitArraySliceToInt, BitArray as $BitArray, List as $List, UtfCodepoint as $UtfCodepoint, } from "../gleam.mjs"; function wibble(n, m) { return n + m; } export function main() { return echo(wibble(1, 2), undefined, "src/module.gleam", 3); } // ...omitted code from `templates/echo.mjs`... ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__echo__echo_with_a_function_call_and_a_message.snap ================================================ --- source: compiler-core/src/javascript/tests/echo.rs expression: "\npub fn main() {\n echo wibble(1, 2) as message()\n}\n\nfn wibble(n: Int, m: Int) { n + m }\nfn message() { \"Hello!\" }\n" --- ----- SOURCE CODE pub fn main() { echo wibble(1, 2) as message() } fn wibble(n: Int, m: Int) { n + m } fn message() { "Hello!" } ----- COMPILED JAVASCRIPT import * as $stdlib$dict from "../../gleam_stdlib/gleam/dict.mjs"; import { Empty as $Empty, NonEmpty as $NonEmpty, CustomType as $CustomType, bitArraySlice, bitArraySliceToInt, BitArray as $BitArray, List as $List, UtfCodepoint as $UtfCodepoint, } from "../gleam.mjs"; function wibble(n, m) { return n + m; } function message() { return "Hello!"; } export function main() { return echo(wibble(1, 2), message(), "src/module.gleam", 3); } // ...omitted code from `templates/echo.mjs`... ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__echo__echo_with_a_panic.snap ================================================ --- source: compiler-core/src/javascript/tests/echo.rs expression: "\npub fn main() {\n echo panic\n}\n" --- ----- SOURCE CODE pub fn main() { echo panic } ----- COMPILED JAVASCRIPT import * as $stdlib$dict from "../../gleam_stdlib/gleam/dict.mjs"; import { Empty as $Empty, NonEmpty as $NonEmpty, CustomType as $CustomType, makeError, bitArraySlice, bitArraySliceToInt, BitArray as $BitArray, List as $List, UtfCodepoint as $UtfCodepoint, } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main() { let _block; throw makeError( "panic", FILEPATH, "my/mod", 3, "main", "`panic` expression evaluated.", {} ) return echo(_block, undefined, "src/module.gleam", 3); } // ...omitted code from `templates/echo.mjs`... ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__echo__echo_with_a_simple_expression.snap ================================================ --- source: compiler-core/src/javascript/tests/echo.rs expression: "\npub fn main() {\n echo 1\n}\n" --- ----- SOURCE CODE pub fn main() { echo 1 } ----- COMPILED JAVASCRIPT import * as $stdlib$dict from "../../gleam_stdlib/gleam/dict.mjs"; import { Empty as $Empty, NonEmpty as $NonEmpty, CustomType as $CustomType, bitArraySlice, bitArraySliceToInt, BitArray as $BitArray, List as $List, UtfCodepoint as $UtfCodepoint, } from "../gleam.mjs"; export function main() { return echo(1, undefined, "src/module.gleam", 3); } // ...omitted code from `templates/echo.mjs`... ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__echo__echo_with_a_simple_expression_and_a_message.snap ================================================ --- source: compiler-core/src/javascript/tests/echo.rs expression: "\npub fn main() {\n echo 1 as \"hello!\"\n}\n" --- ----- SOURCE CODE pub fn main() { echo 1 as "hello!" } ----- COMPILED JAVASCRIPT import * as $stdlib$dict from "../../gleam_stdlib/gleam/dict.mjs"; import { Empty as $Empty, NonEmpty as $NonEmpty, CustomType as $CustomType, bitArraySlice, bitArraySliceToInt, BitArray as $BitArray, List as $List, UtfCodepoint as $UtfCodepoint, } from "../gleam.mjs"; export function main() { return echo(1, "hello!", "src/module.gleam", 3); } // ...omitted code from `templates/echo.mjs`... ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__echo__echo_with_complex_expression_as_a_message.snap ================================================ --- source: compiler-core/src/javascript/tests/echo.rs expression: "\npub fn main() {\n echo 1 as case name() {\n \"Giacomo\" -> \"hello Jak!\"\n _ -> \"hello!\"\n }\n}\n\nfn name() { \"Giacomo\" }\n" --- ----- SOURCE CODE pub fn main() { echo 1 as case name() { "Giacomo" -> "hello Jak!" _ -> "hello!" } } fn name() { "Giacomo" } ----- COMPILED JAVASCRIPT import * as $stdlib$dict from "../../gleam_stdlib/gleam/dict.mjs"; import { Empty as $Empty, NonEmpty as $NonEmpty, CustomType as $CustomType, bitArraySlice, bitArraySliceToInt, BitArray as $BitArray, List as $List, UtfCodepoint as $UtfCodepoint, } from "../gleam.mjs"; function name() { return "Giacomo"; } export function main() { return echo( 1, (() => { let $ = name(); if ($ === "Giacomo") { return "hello Jak!"; } else { return "hello!"; } })(), "src/module.gleam", 3, ); } // ...omitted code from `templates/echo.mjs`... ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__echo__module_named_inspect.snap ================================================ --- source: compiler-core/src/javascript/tests/echo.rs expression: "\nimport other/inspect\n\npub fn main() {\n echo inspect.x\n}\n" --- ----- SOURCE CODE import other/inspect pub fn main() { echo inspect.x } ----- COMPILED JAVASCRIPT import * as $stdlib$dict from "../../gleam_stdlib/gleam/dict.mjs"; import * as $inspect from "../../other/other/inspect.mjs"; import { Empty as $Empty, NonEmpty as $NonEmpty, CustomType as $CustomType, bitArraySlice, bitArraySliceToInt, BitArray as $BitArray, List as $List, UtfCodepoint as $UtfCodepoint, } from "../gleam.mjs"; export function main() { return echo($inspect.x, undefined, "src/module.gleam", 5); } // ...omitted code from `templates/echo.mjs`... ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__echo__multiple_echos_in_a_pipeline.snap ================================================ --- source: compiler-core/src/javascript/tests/echo.rs expression: "\npub fn main() {\n [1, 2, 3]\n |> echo\n |> wibble\n |> echo\n |> wibble\n |> echo\n}\n\npub fn wibble(n) { n }\n" --- ----- SOURCE CODE pub fn main() { [1, 2, 3] |> echo |> wibble |> echo |> wibble |> echo } pub fn wibble(n) { n } ----- COMPILED JAVASCRIPT import * as $stdlib$dict from "../../gleam_stdlib/gleam/dict.mjs"; import { toList, Empty as $Empty, NonEmpty as $NonEmpty, CustomType as $CustomType, bitArraySlice, bitArraySliceToInt, BitArray as $BitArray, List as $List, UtfCodepoint as $UtfCodepoint, } from "../gleam.mjs"; export function wibble(n) { return n; } export function main() { let _pipe = toList([1, 2, 3]); echo(_pipe, undefined, "src/module.gleam", 4) let _pipe$1 = wibble(_pipe); echo(_pipe$1, undefined, "src/module.gleam", 6) let _pipe$2 = wibble(_pipe$1); return echo(_pipe$2, undefined, "src/module.gleam", 8); } // ...omitted code from `templates/echo.mjs`... ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__echo__multiple_echos_inside_expression.snap ================================================ --- source: compiler-core/src/javascript/tests/echo.rs expression: "\npub fn main() {\n echo 1\n echo 2\n}\n" --- ----- SOURCE CODE pub fn main() { echo 1 echo 2 } ----- COMPILED JAVASCRIPT import * as $stdlib$dict from "../../gleam_stdlib/gleam/dict.mjs"; import { Empty as $Empty, NonEmpty as $NonEmpty, CustomType as $CustomType, bitArraySlice, bitArraySliceToInt, BitArray as $BitArray, List as $List, UtfCodepoint as $UtfCodepoint, } from "../gleam.mjs"; export function main() { echo(1, undefined, "src/module.gleam", 3); return echo(2, undefined, "src/module.gleam", 4); } // ...omitted code from `templates/echo.mjs`... ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__externals__at_namespace_module.snap ================================================ --- source: compiler-core/src/javascript/tests/externals.rs expression: "\n@external(javascript, \"@namespace/package\", \"inspect\")\nfn show(x: anything) -> Nil" --- ----- SOURCE CODE @external(javascript, "@namespace/package", "inspect") fn show(x: anything) -> Nil ----- COMPILED JAVASCRIPT import { inspect as show } from "@namespace/package"; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__externals__attribute_erlang.snap ================================================ --- source: compiler-core/src/javascript/tests/externals.rs assertion_line: 141 expression: "\n@external(erlang, \"one\", \"one_erl\")\npub fn one(x: Int) -> Int {\n todo\n}\n\npub fn main() {\n one(1)\n}\n" snapshot_kind: text --- ----- SOURCE CODE @external(erlang, "one", "one_erl") pub fn one(x: Int) -> Int { todo } pub fn main() { one(1) } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function one(x) { throw makeError( "todo", FILEPATH, "my/mod", 4, "one", "`todo` expression evaluated. This code has not yet been implemented.", {} ) } export function main() { return one(1); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__externals__attribute_javascript.snap ================================================ --- source: compiler-core/src/javascript/tests/externals.rs expression: "\n@external(javascript, \"./one.mjs\", \"oneJs\")\npub fn one(x: Int) -> Int {\n todo\n}\n\npub fn main() {\n one(1)\n}\n" --- ----- SOURCE CODE @external(javascript, "./one.mjs", "oneJs") pub fn one(x: Int) -> Int { todo } pub fn main() { one(1) } ----- COMPILED JAVASCRIPT import { oneJs as one } from "./one.mjs"; export { one }; export function main() { return one(1); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__externals__both_externals_no_valid_impl.snap ================================================ --- source: compiler-core/src/javascript/tests/externals.rs expression: "\n@external(javascript, \"one\", \"one\")\npub fn js() -> Nil\n\n@external(erlang, \"one\", \"one\")\npub fn erl() -> Nil\n\npub fn should_not_be_generated() {\n js()\n erl()\n}\n" --- ----- SOURCE CODE @external(javascript, "one", "one") pub fn js() -> Nil @external(erlang, "one", "one") pub fn erl() -> Nil pub fn should_not_be_generated() { js() erl() } ----- COMPILED JAVASCRIPT import { one as js } from "one"; export { js }; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__externals__discarded_names_in_external_are_passed_correctly.snap ================================================ --- source: compiler-core/src/javascript/tests/externals.rs expression: "\n@external(javascript, \"wibble\", \"wobble\")\npub fn woo(_ignored: a) -> Nil\n" --- ----- SOURCE CODE @external(javascript, "wibble", "wobble") pub fn woo(_ignored: a) -> Nil ----- COMPILED JAVASCRIPT import { wobble as woo } from "wibble"; export { woo }; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__externals__duplicate_import.snap ================================================ --- source: compiler-core/src/javascript/tests/externals.rs expression: "\n@external(javascript, \"./the/module.mjs\", \"dup\")\npub fn one() -> Nil\n\n@external(javascript, \"./the/module.mjs\", \"dup\")\npub fn two() -> Nil\n" --- ----- SOURCE CODE @external(javascript, "./the/module.mjs", "dup") pub fn one() -> Nil @external(javascript, "./the/module.mjs", "dup") pub fn two() -> Nil ----- COMPILED JAVASCRIPT import { dup as one, dup as two } from "./the/module.mjs"; export { one, two }; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__externals__erlang_and_javascript.snap ================================================ --- source: compiler-core/src/javascript/tests/externals.rs expression: "\n@external(erlang, \"one\", \"one\")\n@external(javascript, \"./one.mjs\", \"oneJs\")\npub fn one(x: Int) -> Int {\n todo\n}\n\npub fn main() {\n one(1)\n}\n" --- ----- SOURCE CODE @external(erlang, "one", "one") @external(javascript, "./one.mjs", "oneJs") pub fn one(x: Int) -> Int { todo } pub fn main() { one(1) } ----- COMPILED JAVASCRIPT import { oneJs as one } from "./one.mjs"; export { one }; export function main() { return one(1); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__externals__erlang_only.snap ================================================ --- source: compiler-core/src/javascript/tests/externals.rs expression: "\npub fn should_be_generated(x: Int) -> Int {\n x\n}\n\n@external(erlang, \"one\", \"one\")\npub fn should_not_be_generated(x: Int) -> Int\n" --- ----- SOURCE CODE pub fn should_be_generated(x: Int) -> Int { x } @external(erlang, "one", "one") pub fn should_not_be_generated(x: Int) -> Int ----- COMPILED JAVASCRIPT export function should_be_generated(x) { return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__externals__external_fn_escaping.snap ================================================ --- source: compiler-core/src/javascript/tests/externals.rs expression: "\n@external(javascript, \"./ffi.js\", \"then\")\npub fn then(a: a) -> b" --- ----- SOURCE CODE @external(javascript, "./ffi.js", "then") pub fn then(a: a) -> b ----- COMPILED JAVASCRIPT import { then as then$ } from "./ffi.js"; export { then$ }; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__externals__external_type_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/externals.rs expression: "pub type Queue(a)\n\n@external(javascript, \"queue\", \"new\")\npub fn new() -> Queue(a)\n" --- ----- SOURCE CODE pub type Queue(a) @external(javascript, "queue", "new") pub fn new() -> Queue(a) ----- TYPESCRIPT DEFINITIONS export type Queue$ = any; export function new$(): Queue$; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__externals__inline_function.snap ================================================ --- source: compiler-core/src/javascript/tests/externals.rs expression: "\n@external(javascript, \"blah\", \"(x => x)\")\npub fn one(x: Int) -> Int {\n 1\n}\n" --- ----- SOURCE CODE @external(javascript, "blah", "(x => x)") pub fn one(x: Int) -> Int { 1 } ----- ERROR error: Invalid JavaScript function ┌─ /src/one/two.gleam:3:1 │ 3 │ pub fn one(x: Int) -> Int { │ ^^^^^^^^^^^^^^^^^^^^^^^^^ The function `one` has an external JavaScript implementation but the function name `(x => x)` is not valid. ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__externals__module_fn.snap ================================================ --- source: compiler-core/src/javascript/tests/externals.rs expression: "\n@external(javascript, \"utils\", \"inspect\")\nfn show(x: anything) -> Nil" --- ----- SOURCE CODE @external(javascript, "utils", "inspect") fn show(x: anything) -> Nil ----- COMPILED JAVASCRIPT import { inspect as show } from "utils"; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__externals__name_to_escape.snap ================================================ --- source: compiler-core/src/javascript/tests/externals.rs expression: "\n@external(javascript, \"./the/module.mjs\", \"one\")\npub fn class() -> Nil\n" --- ----- SOURCE CODE @external(javascript, "./the/module.mjs", "one") pub fn class() -> Nil ----- COMPILED JAVASCRIPT import { one as class$ } from "./the/module.mjs"; export { class$ }; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__externals__no_body.snap ================================================ --- source: compiler-core/src/javascript/tests/externals.rs expression: "\n@external(javascript, \"one\", \"one\")\npub fn one(x: Int) -> Int\n" --- ----- SOURCE CODE @external(javascript, "one", "one") pub fn one(x: Int) -> Int ----- COMPILED JAVASCRIPT import { one } from "one"; export { one }; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__externals__no_module.snap ================================================ --- source: compiler-core/src/javascript/tests/externals.rs expression: "\n@external(javascript, \"\", \"one\")\npub fn one(x: Int) -> Int {\n 1\n}\n" --- ----- SOURCE CODE @external(javascript, "", "one") pub fn one(x: Int) -> Int { 1 } ----- ERROR error: Invalid JavaScript module ┌─ /src/one/two.gleam:3:1 │ 3 │ pub fn one(x: Int) -> Int { │ ^^^^^^^^^^^^^^^^^^^^^^^^^ The function `one` has an external JavaScript implementation but the module path `` is not valid. ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__externals__pipe_variable_shadow.snap ================================================ --- source: compiler-core/src/javascript/tests/externals.rs expression: "\n@external(javascript, \"module\", \"string\")\nfn name() -> String\n\npub fn main() {\n let name = name()\n name\n}\n" --- ----- SOURCE CODE @external(javascript, "module", "string") fn name() -> String pub fn main() { let name = name() name } ----- COMPILED JAVASCRIPT import { string as name } from "module"; export function main() { let name$1 = name(); return name$1; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__externals__private_attribute_erlang.snap ================================================ --- source: compiler-core/src/javascript/tests/externals.rs assertion_line: 190 expression: "\n@external(erlang, \"one\", \"one_erl\")\nfn one(x: Int) -> Int {\n todo\n}\n\npub fn main() {\n one(1)\n}\n" snapshot_kind: text --- ----- SOURCE CODE @external(erlang, "one", "one_erl") fn one(x: Int) -> Int { todo } pub fn main() { one(1) } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; function one(x) { throw makeError( "todo", FILEPATH, "my/mod", 4, "one", "`todo` expression evaluated. This code has not yet been implemented.", {} ) } export function main() { return one(1); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__externals__private_attribute_javascript.snap ================================================ --- source: compiler-core/src/javascript/tests/externals.rs expression: "\n@external(javascript, \"./one.mjs\", \"oneJs\")\nfn one(x: Int) -> Int {\n todo\n}\n\npub fn main() {\n one(1)\n}\n" --- ----- SOURCE CODE @external(javascript, "./one.mjs", "oneJs") fn one(x: Int) -> Int { todo } pub fn main() { one(1) } ----- COMPILED JAVASCRIPT import { oneJs as one } from "./one.mjs"; export function main() { return one(1); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__externals__private_erlang_and_javascript.snap ================================================ --- source: compiler-core/src/javascript/tests/externals.rs expression: "\n@external(erlang, \"one\", \"one\")\n@external(javascript, \"./one.mjs\", \"oneJs\")\nfn one(x: Int) -> Int {\n todo\n}\n\npub fn main() {\n one(1)\n}\n" --- ----- SOURCE CODE @external(erlang, "one", "one") @external(javascript, "./one.mjs", "oneJs") fn one(x: Int) -> Int { todo } pub fn main() { one(1) } ----- COMPILED JAVASCRIPT import { oneJs as one } from "./one.mjs"; export function main() { return one(1); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__externals__pub_module_fn.snap ================================================ --- source: compiler-core/src/javascript/tests/externals.rs expression: "\n@external(javascript, \"utils\", \"inspect\")\npub fn show(x: anything) -> Nil" --- ----- SOURCE CODE @external(javascript, "utils", "inspect") pub fn show(x: anything) -> Nil ----- COMPILED JAVASCRIPT import { inspect as show } from "utils"; export { show }; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__externals__pub_module_fn_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/externals.rs expression: "\n@external(javascript, \"utils\", \"inspect\")\npub fn show(x: anything) -> Nil" --- ----- SOURCE CODE @external(javascript, "utils", "inspect") pub fn show(x: anything) -> Nil ----- TYPESCRIPT DEFINITIONS export function show(x: any): undefined; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__externals__same_module_multiple_imports.snap ================================================ --- source: compiler-core/src/javascript/tests/externals.rs expression: "\n@external(javascript, \"./the/module.mjs\", \"one\")\npub fn one() -> Nil\n\n@external(javascript, \"./the/module.mjs\", \"two\")\npub fn two() -> Nil\n" --- ----- SOURCE CODE @external(javascript, "./the/module.mjs", "one") pub fn one() -> Nil @external(javascript, "./the/module.mjs", "two") pub fn two() -> Nil ----- COMPILED JAVASCRIPT import { one, two } from "./the/module.mjs"; export { one, two }; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__externals__same_name_external.snap ================================================ --- source: compiler-core/src/javascript/tests/externals.rs expression: "\n@external(javascript, \"thingy\", \"fetch\")\npub fn fetch(request: Nil) -> Nil" --- ----- SOURCE CODE @external(javascript, "thingy", "fetch") pub fn fetch(request: Nil) -> Nil ----- COMPILED JAVASCRIPT import { fetch } from "thingy"; export { fetch }; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__externals__tf_type_name_usage.snap ================================================ --- source: compiler-core/src/javascript/tests/externals.rs expression: "\npub type TESTitem\n\n@external(javascript, \"it\", \"one\")\npub fn one(a: TESTitem) -> TESTitem\n" --- ----- SOURCE CODE pub type TESTitem @external(javascript, "it", "one") pub fn one(a: TESTitem) -> TESTitem ----- TYPESCRIPT DEFINITIONS export type TESTitem$ = any; export function one(a: TESTitem$): TESTitem$; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__externals__type_.snap ================================================ --- source: compiler-core/src/javascript/tests/externals.rs expression: pub type Thing --- ----- SOURCE CODE pub type Thing ----- COMPILED JAVASCRIPT export {} ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__assert_last.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "pub fn main() {\n let assert x = 1\n}\n" --- ----- SOURCE CODE pub fn main() { let assert x = 1 } ----- COMPILED JAVASCRIPT export function main() { let x = 1; return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__bad_comma.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "\nfn function_with_a_long_name_that_is_intended_to_sit_right_on_the_limit() {\n Nil\n}\n\nfn identity(x) {\n x\n}\n\npub fn main() {\n function_with_a_long_name_that_is_intended_to_sit_right_on_the_limit()\n |> identity\n}\n" --- ----- SOURCE CODE fn function_with_a_long_name_that_is_intended_to_sit_right_on_the_limit() { Nil } fn identity(x) { x } pub fn main() { function_with_a_long_name_that_is_intended_to_sit_right_on_the_limit() |> identity } ----- COMPILED JAVASCRIPT function function_with_a_long_name_that_is_intended_to_sit_right_on_the_limit() { return undefined; } function identity(x) { return x; } export function main() { let _pipe = function_with_a_long_name_that_is_intended_to_sit_right_on_the_limit(); return identity(_pipe); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__calling_fn_literal.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "pub fn main() {\n fn(x) { x }(1)\n}\n" --- ----- SOURCE CODE pub fn main() { fn(x) { x }(1) } ----- COMPILED JAVASCRIPT export function main() { return 1; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__calling_functions.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "\npub fn twice(f: fn(t) -> t, x: t) -> t {\n f(f(x))\n}\npub fn add_one(x: Int) -> Int {\n x + 1\n}\npub fn add_two(x: Int) -> Int {\n twice(add_one, x)\n}\n\npub fn take_two(x: Int) -> Int {\n twice(fn(y) {y - 1}, x)\n}\n" --- ----- SOURCE CODE pub fn twice(f: fn(t) -> t, x: t) -> t { f(f(x)) } pub fn add_one(x: Int) -> Int { x + 1 } pub fn add_two(x: Int) -> Int { twice(add_one, x) } pub fn take_two(x: Int) -> Int { twice(fn(y) {y - 1}, x) } ----- COMPILED JAVASCRIPT export function twice(f, x) { return f(f(x)); } export function add_one(x) { return x + 1; } export function add_two(x) { return twice(add_one, x); } export function take_two(x) { return twice((y) => { return y - 1; }, x); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__case_in_call.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "pub fn main(f, x) {\n f(case x {\n 1 -> 2\n _ -> 0\n })\n}\n" --- ----- SOURCE CODE pub fn main(f, x) { f(case x { 1 -> 2 _ -> 0 }) } ----- COMPILED JAVASCRIPT export function main(f, x) { return f( (() => { if (x === 1) { return 2; } else { return 0; } })(), ); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__exported_functions.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "\npub fn add(x, y) {\n x + y\n}" --- ----- SOURCE CODE pub fn add(x, y) { x + y } ----- COMPILED JAVASCRIPT export function add(x, y) { return x + y; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__fn_return_fn_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "pub fn main(f: fn(Int) -> Int) {\n let func = fn(x, y) { f(x) + f(y) }\n func\n}\n" --- ----- SOURCE CODE pub fn main(f: fn(Int) -> Int) { let func = fn(x, y) { f(x) + f(y) } func } ----- TYPESCRIPT DEFINITIONS export function main(f: (x0: number) => number): (x0: number, x1: number) => number; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__function_formatting.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "\npub fn add(the_first_variable_that_should_be_added, the_second_variable_that_should_be_added) {\n the_first_variable_that_should_be_added + the_second_variable_that_should_be_added\n}" --- ----- SOURCE CODE pub fn add(the_first_variable_that_should_be_added, the_second_variable_that_should_be_added) { the_first_variable_that_should_be_added + the_second_variable_that_should_be_added } ----- COMPILED JAVASCRIPT export function add( the_first_variable_that_should_be_added, the_second_variable_that_should_be_added ) { return the_first_variable_that_should_be_added + the_second_variable_that_should_be_added; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__function_formatting1.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "\npub fn this_function_really_does_have_a_ludicrously_unfeasibly_long_name_for_a_function(x, y) {\nx + y\n}" --- ----- SOURCE CODE pub fn this_function_really_does_have_a_ludicrously_unfeasibly_long_name_for_a_function(x, y) { x + y } ----- COMPILED JAVASCRIPT export function this_function_really_does_have_a_ludicrously_unfeasibly_long_name_for_a_function( x, y ) { return x + y; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__function_formatting2.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "\npub fn add(x, y) {\nx + y\n}\n\npub fn long() {\n add(1, add(1, add(1, add(1, add(1, add(1, add(1, add(1, add(1, add(1, add(1, add(1, add(1, add(1, add(1, 1)))))))))))))))\n}" --- ----- SOURCE CODE pub fn add(x, y) { x + y } pub fn long() { add(1, add(1, add(1, add(1, add(1, add(1, add(1, add(1, add(1, add(1, add(1, add(1, add(1, add(1, add(1, 1))))))))))))))) } ----- COMPILED JAVASCRIPT export function add(x, y) { return x + y; } export function long() { return add( 1, add( 1, add( 1, add( 1, add( 1, add( 1, add( 1, add( 1, add(1, add(1, add(1, add(1, add(1, add(1, add(1, 1))))))), ), ), ), ), ), ), ), ); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__function_formatting3.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "\npub fn math(x, y) {\n fn() {\n x + y\n x - y\n 2 * x\n }\n}" --- ----- SOURCE CODE pub fn math(x, y) { fn() { x + y x - y 2 * x } } ----- COMPILED JAVASCRIPT export function math(x, y) { return () => { x + y; x - y; return 2 * x; }; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__function_formatting_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "\npub fn add(the_first_variable_that_should_be_added, the_second_variable_that_should_be_added) {\n the_first_variable_that_should_be_added + the_second_variable_that_should_be_added\n}" --- ----- SOURCE CODE pub fn add(the_first_variable_that_should_be_added, the_second_variable_that_should_be_added) { the_first_variable_that_should_be_added + the_second_variable_that_should_be_added } ----- TYPESCRIPT DEFINITIONS export function add( the_first_variable_that_should_be_added: number, the_second_variable_that_should_be_added: number ): number; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__function_formatting_typescript1.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "\npub fn this_function_really_does_have_a_ludicrously_unfeasibly_long_name_for_a_function(x, y) {\nx + y\n}" --- ----- SOURCE CODE pub fn this_function_really_does_have_a_ludicrously_unfeasibly_long_name_for_a_function(x, y) { x + y } ----- TYPESCRIPT DEFINITIONS export function this_function_really_does_have_a_ludicrously_unfeasibly_long_name_for_a_function( x: number, y: number ): number; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__function_literals_get_properly_wrapped_1.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "pub fn main() {\n fn(n) { n + 1 }(10)\n}\n" --- ----- SOURCE CODE pub fn main() { fn(n) { n + 1 }(10) } ----- COMPILED JAVASCRIPT export function main() { return 10 + 1; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__function_literals_get_properly_wrapped_2.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "pub fn main() {\n { fn(n) { n + 1 } }(10)\n}\n" --- ----- SOURCE CODE pub fn main() { { fn(n) { n + 1 } }(10) } ----- COMPILED JAVASCRIPT export function main() { return 10 + 1; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__function_literals_get_properly_wrapped_3.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "pub fn main() {\n { let a = fn(n) { n + 1 } }(10)\n}\n" --- ----- SOURCE CODE pub fn main() { { let a = fn(n) { n + 1 } }(10) } ----- COMPILED JAVASCRIPT export function main() { return ((n) => { return n + 1; })(10); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__immediately_invoked_function_expressions_include_statement_level.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "\nfn identity(x) { x }\n\npub type Wibble {\n Wibble(a: Int, b: Int)\n}\n\npub fn main() {\n let w = Wibble(1, 2)\n identity(Wibble(..w |> identity, b: 4)) |> identity\n}\n" --- ----- SOURCE CODE fn identity(x) { x } pub type Wibble { Wibble(a: Int, b: Int) } pub fn main() { let w = Wibble(1, 2) identity(Wibble(..w |> identity, b: 4)) |> identity } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Wibble extends $CustomType { constructor(a, b) { super(); this.a = a; this.b = b; } } export const Wibble$Wibble = (a, b) => new Wibble(a, b); export const Wibble$isWibble = (value) => value instanceof Wibble; export const Wibble$Wibble$a = (value) => value.a; export const Wibble$Wibble$0 = (value) => value.a; export const Wibble$Wibble$b = (value) => value.b; export const Wibble$Wibble$1 = (value) => value.b; function identity(x) { return x; } export function main() { let w = new Wibble(1, 2); let _pipe = identity((() => { let _block; let _pipe = w; _block = identity(_pipe); let _record = _block; return new Wibble(_record.a, 4); })()); return identity(_pipe); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__internal_function_gets_ignored_jsdoc.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "\n/// Hello! This is the documentation of the `main`\n/// function, which is internal!\n///\n@internal\npub fn main() { 1 }\n" --- ----- SOURCE CODE /// Hello! This is the documentation of the `main` /// function, which is internal! /// @internal pub fn main() { 1 } ----- COMPILED JAVASCRIPT /** * Hello! This is the documentation of the `main` * function, which is internal! * * @ignore */ export function main() { return 1; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__keyword_in_recursive_function.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "pub fn main(with: Int) -> Nil {\n main(with - 1)\n}\n" --- ----- SOURCE CODE pub fn main(with: Int) -> Nil { main(with - 1) } ----- COMPILED JAVASCRIPT export function main(loop$with) { while (true) { let with$ = loop$with; loop$with = with$ - 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__labelled_argument_ordering.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "\ntype A { A }\ntype B { B }\ntype C { C }\ntype D { D }\n\nfn wibble(a a: A, b b: B, c c: C, d d: D) {\n Nil\n}\n\npub fn main() {\n wibble(A, C, D, b: B)\n wibble(A, C, D, b: B)\n wibble(B, C, D, a: A)\n wibble(B, C, a: A, d: D)\n wibble(B, C, d: D, a: A)\n wibble(B, D, a: A, c: C)\n wibble(B, D, c: C, a: A)\n wibble(C, D, b: B, a: A)\n}\n" --- ----- SOURCE CODE type A { A } type B { B } type C { C } type D { D } fn wibble(a a: A, b b: B, c c: C, d d: D) { Nil } pub fn main() { wibble(A, C, D, b: B) wibble(A, C, D, b: B) wibble(B, C, D, a: A) wibble(B, C, a: A, d: D) wibble(B, C, d: D, a: A) wibble(B, D, a: A, c: C) wibble(B, D, c: C, a: A) wibble(C, D, b: B, a: A) } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; class A extends $CustomType {} class B extends $CustomType {} class C extends $CustomType {} class D extends $CustomType {} function wibble(a, b, c, d) { return undefined; } export function main() { wibble(new A(), new B(), new C(), new D()); wibble(new A(), new B(), new C(), new D()); wibble(new A(), new B(), new C(), new D()); wibble(new A(), new B(), new C(), new D()); wibble(new A(), new B(), new C(), new D()); wibble(new A(), new B(), new C(), new D()); wibble(new A(), new B(), new C(), new D()); return wibble(new A(), new B(), new C(), new D()); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__let_last.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "pub fn main() {\n let x = 1\n}\n" --- ----- SOURCE CODE pub fn main() { let x = 1 } ----- COMPILED JAVASCRIPT export function main() { let x = 1; return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__module_const_fn.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "\npub fn int_identity(i: Int) -> Int { i }\npub const int_identity_alias: fn(Int) -> Int = int_identity\npub fn use_int_identity_alias() { int_identity_alias(42) }\n\npub const compound: #(fn(Int) -> Int, fn(Int) -> Int) = #(int_identity, int_identity_alias)\npub fn use_compound() { compound.0(compound.1(42)) }" --- ----- SOURCE CODE pub fn int_identity(i: Int) -> Int { i } pub const int_identity_alias: fn(Int) -> Int = int_identity pub fn use_int_identity_alias() { int_identity_alias(42) } pub const compound: #(fn(Int) -> Int, fn(Int) -> Int) = #(int_identity, int_identity_alias) pub fn use_compound() { compound.0(compound.1(42)) } ----- COMPILED JAVASCRIPT export const int_identity_alias = int_identity; export const compound = [int_identity, int_identity_alias]; export function int_identity(i) { return i; } export function use_int_identity_alias() { return int_identity_alias(42); } export function use_compound() { return compound[0](compound[1](42)); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__module_const_fn1.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "\npub fn int_identity(i: Int) -> Int { i }\npub const int_identity_alias: fn(Int) -> Int = int_identity\npub const compound: #(fn(Int) -> Int, fn(Int) -> Int) =\n #(int_identity, int_identity_alias)" --- ----- SOURCE CODE pub fn int_identity(i: Int) -> Int { i } pub const int_identity_alias: fn(Int) -> Int = int_identity pub const compound: #(fn(Int) -> Int, fn(Int) -> Int) = #(int_identity, int_identity_alias) ----- TYPESCRIPT DEFINITIONS export const int_identity_alias: (x0: number) => number; export const compound: [(x0: number) => number, (x0: number) => number]; export function int_identity(i: number): number; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__multiple_discard.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "pub fn main(_, _, _) {\n 1\n}\n" --- ----- SOURCE CODE pub fn main(_, _, _) { 1 } ----- COMPILED JAVASCRIPT export function main(_, _1, _2) { return 1; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__no_recur_in_anon_fn.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "pub fn main() {\n fn() { main() }\n 1\n}\n" --- ----- SOURCE CODE pub fn main() { fn() { main() } 1 } ----- COMPILED JAVASCRIPT export function main() { () => { return main(); }; return 1; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__pipe_into_block.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "\nfn side_effects(x) { x }\n\npub fn main() {\n 1\n |> side_effects\n |> {\n side_effects(2)\n side_effects\n }\n}\n" --- ----- SOURCE CODE fn side_effects(x) { x } pub fn main() { 1 |> side_effects |> { side_effects(2) side_effects } } ----- COMPILED JAVASCRIPT function side_effects(x) { return x; } export function main() { let _pipe = 1; let _pipe$1 = side_effects(_pipe); let _block; { side_effects(2); _block = side_effects; } return _block(_pipe$1); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__pipe_last.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "fn id(x) { x }\npub fn main() {\n 1\n |> id\n}\n" --- ----- SOURCE CODE fn id(x) { x } pub fn main() { 1 |> id } ----- COMPILED JAVASCRIPT function id(x) { return x; } export function main() { let _pipe = 1; return id(_pipe); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__pipe_shadow_import.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "\n import wibble.{println}\n pub fn main() {\n let println =\n \"oh dear\"\n |> println\n println\n }" --- ----- SOURCE CODE -- wibble.gleam pub fn println(x: String) { } -- main.gleam import wibble.{println} pub fn main() { let println = "oh dear" |> println println } ----- COMPILED JAVASCRIPT import * as $wibble from "../wibble.mjs"; import { println } from "../wibble.mjs"; export function main() { let _block; let _pipe = "oh dear"; _block = println(_pipe); let println$1 = _block; return println$1; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__pipe_variable_rebinding.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "\npub fn main() {\n let version = 1 |> version()\n version\n}\n\npub fn version(n) {\n Ok(1)\n}" --- ----- SOURCE CODE pub fn main() { let version = 1 |> version() version } pub fn version(n) { Ok(1) } ----- COMPILED JAVASCRIPT import { Ok } from "../gleam.mjs"; export function version(n) { return new Ok(1); } export function main() { let _block; let _pipe = 1; _block = version(_pipe); let version$1 = _block; return version$1; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__pipe_with_block_in_the_middle.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "\nfn side_effects(x) { x }\n\npub fn main() {\n 1\n |> side_effects\n |> {\n side_effects(2)\n side_effects\n }\n |> side_effects\n}\n" --- ----- SOURCE CODE fn side_effects(x) { x } pub fn main() { 1 |> side_effects |> { side_effects(2) side_effects } |> side_effects } ----- COMPILED JAVASCRIPT function side_effects(x) { return x; } export function main() { let _pipe = 1; let _pipe$1 = side_effects(_pipe); let _block; { side_effects(2); _block = side_effects; } let _pipe$2 = _block(_pipe$1); return side_effects(_pipe$2); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__public_function_gets_jsdoc.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "\n/// Hello! This is the documentation of the `main`\n/// function.\n///\npub fn main() { 1 }\n" --- ----- SOURCE CODE /// Hello! This is the documentation of the `main` /// function. /// pub fn main() { 1 } ----- COMPILED JAVASCRIPT /** * Hello! This is the documentation of the `main` * function. */ export function main() { return 1; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__recursion_with_discards.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "pub fn main(f, _) {\n f()\n main(f, 1)\n}\n" --- ----- SOURCE CODE pub fn main(f, _) { f() main(f, 1) } ----- COMPILED JAVASCRIPT export function main(loop$f, _) { while (true) { let f = loop$f; f(); loop$f = f; 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__reserved_word_argument.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "pub fn main(with) {\n with\n}\n" --- ----- SOURCE CODE pub fn main(with) { with } ----- COMPILED JAVASCRIPT export function main(with$) { return with$; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__reserved_word_const.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "const in = 1\n\npub fn export() {\n in\n}\n" --- ----- SOURCE CODE const in = 1 pub fn export() { in } ----- COMPILED JAVASCRIPT const in$ = 1; export function export$() { return in$; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__reserved_word_fn.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "pub fn class() {\n Nil\n}\n" --- ----- SOURCE CODE pub fn class() { Nil } ----- COMPILED JAVASCRIPT export function class$() { return undefined; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__reserved_word_imported.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "import for.{class}\n\npub fn export() {\n class()\n}\n" --- ----- SOURCE CODE -- for.gleam pub fn class() { 1 } -- main.gleam import for.{class} pub fn export() { class() } ----- COMPILED JAVASCRIPT import * as $for from "../for.mjs"; import { class$ } from "../for.mjs"; export function export$() { return class$(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__reserved_word_imported_alias.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "import for.{class as while} as function\n\npub fn export() {\n let delete = function.class\n while()\n}\n" --- ----- SOURCE CODE -- for.gleam pub fn class() { 1 } -- main.gleam import for.{class as while} as function pub fn export() { let delete = function.class while() } ----- COMPILED JAVASCRIPT import * as $function from "../for.mjs"; import { class$ as while$ } from "../for.mjs"; export function export$() { let delete$ = $function.class$; return while$(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__reserved_word_in_function_arguments.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "pub fn main(arguments, eval) {\n #(arguments, eval)\n}\n" --- ----- SOURCE CODE pub fn main(arguments, eval) { #(arguments, eval) } ----- COMPILED JAVASCRIPT export function main(arguments$, eval$) { return [arguments$, eval$]; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__shadowing_current.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "pub fn main() {\n let main = fn() { 0 }\n main()\n}\n" --- ----- SOURCE CODE pub fn main() { let main = fn() { 0 } main() } ----- COMPILED JAVASCRIPT export function main() { let main$1 = () => { return 0; }; return main$1(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__star_slash_in_jsdoc.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs assertion_line: 591 expression: "\n/// */\n///\npub fn main() { 1 }\n" snapshot_kind: text --- ----- SOURCE CODE /// */ /// pub fn main() { 1 } ----- COMPILED JAVASCRIPT /** * *\/ */ export function main() { return 1; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__tail_call.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "\npub fn count(xs, n) {\n case xs {\n [] -> n\n [_, ..xs] -> count(xs, n + 1)\n }\n}\n" --- ----- SOURCE CODE pub fn count(xs, n) { case xs { [] -> n [_, ..xs] -> count(xs, n + 1) } } ----- COMPILED JAVASCRIPT import { Empty as $Empty } from "../gleam.mjs"; export function count(loop$xs, loop$n) { while (true) { let xs = loop$xs; let n = loop$n; if (xs instanceof $Empty) { return n; } else { let xs$1 = xs.tail; loop$xs = xs$1; loop$n = n + 1; } } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__tail_call_doesnt_clobber_tail_position_tracking.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "\npub fn loop(indentation) {\n case indentation > 0 {\n True -> loop(indentation - 1)\n False -> Nil\n }\n}\n" --- ----- SOURCE CODE pub fn loop(indentation) { case indentation > 0 { True -> loop(indentation - 1) False -> Nil } } ----- COMPILED JAVASCRIPT export function loop(loop$indentation) { while (true) { let indentation = loop$indentation; let $ = indentation > 0; if ($) { loop$indentation = indentation - 1; } else { return undefined; } } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__two_pipes_in_a_row.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "\npub type Function(a) {\n Function(fn() -> a)\n}\n\npub fn main() {\n [fn() { 1 } |> Function, fn() { 2 } |> Function]\n}\n" --- ----- SOURCE CODE pub type Function(a) { Function(fn() -> a) } pub fn main() { [fn() { 1 } |> Function, fn() { 2 } |> Function] } ----- COMPILED JAVASCRIPT import { toList, CustomType as $CustomType } from "../gleam.mjs"; export class Function extends $CustomType { constructor($0) { super(); this[0] = $0; } } export const Function$Function = ($0) => new Function($0); export const Function$isFunction = (value) => value instanceof Function; export const Function$Function$0 = (value) => value[0]; export function main() { return toList([ (() => { let _pipe = () => { return 1; }; return new Function(_pipe); })(), (() => { let _pipe = () => { return 2; }; return new Function(_pipe); })(), ]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__variable_rewriting_in_anon_fn_with_matching_parameter.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "pub fn bad() {\n fn(state) {\n let state = state\n state\n }\n}\n" --- ----- SOURCE CODE pub fn bad() { fn(state) { let state = state state } } ----- COMPILED JAVASCRIPT export function bad() { return (state) => { let state$1 = state; return state$1; }; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__variable_rewriting_in_anon_fn_with_matching_parameter_in_case.snap ================================================ --- source: compiler-core/src/javascript/tests/functions.rs expression: "pub fn bad() {\n fn(state) {\n let state = case Nil {\n _ -> state\n }\n state\n }\n}\n" --- ----- SOURCE CODE pub fn bad() { fn(state) { let state = case Nil { _ -> state } state } } ----- COMPILED JAVASCRIPT export function bad() { return (state) => { let _block; let $ = undefined; _block = state; let state$1 = _block; return state$1; }; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__generics__fn_generics_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/generics.rs expression: "pub fn identity(a) -> a {\n a\n}\n" --- ----- SOURCE CODE pub fn identity(a) -> a { a } ----- TYPESCRIPT DEFINITIONS export function identity(a: J): J; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__generics__record_generics_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/generics.rs assertion_line: 15 expression: "pub type Animal(t) {\n Cat(type_: t)\n Dog(type_: t)\n}\n\npub fn main() {\n Cat(type_: 6)\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub type Animal(t) { Cat(type_: t) Dog(type_: t) } pub fn main() { Cat(type_: 6) } ----- TYPESCRIPT DEFINITIONS import type * as _ from "../gleam.d.mts"; export class Cat extends _.CustomType { /** @deprecated */ constructor(type_: I); /** @deprecated */ type_: I; } export function Animal$Cat(type_: I): Animal$; export function Animal$isCat(value: any): value is Animal$; export function Animal$Cat$0(value: Animal$): I; export function Animal$Cat$type_(value: Animal$): I; export class Dog extends _.CustomType { /** @deprecated */ constructor(type_: I); /** @deprecated */ type_: I; } export function Animal$Dog(type_: I): Animal$; export function Animal$isDog(value: any): value is Animal$; export function Animal$Dog$0(value: Animal$): I; export function Animal$Dog$type_(value: Animal$): I; export type Animal$ = Cat | Dog; export function Animal$type_(value: Animal$): I; export function main(): Animal$; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__generics__result_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/generics.rs expression: "pub fn map(result, fun) {\n case result {\n Ok(a) -> Ok(fun(a))\n Error(e) -> Error(e)\n }\n }" --- ----- SOURCE CODE pub fn map(result, fun) { case result { Ok(a) -> Ok(fun(a)) Error(e) -> Error(e) } } ----- TYPESCRIPT DEFINITIONS import type * as _ from "../gleam.d.mts"; export function map(result: _.Result, fun: (x0: T) => V): _.Result< V, S >; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__generics__task_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/generics.rs expression: "pub type Promise(value)\n pub type Task(a) = fn() -> Promise(a)" --- ----- SOURCE CODE pub type Promise(value) pub type Task(a) = fn() -> Promise(a) ----- TYPESCRIPT DEFINITIONS export type Promise$ = any; export type Task = () => Promise$; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__generics__tuple_generics_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/generics.rs expression: "pub fn make_tuple(x: t) -> #(Int, t, Int) {\n #(0, x, 1)\n}\n" --- ----- SOURCE CODE pub fn make_tuple(x: t) -> #(Int, t, Int) { #(0, x, 1) } ----- TYPESCRIPT DEFINITIONS export function make_tuple(x: I): [number, I, number]; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__inlining__blocks_get_preserved_when_needed.snap ================================================ --- source: compiler-core/src/javascript/tests/inlining.rs expression: "\npub fn main() {\n { 4 |> make_adder }(6)\n}\n\nfn make_adder(a) {\n fn(b) { a + b }\n}\n" --- ----- SOURCE CODE pub fn main() { { 4 |> make_adder }(6) } fn make_adder(a) { fn(b) { a + b } } ----- COMPILED JAVASCRIPT function make_adder(a) { return (b) => { return a + b; }; } export function main() { let _block; let _pipe = 4; _block = make_adder(_pipe); return _block(6); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__inlining__blocks_get_preserved_when_needed2.snap ================================================ --- source: compiler-core/src/javascript/tests/inlining.rs expression: "\npub fn main() {\n fn(x) { 1 + x }(2) * 3\n}\n" --- ----- SOURCE CODE pub fn main() { fn(x) { 1 + x }(2) * 3 } ----- COMPILED JAVASCRIPT export function main() { return (1 + 2) * 3; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__inlining__do_not_inline_parameters_that_have_side_effects.snap ================================================ --- source: compiler-core/src/javascript/tests/inlining.rs expression: "\nimport gleam/result\n\npub fn main() {\n result.map(Ok(10), do_side_effects())\n}\n\nfn do_side_effects() {\n let function = fn(x) { x + 1 }\n panic as \"Side effects\"\n function\n}\n" --- ----- SOURCE CODE import gleam/result pub fn main() { result.map(Ok(10), do_side_effects()) } fn do_side_effects() { let function = fn(x) { x + 1 } panic as "Side effects" function } ----- COMPILED JAVASCRIPT import * as $result from "../../gleam_stdlib/gleam/result.mjs"; import { Ok, makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; function do_side_effects() { let function$ = (x) => { return x + 1; }; throw makeError( "panic", FILEPATH, "my/mod", 10, "do_side_effects", "Side effects", {} ) return function$; } export function main() { { let f = do_side_effects(); let $ = new Ok(10); if ($ instanceof Ok) { let value = $[0]; return new Ok(f(value)); } else { return $; } } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__inlining__do_not_inline_parameters_used_more_than_once.snap ================================================ --- source: compiler-core/src/javascript/tests/inlining.rs expression: "\nimport testing\n\npub fn main() {\n testing.always_inline(True)\n}\n" --- ----- SOURCE CODE import testing pub fn main() { testing.always_inline(True) } ----- COMPILED JAVASCRIPT import * as $testing from "../../gleam_stdlib/testing.mjs"; export function main() { { let something = true; if (something) { return something; } else { return something; } } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__inlining__inline_anonymous_function_call.snap ================================================ --- source: compiler-core/src/javascript/tests/inlining.rs expression: "\npub fn main() {\n fn(a, b) { #(a, b) }(42, False)\n}\n" --- ----- SOURCE CODE pub fn main() { fn(a, b) { #(a, b) }(42, False) } ----- COMPILED JAVASCRIPT export function main() { return [42, false]; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__inlining__inline_anonymous_function_in_pipe.snap ================================================ --- source: compiler-core/src/javascript/tests/inlining.rs expression: "\npub fn main() {\n 1 |> fn(x) { x + 1 } |> fn(y) { y * y }\n}\n" --- ----- SOURCE CODE pub fn main() { 1 |> fn(x) { x + 1 } |> fn(y) { y * y } } ----- COMPILED JAVASCRIPT export function main() { let _pipe = 1; let _pipe$1 = _pipe + 1; { let y = _pipe$1; return y * y; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__inlining__inline_function_capture_in_pipe.snap ================================================ --- source: compiler-core/src/javascript/tests/inlining.rs expression: "\npub fn main() {\n 1 |> add(4, _)\n}\n\nfn add(a, b) { a + b }\n" --- ----- SOURCE CODE pub fn main() { 1 |> add(4, _) } fn add(a, b) { a + b } ----- COMPILED JAVASCRIPT function add(a, b) { return a + b; } export function main() { let _pipe = 1; return add(4, _pipe); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__inlining__inline_function_which_calls_other_function.snap ================================================ --- source: compiler-core/src/javascript/tests/inlining.rs expression: "\nimport testing\n\npub fn main() {\n testing.always_inline(Ok(10), Error)\n}\n" --- ----- SOURCE CODE import testing pub fn main() { testing.always_inline(Ok(10), Error) } ----- COMPILED JAVASCRIPT import * as $testing from "../../gleam_stdlib/testing.mjs"; import { Ok, Error } from "../gleam.mjs"; export function main() { let $ = new Ok(10); if ($ instanceof Ok) { let value = $[0]; return new Error(value); } else { return $; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__inlining__inline_function_with_use.snap ================================================ --- source: compiler-core/src/javascript/tests/inlining.rs expression: "\nimport gleam/bool\n\npub fn divide(a, b) {\n use <- bool.guard(when: b == 0, return: 0)\n a / b\n}\n" --- ----- SOURCE CODE import gleam/bool pub fn divide(a, b) { use <- bool.guard(when: b == 0, return: 0) a / b } ----- COMPILED JAVASCRIPT import * as $bool from "../../gleam_stdlib/gleam/bool.mjs"; import { divideInt } from "../gleam.mjs"; export function divide(a, b) { let $ = b === 0; if ($) { return 0; } else { return divideInt(a, b); } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__inlining__inline_function_with_use_and_anonymous.snap ================================================ --- source: compiler-core/src/javascript/tests/inlining.rs expression: "\nimport gleam/bool\n\npub fn divide(a, b) {\n use <- bool.lazy_guard(b == 0, fn() { panic as \"Cannot divide by 0\" })\n a / b\n}\n" --- ----- SOURCE CODE import gleam/bool pub fn divide(a, b) { use <- bool.lazy_guard(b == 0, fn() { panic as "Cannot divide by 0" }) a / b } ----- COMPILED JAVASCRIPT import * as $bool from "../../gleam_stdlib/gleam/bool.mjs"; import { makeError, divideInt } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function divide(a, b) { let $ = b === 0; if ($) { throw makeError( "panic", FILEPATH, "my/mod", 5, "divide", "Cannot divide by 0", {} ) } else { return divideInt(a, b); } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__inlining__inline_function_with_use_becomes_tail_recursive.snap ================================================ --- source: compiler-core/src/javascript/tests/inlining.rs expression: "\nimport gleam/bool\n\npub fn count(from: Int, to: Int) -> Int {\n use <- bool.guard(when: from >= to, return: from)\n echo from\n count(from + 1, to)\n}\n" --- ----- SOURCE CODE import gleam/bool pub fn count(from: Int, to: Int) -> Int { use <- bool.guard(when: from >= to, return: from) echo from count(from + 1, to) } ----- COMPILED JAVASCRIPT import * as $bool from "../../gleam_stdlib/gleam/bool.mjs"; import * as $stdlib$dict from "../../gleam_stdlib/gleam/dict.mjs"; import { Empty as $Empty, NonEmpty as $NonEmpty, CustomType as $CustomType, bitArraySlice, bitArraySliceToInt, BitArray as $BitArray, List as $List, UtfCodepoint as $UtfCodepoint, } from "../gleam.mjs"; export function count(loop$from, loop$to) { while (true) { let from = loop$from; let to = loop$to; let $ = from >= to; if ($) { return from; } else { echo(from, undefined, "src/module.gleam", 6); loop$from = from + 1; loop$to = to; } } } // ...omitted code from `templates/echo.mjs`... ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__inlining__inline_higher_order_function.snap ================================================ --- source: compiler-core/src/javascript/tests/inlining.rs expression: "\nimport gleam/result\n\npub fn main() {\n result.map(over: Ok(10), with: double)\n}\n\nfn double(x) { x + x }\n" --- ----- SOURCE CODE import gleam/result pub fn main() { result.map(over: Ok(10), with: double) } fn double(x) { x + x } ----- COMPILED JAVASCRIPT import * as $result from "../../gleam_stdlib/gleam/result.mjs"; import { Ok } from "../gleam.mjs"; function double(x) { return x + x; } export function main() { let $ = new Ok(10); if ($ instanceof Ok) { let value = $[0]; return new Ok(double(value)); } else { return $; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__inlining__inline_higher_order_function_anonymous.snap ================================================ --- source: compiler-core/src/javascript/tests/inlining.rs expression: "\nimport gleam/result\n\npub fn main() {\n result.try(Ok(10), fn(value) {\n Ok({ value + 2 } * 4)\n })\n}\n" --- ----- SOURCE CODE import gleam/result pub fn main() { result.try(Ok(10), fn(value) { Ok({ value + 2 } * 4) }) } ----- COMPILED JAVASCRIPT import * as $result from "../../gleam_stdlib/gleam/result.mjs"; import { Ok } from "../gleam.mjs"; export function main() { let $ = new Ok(10); if ($ instanceof Ok) { let value = $[0]; return new Ok((value + 2) * 4); } else { return $; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__inlining__inline_higher_order_function_with_capture.snap ================================================ --- source: compiler-core/src/javascript/tests/inlining.rs expression: "\nimport gleam/result\n\npub fn main() {\n result.try(Ok(10), divide(_, 2))\n}\n\nfn divide(a: Int, b: Int) -> Result(Int, Nil) {\n case a % b {\n 0 -> Ok(a / b)\n _ -> Error(Nil)\n }\n}\n" --- ----- SOURCE CODE import gleam/result pub fn main() { result.try(Ok(10), divide(_, 2)) } fn divide(a: Int, b: Int) -> Result(Int, Nil) { case a % b { 0 -> Ok(a / b) _ -> Error(Nil) } } ----- COMPILED JAVASCRIPT import * as $result from "../../gleam_stdlib/gleam/result.mjs"; import { Ok, Error, remainderInt, divideInt } from "../gleam.mjs"; function divide(a, b) { let $ = remainderInt(a, b); if ($ === 0) { return new Ok(divideInt(a, b)); } else { return new Error(undefined); } } export function main() { let $ = new Ok(10); if ($ instanceof Ok) { let value = $[0]; return divide(value, 2); } else { return $; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__inlining__inline_shadowed_variable.snap ================================================ --- source: compiler-core/src/javascript/tests/inlining.rs expression: "\npub fn main() {\n let a = 10\n let b = 20\n\n fn(x) {\n let a = 7\n x + a\n }(a + b)\n\n a\n}\n" --- ----- SOURCE CODE pub fn main() { let a = 10 let b = 20 fn(x) { let a = 7 x + a }(a + b) a } ----- COMPILED JAVASCRIPT export function main() { let a = 10; let b = 20; { let _inline_a_0 = 7; (a + b) + _inline_a_0 }; return a; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__inlining__inline_shadowed_variable_nested.snap ================================================ --- source: compiler-core/src/javascript/tests/inlining.rs expression: "\npub fn sum(a, b) {\n fn(x) {\n let a = 7\n fn(y) {\n let a = 10\n y - a\n }(x + a)\n\n a\n }(a + b)\n\n a\n}\n" --- ----- SOURCE CODE pub fn sum(a, b) { fn(x) { let a = 7 fn(y) { let a = 10 y - a }(x + a) a }(a + b) a } ----- COMPILED JAVASCRIPT export function sum(a, b) { { let _inline_a_0 = 7; { let _inline_a_1 = 10; ((a + b) + _inline_a_0) - _inline_a_1 }; _inline_a_0 }; return a; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__inlining__inline_variable_shadowed_in_case_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/inlining.rs expression: "\npub fn sum() {\n let a = 10\n let b = 20\n\n fn(x) {\n case 7, 8 {\n a, b -> a + b + x\n }\n }(a + b)\n\n a + b\n}\n" --- ----- SOURCE CODE pub fn sum() { let a = 10 let b = 20 fn(x) { case 7, 8 { a, b -> a + b + x } }(a + b) a + b } ----- COMPILED JAVASCRIPT export function sum() { let a = 10; let b = 20; let $ = 7; let $1 = 8; let _inline_a_0 = $; let _inline_b_1 = $1; (_inline_a_0 + _inline_b_1) + (a + b) return a + b; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__inlining__inline_variable_shadowing_case_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/inlining.rs expression: "\npub fn sum() {\n case 1, 2 {\n a, b -> fn(x) {\n let a = 7\n x + a\n }(a + b)\n }\n}\n" --- ----- SOURCE CODE pub fn sum() { case 1, 2 { a, b -> fn(x) { let a = 7 x + a }(a + b) } } ----- COMPILED JAVASCRIPT export function sum() { let $ = 1; let $1 = 2; let a = $; let b = $1; let _inline_a_0 = 7; return (a + b) + _inline_a_0; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__inlining__inline_variable_shadowing_parameter.snap ================================================ --- source: compiler-core/src/javascript/tests/inlining.rs expression: "\npub fn sum(a, b) {\n fn(x) {\n let a = 7\n x + a\n }(a + b)\n\n a\n}\n" --- ----- SOURCE CODE pub fn sum(a, b) { fn(x) { let a = 7 x + a }(a + b) a } ----- COMPILED JAVASCRIPT export function sum(a, b) { { let _inline_a_0 = 7; (a + b) + _inline_a_0 }; return a; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__inlining__inlining_works_properly_with_record_updates.snap ================================================ --- source: compiler-core/src/javascript/tests/inlining.rs expression: "\nimport gleam/result\n\npub type Wibble {\n Wibble(a: Int, b: Int)\n}\n\npub fn main() {\n let w = Wibble(1, 2)\n use b <- result.map(Ok(3))\n Wibble(..w, b:)\n}\n" --- ----- SOURCE CODE import gleam/result pub type Wibble { Wibble(a: Int, b: Int) } pub fn main() { let w = Wibble(1, 2) use b <- result.map(Ok(3)) Wibble(..w, b:) } ----- COMPILED JAVASCRIPT import * as $result from "../../gleam_stdlib/gleam/result.mjs"; import { Ok, CustomType as $CustomType } from "../gleam.mjs"; export class Wibble extends $CustomType { constructor(a, b) { super(); this.a = a; this.b = b; } } export const Wibble$Wibble = (a, b) => new Wibble(a, b); export const Wibble$isWibble = (value) => value instanceof Wibble; export const Wibble$Wibble$a = (value) => value.a; export const Wibble$Wibble$0 = (value) => value.a; export const Wibble$Wibble$b = (value) => value.b; export const Wibble$Wibble$1 = (value) => value.b; export function main() { let w = new Wibble(1, 2); let $ = new Ok(3); if ($ instanceof Ok) { let value = $[0]; return new Ok(new Wibble(w.a, value)); } else { return $; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__inlining__inlining_works_through_blocks.snap ================================================ --- source: compiler-core/src/javascript/tests/inlining.rs expression: "\npub fn main() {\n { fn(x) { Ok(x + 1) } }(41)\n}\n" --- ----- SOURCE CODE pub fn main() { { fn(x) { Ok(x + 1) } }(41) } ----- COMPILED JAVASCRIPT import { Ok } from "../gleam.mjs"; export function main() { return new Ok(41 + 1); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__inlining__parameters_from_nested_functions_are_correctly_inlined.snap ================================================ --- source: compiler-core/src/javascript/tests/inlining.rs expression: "\nimport gleam/result\n\npub fn halve_all(a, b, c) {\n use x <- result.try(divide(a, 2))\n use y <- result.try(divide(b, 2))\n use z <- result.map(divide(c, 2))\n\n #(x, y, z)\n}\n\nfn divide(a, b) {\n case a % b {\n 0 -> Ok(a / b)\n _ -> Error(Nil)\n }\n}\n" --- ----- SOURCE CODE import gleam/result pub fn halve_all(a, b, c) { use x <- result.try(divide(a, 2)) use y <- result.try(divide(b, 2)) use z <- result.map(divide(c, 2)) #(x, y, z) } fn divide(a, b) { case a % b { 0 -> Ok(a / b) _ -> Error(Nil) } } ----- COMPILED JAVASCRIPT import * as $result from "../../gleam_stdlib/gleam/result.mjs"; import { Ok, Error, remainderInt, divideInt } from "../gleam.mjs"; function divide(a, b) { let $ = remainderInt(a, b); if ($ === 0) { return new Ok(divideInt(a, b)); } else { return new Error(undefined); } } export function halve_all(a, b, c) { let $ = divide(a, 2); if ($ instanceof Ok) { let value = $[0]; let x = value; let $1 = divide(b, 2); if ($1 instanceof Ok) { let _inline_value_0 = $1[0]; let y = _inline_value_0; let $2 = divide(c, 2); if ($2 instanceof Ok) { let _inline_value_1 = $2[0]; return new Ok([x, y, _inline_value_1]); } else { return $2; } } else { return $1; } } else { return $; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__lists__case.snap ================================================ --- source: compiler-core/src/javascript/tests/lists.rs assertion_line: 89 expression: "\npub fn go(xs) {\n case xs {\n [] -> 0\n [_] -> 1\n [_, _] -> 2\n _ -> 9999\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(xs) { case xs { [] -> 0 [_] -> 1 [_, _] -> 2 _ -> 9999 } } ----- COMPILED JAVASCRIPT import { Empty as $Empty } from "../gleam.mjs"; export function go(xs) { if (xs instanceof $Empty) { return 0; } else { let $ = xs.tail; if ($ instanceof $Empty) { return 1; } else { let $1 = $.tail; if ($1 instanceof $Empty) { return 2; } else { return 9999; } } } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__lists__equality.snap ================================================ --- source: compiler-core/src/javascript/tests/lists.rs assertion_line: 77 expression: "\npub fn go() {\n [] == [1]\n [] != [1]\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { [] == [1] [] != [1] } ----- COMPILED JAVASCRIPT import { toList, isEqual } from "../gleam.mjs"; export function go() { isEqual(toList([]), toList([1])); return !isEqual(toList([]), toList([1])); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__lists__list_constants.snap ================================================ --- source: compiler-core/src/javascript/tests/lists.rs assertion_line: 42 expression: "\npub const a = []\npub const b = [1, 2, 3]\n" snapshot_kind: text --- ----- SOURCE CODE pub const a = [] pub const b = [1, 2, 3] ----- COMPILED JAVASCRIPT import { toList } from "../gleam.mjs"; export const a = /* @__PURE__ */ toList([]); export const b = /* @__PURE__ */ toList([1, 2, 3]); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__lists__list_constants_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/lists.rs expression: "\npub const a = []\npub const b = [1, 2, 3]\n" --- ----- SOURCE CODE pub const a = [] pub const b = [1, 2, 3] ----- TYPESCRIPT DEFINITIONS import type * as _ from "../gleam.d.mts"; export const a: _.List; export const b: _.List; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__lists__list_destructuring.snap ================================================ --- source: compiler-core/src/javascript/tests/lists.rs expression: "\npub fn go(x, y) {\n let assert [] = x\n let assert [a] = x\n let assert [1, 2] = x\n let assert [_, #(3, b)] = y\n let assert [head, ..tail] = y\n}\n" --- ----- SOURCE CODE pub fn go(x, y) { let assert [] = x let assert [a] = x let assert [1, 2] = x let assert [_, #(3, b)] = y let assert [head, ..tail] = y } ----- COMPILED JAVASCRIPT import { Empty as $Empty, makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x, y) { if (!(x instanceof $Empty)) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 21, end: 38, pattern_start: 32, pattern_end: 34 } ) } let a; if (x instanceof $Empty) { throw makeError( "let_assert", FILEPATH, "my/mod", 4, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 41, end: 59, pattern_start: 52, pattern_end: 55 } ) } else { let $ = x.tail; if ($ instanceof $Empty) { a = x.head; } else { throw makeError( "let_assert", FILEPATH, "my/mod", 4, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 41, end: 59, pattern_start: 52, pattern_end: 55 } ) } } if (x instanceof $Empty) { throw makeError( "let_assert", FILEPATH, "my/mod", 5, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 62, end: 83, pattern_start: 73, pattern_end: 79 } ) } else { let $1 = x.tail; if ($1 instanceof $Empty) { throw makeError( "let_assert", FILEPATH, "my/mod", 5, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 62, end: 83, pattern_start: 73, pattern_end: 79 } ) } else { let $2 = $1.tail; if ($2 instanceof $Empty) { let $3 = x.head; if ($3 === 1) { let $4 = $1.head; if (!($4 === 2)) { throw makeError( "let_assert", FILEPATH, "my/mod", 5, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 62, end: 83, pattern_start: 73, pattern_end: 79 } ) } } else { throw makeError( "let_assert", FILEPATH, "my/mod", 5, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 62, end: 83, pattern_start: 73, pattern_end: 79 } ) } } else { throw makeError( "let_assert", FILEPATH, "my/mod", 5, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 62, end: 83, pattern_start: 73, pattern_end: 79 } ) } } } let b; if (y instanceof $Empty) { throw makeError( "let_assert", FILEPATH, "my/mod", 6, "go", "Pattern match failed, no pattern matched the value.", { value: y, start: 86, end: 113, pattern_start: 97, pattern_end: 109 } ) } else { let $5 = y.tail; if ($5 instanceof $Empty) { throw makeError( "let_assert", FILEPATH, "my/mod", 6, "go", "Pattern match failed, no pattern matched the value.", { value: y, start: 86, end: 113, pattern_start: 97, pattern_end: 109 } ) } else { let $6 = $5.tail; if ($6 instanceof $Empty) { let $7 = $5.head[0]; if ($7 === 3) { b = $5.head[1]; } else { throw makeError( "let_assert", FILEPATH, "my/mod", 6, "go", "Pattern match failed, no pattern matched the value.", { value: y, start: 86, end: 113, pattern_start: 97, pattern_end: 109 } ) } } else { throw makeError( "let_assert", FILEPATH, "my/mod", 6, "go", "Pattern match failed, no pattern matched the value.", { value: y, start: 86, end: 113, pattern_start: 97, pattern_end: 109 } ) } } } let head; let tail; if (y instanceof $Empty) { throw makeError( "let_assert", FILEPATH, "my/mod", 7, "go", "Pattern match failed, no pattern matched the value.", { value: y, start: 116, end: 145, pattern_start: 127, pattern_end: 141 } ) } else { head = y.head; tail = y.tail; } return y; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__lists__list_literals.snap ================================================ --- source: compiler-core/src/javascript/tests/lists.rs assertion_line: 5 expression: "\npub fn go(x) {\n []\n [1]\n [1, 2]\n [1, 2, ..x]\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { [] [1] [1, 2] [1, 2, ..x] } ----- COMPILED JAVASCRIPT import { toList, prepend as listPrepend } from "../gleam.mjs"; export function go(x) { toList([]); toList([1]); toList([1, 2]); return listPrepend(1, listPrepend(2, x)); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__lists__long_list_literals.snap ================================================ --- source: compiler-core/src/javascript/tests/lists.rs assertion_line: 19 expression: "\npub fn go() {\n [111111111111111111111111111111111111111111111111111111111111111111111111]\n [11111111111111111111111111111111111111111111, 1111111111111111111111111111111111111111111]\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { [111111111111111111111111111111111111111111111111111111111111111111111111] [11111111111111111111111111111111111111111111, 1111111111111111111111111111111111111111111] } ----- COMPILED JAVASCRIPT import { toList } from "../gleam.mjs"; export function go() { toList([ 111111111111111111111111111111111111111111111111111111111111111111111111, ]); return toList([ 11111111111111111111111111111111111111111111, 1111111111111111111111111111111111111111111, ]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__lists__multi_line_list_literals.snap ================================================ --- source: compiler-core/src/javascript/tests/lists.rs assertion_line: 31 expression: "\npub fn go(x) {\n [{True 1}]\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { [{True 1}] } ----- COMPILED JAVASCRIPT import { toList } from "../gleam.mjs"; export function go(x) { return toList([ (() => { true; return 1; })(), ]); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__lists__tight_empty_list.snap ================================================ --- source: compiler-core/src/javascript/tests/lists.rs assertion_line: 106 expression: "\npub fn go(func) {\n let huuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuge_variable = []\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(func) { let huuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuge_variable = [] } ----- COMPILED JAVASCRIPT import { toList } from "../gleam.mjs"; export function go(func) { let huuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuge_variable = toList([]); return huuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuge_variable; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__modules__alias_aliased_constant.snap ================================================ --- source: compiler-core/src/javascript/tests/modules.rs assertion_line: 59 expression: "\nimport rocket_ship.{ x as y }\npub const z = y\n" snapshot_kind: text --- ----- SOURCE CODE -- rocket_ship.gleam pub const x = 1 -- main.gleam import rocket_ship.{ x as y } pub const z = y ----- COMPILED JAVASCRIPT import * as $rocket_ship from "../rocket_ship.mjs"; import { x as y } from "../rocket_ship.mjs"; export const z = y; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__modules__alias_constant.snap ================================================ --- source: compiler-core/src/javascript/tests/modules.rs expression: "\nimport rocket_ship as boop\npub fn go() { boop.x }\n" --- ----- SOURCE CODE -- rocket_ship.gleam pub const x = 1 -- main.gleam import rocket_ship as boop pub fn go() { boop.x } ----- COMPILED JAVASCRIPT import * as $boop from "../rocket_ship.mjs"; export function go() { return $boop.x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__modules__alias_fn_call.snap ================================================ --- source: compiler-core/src/javascript/tests/modules.rs expression: "\nimport rocket_ship as boop\npub fn go() { boop.go() }\n" --- ----- SOURCE CODE -- rocket_ship.gleam pub fn go() { 1 } -- main.gleam import rocket_ship as boop pub fn go() { boop.go() } ----- COMPILED JAVASCRIPT import * as $boop from "../rocket_ship.mjs"; export function go() { return $boop.go(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__modules__aliased_unqualified_fn_call.snap ================================================ --- source: compiler-core/src/javascript/tests/modules.rs expression: "import rocket_ship.{launch as boom_time}\npub fn go() { boom_time() }\n" --- ----- SOURCE CODE -- rocket_ship.gleam pub fn launch() { 1 } -- main.gleam import rocket_ship.{launch as boom_time} pub fn go() { boom_time() } ----- COMPILED JAVASCRIPT import * as $rocket_ship from "../rocket_ship.mjs"; import { launch as boom_time } from "../rocket_ship.mjs"; export function go() { return boom_time(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__modules__constant.snap ================================================ --- source: compiler-core/src/javascript/tests/modules.rs expression: "\nimport rocket_ship\npub fn go() { rocket_ship.x }\n" --- ----- SOURCE CODE -- rocket_ship.gleam pub const x = 1 -- main.gleam import rocket_ship pub fn go() { rocket_ship.x } ----- COMPILED JAVASCRIPT import * as $rocket_ship from "../rocket_ship.mjs"; export function go() { return $rocket_ship.x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__modules__constant_module_access_with_keyword.snap ================================================ --- source: compiler-core/src/javascript/tests/modules.rs expression: "\nimport rocket_ship\npub const variable = rocket_ship.class\n" --- ----- SOURCE CODE -- rocket_ship.gleam pub const class = 1 -- main.gleam import rocket_ship pub const variable = rocket_ship.class ----- COMPILED JAVASCRIPT import * as $rocket_ship from "../rocket_ship.mjs"; export const variable = $rocket_ship.class$; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__modules__different_package_import.snap ================================================ --- source: compiler-core/src/javascript/tests/modules.rs expression: "import one\npub fn go() { one.go() }\n" --- ----- SOURCE CODE import one pub fn go() { one.go() } ----- COMPILED JAVASCRIPT import * as $one from "../../other_package/one.mjs"; export function go() { return $one.go(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__modules__discarded_duplicate_import.snap ================================================ --- source: compiler-core/src/javascript/tests/modules.rs expression: "\nimport esa/rocket_ship\nimport nasa/rocket_ship as _nasa_rocket\npub fn go() { rocket_ship.go() }\n" --- ----- SOURCE CODE -- esa/rocket_ship.gleam pub fn go() { 1 } -- nasa/rocket_ship.gleam pub fn go() { 1 } -- main.gleam import esa/rocket_ship import nasa/rocket_ship as _nasa_rocket pub fn go() { rocket_ship.go() } ----- COMPILED JAVASCRIPT import * as $rocket_ship from "../esa/rocket_ship.mjs"; export function go() { return $rocket_ship.go(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__modules__discarded_duplicate_import_with_unqualified.snap ================================================ --- source: compiler-core/src/javascript/tests/modules.rs expression: "\nimport esa/rocket_ship\nimport nasa/rocket_ship.{go} as _nasa_rocket\npub fn esa_go() { rocket_ship.go() }\npub fn nasa_go() { go() }\n" --- ----- SOURCE CODE -- esa/rocket_ship.gleam pub fn go() { 1 } -- nasa/rocket_ship.gleam pub fn go() { 1 } -- main.gleam import esa/rocket_ship import nasa/rocket_ship.{go} as _nasa_rocket pub fn esa_go() { rocket_ship.go() } pub fn nasa_go() { go() } ----- COMPILED JAVASCRIPT import * as $rocket_ship from "../esa/rocket_ship.mjs"; import { go } from "../nasa/rocket_ship.mjs"; export function esa_go() { return $rocket_ship.go(); } export function nasa_go() { return go(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__modules__import_with_keyword.snap ================================================ --- source: compiler-core/src/javascript/tests/modules.rs expression: "\nimport rocket_ship.{class, in as while}\npub fn main() {\n #(class, while)\n}\n" --- ----- SOURCE CODE import rocket_ship.{class, in as while} pub fn main() { #(class, while) } ----- COMPILED JAVASCRIPT import * as $rocket_ship from "../rocket_ship.mjs"; import { class$, in$ as while$ } from "../rocket_ship.mjs"; export function main() { return [class$, while$]; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__modules__multiple_unqualified_fn_call.snap ================================================ --- source: compiler-core/src/javascript/tests/modules.rs expression: "import rocket_ship.{a,b as bb}\npub fn go() { a() + bb() }\n" --- ----- SOURCE CODE import rocket_ship.{a,b as bb} pub fn go() { a() + bb() } ----- COMPILED JAVASCRIPT import * as $rocket_ship from "../rocket_ship.mjs"; import { a, b as bb } from "../rocket_ship.mjs"; export function go() { return a() + bb(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__modules__nested_fn_call.snap ================================================ --- source: compiler-core/src/javascript/tests/modules.rs expression: "import one/two\npub fn go() { two.go() }" --- ----- SOURCE CODE -- one/two.gleam pub fn go() { 1 } -- main.gleam import one/two pub fn go() { two.go() } ----- COMPILED JAVASCRIPT import * as $two from "../one/two.mjs"; export function go() { return $two.go(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__modules__nested_module_constant.snap ================================================ --- source: compiler-core/src/javascript/tests/modules.rs expression: "\nimport rocket_ship/launcher\npub fn go() { launcher.x }\n" --- ----- SOURCE CODE import rocket_ship/launcher pub fn go() { launcher.x } ----- COMPILED JAVASCRIPT import * as $launcher from "../rocket_ship/launcher.mjs"; export function go() { return $launcher.x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__modules__nested_nested_fn_call.snap ================================================ --- source: compiler-core/src/javascript/tests/modules.rs expression: "import one/two/three\npub fn go() { three.go() }" --- ----- SOURCE CODE -- one/two/three.gleam pub fn go() { 1 } -- main.gleam import one/two/three pub fn go() { three.go() } ----- COMPILED JAVASCRIPT import * as $three from "../one/two/three.mjs"; export function go() { return $three.go(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__modules__nested_same_package.snap ================================================ --- source: compiler-core/src/javascript/tests/modules.rs expression: "import one/two/three\npub fn go() { three.go() }\n" --- ----- SOURCE CODE -- one/two/three.gleam pub fn go() { 1 } -- main.gleam import one/two/three pub fn go() { three.go() } ----- COMPILED JAVASCRIPT import * as $three from "../one/two/three.mjs"; export function go() { return $three.go(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__modules__renamed_module.snap ================================================ --- source: compiler-core/src/javascript/tests/modules.rs assertion_line: 70 expression: "\nimport x as y\npub const z = y.v\n" snapshot_kind: text --- ----- SOURCE CODE -- x.gleam pub const v = 1 -- main.gleam import x as y pub const z = y.v ----- COMPILED JAVASCRIPT import * as $y from "../x.mjs"; export const z = $y.v; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__modules__unqualified_fn_call.snap ================================================ --- source: compiler-core/src/javascript/tests/modules.rs expression: "import rocket_ship.{launch}\npub fn go() { launch() }\n" --- ----- SOURCE CODE -- rocket_ship.gleam pub fn launch() { 1 } -- main.gleam import rocket_ship.{launch} pub fn go() { launch() } ----- COMPILED JAVASCRIPT import * as $rocket_ship from "../rocket_ship.mjs"; import { launch } from "../rocket_ship.mjs"; export function go() { return launch(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__complex_division_by_non_zero_float.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "pub fn main() {\n { 1.1 +. 2.0 } /. 2.3\n}" --- ----- SOURCE CODE pub fn main() { { 1.1 +. 2.0 } /. 2.3 } ----- COMPILED JAVASCRIPT export function main() { return (1.1 + 2.0) / 2.3; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__complex_division_by_non_zero_int.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "pub fn main() {\n { 1 + 2 } / 3\n}" --- ----- SOURCE CODE pub fn main() { { 1 + 2 } / 3 } ----- COMPILED JAVASCRIPT export function main() { return globalThis.Math.trunc((1 + 2) / 3); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__complex_remainder_by_non_zero_int.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "pub fn main() {\n { 1 + 2 } % 3\n}" --- ----- SOURCE CODE pub fn main() { { 1 + 2 } % 3 } ----- COMPILED JAVASCRIPT export function main() { return (1 + 2) % 3; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__division_by_non_zero_float.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "pub fn main() {\n 1.1 /. 2.3\n}" --- ----- SOURCE CODE pub fn main() { 1.1 /. 2.3 } ----- COMPILED JAVASCRIPT export function main() { return 1.1 / 2.3; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__division_by_non_zero_int.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "pub fn main() {\n 1 / 2\n}" --- ----- SOURCE CODE pub fn main() { 1 / 2 } ----- COMPILED JAVASCRIPT export function main() { return globalThis.Math.trunc(1 / 2); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__division_by_zero_float.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "pub fn main() {\n 1.1 /. 0.0\n}" --- ----- SOURCE CODE pub fn main() { 1.1 /. 0.0 } ----- COMPILED JAVASCRIPT export function main() { return 0.0; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__division_by_zero_int.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "pub fn main() {\n 1 / 0\n}" --- ----- SOURCE CODE pub fn main() { 1 / 0 } ----- COMPILED JAVASCRIPT export function main() { return 0; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__division_inf_by_inf_float.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn main(x) {\n -100.001e123_456_789 /. 100.001e123_456_789\n}\n" --- ----- SOURCE CODE pub fn main(x) { -100.001e123_456_789 /. 100.001e123_456_789 } ----- ERROR error: Float outside of valid range ┌─ /src/one/two.gleam:3:3 │ 3 │ -100.001e123_456_789 /. 100.001e123_456_789 │ ^^^^^^^^^^^^^^^^^^^^ This float value is too large to be represented by a floating point type: float values must be in the range -1.7976931348623157e308 - 1.7976931348623157e308. error: Float outside of valid range ┌─ /src/one/two.gleam:3:27 │ 3 │ -100.001e123_456_789 /. 100.001e123_456_789 │ ^^^^^^^^^^^^^^^^^^^ This float value is too large to be represented by a floating point type: float values must be in the range -1.7976931348623157e308 - 1.7976931348623157e308. ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__float_divide_complex_expr.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn go() {\n case 1.0 >=. 0.0 {\n True -> 2.0\n False -> 4.0\n } /. 2.0\n}\n" --- ----- SOURCE CODE pub fn go() { case 1.0 >=. 0.0 { True -> 2.0 False -> 4.0 } /. 2.0 } ----- COMPILED JAVASCRIPT export function go() { return (() => { let $ = 1.0 >= 0.0; if ($) { return 2.0; } else { return 4.0; } })() / 2.0; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__float_equality.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs assertion_line: 181 expression: "\npub fn go() {\n 1.0 != 2.0\n 1.0 == 2.0\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { 1.0 != 2.0 1.0 == 2.0 } ----- COMPILED JAVASCRIPT export function go() { 1.0 !== 2.0; return 1.0 === 2.0; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__float_equality1.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs assertion_line: 193 expression: "\npub fn go(y) {\n let x = 1.0\n x == y\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(y) { let x = 1.0 x == y } ----- COMPILED JAVASCRIPT export function go(y) { let x = 1.0; return x === y; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__float_literals.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn go() {\n 1.5\n 2.0\n -0.1\n 1.\n}\n" --- ----- SOURCE CODE pub fn go() { 1.5 2.0 -0.1 1. } ----- COMPILED JAVASCRIPT export function go() { 1.5; 2.0; -0.1; return 1.0; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__float_operators.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn go() {\n 1.0 +. 1.4 // => 2.4\n 5.0 -. 1.5 // => 3.5\n 5.0 /. 2.0 // => 2.5\n 3.0 *. 3.1 // => 9.3\n\n 2.0 >. 1.0 // => True\n 2.0 <. 1.0 // => False\n 2.0 >=. 1.0 // => True\n 2.0 <=. 1.0 // => False\n}\n" --- ----- SOURCE CODE pub fn go() { 1.0 +. 1.4 // => 2.4 5.0 -. 1.5 // => 3.5 5.0 /. 2.0 // => 2.5 3.0 *. 3.1 // => 9.3 2.0 >. 1.0 // => True 2.0 <. 1.0 // => False 2.0 >=. 1.0 // => True 2.0 <=. 1.0 // => False } ----- COMPILED JAVASCRIPT export function go() { 1.0 + 1.4; 5.0 - 1.5; 5.0 / 2.0; 3.0 * 3.1; 2.0 > 1.0; 2.0 < 1.0; 2.0 >= 1.0; return 2.0 <= 1.0; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__float_scientific_literals.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn go() {\n 0.01e-1\n 0.01e-0\n -10.01e-1\n -10.01e-0\n -100.001e-523\n -100.001e-123_456_789\n}\n" --- ----- SOURCE CODE pub fn go() { 0.01e-1 0.01e-0 -10.01e-1 -10.01e-0 -100.001e-523 -100.001e-123_456_789 } ----- COMPILED JAVASCRIPT export function go() { 0.001; 0.01; -1.001; -10.01; -0.0; return -0.0; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__inf_float_case_statement.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn main(x) {\n case x {\n 100.001e123_456_789 -> \"wobble\"\n _ -> \"wibble\"\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { 100.001e123_456_789 -> "wobble" _ -> "wibble" } } ----- ERROR error: Float outside of valid range ┌─ /src/one/two.gleam:4:3 │ 4 │ 100.001e123_456_789 -> "wobble" │ ^^^^^^^^^^^^^^^^^^^ This float value is too large to be represented by a floating point type: float values must be in the range -1.7976931348623157e308 - 1.7976931348623157e308. ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__int_divide_complex_expr.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn go() {\n case 1 >= 0 {\n True -> 2\n False -> 4\n } / 2\n}\n" --- ----- SOURCE CODE pub fn go() { case 1 >= 0 { True -> 2 False -> 4 } / 2 } ----- COMPILED JAVASCRIPT export function go() { return globalThis.Math.trunc( (() => { let $ = 1 >= 0; if ($) { return 2; } else { return 4; } })() / 2 ); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__int_equality.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs assertion_line: 157 expression: "\npub fn go() {\n 1 != 2\n 1 == 2\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { 1 != 2 1 == 2 } ----- COMPILED JAVASCRIPT export function go() { 1 !== 2; return 1 === 2; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__int_equality1.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs assertion_line: 169 expression: "\npub fn go(y) {\n let x = 1\n x == y\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(y) { let x = 1 x == y } ----- COMPILED JAVASCRIPT export function go(y) { let x = 1; return x === y; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__int_literals.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs assertion_line: 5 expression: "\npub fn go() {\n 1\n 2\n -3\n 4001\n 0b00001111\n 0o17\n 0xF\n 1_000\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { 1 2 -3 4001 0b00001111 0o17 0xF 1_000 } ----- COMPILED JAVASCRIPT export function go() { 1; 2; -3; 4001; 0b1111; 0o17; 0xF; return 1_000; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__int_mod_complex_expr.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn go() {\n case 1 >= 0 {\n True -> 2\n False -> 4\n } % 2\n}\n" --- ----- SOURCE CODE pub fn go() { case 1 >= 0 { True -> 2 False -> 4 } % 2 } ----- COMPILED JAVASCRIPT export function go() { return (() => { let $ = 1 >= 0; if ($) { return 2; } else { return 4; } })() % 2; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__int_negation.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs assertion_line: 227 expression: "\npub fn go() {\n let a = 3\n let b = -a\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { let a = 3 let b = -a } ----- COMPILED JAVASCRIPT export function go() { let a = 3; let b = - a; return b; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__int_operators.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn go() {\n 1 + 1 // => 2\n 5 - 1 // => 4\n 5 / 2 // => 2\n 3 * 3 // => 9\n 5 % 2 // => 1\n 2 > 1 // => True\n 2 < 1 // => False\n 2 >= 1 // => True\n 2 <= 1 // => False\n}\n" --- ----- SOURCE CODE pub fn go() { 1 + 1 // => 2 5 - 1 // => 4 5 / 2 // => 2 3 * 3 // => 9 5 % 2 // => 1 2 > 1 // => True 2 < 1 // => False 2 >= 1 // => True 2 <= 1 // => False } ----- COMPILED JAVASCRIPT export function go() { 1 + 1; 5 - 1; globalThis.Math.trunc(5 / 2); 3 * 3; 5 % 2; 2 > 1; 2 < 1; 2 >= 1; return 2 <= 1; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__int_patterns.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn go(x) {\n let assert 4 = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert 4 = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { if (!(x === 4)) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 34, pattern_start: 29, pattern_end: 30 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__many_preceeding_zeros_float.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn main() {\n 0000_000_00_9_179.1\n}\n" --- ----- SOURCE CODE pub fn main() { 0000_000_00_9_179.1 } ----- COMPILED JAVASCRIPT export function main() { return 9179.1; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__many_preceeding_zeros_float_const.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub const x = 0000_000_00_9_179.1\n" --- ----- SOURCE CODE pub const x = 0000_000_00_9_179.1 ----- COMPILED JAVASCRIPT export const x = 9_179.1; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__many_preceeding_zeros_float_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn main(x) {\n let assert 0000_000_00_9_179.1 = x\n}\n" --- ----- SOURCE CODE pub fn main(x) { let assert 0000_000_00_9_179.1 = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main(x) { if (!(x === 9179.1)) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "main", "Pattern match failed, no pattern matched the value.", { value: x, start: 20, end: 54, pattern_start: 31, pattern_end: 50 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__many_preceeding_zeros_int.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn main() {\n 0000_000_00_9_179\n}\n" --- ----- SOURCE CODE pub fn main() { 0000_000_00_9_179 } ----- COMPILED JAVASCRIPT export function main() { return 9_179; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__many_preceeding_zeros_int_const.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub const x = 0000_000_00_9_179\n" --- ----- SOURCE CODE pub const x = 0000_000_00_9_179 ----- COMPILED JAVASCRIPT export const x = 9_179; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__many_preceeding_zeros_int_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn main(x) {\n let assert 0000_000_00_9_179 = x\n}\n" --- ----- SOURCE CODE pub fn main(x) { let assert 0000_000_00_9_179 = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main(x) { if (!(x === 9179)) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "main", "Pattern match failed, no pattern matched the value.", { value: x, start: 20, end: 52, pattern_start: 31, pattern_end: 48 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__operator_precedence.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs assertion_line: 205 expression: "\npub fn go() {\n 2.4 *. { 3.5 +. 6.0 }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { 2.4 *. { 3.5 +. 6.0 } } ----- COMPILED JAVASCRIPT export function go() { return 2.4 * (3.5 + 6.0); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__preceeding_zeros_float.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn main() {\n 09_179.1\n}\n" --- ----- SOURCE CODE pub fn main() { 09_179.1 } ----- COMPILED JAVASCRIPT export function main() { return 9179.1; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__preceeding_zeros_float_const.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs assertion_line: 286 expression: "\npub const x = 09_179.1\n" snapshot_kind: text --- ----- SOURCE CODE pub const x = 09_179.1 ----- COMPILED JAVASCRIPT export const x = 9_179.1; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__preceeding_zeros_float_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn main(x) {\n let assert 09_179.1 = x\n}\n" --- ----- SOURCE CODE pub fn main(x) { let assert 09_179.1 = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main(x) { if (!(x === 9179.1)) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "main", "Pattern match failed, no pattern matched the value.", { value: x, start: 20, end: 43, pattern_start: 31, pattern_end: 39 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__preceeding_zeros_int.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs assertion_line: 252 expression: "\npub fn main() {\n 09_179\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn main() { 09_179 } ----- COMPILED JAVASCRIPT export function main() { return 9_179; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__preceeding_zeros_int_const.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs assertion_line: 276 expression: "\npub const x = 09_179\n" snapshot_kind: text --- ----- SOURCE CODE pub const x = 09_179 ----- COMPILED JAVASCRIPT export const x = 9_179; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__preceeding_zeros_int_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn main(x) {\n let assert 09_179 = x\n}\n" --- ----- SOURCE CODE pub fn main(x) { let assert 09_179 = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main(x) { if (!(x === 9179)) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "main", "Pattern match failed, no pattern matched the value.", { value: x, start: 20, end: 41, pattern_start: 31, pattern_end: 37 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__remainder.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn go() {\n 5 % 0 // => 0\n}\n" --- ----- SOURCE CODE pub fn go() { 5 % 0 // => 0 } ----- COMPILED JAVASCRIPT export function go() { return 0; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__remainder_by_non_zero_int.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "pub fn main() {\n 1 % 2\n}" --- ----- SOURCE CODE pub fn main() { 1 % 2 } ----- COMPILED JAVASCRIPT export function main() { return 1 % 2; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__remainder_by_zero_int.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "pub fn main() {\n 1 % 0\n}" --- ----- SOURCE CODE pub fn main() { 1 % 0 } ----- COMPILED JAVASCRIPT export function main() { return 0; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__repeated_int_negation.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs assertion_line: 239 expression: "\npub fn go() {\n let a = 3\n let b = --a\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { let a = 3 let b = --a } ----- COMPILED JAVASCRIPT export function go() { let a = 3; let b = - - a; return b; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__underscore_after_binary_prefix.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn main() {\n 0b_10_01\n}\n" --- ----- SOURCE CODE pub fn main() { 0b_10_01 } ----- COMPILED JAVASCRIPT export function main() { return 0b10_01; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__underscore_after_decimal_point.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn main() {\n 0._1\n}\n" --- ----- SOURCE CODE pub fn main() { 0._1 } ----- COMPILED JAVASCRIPT export function main() { return 0.1; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__underscore_after_decimal_point_case_statement.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn main(x) {\n case x {\n 0._1 -> \"wobble\"\n _ -> \"wibble\"\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { 0._1 -> "wobble" _ -> "wibble" } } ----- COMPILED JAVASCRIPT export function main(x) { if (x === 0.1) { return "wobble"; } else { return "wibble"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__underscore_after_hexadecimal_prefix.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn main() {\n 0x_12_34\n}\n" --- ----- SOURCE CODE pub fn main() { 0x_12_34 } ----- COMPILED JAVASCRIPT export function main() { return 0x12_34; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__underscore_after_octal_prefix.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn main() {\n 0o_12_34\n}\n" --- ----- SOURCE CODE pub fn main() { 0o_12_34 } ----- COMPILED JAVASCRIPT export function main() { return 0o12_34; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__underscore_after_zero.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn main() {\n 0_1_2_3\n}\n" --- ----- SOURCE CODE pub fn main() { 0_1_2_3 } ----- COMPILED JAVASCRIPT export function main() { return 1_2_3; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__underscore_after_zero_after_binary_prefix.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn main() {\n 0b0_1_0_1\n}\n" --- ----- SOURCE CODE pub fn main() { 0b0_1_0_1 } ----- COMPILED JAVASCRIPT export function main() { return 0b1_0_1; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__underscore_after_zero_after_hex_prefix.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn main() {\n 0x0_1_2_3\n}\n" --- ----- SOURCE CODE pub fn main() { 0x0_1_2_3 } ----- COMPILED JAVASCRIPT export function main() { return 0x1_2_3; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__underscore_after_zero_after_octal_prefix.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn main() {\n 0o0_1_2_3\n}\n" --- ----- SOURCE CODE pub fn main() { 0o0_1_2_3 } ----- COMPILED JAVASCRIPT export function main() { return 0o1_2_3; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__wide_float_div.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn go() {\n 111111111111111111111111111111. /. 22222222222222222222222222222222222.\n}\n" --- ----- SOURCE CODE pub fn go() { 111111111111111111111111111111. /. 22222222222222222222222222222222222. } ----- COMPILED JAVASCRIPT export function go() { return 1.111111111111111e29 / 2.222222222222222e34; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__zero_after_underscore_after_binary_prefix.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn main() {\n 0b_0_1_0_1\n}\n" --- ----- SOURCE CODE pub fn main() { 0b_0_1_0_1 } ----- COMPILED JAVASCRIPT export function main() { return 0b1_0_1; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__zero_after_underscore_after_hex_prefix.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn main() {\n 0x_0_1_2_3\n}\n" --- ----- SOURCE CODE pub fn main() { 0x_0_1_2_3 } ----- COMPILED JAVASCRIPT export function main() { return 0x1_2_3; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__numbers__zero_after_underscore_after_octal_prefix.snap ================================================ --- source: compiler-core/src/javascript/tests/numbers.rs expression: "\npub fn main() {\n 0o_0_1_2_3\n}\n" --- ----- SOURCE CODE pub fn main() { 0o_0_1_2_3 } ----- COMPILED JAVASCRIPT export function main() { return 0o1_2_3; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__panic__as_expression.snap ================================================ --- source: compiler-core/src/javascript/tests/panic.rs assertion_line: 39 expression: "\npub fn go(f) {\n let boop = panic\n f(panic)\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(f) { let boop = panic f(panic) } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(f) { let _block; throw makeError( "panic", FILEPATH, "my/mod", 3, "go", "`panic` expression evaluated.", {} ) let boop = _block; return f( (() => { throw makeError( "panic", FILEPATH, "my/mod", 4, "go", "`panic` expression evaluated.", {} ) })(), ); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__panic__bare.snap ================================================ --- source: compiler-core/src/javascript/tests/panic.rs assertion_line: 5 expression: "\npub fn go() {\n panic\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { panic } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go() { throw makeError( "panic", FILEPATH, "my/mod", 3, "go", "`panic` expression evaluated.", {} ) } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__panic__bare_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/panic.rs expression: "\npub fn go() {\n panic\n}\n" --- ----- SOURCE CODE pub fn go() { panic } ----- TYPESCRIPT DEFINITIONS export function go(): any; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__panic__case.snap ================================================ --- source: compiler-core/src/javascript/tests/panic.rs assertion_line: 74 expression: "\npub fn go(x) {\n case x {\n _ -> panic\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { _ -> panic } } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { throw makeError( "panic", FILEPATH, "my/mod", 4, "go", "`panic` expression evaluated.", {} ) } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__panic__case_message.snap ================================================ --- source: compiler-core/src/javascript/tests/panic.rs assertion_line: 88 expression: "\npub fn crash(message) {\n panic as case message {\n Ok(message) -> message\n Error(_) -> \"No message provided\"\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn crash(message) { panic as case message { Ok(message) -> message Error(_) -> "No message provided" } } ----- COMPILED JAVASCRIPT import { Ok, makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function crash(message) { let _block; if (message instanceof Ok) { let message$1 = message[0]; _block = message$1; } else { _block = "No message provided"; } throw makeError("panic", FILEPATH, "my/mod", 3, "crash", _block, {}) } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__panic__panic_as.snap ================================================ --- source: compiler-core/src/javascript/tests/panic.rs assertion_line: 16 expression: "\npub fn go() {\n let x = \"wibble\"\n panic as x\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { let x = "wibble" panic as x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go() { let x = "wibble"; throw makeError("panic", FILEPATH, "my/mod", 4, "go", x, {}) } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__panic__pipe.snap ================================================ --- source: compiler-core/src/javascript/tests/panic.rs assertion_line: 51 expression: "\npub fn go(f) {\n f |> panic\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(f) { f |> panic } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(f) { let _pipe = f; let _block; throw makeError( "panic", FILEPATH, "my/mod", 3, "go", "`panic` expression evaluated.", {} ) return _block(_pipe); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__panic__sequence.snap ================================================ --- source: compiler-core/src/javascript/tests/panic.rs assertion_line: 62 expression: "\npub fn go(at_the_disco) {\n panic\n at_the_disco\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(at_the_disco) { panic at_the_disco } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(at_the_disco) { throw makeError( "panic", FILEPATH, "my/mod", 3, "go", "`panic` expression evaluated.", {} ) return at_the_disco; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__prelude__qualified_error.snap ================================================ --- source: compiler-core/src/javascript/tests/prelude.rs expression: "import gleam\npub fn go() { gleam.Error(1) }\n" --- ----- SOURCE CODE import gleam pub fn go() { gleam.Error(1) } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; export function go() { return new $gleam.Error(1); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__prelude__qualified_nil.snap ================================================ --- source: compiler-core/src/javascript/tests/prelude.rs expression: "import gleam\npub fn go() { gleam.Nil }\n" --- ----- SOURCE CODE import gleam pub fn go() { gleam.Nil } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; export function go() { return undefined; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__prelude__qualified_nil_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/prelude.rs expression: "import gleam\npub fn go() { gleam.Nil }\n" --- ----- SOURCE CODE import gleam pub fn go() { gleam.Nil } ----- TYPESCRIPT DEFINITIONS export function go(): undefined; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__prelude__qualified_ok.snap ================================================ --- source: compiler-core/src/javascript/tests/prelude.rs expression: "import gleam\npub fn go() { gleam.Ok(1) }\n" --- ----- SOURCE CODE import gleam pub fn go() { gleam.Ok(1) } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; export function go() { return new $gleam.Ok(1); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__prelude__qualified_ok_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/prelude.rs expression: "import gleam\npub fn go() { gleam.Ok(1) }\n" --- ----- SOURCE CODE import gleam pub fn go() { gleam.Ok(1) } ----- TYPESCRIPT DEFINITIONS import type * as _ from "../gleam.d.mts"; export function go(): _.Result; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__prelude__qualified_prelude_value_does_not_conflict_with_local_value.snap ================================================ --- source: compiler-core/src/javascript/tests/prelude.rs expression: "\nimport gleam\n\npub type Result(a, e) {\n Ok(a)\n Error(e)\n}\n\npub fn main() {\n gleam.Ok(10)\n}\n" --- ----- SOURCE CODE import gleam pub type Result(a, e) { Ok(a) Error(e) } pub fn main() { gleam.Ok(10) } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; import { CustomType as $CustomType } from "../gleam.mjs"; export class Ok extends $CustomType { constructor($0) { super(); this[0] = $0; } } export const Result$Ok = ($0) => new Ok($0); export const Result$isOk = (value) => value instanceof Ok; export const Result$Ok$0 = (value) => value[0]; export class Error extends $CustomType { constructor($0) { super(); this[0] = $0; } } export const Result$Error = ($0) => new Error($0); export const Result$isError = (value) => value instanceof Error; export const Result$Error$0 = (value) => value[0]; export function main() { return new $gleam.Ok(10); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__prelude__qualified_prelude_value_does_not_conflict_with_local_value_constant.snap ================================================ --- source: compiler-core/src/javascript/tests/prelude.rs expression: "\nimport gleam\n\npub type Result(a, e) {\n Ok(a)\n Error(e)\n}\n\npub const error = gleam.Error(\"Bad\")\n" --- ----- SOURCE CODE import gleam pub type Result(a, e) { Ok(a) Error(e) } pub const error = gleam.Error("Bad") ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; import { CustomType as $CustomType } from "../gleam.mjs"; export class Ok extends $CustomType { constructor($0) { super(); this[0] = $0; } } export const Result$Ok = ($0) => new Ok($0); export const Result$isOk = (value) => value instanceof Ok; export const Result$Ok$0 = (value) => value[0]; export class Error extends $CustomType { constructor($0) { super(); this[0] = $0; } } export const Result$Error = ($0) => new Error($0); export const Result$isError = (value) => value instanceof Error; export const Result$Error$0 = (value) => value[0]; export const error = /* @__PURE__ */ new $gleam.Error("Bad"); ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__prelude__qualified_prelude_value_does_not_conflict_with_local_value_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/prelude.rs expression: "\nimport gleam\n\npub type Result(a, e) {\n Ok(a)\n Error(e)\n}\n\npub fn go(x) {\n case x {\n gleam.Ok(x) -> x\n gleam.Error(_) -> 0\n }\n}\n" --- ----- SOURCE CODE import gleam pub type Result(a, e) { Ok(a) Error(e) } pub fn go(x) { case x { gleam.Ok(x) -> x gleam.Error(_) -> 0 } } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; import { CustomType as $CustomType } from "../gleam.mjs"; export class Ok extends $CustomType { constructor($0) { super(); this[0] = $0; } } export const Result$Ok = ($0) => new Ok($0); export const Result$isOk = (value) => value instanceof Ok; export const Result$Ok$0 = (value) => value[0]; export class Error extends $CustomType { constructor($0) { super(); this[0] = $0; } } export const Result$Error = ($0) => new Error($0); export const Result$isError = (value) => value instanceof Error; export const Result$Error$0 = (value) => value[0]; export function go(x) { if (x instanceof $gleam.Ok) { let x$1 = x[0]; return x$1; } else { return 0; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__records__const_record_update_generic_respecialization.snap ================================================ --- source: compiler-core/src/javascript/tests/records.rs assertion_line: 119 expression: "\npub type Box(a) {\n Box(name: String, value: a)\n}\n\npub const base = Box(\"score\", 50)\npub const updated = Box(..base, value: \"Hello\")\n\npub fn main() {\n #(base, updated)\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub type Box(a) { Box(name: String, value: a) } pub const base = Box("score", 50) pub const updated = Box(..base, value: "Hello") pub fn main() { #(base, updated) } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Box extends $CustomType { constructor(name, value) { super(); this.name = name; this.value = value; } } export const Box$Box = (name, value) => new Box(name, value); export const Box$isBox = (value) => value instanceof Box; export const Box$Box$name = (value) => value.name; export const Box$Box$0 = (value) => value.name; export const Box$Box$value = (value) => value.value; export const Box$Box$1 = (value) => value.value; export const base = /* @__PURE__ */ new Box("score", 50); export const updated = /* @__PURE__ */ new Box("score", "Hello"); export function main() { return [base, updated]; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__records__constant_record_update_with_unlabelled_fields.snap ================================================ --- source: compiler-core/src/javascript/tests/records.rs expression: "\npub type Wibble {\n Wibble(Int, Float, b: Bool, s: String)\n}\n\npub const record = Wibble(1, 3.14, True, \"Hello\")\npub const updated = Wibble(..record, b: False)\n\npub fn main() {\n updated\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(Int, Float, b: Bool, s: String) } pub const record = Wibble(1, 3.14, True, "Hello") pub const updated = Wibble(..record, b: False) pub fn main() { updated } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Wibble extends $CustomType { constructor($0, $1, b, s) { super(); this[0] = $0; this[1] = $1; this.b = b; this.s = s; } } export const Wibble$Wibble = ($0, $1, b, s) => new Wibble($0, $1, b, s); export const Wibble$isWibble = (value) => value instanceof Wibble; export const Wibble$Wibble$0 = (value) => value[0]; export const Wibble$Wibble$1 = (value) => value[1]; export const Wibble$Wibble$b = (value) => value.b; export const Wibble$Wibble$2 = (value) => value.b; export const Wibble$Wibble$s = (value) => value.s; export const Wibble$Wibble$3 = (value) => value.s; export const record = /* @__PURE__ */ new Wibble(1, 3.14, true, "Hello"); export const updated = /* @__PURE__ */ new Wibble(1, 3.14, false, "Hello"); export function main() { return updated; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__records__field_named_constructor_is_escaped.snap ================================================ --- source: compiler-core/src/javascript/tests/records.rs expression: "\npub type Wibble {\n Wibble(constructor: fn() -> Wibble)\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(constructor: fn() -> Wibble) } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Wibble extends $CustomType { constructor(constructor) { super(); this.constructor$ = constructor; } } export const Wibble$Wibble = (constructor) => new Wibble(constructor); export const Wibble$isWibble = (value) => value instanceof Wibble; export const Wibble$Wibble$constructor = (value) => value.constructor$; export const Wibble$Wibble$0 = (value) => value.constructor$; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__records__field_named_prototype_is_escaped.snap ================================================ --- source: compiler-core/src/javascript/tests/records.rs expression: "\npub type Wibble {\n Wibble(prototype: String)\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(prototype: String) } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Wibble extends $CustomType { constructor(prototype) { super(); this.prototype$ = prototype; } } export const Wibble$Wibble = (prototype) => new Wibble(prototype); export const Wibble$isWibble = (value) => value instanceof Wibble; export const Wibble$Wibble$prototype = (value) => value.prototype$; export const Wibble$Wibble$0 = (value) => value.prototype$; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__records__field_named_then_is_escaped.snap ================================================ --- source: compiler-core/src/javascript/tests/records.rs expression: "\npub type Wibble {\n Wibble(then: fn() -> Int)\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(then: fn() -> Int) } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Wibble extends $CustomType { constructor(then$) { super(); this.then$ = then$; } } export const Wibble$Wibble = (then$) => new Wibble(then$); export const Wibble$isWibble = (value) => value instanceof Wibble; export const Wibble$Wibble$then = (value) => value.then$; export const Wibble$Wibble$0 = (value) => value.then$; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__records__field_named_x0.snap ================================================ --- source: compiler-core/src/javascript/tests/records.rs expression: "\npub type Wibble {\n Wibble(Int, x0: String)\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(Int, x0: String) } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Wibble extends $CustomType { constructor($0, x0) { super(); this[0] = $0; this.x0 = x0; } } export const Wibble$Wibble = ($0, x0) => new Wibble($0, x0); export const Wibble$isWibble = (value) => value instanceof Wibble; export const Wibble$Wibble$0 = (value) => value[0]; export const Wibble$Wibble$x0 = (value) => value.x0; export const Wibble$Wibble$1 = (value) => value.x0; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__records__record_accessor_multiple_variants.snap ================================================ --- source: compiler-core/src/javascript/tests/records.rs assertion_line: 18 expression: "\npub type Person {\n Teacher(name: String, title: String)\n Student(name: String, age: Int)\n}\npub fn get_name(person: Person) { person.name }" snapshot_kind: text --- ----- SOURCE CODE pub type Person { Teacher(name: String, title: String) Student(name: String, age: Int) } pub fn get_name(person: Person) { person.name } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Teacher extends $CustomType { constructor(name, title) { super(); this.name = name; this.title = title; } } export const Person$Teacher = (name, title) => new Teacher(name, title); export const Person$isTeacher = (value) => value instanceof Teacher; export const Person$Teacher$name = (value) => value.name; export const Person$Teacher$0 = (value) => value.name; export const Person$Teacher$title = (value) => value.title; export const Person$Teacher$1 = (value) => value.title; export class Student extends $CustomType { constructor(name, age) { super(); this.name = name; this.age = age; } } export const Person$Student = (name, age) => new Student(name, age); export const Person$isStudent = (value) => value instanceof Student; export const Person$Student$name = (value) => value.name; export const Person$Student$0 = (value) => value.name; export const Person$Student$age = (value) => value.age; export const Person$Student$1 = (value) => value.age; export const Person$name = (value) => value.name; export function get_name(person) { return person.name; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__records__record_accessor_multiple_variants_parameterised_types.snap ================================================ --- source: compiler-core/src/javascript/tests/records.rs assertion_line: 61 expression: "\npub type Person {\n Teacher(name: String, age: List(Int), title: String)\n Student(name: String, age: List(Int))\n}\npub fn get_name(person: Person) { person.name }\npub fn get_age(person: Person) { person.age }" snapshot_kind: text --- ----- SOURCE CODE pub type Person { Teacher(name: String, age: List(Int), title: String) Student(name: String, age: List(Int)) } pub fn get_name(person: Person) { person.name } pub fn get_age(person: Person) { person.age } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Teacher extends $CustomType { constructor(name, age, title) { super(); this.name = name; this.age = age; this.title = title; } } export const Person$Teacher = (name, age, title) => new Teacher(name, age, title); export const Person$isTeacher = (value) => value instanceof Teacher; export const Person$Teacher$name = (value) => value.name; export const Person$Teacher$0 = (value) => value.name; export const Person$Teacher$age = (value) => value.age; export const Person$Teacher$1 = (value) => value.age; export const Person$Teacher$title = (value) => value.title; export const Person$Teacher$2 = (value) => value.title; export class Student extends $CustomType { constructor(name, age) { super(); this.name = name; this.age = age; } } export const Person$Student = (name, age) => new Student(name, age); export const Person$isStudent = (value) => value instanceof Student; export const Person$Student$name = (value) => value.name; export const Person$Student$0 = (value) => value.name; export const Person$Student$age = (value) => value.age; export const Person$Student$1 = (value) => value.age; export const Person$age = (value) => value.age; export const Person$name = (value) => value.name; export function get_name(person) { return person.name; } export function get_age(person) { return person.age; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__records__record_accessor_multiple_variants_positions_other_than_first.snap ================================================ --- source: compiler-core/src/javascript/tests/records.rs assertion_line: 32 expression: "\npub type Person {\n Teacher(name: String, age: Int, title: String)\n Student(name: String, age: Int)\n}\npub fn get_name(person: Person) { person.name }\npub fn get_age(person: Person) { person.age }" snapshot_kind: text --- ----- SOURCE CODE pub type Person { Teacher(name: String, age: Int, title: String) Student(name: String, age: Int) } pub fn get_name(person: Person) { person.name } pub fn get_age(person: Person) { person.age } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Teacher extends $CustomType { constructor(name, age, title) { super(); this.name = name; this.age = age; this.title = title; } } export const Person$Teacher = (name, age, title) => new Teacher(name, age, title); export const Person$isTeacher = (value) => value instanceof Teacher; export const Person$Teacher$name = (value) => value.name; export const Person$Teacher$0 = (value) => value.name; export const Person$Teacher$age = (value) => value.age; export const Person$Teacher$1 = (value) => value.age; export const Person$Teacher$title = (value) => value.title; export const Person$Teacher$2 = (value) => value.title; export class Student extends $CustomType { constructor(name, age) { super(); this.name = name; this.age = age; } } export const Person$Student = (name, age) => new Student(name, age); export const Person$isStudent = (value) => value instanceof Student; export const Person$Student$name = (value) => value.name; export const Person$Student$0 = (value) => value.name; export const Person$Student$age = (value) => value.age; export const Person$Student$1 = (value) => value.age; export const Person$age = (value) => value.age; export const Person$name = (value) => value.name; export function get_name(person) { return person.name; } export function get_age(person) { return person.age; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__records__record_accessor_multiple_with_first_position_different_types.snap ================================================ --- source: compiler-core/src/javascript/tests/records.rs assertion_line: 47 expression: "\npub type Person {\n Teacher(name: Nil, age: Int)\n Student(name: String, age: Int)\n}\npub fn get_age(person: Person) { person.age }" snapshot_kind: text --- ----- SOURCE CODE pub type Person { Teacher(name: Nil, age: Int) Student(name: String, age: Int) } pub fn get_age(person: Person) { person.age } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Teacher extends $CustomType { constructor(name, age) { super(); this.name = name; this.age = age; } } export const Person$Teacher = (name, age) => new Teacher(name, age); export const Person$isTeacher = (value) => value instanceof Teacher; export const Person$Teacher$name = (value) => value.name; export const Person$Teacher$0 = (value) => value.name; export const Person$Teacher$age = (value) => value.age; export const Person$Teacher$1 = (value) => value.age; export class Student extends $CustomType { constructor(name, age) { super(); this.name = name; this.age = age; } } export const Person$Student = (name, age) => new Student(name, age); export const Person$isStudent = (value) => value instanceof Student; export const Person$Student$name = (value) => value.name; export const Person$Student$0 = (value) => value.name; export const Person$Student$age = (value) => value.age; export const Person$Student$1 = (value) => value.age; export const Person$age = (value) => value.age; export function get_age(person) { return person.age; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__records__record_accessors.snap ================================================ --- source: compiler-core/src/javascript/tests/records.rs expression: "\npub type Person { Person(name: String, age: Int) }\npub fn get_age(person: Person) { person.age }\npub fn get_name(person: Person) { person.name }\n" --- ----- SOURCE CODE pub type Person { Person(name: String, age: Int) } pub fn get_age(person: Person) { person.age } pub fn get_name(person: Person) { person.name } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Person extends $CustomType { constructor(name, age) { super(); this.name = name; this.age = age; } } export const Person$Person = (name, age) => new Person(name, age); export const Person$isPerson = (value) => value instanceof Person; export const Person$Person$name = (value) => value.name; export const Person$Person$0 = (value) => value.name; export const Person$Person$age = (value) => value.age; export const Person$Person$1 = (value) => value.age; export function get_age(person) { return person.age; } export function get_name(person) { return person.name; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__records__record_update_with_unlabelled_fields.snap ================================================ --- source: compiler-core/src/javascript/tests/records.rs expression: "\npub type Wibble {\n Wibble(Int, Float, b: Bool, s: String)\n}\n\npub fn main() {\n let record = Wibble(1, 3.14, True, \"Hello\")\n Wibble(..record, b: False)\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(Int, Float, b: Bool, s: String) } pub fn main() { let record = Wibble(1, 3.14, True, "Hello") Wibble(..record, b: False) } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; export class Wibble extends $CustomType { constructor($0, $1, b, s) { super(); this[0] = $0; this[1] = $1; this.b = b; this.s = s; } } export const Wibble$Wibble = ($0, $1, b, s) => new Wibble($0, $1, b, s); export const Wibble$isWibble = (value) => value instanceof Wibble; export const Wibble$Wibble$0 = (value) => value[0]; export const Wibble$Wibble$1 = (value) => value[1]; export const Wibble$Wibble$b = (value) => value.b; export const Wibble$Wibble$2 = (value) => value.b; export const Wibble$Wibble$s = (value) => value.s; export const Wibble$Wibble$3 = (value) => value.s; export function main() { let record = new Wibble(1, 3.14, true, "Hello"); return new Wibble(record[0], record[1], false, record.s); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__recursion__not_tco_due_to_assignment.snap ================================================ --- source: compiler-core/src/javascript/tests/recursion.rs expression: "\npub fn main(x) {\n let z = {\n let y = x\n main(y - 1)\n }\n z\n}\n" --- ----- SOURCE CODE pub fn main(x) { let z = { let y = x main(y - 1) } z } ----- COMPILED JAVASCRIPT export function main(x) { let _block; { let y = x; _block = main(y - 1); } let z = _block; return z; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__recursion__shadowing_so_not_recursive.snap ================================================ --- source: compiler-core/src/javascript/tests/recursion.rs expression: "\npub fn map(map) {\n map()\n}\n" --- ----- SOURCE CODE pub fn map(map) { map() } ----- COMPILED JAVASCRIPT export function map(map) { return map(); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__recursion__tco.snap ================================================ --- source: compiler-core/src/javascript/tests/recursion.rs expression: "\npub fn main(x) {\n case x {\n 0 -> Nil\n _ -> main(x - 1)\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { 0 -> Nil _ -> main(x - 1) } } ----- COMPILED JAVASCRIPT export function main(loop$x) { while (true) { let x = loop$x; if (x === 0) { return undefined; } else { loop$x = x - 1; } } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__recursion__tco_case_block.snap ================================================ --- source: compiler-core/src/javascript/tests/recursion.rs expression: "\npub fn main(x) {\n case x {\n 0 -> Nil\n _ -> {\n let y = x\n main(y - 1)\n }\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { 0 -> Nil _ -> { let y = x main(y - 1) } } } ----- COMPILED JAVASCRIPT export function main(loop$x) { while (true) { let x = loop$x; if (x === 0) { return undefined; } else { let y = x; loop$x = y - 1; } } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__results__aliased_error.snap ================================================ --- source: compiler-core/src/javascript/tests/results.rs expression: "import gleam.{Error as Thing}\npub fn main() { Thing(1) }" --- ----- SOURCE CODE import gleam.{Error as Thing} pub fn main() { Thing(1) } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; import { Error as Thing } from "../gleam.mjs"; export function main() { return new Thing(1); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__results__aliased_error_fn.snap ================================================ --- source: compiler-core/src/javascript/tests/results.rs expression: "import gleam.{Error as Thing}\npub fn main() { Thing }" --- ----- SOURCE CODE import gleam.{Error as Thing} pub fn main() { Thing } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; import { Error as Thing } from "../gleam.mjs"; export function main() { return (var0) => { return new Thing(var0); }; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__results__aliased_ok.snap ================================================ --- source: compiler-core/src/javascript/tests/results.rs expression: "import gleam.{Ok as Thing}\npub fn main() { Thing(1) }" --- ----- SOURCE CODE import gleam.{Ok as Thing} pub fn main() { Thing(1) } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; import { Ok as Thing } from "../gleam.mjs"; export function main() { return new Thing(1); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__results__aliased_ok_fn.snap ================================================ --- source: compiler-core/src/javascript/tests/results.rs expression: "import gleam.{Ok as Thing}\npub fn main() { Thing }" --- ----- SOURCE CODE import gleam.{Ok as Thing} pub fn main() { Thing } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; import { Ok as Thing } from "../gleam.mjs"; export function main() { return (var0) => { return new Thing(var0); }; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__results__error.snap ================================================ --- source: compiler-core/src/javascript/tests/results.rs expression: "pub fn main() { Error(1) }" --- ----- SOURCE CODE pub fn main() { Error(1) } ----- COMPILED JAVASCRIPT import { Error } from "../gleam.mjs"; export function main() { return new Error(1); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__results__error_fn.snap ================================================ --- source: compiler-core/src/javascript/tests/results.rs expression: "pub fn main() { Error }" --- ----- SOURCE CODE pub fn main() { Error } ----- COMPILED JAVASCRIPT import { Error } from "../gleam.mjs"; export function main() { return (var0) => { return new Error(var0); }; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__results__ok.snap ================================================ --- source: compiler-core/src/javascript/tests/results.rs expression: "pub fn main() { Ok(1) }" --- ----- SOURCE CODE pub fn main() { Ok(1) } ----- COMPILED JAVASCRIPT import { Ok } from "../gleam.mjs"; export function main() { return new Ok(1); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__results__ok_fn.snap ================================================ --- source: compiler-core/src/javascript/tests/results.rs expression: "pub fn main() { Ok }" --- ----- SOURCE CODE pub fn main() { Ok } ----- COMPILED JAVASCRIPT import { Ok } from "../gleam.mjs"; export function main() { return (var0) => { return new Ok(var0); }; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__results__qualified_error.snap ================================================ --- source: compiler-core/src/javascript/tests/results.rs expression: "import gleam\npub fn main() { gleam.Error(1) }" --- ----- SOURCE CODE import gleam pub fn main() { gleam.Error(1) } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; export function main() { return new $gleam.Error(1); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__results__qualified_error_fn.snap ================================================ --- source: compiler-core/src/javascript/tests/results.rs expression: "import gleam\npub fn main() { gleam.Error }" --- ----- SOURCE CODE import gleam pub fn main() { gleam.Error } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; export function main() { return (var0) => { return new $gleam.Error(var0); }; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__results__qualified_ok.snap ================================================ --- source: compiler-core/src/javascript/tests/results.rs expression: "import gleam\npub fn main() { gleam.Ok(1) }" --- ----- SOURCE CODE import gleam pub fn main() { gleam.Ok(1) } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; export function main() { return new $gleam.Ok(1); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__results__qualified_ok_fn.snap ================================================ --- source: compiler-core/src/javascript/tests/results.rs expression: "import gleam\npub fn main() { gleam.Ok }" --- ----- SOURCE CODE import gleam pub fn main() { gleam.Ok } ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; export function main() { return (var0) => { return new $gleam.Ok(var0); }; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__strings__ascii_as_unicode_escape_sequence.snap ================================================ --- source: compiler-core/src/javascript/tests/strings.rs expression: "\npub fn y() -> String {\n \"\\u{79}\"\n}\n" --- ----- SOURCE CODE pub fn y() -> String { "\u{79}" } ----- COMPILED JAVASCRIPT export function y() { return "\u{79}"; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__strings__case.snap ================================================ --- source: compiler-core/src/javascript/tests/strings.rs assertion_line: 84 expression: "\npub fn go(a) {\n case a {\n \"\" -> 0\n \"one\" -> 1\n \"two\" -> 2\n _ -> 3\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(a) { case a { "" -> 0 "one" -> 1 "two" -> 2 _ -> 3 } } ----- COMPILED JAVASCRIPT export function go(a) { if (a === "") { return 0; } else if (a === "one") { return 1; } else if (a === "two") { return 2; } else { return 3; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__strings__const_concat.snap ================================================ --- source: compiler-core/src/javascript/tests/strings.rs assertion_line: 230 expression: "\npub const cute = \"cute\"\npub const cute_bee = cute <> \"bee\"\n\npub fn main() {\n cute_bee\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub const cute = "cute" pub const cute_bee = cute <> "bee" pub fn main() { cute_bee } ----- COMPILED JAVASCRIPT export const cute = "cute"; export const cute_bee = cute + "bee"; export function main() { return cute_bee; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__strings__const_concat_multiple.snap ================================================ --- source: compiler-core/src/javascript/tests/strings.rs assertion_line: 244 expression: "\npub const cute = \"cute\"\npub const cute_bee = cute <> \"bee\"\npub const cute_cute_bee_buzz = cute <> cute_bee <> \"buzz\"\n\npub fn main() {\n cute_cute_bee_buzz\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub const cute = "cute" pub const cute_bee = cute <> "bee" pub const cute_cute_bee_buzz = cute <> cute_bee <> "buzz" pub fn main() { cute_cute_bee_buzz } ----- COMPILED JAVASCRIPT export const cute = "cute"; export const cute_bee = cute + "bee"; export const cute_cute_bee_buzz = cute + cute_bee + "buzz"; export function main() { return cute_cute_bee_buzz; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__strings__discard_concat_rest_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/strings.rs expression: "\npub fn go(x) {\n case x {\n \"Hello, \" <> _ -> Nil\n _ -> Nil\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { "Hello, " <> _ -> Nil _ -> Nil } } ----- COMPILED JAVASCRIPT export function go(x) { if (x.startsWith("Hello, ")) { return undefined; } else { return undefined; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__strings__equality.snap ================================================ --- source: compiler-core/src/javascript/tests/strings.rs assertion_line: 71 expression: "\npub fn go(a) {\n a == \"ok\"\n a != \"ok\"\n a == a\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(a) { a == "ok" a != "ok" a == a } ----- COMPILED JAVASCRIPT export function go(a) { a === "ok"; a !== "ok"; return a === a; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__strings__string_concat.snap ================================================ --- source: compiler-core/src/javascript/tests/strings.rs assertion_line: 100 expression: "\npub fn go() {\n \"Hello, \" <> \"Joe\"\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { "Hello, " <> "Joe" } ----- COMPILED JAVASCRIPT export function go() { return "Hello, " + "Joe"; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__strings__string_literals.snap ================================================ --- source: compiler-core/src/javascript/tests/strings.rs assertion_line: 49 expression: "\npub fn go() {\n \"Hello, Gleam!\"\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { "Hello, Gleam!" } ----- COMPILED JAVASCRIPT export function go() { return "Hello, Gleam!"; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__strings__string_patterns.snap ================================================ --- source: compiler-core/src/javascript/tests/strings.rs expression: "\npub fn go(x) {\n let assert \"Hello\" = x\n}\n" --- ----- SOURCE CODE pub fn go(x) { let assert "Hello" = x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(x) { if (!(x === "Hello")) { throw makeError( "let_assert", FILEPATH, "my/mod", 3, "go", "Pattern match failed, no pattern matched the value.", { value: x, start: 18, end: 40, pattern_start: 29, pattern_end: 36 } ) } return x; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__strings__string_prefix.snap ================================================ --- source: compiler-core/src/javascript/tests/strings.rs expression: "\npub fn go(x) {\n case x {\n \"Hello, \" <> name -> name\n _ -> \"Unknown\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { "Hello, " <> name -> name _ -> "Unknown" } } ----- COMPILED JAVASCRIPT export function go(x) { if (x.startsWith("Hello, ")) { let name = x.slice(7); return name; } else { return "Unknown"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__strings__string_prefix_assignment.snap ================================================ --- source: compiler-core/src/javascript/tests/strings.rs expression: "\npub fn go(x) {\n case x {\n \"Hello, \" as greeting <> name -> greeting\n _ -> \"Unknown\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { "Hello, " as greeting <> name -> greeting _ -> "Unknown" } } ----- COMPILED JAVASCRIPT export function go(x) { if (x.startsWith("Hello, ")) { let greeting = "Hello, "; let name = x.slice(7); return greeting; } else { return "Unknown"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__strings__string_prefix_assignment_with_multiple_subjects.snap ================================================ --- source: compiler-core/src/javascript/tests/strings.rs expression: "\npub fn go(x) {\n case x {\n \"1\" as prefix <> _ | \"11\" as prefix <> _ -> prefix\n _ -> \"Unknown\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { "1" as prefix <> _ | "11" as prefix <> _ -> prefix _ -> "Unknown" } } ----- COMPILED JAVASCRIPT export function go(x) { if (x.startsWith("1")) { let prefix = "1"; return prefix; } else { return "Unknown"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__strings__string_prefix_assignment_with_utf_escape_sequence.snap ================================================ --- source: compiler-core/src/javascript/tests/strings.rs expression: "\npub fn go(x) {\n case x {\n \"\\u{0032} \" as greeting <> name -> greeting\n \"\\u{0007ff} \" as greeting <> name -> greeting\n \"\\u{00ffff} \" as greeting <> name -> greeting\n \"\\u{10ffff} \" as greeting <> name -> greeting\n _ -> \"Unknown\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { "\u{0032} " as greeting <> name -> greeting "\u{0007ff} " as greeting <> name -> greeting "\u{00ffff} " as greeting <> name -> greeting "\u{10ffff} " as greeting <> name -> greeting _ -> "Unknown" } } ----- COMPILED JAVASCRIPT export function go(x) { if (x.startsWith("\u{0032} ")) { let greeting = "\u{0032} "; let name = x.slice(2); return greeting; } else if (x.startsWith("\u{0007ff} ")) { let greeting = "\u{0007ff} "; let name = x.slice(2); return greeting; } else if (x.startsWith("\u{00ffff} ")) { let greeting = "\u{00ffff} "; let name = x.slice(2); return greeting; } else if (x.startsWith("\u{10ffff} ")) { let greeting = "\u{10ffff} "; let name = x.slice(3); return greeting; } else { return "Unknown"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__strings__string_prefix_shadowing.snap ================================================ --- source: compiler-core/src/javascript/tests/strings.rs expression: "\npub fn go(x) {\n case x {\n \"Hello, \" as x <> name -> x\n _ -> \"Unknown\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { "Hello, " as x <> name -> x _ -> "Unknown" } } ----- COMPILED JAVASCRIPT export function go(x) { if (x.startsWith("Hello, ")) { let x$1 = "Hello, "; let name = x.slice(7); return x$1; } else { return "Unknown"; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__strings__string_prefix_utf16.snap ================================================ --- source: compiler-core/src/javascript/tests/strings.rs expression: "\npub fn go(x) {\n case \"Θ wibble wobble\" {\n \"Θ\" <> rest -> rest\n _ -> \"\"\n }\n case \"🫥 is neutral dotted\" {\n \"🫥\" <> rest -> rest\n _ -> \"\"\n }\n case \"🇺🇸 is a cluster\" {\n \"🇺🇸\" <> rest -> rest\n _ -> \"\"\n }\n case \"\\\" is a an escaped quote\" {\n \"\\\"\" <> rest -> rest\n _ -> \"\"\n }\n case \"\\\\ is a an escaped backslash\" {\n \"\\\\\" <> rest -> rest\n _ -> \"\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case "Θ wibble wobble" { "Θ" <> rest -> rest _ -> "" } case "🫥 is neutral dotted" { "🫥" <> rest -> rest _ -> "" } case "🇺🇸 is a cluster" { "🇺🇸" <> rest -> rest _ -> "" } case "\" is a an escaped quote" { "\"" <> rest -> rest _ -> "" } case "\\ is a an escaped backslash" { "\\" <> rest -> rest _ -> "" } } ----- COMPILED JAVASCRIPT export function go(x) { let $ = "Θ wibble wobble"; if ($.startsWith("Θ")) { let rest = $.slice(1); rest } else { "" } let $1 = "🫥 is neutral dotted"; if ($1.startsWith("🫥")) { let rest = $1.slice(2); rest } else { "" } let $2 = "🇺🇸 is a cluster"; if ($2.startsWith("🇺🇸")) { let rest = $2.slice(4); rest } else { "" } let $3 = "\" is a an escaped quote"; if ($3.startsWith("\"")) { let rest = $3.slice(1); rest } else { "" } let $4 = "\\ is a an escaped backslash"; if ($4.startsWith("\\")) { let rest = $4.slice(1); return rest; } else { return ""; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__strings__unicode1.snap ================================================ --- source: compiler-core/src/javascript/tests/strings.rs expression: "\npub fn emoji() -> String {\n \"\\u{1f600}\"\n}\n" --- ----- SOURCE CODE pub fn emoji() -> String { "\u{1f600}" } ----- COMPILED JAVASCRIPT export function emoji() { return "\u{1f600}"; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__strings__unicode2.snap ================================================ --- source: compiler-core/src/javascript/tests/strings.rs expression: "\npub fn y_with_dieresis() -> String {\n \"\\u{0308}y\"\n}\n" --- ----- SOURCE CODE pub fn y_with_dieresis() -> String { "\u{0308}y" } ----- COMPILED JAVASCRIPT export function y_with_dieresis() { return "\u{0308}y"; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__strings__unicode_escape_sequence_6_digits.snap ================================================ --- source: compiler-core/src/javascript/tests/strings.rs expression: "\npub fn unicode_escape_sequence_6_digits() -> String {\n \"\\u{10abcd}\"\n}\n" --- ----- SOURCE CODE pub fn unicode_escape_sequence_6_digits() -> String { "\u{10abcd}" } ----- COMPILED JAVASCRIPT export function unicode_escape_sequence_6_digits() { return "\u{10abcd}"; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__todo__as_expression.snap ================================================ --- source: compiler-core/src/javascript/tests/todo.rs assertion_line: 51 expression: "\npub fn go(f) {\n let boop = todo as \"I should do this\"\n f(todo as \"Boom\")\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(f) { let boop = todo as "I should do this" f(todo as "Boom") } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go(f) { let _block; throw makeError("todo", FILEPATH, "my/mod", 3, "go", "I should do this", {}) let boop = _block; return f( (() => { throw makeError("todo", FILEPATH, "my/mod", 4, "go", "Boom", {}) })(), ); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__todo__case_message.snap ================================================ --- source: compiler-core/src/javascript/tests/todo.rs assertion_line: 64 expression: "\npub fn unimplemented(message) {\n panic as case message {\n Ok(message) -> message\n Error(_) -> \"No message provided\"\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn unimplemented(message) { panic as case message { Ok(message) -> message Error(_) -> "No message provided" } } ----- COMPILED JAVASCRIPT import { Ok, makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function unimplemented(message) { let _block; if (message instanceof Ok) { let message$1 = message[0]; _block = message$1; } else { _block = "No message provided"; } throw makeError("panic", FILEPATH, "my/mod", 3, "unimplemented", _block, {}) } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__todo__inside_fn.snap ================================================ --- source: compiler-core/src/javascript/tests/todo.rs assertion_line: 78 expression: "\npub fn main() {\n fn() { todo }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn main() { fn() { todo } } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main() { return () => { throw makeError( "todo", FILEPATH, "my/mod", 3, "main", "`todo` expression evaluated. This code has not yet been implemented.", {} ) }; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__todo__with_message.snap ================================================ --- source: compiler-core/src/javascript/tests/todo.rs assertion_line: 27 expression: "\npub fn go() {\n todo as \"I should do this\"\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { todo as "I should do this" } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go() { throw makeError("todo", FILEPATH, "my/mod", 3, "go", "I should do this", {}) } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__todo__with_message_expr.snap ================================================ --- source: compiler-core/src/javascript/tests/todo.rs assertion_line: 38 expression: "\npub fn go() {\n let x = \"I should \" <> \"do this\"\n todo as x\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { let x = "I should " <> "do this" todo as x } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go() { let x = "I should " + "do this"; throw makeError("todo", FILEPATH, "my/mod", 4, "go", x, {}) } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__todo__without_message.snap ================================================ --- source: compiler-core/src/javascript/tests/todo.rs assertion_line: 5 expression: "\npub fn go() {\n todo\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { todo } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function go() { throw makeError( "todo", FILEPATH, "my/mod", 3, "go", "`todo` expression evaluated. This code has not yet been implemented.", {} ) } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__todo__without_message_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/todo.rs expression: "\npub fn go() {\n todo\n}\n" --- ----- SOURCE CODE pub fn go() { todo } ----- TYPESCRIPT DEFINITIONS export function go(): any; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__tuples__case.snap ================================================ --- source: compiler-core/src/javascript/tests/tuples.rs assertion_line: 122 expression: "\npub fn go(a) {\n case a {\n #(2, a) -> a\n #(1, 1) -> 1\n #(a, b) -> a + b\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(a) { case a { #(2, a) -> a #(1, 1) -> 1 #(a, b) -> a + b } } ----- COMPILED JAVASCRIPT export function go(a) { let $ = a[0]; if ($ === 2) { let a$1 = a[1]; return a$1; } else if ($ === 1) { let $1 = a[1]; if ($1 === 1) { return 1; } else { let a$1 = $; let b = $1; return a$1 + b; } } else { let a$1 = $; let b = a[1]; return a$1 + b; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__tuples__constant_tuples.snap ================================================ --- source: compiler-core/src/javascript/tests/tuples.rs assertion_line: 85 expression: "\npub const a = \"Hello\"\npub const b = 1\npub const c = 2.0\npub const e = #(\"bob\", \"dug\")\n " snapshot_kind: text --- ----- SOURCE CODE pub const a = "Hello" pub const b = 1 pub const c = 2.0 pub const e = #("bob", "dug") ----- COMPILED JAVASCRIPT export const a = "Hello"; export const b = 1; export const c = 2.0; export const e = ["bob", "dug"]; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__tuples__constant_tuples1.snap ================================================ --- source: compiler-core/src/javascript/tests/tuples.rs assertion_line: 97 expression: "\npub const e = #(\n \"loooooooooooooong\", \"loooooooooooong\", \"loooooooooooooong\",\n \"loooooooooooooong\", \"loooooooooooong\", \"loooooooooooooong\",\n)\n" snapshot_kind: text --- ----- SOURCE CODE pub const e = #( "loooooooooooooong", "loooooooooooong", "loooooooooooooong", "loooooooooooooong", "loooooooooooong", "loooooooooooooong", ) ----- COMPILED JAVASCRIPT export const e = [ "loooooooooooooong", "loooooooooooong", "loooooooooooooong", "loooooooooooooong", "loooooooooooong", "loooooooooooooong", ]; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__tuples__nested_pattern.snap ================================================ --- source: compiler-core/src/javascript/tests/tuples.rs assertion_line: 137 expression: "\npub fn go(x) {\n case x {\n #(2, #(a, b)) -> a + b\n _ -> 1\n }\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go(x) { case x { #(2, #(a, b)) -> a + b _ -> 1 } } ----- COMPILED JAVASCRIPT export function go(x) { let $ = x[0]; if ($ === 2) { let a = x[1][0]; let b = x[1][1]; return a + b; } else { return 1; } } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__tuples__tuple.snap ================================================ --- source: compiler-core/src/javascript/tests/tuples.rs assertion_line: 5 expression: "\npub fn go() {\n #(\"1\", \"2\", \"3\")\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { #("1", "2", "3") } ----- COMPILED JAVASCRIPT export function go() { return ["1", "2", "3"]; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__tuples__tuple1.snap ================================================ --- source: compiler-core/src/javascript/tests/tuples.rs assertion_line: 16 expression: "\npub fn go() {\n #(\n \"1111111111111111111111111111111\",\n #(\"1111111111111111111111111111111\", \"2\", \"3\"),\n \"3\",\n )\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { #( "1111111111111111111111111111111", #("1111111111111111111111111111111", "2", "3"), "3", ) } ----- COMPILED JAVASCRIPT export function go() { return [ "1111111111111111111111111111111", ["1111111111111111111111111111111", "2", "3"], "3", ]; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__tuples__tuple_access.snap ================================================ --- source: compiler-core/src/javascript/tests/tuples.rs assertion_line: 42 expression: "\npub fn go() {\n #(1, 2).0\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { #(1, 2).0 } ----- COMPILED JAVASCRIPT export function go() { return [1, 2][0]; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__tuples__tuple_formatting_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/tuples.rs expression: "\npub const e = #(\n \"a\", \"a\", \"a\", \"a\", \"a\", \"a\", \"a\",\n \"a\", \"a\", \"a\", \"a\", \"a\", \"a\", \"a\",\n \"a\", \"a\", \"a\", \"a\", \"a\", \"a\", \"a\",\n)\n" --- ----- SOURCE CODE pub const e = #( "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", ) ----- TYPESCRIPT DEFINITIONS export const e: [ string, string, string, string, string, string, string, string, string, string, string, string, string, string, string, string, string, string, string, string, string ]; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__tuples__tuple_typescript.snap ================================================ --- source: compiler-core/src/javascript/tests/tuples.rs expression: "\npub fn go() {\n #(\"1\", \"2\", \"3\")\n}\n" --- ----- SOURCE CODE pub fn go() { #("1", "2", "3") } ----- TYPESCRIPT DEFINITIONS export function go(): [string, string, string]; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__tuples__tuple_with_block_element.snap ================================================ --- source: compiler-core/src/javascript/tests/tuples.rs assertion_line: 53 expression: "\npub fn go() {\n #(\n \"1\",\n {\n \"2\"\n \"3\"\n },\n )\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { #( "1", { "2" "3" }, ) } ----- COMPILED JAVASCRIPT export function go() { return [ "1", (() => { "2"; return "3"; })(), ]; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__tuples__tuple_with_block_element1.snap ================================================ --- source: compiler-core/src/javascript/tests/tuples.rs assertion_line: 70 expression: "\npub fn go() {\n #(\n \"1111111111111111111111111111111\",\n #(\"1111111111111111111111111111111\", \"2\", \"3\"),\n \"3\",\n )\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn go() { #( "1111111111111111111111111111111", #("1111111111111111111111111111111", "2", "3"), "3", ) } ----- COMPILED JAVASCRIPT export function go() { return [ "1111111111111111111111111111111", ["1111111111111111111111111111111", "2", "3"], "3", ]; } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__type_alias__import_indirect_type_alias.snap ================================================ --- source: compiler-core/src/javascript/tests/type_alias.rs expression: "\nimport wobble\n\npub fn main(x: wobble.Wobble) {\n Nil\n}\n" --- ----- SOURCE CODE import wobble pub fn main(x: wobble.Wobble) { Nil } ----- TYPESCRIPT DEFINITIONS import type * as $wibble from "../../wibble/wibble.d.mts"; export function main(x: $wibble.Wibble$): undefined; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__type_alias__private_type_in_opaque_type.snap ================================================ --- source: compiler-core/src/javascript/tests/type_alias.rs expression: "\ntype PrivateType {\n PrivateType\n}\n\npub opaque type OpaqueType {\n OpaqueType(PrivateType)\n}\n" --- ----- SOURCE CODE type PrivateType { PrivateType } pub opaque type OpaqueType { OpaqueType(PrivateType) } ----- TYPESCRIPT DEFINITIONS import type * as _ from "../gleam.d.mts"; declare class PrivateType extends _.CustomType {} type PrivateType$ = PrivateType; declare class OpaqueType extends _.CustomType { /** @deprecated */ constructor(argument$0: PrivateType$); /** @deprecated */ 0: PrivateType$; } export type OpaqueType$ = OpaqueType; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__type_alias__type_alias.snap ================================================ --- source: compiler-core/src/javascript/tests/type_alias.rs expression: "\npub type Headers = List(#(String, String))\n" --- ----- SOURCE CODE pub type Headers = List(#(String, String)) ----- TYPESCRIPT DEFINITIONS import type * as _ from "../gleam.d.mts"; export type Headers = _.List<[string, string]>; ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__use___arity_1.snap ================================================ --- source: compiler-core/src/javascript/tests/use_.rs expression: "\npub fn main() {\n use <- pair()\n 123\n}\n\nfn pair(f) {\n let x = f()\n #(x, x)\n}\n" --- ----- SOURCE CODE pub fn main() { use <- pair() 123 } fn pair(f) { let x = f() #(x, x) } ----- COMPILED JAVASCRIPT function pair(f) { let x = f(); return [x, x]; } export function main() { return pair(() => { return 123; }); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__use___arity_2.snap ================================================ --- source: compiler-core/src/javascript/tests/use_.rs expression: "\npub fn main() {\n use <- pair(1.0)\n 123\n}\n\nfn pair(x, f) {\n let y = f()\n #(x, y)\n}\n" --- ----- SOURCE CODE pub fn main() { use <- pair(1.0) 123 } fn pair(x, f) { let y = f() #(x, y) } ----- COMPILED JAVASCRIPT function pair(x, f) { let y = f(); return [x, y]; } export function main() { return pair(1.0, () => { return 123; }); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__use___arity_3.snap ================================================ --- source: compiler-core/src/javascript/tests/use_.rs expression: "\npub fn main() {\n use <- trip(1.0, \"\")\n 123\n}\n\nfn trip(x, y, f) {\n let z = f()\n #(x, y, z)\n}\n" --- ----- SOURCE CODE pub fn main() { use <- trip(1.0, "") 123 } fn trip(x, y, f) { let z = f() #(x, y, z) } ----- COMPILED JAVASCRIPT function trip(x, y, f) { let z = f(); return [x, y, z]; } export function main() { return trip(1.0, "", () => { return 123; }); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__use___no_callback_body.snap ================================================ --- source: compiler-core/src/javascript/tests/use_.rs assertion_line: 56 expression: "\npub fn main() {\n let thingy = fn(f) { f() }\n use <- thingy()\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn main() { let thingy = fn(f) { f() } use <- thingy() } ----- COMPILED JAVASCRIPT import { makeError } from "../gleam.mjs"; const FILEPATH = "src/module.gleam"; export function main() { let thingy = (f) => { return f(); }; return thingy( () => { throw makeError( "todo", FILEPATH, "my/mod", 4, "main", "`todo` expression evaluated. This code has not yet been implemented.", {} ) }, ); } ================================================ FILE: compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__use___patterns.snap ================================================ --- source: compiler-core/src/javascript/tests/use_.rs expression: "\npub fn main() {\n use Box(x) <- apply(Box(1))\n x\n}\n\ntype Box(a) {\n Box(a)\n}\n\nfn apply(arg, fun) {\n fun(arg)\n}\n" --- ----- SOURCE CODE pub fn main() { use Box(x) <- apply(Box(1)) x } type Box(a) { Box(a) } fn apply(arg, fun) { fun(arg) } ----- COMPILED JAVASCRIPT import { CustomType as $CustomType } from "../gleam.mjs"; class Box extends $CustomType { constructor($0) { super(); this[0] = $0; } } function apply(arg, fun) { return fun(arg); } export function main() { return apply( new Box(1), (_use0) => { let x; x = _use0[0]; return x; }, ); } ================================================ FILE: compiler-core/src/javascript/tests/strings.rs ================================================ use crate::assert_js; #[test] fn unicode1() { assert_js!( r#" pub fn emoji() -> String { "\u{1f600}" } "#, ); } #[test] fn unicode2() { assert_js!( r#" pub fn y_with_dieresis() -> String { "\u{0308}y" } "#, ); } #[test] fn ascii_as_unicode_escape_sequence() { assert_js!( r#" pub fn y() -> String { "\u{79}" } "#, ) } #[test] fn unicode_escape_sequence_6_digits() { assert_js!( r#" pub fn unicode_escape_sequence_6_digits() -> String { "\u{10abcd}" } "#, ); } #[test] fn string_literals() { assert_js!( r#" pub fn go() { "Hello, Gleam!" } "#, ); } #[test] fn string_patterns() { assert_js!( r#" pub fn go(x) { let assert "Hello" = x } "#, ); } #[test] fn equality() { assert_js!( r#" pub fn go(a) { a == "ok" a != "ok" a == a } "#, ); } #[test] fn case() { assert_js!( r#" pub fn go(a) { case a { "" -> 0 "one" -> 1 "two" -> 2 _ -> 3 } } "#, ); } #[test] fn string_concat() { assert_js!( r#" pub fn go() { "Hello, " <> "Joe" } "#, ); } #[test] fn string_prefix() { assert_js!( r#" pub fn go(x) { case x { "Hello, " <> name -> name _ -> "Unknown" } } "#, ); } #[test] fn string_prefix_utf16() { assert_js!( r#" pub fn go(x) { case "Θ wibble wobble" { "Θ" <> rest -> rest _ -> "" } case "🫥 is neutral dotted" { "🫥" <> rest -> rest _ -> "" } case "🇺🇸 is a cluster" { "🇺🇸" <> rest -> rest _ -> "" } case "\" is a an escaped quote" { "\"" <> rest -> rest _ -> "" } case "\\ is a an escaped backslash" { "\\" <> rest -> rest _ -> "" } } "#, ); } #[test] fn discard_concat_rest_pattern() { // We can discard the right hand side, it parses and type checks ok assert_js!( r#" pub fn go(x) { case x { "Hello, " <> _ -> Nil _ -> Nil } } "#, ); } #[test] fn string_prefix_assignment() { assert_js!( r#" pub fn go(x) { case x { "Hello, " as greeting <> name -> greeting _ -> "Unknown" } } "#, ) } #[test] fn string_prefix_assignment_with_utf_escape_sequence() { assert_js!( r#" pub fn go(x) { case x { "\u{0032} " as greeting <> name -> greeting "\u{0007ff} " as greeting <> name -> greeting "\u{00ffff} " as greeting <> name -> greeting "\u{10ffff} " as greeting <> name -> greeting _ -> "Unknown" } } "#, ) } #[test] fn string_prefix_shadowing() { assert_js!( r#" pub fn go(x) { case x { "Hello, " as x <> name -> x _ -> "Unknown" } } "#, ) } // https://github.com/gleam-lang/gleam/issues/2471 #[test] fn string_prefix_assignment_with_multiple_subjects() { assert_js!( r#" pub fn go(x) { case x { "1" as prefix <> _ | "11" as prefix <> _ -> prefix _ -> "Unknown" } } "#, ) } #[test] fn const_concat() { assert_js!( r#" pub const cute = "cute" pub const cute_bee = cute <> "bee" pub fn main() { cute_bee } "# ); } #[test] fn const_concat_multiple() { assert_js!( r#" pub const cute = "cute" pub const cute_bee = cute <> "bee" pub const cute_cute_bee_buzz = cute <> cute_bee <> "buzz" pub fn main() { cute_cute_bee_buzz } "# ); } ================================================ FILE: compiler-core/src/javascript/tests/todo.rs ================================================ use crate::{assert_js, assert_ts_def}; #[test] fn without_message() { assert_js!( r#" pub fn go() { todo } "#, ); } #[test] fn without_message_typescript() { assert_ts_def!( r#" pub fn go() { todo } "#, ); } #[test] fn with_message() { assert_js!( r#" pub fn go() { todo as "I should do this" } "#, ); } #[test] fn with_message_expr() { assert_js!( r#" pub fn go() { let x = "I should " <> "do this" todo as x } "#, ); } // https://github.com/gleam-lang/gleam/issues/1238 #[test] fn as_expression() { assert_js!( r#" pub fn go(f) { let boop = todo as "I should do this" f(todo as "Boom") } "#, ); } // https://github.com/gleam-lang/gleam/issues/4471 #[test] fn case_message() { assert_js!( r#" pub fn unimplemented(message) { panic as case message { Ok(message) -> message Error(_) -> "No message provided" } } "# ); } #[test] fn inside_fn() { assert_js!( r#" pub fn main() { fn() { todo } } "# ); } ================================================ FILE: compiler-core/src/javascript/tests/tuples.rs ================================================ use crate::{assert_js, assert_ts_def}; #[test] fn tuple() { assert_js!( r#" pub fn go() { #("1", "2", "3") } "#, ); } #[test] fn tuple1() { assert_js!( r#" pub fn go() { #( "1111111111111111111111111111111", #("1111111111111111111111111111111", "2", "3"), "3", ) } "#, ); } #[test] fn tuple_typescript() { assert_ts_def!( r#" pub fn go() { #("1", "2", "3") } "#, ); } #[test] fn tuple_access() { assert_js!( r#" pub fn go() { #(1, 2).0 } "#, ) } #[test] fn tuple_with_block_element() { assert_js!( r#" pub fn go() { #( "1", { "2" "3" }, ) } "#, ); } #[test] fn tuple_with_block_element1() { assert_js!( r#" pub fn go() { #( "1111111111111111111111111111111", #("1111111111111111111111111111111", "2", "3"), "3", ) } "#, ); } #[test] fn constant_tuples() { assert_js!( r#" pub const a = "Hello" pub const b = 1 pub const c = 2.0 pub const e = #("bob", "dug") "#, ); } #[test] fn constant_tuples1() { assert_js!( r#" pub const e = #( "loooooooooooooong", "loooooooooooong", "loooooooooooooong", "loooooooooooooong", "loooooooooooong", "loooooooooooooong", ) "# ); } #[test] fn tuple_formatting_typescript() { assert_ts_def!( r#" pub const e = #( "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", ) "# ); } #[test] fn case() { assert_js!( r#" pub fn go(a) { case a { #(2, a) -> a #(1, 1) -> 1 #(a, b) -> a + b } } "# ); } #[test] fn nested_pattern() { assert_js!( r#" pub fn go(x) { case x { #(2, #(a, b)) -> a + b _ -> 1 } } "# ); } ================================================ FILE: compiler-core/src/javascript/tests/type_alias.rs ================================================ use crate::assert_ts_def; #[test] fn type_alias() { assert_ts_def!( r#" pub type Headers = List(#(String, String)) "#, ); } #[test] fn private_type_in_opaque_type() { assert_ts_def!( r#" type PrivateType { PrivateType } pub opaque type OpaqueType { OpaqueType(PrivateType) } "#, ); } #[test] fn import_indirect_type_alias() { assert_ts_def!( ( "wibble", "wibble", r#" pub type Wibble { Wibble(Int) } "# ), ( "wobble", "wobble", r#" import wibble pub type Wobble = wibble.Wibble "# ), r#" import wobble pub fn main(x: wobble.Wobble) { Nil } "#, ); } ================================================ FILE: compiler-core/src/javascript/tests/use_.rs ================================================ use crate::assert_js; #[test] fn arity_1() { assert_js!( r#" pub fn main() { use <- pair() 123 } fn pair(f) { let x = f() #(x, x) } "#, ) } #[test] fn arity_2() { assert_js!( r#" pub fn main() { use <- pair(1.0) 123 } fn pair(x, f) { let y = f() #(x, y) } "#, ) } #[test] fn arity_3() { assert_js!( r#" pub fn main() { use <- trip(1.0, "") 123 } fn trip(x, y, f) { let z = f() #(x, y, z) } "#, ) } #[test] fn no_callback_body() { assert_js!( r#" pub fn main() { let thingy = fn(f) { f() } use <- thingy() } "# ); } #[test] fn patterns() { assert_js!( r#" pub fn main() { use Box(x) <- apply(Box(1)) x } type Box(a) { Box(a) } fn apply(arg, fun) { fun(arg) } "# ); } ================================================ FILE: compiler-core/src/javascript/tests.rs ================================================ use crate::{ analyse::TargetSupport, build::{Origin, Target}, config::PackageConfig, inline, javascript::*, uid::UniqueIdGenerator, warning::{TypeWarningEmitter, WarningEmitter}, }; use camino::{Utf8Path, Utf8PathBuf}; mod assert; mod assignments; mod bit_arrays; mod blocks; mod bools; mod case; mod case_clause_guards; mod consts; mod custom_types; mod echo; mod externals; mod functions; mod generics; mod inlining; mod lists; mod modules; mod numbers; mod panic; mod prelude; mod records; mod recursion; mod results; mod strings; mod todo; mod tuples; mod type_alias; mod use_; pub static CURRENT_PACKAGE: &str = "thepackage"; #[macro_export] macro_rules! assert_js { ($(($name:literal, $module_src:literal)),+, $src:literal $(,)?) => { let compiled = $crate::javascript::tests::compile_js($src, vec![$(($crate::javascript::tests::CURRENT_PACKAGE, $name, $module_src)),*]); let mut output = String::from("----- SOURCE CODE\n"); for (name, src) in [$(($name, $module_src)),*] { output.push_str(&format!("-- {name}.gleam\n{src}\n\n")); } output.push_str(&format!("-- main.gleam\n{}\n\n----- COMPILED JAVASCRIPT\n{compiled}", $src)); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }; ($(($dep_package:expr, $dep_name:expr, $dep_src:expr)),+, $src:literal $(,)?) => {{ let compiled = $crate::javascript::tests::compile_js($src, vec![$(($dep_package, $dep_name, $dep_src)),*]); let output = format!( "----- SOURCE CODE\n{}\n\n----- COMPILED JAVASCRIPT\n{}", $src, compiled ); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }}; (($dep_package:expr, $dep_name:expr, $dep_src:expr), $src:expr, $js:expr $(,)?) => {{ let output = $crate::javascript::tests::compile_js($src, Some(($dep_package, $dep_name, $dep_src))); assert_eq!(($src, output), ($src, $js.to_string())); }}; ($src:expr $(,)?) => {{ let compiled = $crate::javascript::tests::compile_js($src, vec![]); let output = format!( "----- SOURCE CODE\n{}\n\n----- COMPILED JAVASCRIPT\n{}", $src, compiled ); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }}; ($src:expr, $js:expr $(,)?) => {{ let output = $crate::javascript::tests::compile_js($src, vec![]); assert_eq!(($src, output), ($src, $js.to_string())); }}; } #[macro_export] macro_rules! assert_ts_def { (($dep_1_package:expr, $dep_1_name:expr, $dep_1_src:expr), ($dep_2_package:expr, $dep_2_name:expr, $dep_2_src:expr), $src:expr $(,)?) => {{ let compiled = $crate::javascript::tests::compile_ts( $src, vec![ ($dep_1_package, $dep_1_name, $dep_1_src), ($dep_2_package, $dep_2_name, $dep_2_src), ], ); let output = format!( "----- SOURCE CODE\n{}\n\n----- TYPESCRIPT DEFINITIONS\n{}", $src, compiled ); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }}; (($dep_package:expr, $dep_name:expr, $dep_src:expr), $src:expr $(,)?) => {{ let compiled = $crate::javascript::tests::compile_ts($src, vec![($dep_package, $dep_name, $dep_src)]); let output = format!( "----- SOURCE CODE\n{}\n\n----- TYPESCRIPT DEFINITIONS\n{}", $src, compiled ); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }}; ($src:expr $(,)?) => {{ let compiled = $crate::javascript::tests::compile_ts($src, vec![]); let output = format!( "----- SOURCE CODE\n{}\n\n----- TYPESCRIPT DEFINITIONS\n{}", $src, compiled ); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }}; } pub fn compile(src: &str, deps: Vec<(&str, &str, &str)>) -> TypedModule { let mut modules = im::HashMap::new(); let ids = UniqueIdGenerator::new(); // DUPE: preludeinsertion // TODO: Currently we do this here and also in the tests. It would be better // to have one place where we create all this required state for use in each // place. let _ = modules.insert( PRELUDE_MODULE_NAME.into(), crate::type_::build_prelude(&ids), ); let mut direct_dependencies = HashMap::from_iter(vec![]); deps.iter().for_each(|(dep_package, dep_name, dep_src)| { let mut dep_config = PackageConfig::default(); dep_config.name = (*dep_package).into(); let parsed = crate::parse::parse_module( Utf8PathBuf::from("test/path"), dep_src, &WarningEmitter::null(), ) .expect("dep syntax error"); let mut ast = parsed.module; ast.name = (*dep_name).into(); let line_numbers = LineNumbers::new(dep_src); let dep = crate::analyse::ModuleAnalyzerConstructor::<()> { target: Target::JavaScript, ids: &ids, origin: Origin::Src, importable_modules: &modules, warnings: &TypeWarningEmitter::null(), direct_dependencies: &HashMap::new(), dev_dependencies: &std::collections::HashSet::new(), target_support: TargetSupport::Enforced, package_config: &dep_config, } .infer_module(ast, line_numbers, "".into()) .expect("should successfully infer"); let _ = modules.insert((*dep_name).into(), dep.type_info); let _ = direct_dependencies.insert((*dep_package).into(), ()); }); let parsed = crate::parse::parse_module(Utf8PathBuf::from("test/path"), src, &WarningEmitter::null()) .expect("syntax error"); let mut ast = parsed.module; ast.name = "my/mod".into(); let line_numbers = LineNumbers::new(src); let mut config = PackageConfig::default(); config.name = "thepackage".into(); let module = crate::analyse::ModuleAnalyzerConstructor::<()> { target: Target::JavaScript, ids: &ids, origin: Origin::Src, importable_modules: &modules, warnings: &TypeWarningEmitter::null(), direct_dependencies: &direct_dependencies, dev_dependencies: &std::collections::HashSet::new(), target_support: TargetSupport::NotEnforced, package_config: &config, } .infer_module(ast, line_numbers, "src/module.gleam".into()) .expect("should successfully infer"); inline::module(module, &modules) } pub fn compile_js(src: &str, deps: Vec<(&str, &str, &str)>) -> String { let ast = compile(src, deps); let line_numbers = LineNumbers::new(src); let stdlib_package = StdlibPackage::Present; let output = module(ModuleConfig { module: &ast, line_numbers: &line_numbers, src: &"".into(), typescript: TypeScriptDeclarations::None, stdlib_package, path: Utf8Path::new("src/module.gleam"), project_root: "project/root".into(), }); output.replace( std::include_str!("../../templates/echo.mjs"), "// ...omitted code from `templates/echo.mjs`...", ) } pub fn compile_ts(src: &str, deps: Vec<(&str, &str, &str)>) -> String { let ast = compile(src, deps); ts_declaration(&ast) } ================================================ FILE: compiler-core/src/javascript/typescript.rs ================================================ //! This module is responsible for generating TypeScript type declaration files. //! This code is run during the code generation phase along side the normal //! Javascript code emission. Here we walk through the typed AST and translate //! the Gleam statements into their TypeScript equivalent. Unlike the Javascript //! code generation, the TypeScript generation only needs to look at the module //! statements and not the expressions that may be _inside_ those statements. //! This is due to the TypeScript declarations only caring about inputs and outputs //! rather than _how_ those outputs are generated. //! //! ## Links //! //! use crate::ast::{ AssignName, Publicity, TypedCustomType, TypedFunction, TypedModuleConstant, TypedTypeAlias, }; use crate::javascript::import::Member; use crate::type_::{PRELUDE_MODULE_NAME, RecordAccessor, is_prelude_module}; use crate::{ ast::{TypedModule, TypedRecordConstructor}, docvec, javascript::JavaScriptCodegenTarget, pretty::{Document, Documentable, break_}, type_::{Type, TypeVar}, }; use ecow::{EcoString, eco_format}; use itertools::Itertools; use std::{collections::HashMap, ops::Deref, sync::Arc}; use super::{INDENT, concat, import::Imports, join, line, lines, wrap_arguments}; /// When rendering a type variable to an TypeScript type spec we need all type /// variables with the same id to end up with the same name in the generated /// TypeScript. This function converts a usize into base 26 A-Z for this purpose. fn id_to_type_var(id: u64) -> Document<'static> { if id < 26 { return std::iter::once( std::char::from_u32((id % 26 + 65) as u32).expect("id_to_type_var 0"), ) .collect::() .to_doc(); } let mut name = vec![]; let mut last_char = id; while last_char >= 26 { name.push(std::char::from_u32((last_char % 26 + 65) as u32).expect("id_to_type_var 1")); last_char /= 26; } name.push(std::char::from_u32((last_char % 26 + 64) as u32).expect("id_to_type_var 2")); name.reverse(); name.into_iter().collect::().to_doc() } fn name_with_generics<'a>( name: Document<'a>, types: impl IntoIterator>, ) -> Document<'a> { let generic_usages = collect_generic_usages(HashMap::new(), types); let generic_names: Vec> = generic_usages .keys() .sorted() .map(|id| id_to_type_var(*id)) .collect(); docvec![ name, if generic_names.is_empty() { super::nil() } else { wrap_generic_arguments(generic_names) }, ] } /// A generic can either be rendered as an actual type variable such as `A` or `B`, /// or it can be rendered as `any` depending on how many usages it has. If it /// has only 1 usage it is an `any` type. If it has more than 1 usage it is a /// TS generic. This function gathers usages for this determination. /// /// Examples: /// fn(a) -> String // `a` is `any` /// `fn()` -> Result(a, b) // `a` and `b` are `any` /// fn(a) -> a // `a` is a generic fn collect_generic_usages<'a>( mut ids: HashMap, types: impl IntoIterator>, ) -> HashMap { for type_ in types { generic_ids(type_, &mut ids); } ids } fn generic_ids(type_: &Type, ids: &mut HashMap) { match type_ { Type::Var { type_ } => match type_.borrow().deref() { TypeVar::Unbound { id, .. } | TypeVar::Generic { id, .. } => { let count = ids.entry(*id).or_insert(0); *count += 1; } TypeVar::Link { type_ } => generic_ids(type_, ids), }, Type::Named { arguments, .. } => { for argument in arguments { generic_ids(argument, ids) } } Type::Fn { arguments, return_ } => { for argument in arguments { generic_ids(argument, ids) } generic_ids(return_, ids); } Type::Tuple { elements } => { for element in elements { generic_ids(element, ids) } } } } /// Prints a Gleam tuple in the TypeScript equivalent syntax /// fn tuple<'a>(elements: impl IntoIterator>) -> Document<'a> { break_("", "") .append(join(elements, break_(",", ", "))) .nest(INDENT) .append(break_("", "")) .surround("[", "]") .group() } fn wrap_generic_arguments<'a, I>(arguments: I) -> Document<'a> where I: IntoIterator>, { break_("", "") .append(join(arguments, break_(",", ", "))) .nest(INDENT) .append(break_("", "")) .surround("<", ">") .group() } /// Returns a name that can be used as a TypeScript type name. If there is a /// naming clash a '_' will be appended. /// fn ts_safe_type_name(mut name: String) -> EcoString { if matches!( name.as_str(), "any" | "boolean" | "constructor" | "declare" | "get" | "module" | "require" | "number" | "set" | "string" | "symbol" | "type" | "from" | "of" ) { name.push('_'); EcoString::from(name) } else { super::maybe_escape_identifier_string(&name) } } /// The `TypeScriptGenerator` contains the logic of how to convert Gleam's typed /// AST into the equivalent TypeScript type declaration file. /// #[derive(Debug)] pub struct TypeScriptGenerator<'a> { module: &'a TypedModule, aliased_module_names: HashMap<&'a str, &'a str>, tracker: UsageTracker, current_module_name_segments_count: usize, } impl<'a> TypeScriptGenerator<'a> { pub fn new(module: &'a TypedModule) -> Self { let current_module_name_segments_count = module.name.split('/').count(); Self { module, aliased_module_names: HashMap::new(), tracker: UsageTracker::default(), current_module_name_segments_count, } } pub fn compile(&mut self) -> Document<'a> { let mut imports = self.collect_imports(); let statements = self.definitions(&mut imports); // Two lines between each statement let mut statements = Itertools::intersperse(statements.into_iter(), lines(2)).collect_vec(); // Put it all together if self.prelude_used() { let path = self.import_path(&self.module.type_info.package, PRELUDE_MODULE_NAME); imports.register_module(path, ["_".into()], []); } if imports.is_empty() && statements.is_empty() { docvec!["export {}", line()] } else if imports.is_empty() { statements.push(line()); statements.to_doc() } else if statements.is_empty() { imports.into_doc(JavaScriptCodegenTarget::TypeScriptDeclarations) } else { docvec![ imports.into_doc(JavaScriptCodegenTarget::TypeScriptDeclarations), line(), statements, line() ] } } fn collect_imports(&mut self) -> Imports<'a> { let mut imports = Imports::new(); for function in &self.module.definitions.functions { for argument in &function.arguments { self.collect_imports_for_type(&argument.type_, &mut imports); } self.collect_imports_for_type(&function.return_type, &mut imports); } for type_alias in &self.module.definitions.type_aliases { self.collect_imports_for_type(&type_alias.type_, &mut imports); } for custom_type in &self.module.definitions.custom_types { for type_ in &custom_type.typed_parameters { self.collect_imports_for_type(type_, &mut imports); } for constructor in &custom_type.constructors { for argument in &constructor.arguments { self.collect_imports_for_type(&argument.type_, &mut imports); } } } for constant in &self.module.definitions.constants { self.collect_imports_for_type(&constant.type_, &mut imports); } for import in &self.module.definitions.imports { match &import.as_name { Some((AssignName::Variable(name), _)) => { let _ = self.aliased_module_names.insert(&import.module, name); } Some((AssignName::Discard(_), _)) | None => (), } } imports } /// Recurses through a type and any types it references, registering all of their imports. /// fn collect_imports_for_type<'b>(&mut self, type_: &'b Type, imports: &mut Imports<'a>) { match &type_ { Type::Named { package, module, arguments, .. } => { let is_prelude = module == "gleam" && package.is_empty(); let is_current_module = *module == self.module.name; if !is_prelude && !is_current_module { self.register_import(imports, package, module); } for argument in arguments { self.collect_imports_for_type(argument, imports); } } Type::Fn { arguments, return_ } => { for argument in arguments { self.collect_imports_for_type(argument, imports); } self.collect_imports_for_type(return_, imports); } Type::Tuple { elements } => { for element in elements { self.collect_imports_for_type(element, imports); } } Type::Var { type_ } => { if let TypeVar::Link { type_ } = type_ .as_ref() .try_borrow() .expect("borrow type after inference") .deref() { self.collect_imports_for_type(type_, imports); } } } } /// Registers an import of an external module so that it can be added to /// the top of the generated Document. The module names are prefixed with a /// "$" symbol to prevent any clashes with other Gleam names that may be /// used in this module. /// fn register_import<'b>( &mut self, imports: &mut Imports<'a>, package: &'b str, module: &'b str, ) { let path = self.import_path(package, module); imports.register_module(path, [self.module_name(module)], []); } /// Calculates the path of where to import an external module from /// fn import_path<'b>(&self, package: &'b str, module: &'b str) -> EcoString { // DUPE: current_module_name_segments_count // TODO: strip shared prefixed between current module and imported // module to avoid descending and climbing back out again if package == self.module.type_info.package || package.is_empty() { // Same package match self.current_module_name_segments_count { 1 => eco_format!("./{module}.d.mts"), _ => { let prefix = "../".repeat(self.current_module_name_segments_count - 1); eco_format!("{prefix}{module}.d.mts") } } } else { // Different package let prefix = "../".repeat(self.current_module_name_segments_count); eco_format!("{prefix}{package}/{module}.d.mts") } } fn definitions(&mut self, imports: &mut Imports<'_>) -> Vec> { let mut documents = vec![]; for custom_type in &self.module.definitions.custom_types { if let Some(mut new_documents) = self.custom_type_definition(custom_type, imports) { documents.append(&mut new_documents); } } for type_alias in &self.module.definitions.type_aliases { if let Some(document) = self.type_alias(type_alias) { documents.push(document); } } for constant in &self.module.definitions.constants { if let Some(document) = self.module_constant(constant) { documents.push(document); } } for function in &self.module.definitions.functions { if let Some(document) = self.module_function(function) { documents.push(document); } } documents } fn type_alias(&mut self, type_alias: &TypedTypeAlias) -> Option> { if !type_alias.publicity.is_importable() { return None; } Some(docvec![ "export type ", ts_safe_type_name(type_alias.alias.to_string()), " = ", self.print_type(&type_alias.type_), ";" ]) } /// Converts a Gleam custom type definition into the TypeScript equivalent. /// In Gleam, all custom types have one to many concrete constructors. This /// function first converts the constructors into TypeScript then finally /// emits a union type to represent the TypeScript type itself. Because in /// Gleam constructors can have the same name as the custom type, here we /// append a "$" symbol to the emitted TypeScript type to prevent those /// naming classes. /// fn custom_type_definition( &mut self, custom_type: &'a TypedCustomType, imports: &mut Imports<'_>, ) -> Option>> { let TypedCustomType { name, publicity, constructors, opaque, typed_parameters, external_javascript, .. } = custom_type; // Constructors for opaque and private types are not exported let constructor_publicity = if publicity.is_private() || *opaque { Publicity::Private } else { Publicity::Public }; let type_name = name_with_generics(eco_format!("{name}$").to_doc(), typed_parameters); let mut definitions = constructors .iter() .map(|constructor| { self.variant_definition( constructor, constructor_publicity, name, &type_name, typed_parameters, ) }) .collect_vec(); let definition = if constructors.is_empty() { if let Some((module, external_name, _location)) = external_javascript { let member = Member { name: external_name.to_doc(), alias: Some(eco_format!("{name}$").to_doc()), }; imports.register_export(eco_format!("{name}$")); imports.register_module(module.clone(), [], [member]); return Some(Vec::new()); } else { "any".to_doc() } } else { let constructors = constructors.iter().map(|x| { name_with_generics( super::maybe_escape_identifier(&x.name).to_doc(), x.arguments.iter().map(|a| &a.type_), ) }); join(constructors, break_("| ", " | ")) }; let head = if publicity.is_private() { "type " } else { "export type " }; definitions.push(docvec![head, type_name.clone(), " = ", definition, ";",]); // Generate getters for fields shared between variants if let Some(accessors_map) = self.module.type_info.accessors.get(name) && !accessors_map.shared_accessors.is_empty() // Don't bother generating shared getters when there's only one variant, // since the specific accessors can always be uses instead. && constructors.len() != 1 // Only generate accessors for the API if the constructors are public && constructor_publicity.is_public() { definitions.push(self.shared_custom_type_fields( name, &type_name, typed_parameters, &accessors_map.shared_accessors, )); } Some(definitions) } fn variant_definition( &mut self, constructor: &'a TypedRecordConstructor, publicity: Publicity, type_name: &'a str, type_name_with_generics: &Document<'a>, type_parameters: &'a [Arc], ) -> Document<'a> { self.set_prelude_used(); let class_definition = self.variant_class_definition(constructor, publicity); // If the custom type is private or opaque, we don't need to generate API // functions for it. if publicity.is_private() { return class_definition; } let constructor_definition = self.variant_constructor_definition( constructor, type_name, type_name_with_generics, type_parameters, ); let variant_check_definition = self.variant_check_definition(constructor, type_name, type_parameters); let fields_definition = self.variant_fields_definition( constructor, type_name, type_name_with_generics, type_parameters, ); docvec![ class_definition, line(), constructor_definition, line(), variant_check_definition, fields_definition, ] } fn variant_class_definition( &mut self, constructor: &'a TypedRecordConstructor, publicity: Publicity, ) -> Document<'a> { let head = docvec![ if publicity.is_public() { "export ".to_doc() } else { "declare ".to_doc() }, "class ", name_with_generics( super::maybe_escape_identifier(&constructor.name).to_doc(), constructor.arguments.iter().map(|a| &a.type_) ), " extends _.CustomType {" ]; if constructor.arguments.is_empty() { return head.append("}"); }; let class_body = docvec![ line(), "/** @deprecated */", line(), // First add the constructor "constructor", wrap_arguments( constructor .arguments .iter() .enumerate() .map(|(i, argument)| { let name = argument .label .as_ref() .map(|(_, s)| super::maybe_escape_identifier(s)) .unwrap_or_else(|| eco_format!("argument${i}")) .to_doc(); docvec![ name, ": ", self.do_print_force_generic_param(&argument.type_) ] }) ), ";", line(), // Then add each field to the class join( constructor.arguments.iter().enumerate().map(|(i, arg)| { let name = arg .label .as_ref() .map(|(_, s)| super::maybe_escape_identifier(s)) .unwrap_or_else(|| eco_format!("{i}")) .to_doc(); docvec![ "/** @deprecated */", line(), name, ": ", self.do_print_force_generic_param(&arg.type_), ";" ] }), line(), ), ] .nest(INDENT); docvec![head, class_body, line(), "}"] } fn variant_constructor_definition( &mut self, constructor: &'a TypedRecordConstructor, type_name: &'a str, type_name_with_generics: &Document<'a>, type_parameters: &'a [Arc], ) -> Document<'a> { let mut arguments = Vec::new(); for (index, parameter) in constructor.arguments.iter().enumerate() { let name = if let Some((_, label)) = ¶meter.label { super::maybe_escape_identifier(label) } else { eco_format!("${index}") }; arguments.push(docvec![ name, ": ", self.do_print_force_generic_param(¶meter.type_) ]) } let function_name = eco_format!( "{type_name}${variant_name}", variant_name = constructor.name ) .to_doc(); let has_arguments = !arguments.is_empty(); docvec![ "export function ", name_with_generics(function_name, type_parameters), "(", docvec![break_("", ""), join(arguments, break_(",", ", ")),].nest(INDENT), break_(if has_arguments { "," } else { "" }, ""), "): ", type_name_with_generics.clone(), ";" ] .group() } fn variant_check_definition( &self, constructor: &'a TypedRecordConstructor, type_name: &'a str, type_parameters: &'a [Arc], ) -> Document<'a> { let function_name = eco_format!( "{type_name}$is{variant_name}", variant_name = constructor.name ) .to_doc(); let mut document = docvec![ "export function ", name_with_generics(function_name, type_parameters), "(", docvec![break_("", "",), "value: any"].nest(INDENT), break_(",", ""), "): value is ", type_name, "$", ]; if !type_parameters.is_empty() { for i in 0..type_parameters.len() { if i == 0 { document = document.append("'); }; document = document.append(';'); document.group() } fn variant_fields_definition( &mut self, constructor: &'a TypedRecordConstructor, type_name: &'a str, type_name_with_generics: &Document<'a>, type_parameters: &'a [Arc], ) -> Document<'a> { let mut functions = Vec::new(); for (index, argument) in constructor.arguments.iter().enumerate() { // Always generate the accessor for the value at this index. Although // this is not necessary when a label is present, we want to make sure // that adding a label to a record isn't a breaking change. For this // reason, we need to generate an index getter even when a label is // present to ensure consistent behaviour between labelled and unlabelled // field access. let function_name = eco_format!( "{type_name}${variant_name}${index}", variant_name = constructor.name ) .to_doc(); functions.push( docvec![ line(), "export function ", name_with_generics(function_name, type_parameters), "(", docvec![break_("", "",), "value: ", type_name_with_generics.clone(),] .nest(INDENT), break_(",", ""), "): ", self.do_print_force_generic_param(&argument.type_), ";", ] .group(), ); // If the argument is labelled, also generate a getter for the labelled // argument. if let Some((_, label)) = &argument.label { let function_name = eco_format!( "{type_name}${variant_name}${label}", variant_name = constructor.name ) .to_doc(); functions.push( docvec![ line(), "export function ", name_with_generics(function_name, type_parameters), "(", docvec![break_("", "",), "value: ", type_name_with_generics.clone(),] .nest(INDENT), break_(",", ""), "): ", self.do_print_force_generic_param(&argument.type_), ";", ] .group(), ); } } concat(functions) } fn shared_custom_type_fields( &mut self, type_name: &'a str, type_name_with_generics: &Document<'a>, type_parameters: &'a [Arc], shared_accessors: &HashMap, ) -> Document<'a> { let accessors = shared_accessors .iter() .sorted_by_key(|(name, _)| *name) .map(|(field, accessor)| { let function_name = eco_format!("{type_name}${field}").to_doc(); docvec![ "export function ", name_with_generics(function_name, type_parameters), "(", docvec![break_("", "",), "value: ", type_name_with_generics.clone(),] .nest(INDENT), break_(",", ""), "): ", self.do_print_force_generic_param(&accessor.type_), ";" ] .group() }); join(accessors, line()) } fn module_constant(&mut self, constant: &TypedModuleConstant) -> Option> { if !constant.publicity.is_importable() { return None; } Some(docvec![ "export const ", super::maybe_escape_identifier(&constant.name), ": ", self.print_type(&constant.value.type_()), ";", ]) } fn module_function(&mut self, function: &'a TypedFunction) -> Option> { let TypedFunction { name: Some((_, name)), arguments, publicity, return_type, .. } = function else { return None; }; if !publicity.is_importable() { return None; } let generic_usages = collect_generic_usages( HashMap::new(), std::iter::once(return_type).chain(arguments.iter().map(|a| &a.type_)), ); let generic_names: Vec> = generic_usages .iter() .filter(|(_id, use_count)| **use_count > 1) .sorted_by_key(|x| x.0) .map(|(id, _use_count)| id_to_type_var(*id)) .collect(); Some(docvec![ "export function ", super::maybe_escape_identifier(name), if generic_names.is_empty() { super::nil() } else { wrap_generic_arguments(generic_names) }, wrap_arguments(arguments.iter().enumerate().map(|(i, argument)| { match argument.get_variable_name() { None => { docvec![ "x", i, ": ", self.print_type_with_generic_usages(&argument.type_, &generic_usages) ] } Some(name) => docvec![ super::maybe_escape_identifier(name), ": ", self.print_type_with_generic_usages(&argument.type_, &generic_usages) ], } }),), ": ", self.print_type_with_generic_usages(return_type, &generic_usages), ";", ]) } /// Converts a Gleam type into a TypeScript type string /// pub fn print_type(&mut self, type_: &Type) -> Document<'static> { self.do_print(type_, GenericPrinting::AsAny) } /// Helper function for generating a TypeScript type string after collecting /// all of the generics used in a statement /// pub fn print_type_with_generic_usages( &mut self, type_: &Type, generic_usages: &HashMap, ) -> Document<'static> { self.do_print(type_, GenericPrinting::FromUsage(generic_usages)) } /// Get the locally used name for a module. Either the last segment, or the /// alias if one was given when imported. /// fn module_name(&self, name: &str) -> EcoString { // The prelude is always `_` if name.is_empty() { return "_".into(); } let name = match self.aliased_module_names.get(name) { Some(name) => name, None => name.split('/').next_back().expect("Non empty module path"), }; eco_format!("${name}") } fn do_print( &mut self, type_: &Type, generic_printing: GenericPrinting<'_>, ) -> Document<'static> { match type_ { Type::Var { type_ } => self.print_var(&type_.borrow(), generic_printing), Type::Named { name, module, arguments, .. } if is_prelude_module(module) => { self.print_prelude_type(name, arguments, generic_printing) } Type::Named { name, arguments, module, .. } => self.print_type_app(name, arguments, module, generic_printing), Type::Fn { arguments, return_ } => self.print_fn(arguments, return_, generic_printing), Type::Tuple { elements } => tuple( elements .iter() .map(|element| self.do_print(element, generic_printing)), ), } } fn do_print_force_generic_param(&mut self, type_: &Type) -> Document<'static> { match type_ { Type::Var { type_ } => self.print_var(&type_.borrow(), GenericPrinting::AlwaysGeneric), Type::Named { name, module, arguments, .. } if is_prelude_module(module) => { self.print_prelude_type(name, arguments, GenericPrinting::AlwaysGeneric) } Type::Named { name, arguments, module, .. } => self.print_type_app(name, arguments, module, GenericPrinting::AlwaysGeneric), Type::Fn { arguments, return_ } => { self.print_fn(arguments, return_, GenericPrinting::AlwaysGeneric) } Type::Tuple { elements } => tuple( elements .iter() .map(|element| self.do_print(element, GenericPrinting::AlwaysGeneric)), ), } } fn print_var( &mut self, type_: &TypeVar, generic_printing: GenericPrinting<'_>, ) -> Document<'static> { match type_ { TypeVar::Unbound { id } | TypeVar::Generic { id } => match generic_printing { GenericPrinting::FromUsage(usages) => match usages.get(id) { Some(&0) => super::nil(), Some(&1) => "any".to_doc(), _ => id_to_type_var(*id), }, GenericPrinting::AlwaysGeneric => id_to_type_var(*id), GenericPrinting::AsAny => "any".to_doc(), }, TypeVar::Link { type_ } => self.do_print(type_, generic_printing), } } /// Prints a type coming from the Gleam prelude module. These are often the /// low level types the rest of the type system are built up from. If there /// is no built-in TypeScript equivalent, the type is prefixed with "_." /// and the Gleam prelude namespace will be imported during the code emission. /// fn print_prelude_type( &mut self, name: &str, arguments: &[Arc], generic_printing: GenericPrinting<'_>, ) -> Document<'static> { match name { "Nil" => "undefined".to_doc(), "Int" | "Float" => "number".to_doc(), "UtfCodepoint" => { self.tracker.prelude_used = true; "_.UtfCodepoint".to_doc() } "String" => "string".to_doc(), "Bool" => "boolean".to_doc(), "BitArray" => { self.tracker.prelude_used = true; "_.BitArray".to_doc() } "List" => { self.tracker.prelude_used = true; docvec![ "_.List", wrap_generic_arguments( arguments .iter() .map(|argument| self.do_print(argument, generic_printing)) ) ] } "Result" => { self.tracker.prelude_used = true; docvec![ "_.Result", wrap_generic_arguments( arguments.iter().map(|x| self.do_print(x, generic_printing)) ) ] } // Getting here should mean we either forgot a built-in type or there is a // compiler error name => panic!("{name} is not a built-in type."), } } /// Prints a "named" programmer-defined Gleam type into the TypeScript /// equivalent. /// fn print_type_app( &mut self, name: &str, arguments: &[Arc], module: &str, generic_printing: GenericPrinting<'_>, ) -> Document<'static> { let name = eco_format!("{}$", ts_safe_type_name(name.to_string())); let name = match module == self.module.name { true => name.to_doc(), false => { // If type comes from a separate module, use that module's name // as a TypeScript namespace prefix docvec![self.module_name(module), ".", name] } }; if arguments.is_empty() { return name; } // If the App type takes arguments, pass them in as TypeScript generics docvec![ name, wrap_generic_arguments( arguments .iter() .map(|argument| self.do_print(argument, generic_printing)) ) ] } /// Prints the TypeScript type for an anonymous function (aka lambda) /// fn print_fn( &mut self, arguments: &[Arc], return_: &Type, generic_printing: GenericPrinting<'_>, ) -> Document<'static> { docvec![ wrap_arguments(arguments.iter().enumerate().map(|(idx, argument)| docvec![ "x", idx, ": ", self.do_print(argument, generic_printing) ])), " => ", self.do_print(return_, generic_printing) ] } /// Allows an outside module to mark the Gleam prelude as "used" /// pub fn set_prelude_used(&mut self) { self.tracker.prelude_used = true } /// Returns if the Gleam prelude has been used at all during the process /// of printing the TypeScript types /// pub fn prelude_used(&self) -> bool { self.tracker.prelude_used } } #[derive(Debug, Default)] pub(crate) struct UsageTracker { pub prelude_used: bool, } /// How to print generic type parameters when generating declarations. #[derive(Debug, Clone, Copy)] enum GenericPrinting<'a> { /// Print the generic parameters based on how many times they are used: /// - If a parameter isn't used, it is not printed. /// - If a parameter is used exactly once, it is printed as `any`. /// - If a parameter is used more than once, it is printed as a generic. /// /// For example, the following function: /// /// ```gleam /// pub fn wibble(thing: one, other_thing: other) -> one { ... } /// ``` /// /// Would result in the following declaration: /// /// ```typescript /// export function wibble(thing: A, other_thing: any): A; /// ``` /// FromUsage(&'a HashMap), /// Print every generic parameter as a generic in TypeScript. This is used in /// custom types where every generic needs to be emitted, even if it is only /// referenced once, or not at all. /// /// For example: /// /// ```gleam /// pub type Wibble(multiple, single, phantom) { /// Wibble(a: single, b: multiple, b: multiple) /// } /// ``` /// /// Generates more or less the following TypeScript: /// /// ```typescript /// export class Wibble extends CustomType { /// a: A; /// b: B; /// c: B; /// } /// ``` /// AlwaysGeneric, /// Print generic parameters as TypeScript `any`. This is used in constants, /// where generics are not allowed. AsAny, } ================================================ FILE: compiler-core/src/javascript.rs ================================================ mod decision; mod expression; mod import; #[cfg(test)] mod tests; mod typescript; use std::collections::HashMap; use num_bigint::BigInt; use num_traits::ToPrimitive; use crate::build::Target; use crate::build::package_compiler::StdlibPackage; use crate::codegen::TypeScriptDeclarations; use crate::type_::{PRELUDE_MODULE_NAME, RecordAccessor}; use crate::{ ast::{Import, *}, docvec, line_numbers::LineNumbers, pretty::*, }; use camino::Utf8Path; use ecow::{EcoString, eco_format}; use expression::Context; use itertools::Itertools; use self::import::{Imports, Member}; const INDENT: isize = 2; pub const PRELUDE: &str = include_str!("../templates/prelude.mjs"); pub const PRELUDE_TS_DEF: &str = include_str!("../templates/prelude.d.mts"); #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum JavaScriptCodegenTarget { JavaScript, TypeScriptDeclarations, } #[derive(Debug)] pub struct Generator<'a> { line_numbers: &'a LineNumbers, module: &'a TypedModule, tracker: UsageTracker, module_scope: im::HashMap, current_module_name_segments_count: usize, typescript: TypeScriptDeclarations, stdlib_package: StdlibPackage, /// Relative path to the module, surrounded in `"`s to make it a string, and with `\`s escaped /// to `\\`. src_path: EcoString, } impl<'a> Generator<'a> { pub fn new(config: ModuleConfig<'a>) -> Self { let ModuleConfig { typescript, stdlib_package, module, line_numbers, src: _, path: _, project_root, } = config; let current_module_name_segments_count = module.name.split('/').count(); let src_path = &module.type_info.src_path; let src_path = src_path .strip_prefix(project_root) .unwrap_or(src_path) .as_str(); let src_path = eco_format!("\"{src_path}\"").replace("\\", "\\\\"); Self { current_module_name_segments_count, line_numbers, module, src_path, tracker: UsageTracker::default(), module_scope: Default::default(), typescript, stdlib_package, } } fn type_reference(&self) -> Document<'a> { if self.typescript == TypeScriptDeclarations::None { return nil(); } // Get the name of the module relative the directory (similar to basename) let module = self .module .name .as_str() .split('/') .next_back() .expect("JavaScript generator could not identify imported module name."); docvec!["/// ", line()] } pub fn compile(&mut self) -> Document<'a> { // Determine what JavaScript imports we need to generate let mut imports = self.collect_imports(); // Determine what names are defined in the module scope so we know to // rename any variables that are defined within functions using the same // names. self.register_module_definitions_in_scope(); // Generate JavaScript code for each statement. let statements = self.definitions(); // Two lines between each statement let mut statements = Itertools::intersperse(statements.into_iter(), lines(2)).collect_vec(); // Import any prelude functions that have been used if self.tracker.ok_used { self.register_prelude_usage(&mut imports, "Ok", None); }; if self.tracker.error_used { self.register_prelude_usage(&mut imports, "Error", None); }; if self.tracker.list_used { self.register_prelude_usage(&mut imports, "toList", None); }; if self.tracker.list_empty_class_used || self.tracker.echo_used { self.register_prelude_usage(&mut imports, "Empty", Some("$Empty")); }; if self.tracker.list_non_empty_class_used || self.tracker.echo_used { self.register_prelude_usage(&mut imports, "NonEmpty", Some("$NonEmpty")); }; if self.tracker.prepend_used { self.register_prelude_usage(&mut imports, "prepend", Some("listPrepend")); }; if self.tracker.custom_type_used || self.tracker.echo_used { self.register_prelude_usage(&mut imports, "CustomType", Some("$CustomType")); }; if self.tracker.make_error_used { self.register_prelude_usage(&mut imports, "makeError", None); }; if self.tracker.int_remainder_used { self.register_prelude_usage(&mut imports, "remainderInt", None); }; if self.tracker.float_division_used { self.register_prelude_usage(&mut imports, "divideFloat", None); }; if self.tracker.int_division_used { self.register_prelude_usage(&mut imports, "divideInt", None); }; if self.tracker.object_equality_used { self.register_prelude_usage(&mut imports, "isEqual", None); }; if self.tracker.bit_array_literal_used { self.register_prelude_usage(&mut imports, "toBitArray", None); } if self.tracker.bit_array_slice_used || self.tracker.echo_used { self.register_prelude_usage(&mut imports, "bitArraySlice", None); } if self.tracker.bit_array_slice_to_float_used { self.register_prelude_usage(&mut imports, "bitArraySliceToFloat", None); } if self.tracker.bit_array_slice_to_int_used || self.tracker.echo_used { self.register_prelude_usage(&mut imports, "bitArraySliceToInt", None); } if self.tracker.sized_integer_segment_used { self.register_prelude_usage(&mut imports, "sizedInt", None); } if self.tracker.string_bit_array_segment_used { self.register_prelude_usage(&mut imports, "stringBits", None); } if self.tracker.string_utf16_bit_array_segment_used { self.register_prelude_usage(&mut imports, "stringToUtf16", None); } if self.tracker.string_utf32_bit_array_segment_used { self.register_prelude_usage(&mut imports, "stringToUtf32", None); } if self.tracker.codepoint_bit_array_segment_used { self.register_prelude_usage(&mut imports, "codepointBits", None); } if self.tracker.codepoint_utf16_bit_array_segment_used { self.register_prelude_usage(&mut imports, "codepointToUtf16", None); } if self.tracker.codepoint_utf32_bit_array_segment_used { self.register_prelude_usage(&mut imports, "codepointToUtf32", None); } if self.tracker.float_bit_array_segment_used { self.register_prelude_usage(&mut imports, "sizedFloat", None); } let echo_definition = self.echo_definition(&mut imports); let type_reference = self.type_reference(); let filepath_definition = self.filepath_definition(); // Put it all together if imports.is_empty() && statements.is_empty() { docvec![ type_reference, filepath_definition, "export {}", line(), echo_definition ] } else if imports.is_empty() { statements.push(line()); docvec![ type_reference, filepath_definition, statements, echo_definition ] } else if statements.is_empty() { docvec![ type_reference, imports.into_doc(JavaScriptCodegenTarget::JavaScript), filepath_definition, echo_definition, ] } else { docvec![ type_reference, imports.into_doc(JavaScriptCodegenTarget::JavaScript), line(), filepath_definition, statements, line(), echo_definition ] } } fn echo_definition(&mut self, imports: &mut Imports<'a>) -> Document<'a> { if !self.tracker.echo_used { return nil(); } if StdlibPackage::Present == self.stdlib_package { let value = Some(( AssignName::Variable("stdlib$dict".into()), SrcSpan::default(), )); self.register_import(imports, "gleam_stdlib", "gleam/dict", &value, &[]); } self.register_prelude_usage(imports, "BitArray", Some("$BitArray")); self.register_prelude_usage(imports, "List", Some("$List")); self.register_prelude_usage(imports, "UtfCodepoint", Some("$UtfCodepoint")); docvec![line(), std::include_str!("../templates/echo.mjs"), line()] } fn register_prelude_usage( &self, imports: &mut Imports<'a>, name: &'static str, alias: Option<&'static str>, ) { let path = self.import_path(&self.module.type_info.package, PRELUDE_MODULE_NAME); let member = Member { name: name.to_doc(), alias: alias.map(|a| a.to_doc()), }; imports.register_module(path, [], [member]); } fn custom_type_definition( &mut self, custom_type: &'a TypedCustomType, ) -> Option>> { if self .module .unused_definition_positions .contains(&custom_type.location.start) { return None; } let TypedCustomType { name, publicity, constructors, opaque, .. } = custom_type; // If there's no constructors then there's nothing to do here. if constructors.is_empty() { return Some(vec![]); } self.tracker.custom_type_used = true; let constructor_publicity = if *opaque || publicity.is_private() { Publicity::Private } else { Publicity::Public }; let mut definitions = constructors .iter() .map(|constructor| self.variant_definition(constructor, name, constructor_publicity)) .collect_vec(); // Generate getters for fields shared between variants if let Some(accessors_map) = self.module.type_info.accessors.get(name) && !accessors_map.shared_accessors.is_empty() // Don't bother generating shared getters when there's only one variant, // since the specific accessors can always be uses instead. && constructors.len() != 1 // Only generate accessors for the API if the constructors are public && constructor_publicity.is_public() { definitions.push(self.shared_custom_type_fields(name, &accessors_map.shared_accessors)); } Some(definitions) } fn variant_definition( &self, constructor: &'a TypedRecordConstructor, type_name: &'a str, publicity: Publicity, ) -> Document<'a> { let class_definition = self.variant_class_definition(constructor, publicity); // If the custom type is private or opaque, we don't need to generate API // functions for it. if publicity.is_private() { return class_definition; } let constructor_definition = self.variant_constructor_definition(constructor, type_name); let variant_check_definition = self.variant_check_definition(constructor, type_name); let fields_definition = self.variant_fields_definition(constructor, type_name); docvec![ class_definition, line(), constructor_definition, line(), variant_check_definition, fields_definition, ] } fn variant_constructor_definition( &self, constructor: &'a TypedRecordConstructor, type_name: &'a str, ) -> Document<'a> { let mut arguments = Vec::new(); for (index, parameter) in constructor.arguments.iter().enumerate() { if let Some((_, label)) = ¶meter.label { arguments.push(maybe_escape_identifier(label).to_doc()); } else { arguments.push(eco_format!("${index}").to_doc()); } } let construction = docvec![ break_("", " "), "new ", constructor.name.as_str(), "(", join(arguments.clone(), break_(",", ", ")).group(), ");" ] .group(); docvec![ "export const ", type_name, "$", constructor.name.as_str(), " = (", join(arguments, break_(",", ", ")), ") =>", construction.nest(INDENT), ] } fn variant_check_definition( &self, constructor: &'a TypedRecordConstructor, type_name: &'a str, ) -> Document<'a> { let construction = docvec![ break_("", " "), "value instanceof ", constructor.name.as_str(), ";" ] .group(); docvec![ "export const ", type_name, "$is", constructor.name.as_str(), " = (value) =>", construction.nest(INDENT), ] } fn variant_fields_definition( &self, constructor: &'a TypedRecordConstructor, type_name: &'a str, ) -> Document<'a> { let mut functions = Vec::new(); for (index, argument) in constructor.arguments.iter().enumerate() { // Always generate the accessor for the value at this index. Although // this is not necessary when a label is present, we want to make sure // that adding a label to a record isn't a breaking change. For this // reason, we need to generate an index getter even when a label is // present to ensure consistent behaviour between labelled and unlabelled // field access. let function_name = eco_format!( "{type_name}${record_name}${index}", record_name = constructor.name, ); let contents; // If the argument is labelled, also generate a getter for the labelled // argument. if let Some((_, label)) = &argument.label { let function_name = eco_format!( "{type_name}${record_name}${label}", record_name = constructor.name, ); contents = docvec![break_("", " "), "value.", maybe_escape_property(label), ";"].group(); functions.push(docvec![ line(), "export const ", function_name, " = (value) =>", contents.clone().nest(INDENT), ]); } else { contents = docvec![break_("", " "), "value[", index, "];"].group() } functions.push(docvec![ line(), "export const ", function_name, " = (value) =>", contents.nest(INDENT), ]); } concat(functions) } fn shared_custom_type_fields( &self, type_name: &'a str, shared_accessors: &HashMap, ) -> Document<'a> { let accessors = shared_accessors.keys().sorted().map(|field| { let function_name = eco_format!("{type_name}${field}"); let contents = docvec![break_("", " "), "value.", maybe_escape_property(field), ";"].group(); docvec![ "export const ", function_name, " = (value) =>", contents.nest(INDENT), ] }); concat(Itertools::intersperse(accessors, line())) } fn variant_class_definition( &self, constructor: &'a TypedRecordConstructor, publicity: Publicity, ) -> Document<'a> { fn parameter((i, arg): (usize, &TypedRecordConstructorArg)) -> Document<'_> { arg.label .as_ref() .map(|(_, s)| maybe_escape_identifier(s)) .unwrap_or_else(|| eco_format!("${i}")) .to_doc() } let doc = if let Some((_, documentation)) = &constructor.documentation { jsdoc_comment(documentation, publicity).append(line()) } else { nil() }; let head = if publicity.is_public() { "export class " } else { "class " }; let head = docvec![head, &constructor.name, " extends $CustomType {"]; if constructor.arguments.is_empty() { return head.append("}"); }; let parameters = join( constructor.arguments.iter().enumerate().map(parameter), break_(",", ", "), ); let constructor_body = join( constructor.arguments.iter().enumerate().map(|(i, arg)| { let var = parameter((i, arg)); match &arg.label { None => docvec!["this[", i, "] = ", var, ";"], Some((_, name)) => { docvec!["this.", maybe_escape_property(name), " = ", var, ";"] } } }), line(), ); let class_body = docvec![ line(), "constructor(", parameters, ") {", docvec![line(), "super();", line(), constructor_body].nest(INDENT), line(), "}", ] .nest(INDENT); docvec![doc, head, class_body, line(), "}"] } fn definitions(&mut self) -> Vec> { let mut definitions = vec![]; for custom_type in &self.module.definitions.custom_types { if let Some(mut new_definitions) = self.custom_type_definition(custom_type) { definitions.append(&mut new_definitions) } } for constant in &self.module.definitions.constants { if let Some(definition) = self.module_constant(constant) { definitions.push(definition) } } for function in &self.module.definitions.functions { if let Some(definition) = self.module_function(function) { definitions.push(definition) } } definitions } fn collect_imports(&mut self) -> Imports<'a> { let mut imports = Imports::new(); for Import { module, as_name, unqualified_values, package, .. } in &self.module.definitions.imports { self.register_import(&mut imports, package, module, as_name, unqualified_values); } for function in &self.module.definitions.functions { if let Some((_, name)) = &function.name && let Some((module, external_function, _)) = &function.external_javascript { self.register_external_function( &mut imports, function.publicity, name, module, external_function, ) } } imports } fn import_path(&self, package: &'a str, module: &'a str) -> EcoString { // TODO: strip shared prefixed between current module and imported // module to avoid descending and climbing back out again if package == self.module.type_info.package || package.is_empty() { // Same package match self.current_module_name_segments_count { 1 => eco_format!("./{module}.mjs"), _ => { let prefix = "../".repeat(self.current_module_name_segments_count - 1); eco_format!("{prefix}{module}.mjs") } } } else { // Different package let prefix = "../".repeat(self.current_module_name_segments_count); eco_format!("{prefix}{package}/{module}.mjs") } } fn register_import( &mut self, imports: &mut Imports<'a>, package: &'a str, module: &'a str, as_name: &Option<(AssignName, SrcSpan)>, unqualified: &[UnqualifiedImport], ) { let get_name = |module: &'a str| { module .split('/') .next_back() .expect("JavaScript generator could not identify imported module name.") }; let (discarded, module_name) = match as_name { None => (false, get_name(module)), Some((AssignName::Discard(_), _)) => (true, get_name(module)), Some((AssignName::Variable(name), _)) => (false, name.as_str()), }; let module_name = eco_format!("${module_name}"); let path = self.import_path(package, module); let unqualified_imports = unqualified.iter().map(|i| { let alias = i.as_name.as_ref().map(|n| { self.register_in_scope(n); maybe_escape_identifier(n).to_doc() }); let name = maybe_escape_identifier(&i.name).to_doc(); Member { name, alias } }); let aliases = if discarded { vec![] } else { vec![module_name] }; imports.register_module(path, aliases, unqualified_imports); } fn register_external_function( &mut self, imports: &mut Imports<'a>, publicity: Publicity, name: &'a str, module: &'a str, fun: &'a str, ) { let needs_escaping = !is_usable_js_identifier(name); let member = Member { name: fun.to_doc(), alias: if name == fun && !needs_escaping { None } else if needs_escaping { Some(escape_identifier(name).to_doc()) } else { Some(name.to_doc()) }, }; if publicity.is_importable() { imports.register_export(maybe_escape_identifier_string(name)) } imports.register_module(EcoString::from(module), [], [member]); } fn module_constant(&mut self, constant: &'a TypedModuleConstant) -> Option> { let TypedModuleConstant { documentation, location, publicity, name, value, .. } = constant; // We don't generate any code for unused constants. if self .module .unused_definition_positions .contains(&location.start) { return None; } let head = if publicity.is_private() { "const " } else { "export const " }; let mut generator = expression::Generator::new( self.module.name.clone(), self.src_path.clone(), self.line_numbers, "".into(), vec![], &mut self.tracker, self.module_scope.clone(), ); let document = generator.constant_expression(Context::Constant, value); let jsdoc = if let Some((_, documentation)) = documentation { jsdoc_comment(documentation, *publicity).append(line()) } else { nil() }; Some(docvec![ jsdoc, head, maybe_escape_identifier(name), " = ", document, ";", ]) } fn register_in_scope(&mut self, name: &str) { let _ = self.module_scope.insert(name.into(), 0); } fn module_function(&mut self, function: &'a TypedFunction) -> Option> { // We don't generate any code for unused functions. if self .module .unused_definition_positions .contains(&function.location.start) { return None; } // If there's an external JavaScript implementation then it will be imported, // so we don't need to generate a function definition. if function.external_javascript.is_some() { return None; } // If the function does not support JavaScript then we don't need to generate // a function definition. if !function.implementations.supports(Target::JavaScript) { return None; } let (_, name) = function .name .as_ref() .expect("A module's function must be named"); let argument_names = function .arguments .iter() .map(|arg| arg.names.get_variable_name()) .collect(); let mut generator = expression::Generator::new( self.module.name.clone(), self.src_path.clone(), self.line_numbers, name.clone(), argument_names, &mut self.tracker, self.module_scope.clone(), ); let function_doc = match &function.documentation { None => nil(), Some((_, documentation)) => { jsdoc_comment(documentation, function.publicity).append(line()) } }; let head = if function.publicity.is_private() { "function " } else { "export function " }; let body = generator.function_body(function.body.as_slice(), function.arguments.as_slice()); Some(docvec![ function_doc, head, maybe_escape_identifier(name.as_str()), fun_arguments(function.arguments.as_slice(), generator.tail_recursion_used), " {", docvec![line(), body].nest(INDENT).group(), line(), "}", ]) } fn register_module_definitions_in_scope(&mut self) { for constant in &self.module.definitions.constants { self.register_in_scope(&constant.name) } for function in &self.module.definitions.functions { if let Some((_, name)) = &function.name { self.register_in_scope(name); } } for import in &self.module.definitions.imports { for unqualified_value in &import.unqualified_values { self.register_in_scope(unqualified_value.used_name()) } } } fn filepath_definition(&self) -> Document<'a> { if !self.tracker.make_error_used { return nil(); } docvec!["const FILEPATH = ", self.src_path.clone(), ';', lines(2)] } } fn jsdoc_comment(documentation: &EcoString, publicity: Publicity) -> Document<'_> { let doc_lines = documentation .trim_end() .split('\n') .map(|line| eco_format!(" *{line}", line = line.replace("*/", "*\\/")).to_doc()) .collect_vec(); // We start with the documentation of the function let doc_body = join(doc_lines, line()); let mut doc = docvec!["/**", line(), doc_body, line()]; if !publicity.is_public() { // If the function is not public we hide the documentation using // the `@ignore` tag: https://jsdoc.app/tags-ignore doc = docvec![doc, " * ", line(), " * @ignore", line()]; } // And finally we close the doc comment docvec![doc, " */"] } #[derive(Debug)] pub struct ModuleConfig<'a> { pub module: &'a TypedModule, pub line_numbers: &'a LineNumbers, pub src: &'a EcoString, pub typescript: TypeScriptDeclarations, pub stdlib_package: StdlibPackage, pub path: &'a Utf8Path, pub project_root: &'a Utf8Path, } pub fn module(config: ModuleConfig<'_>) -> String { let document = Generator::new(config).compile(); document.to_pretty_string(80) } pub fn ts_declaration(module: &TypedModule) -> String { let document = typescript::TypeScriptGenerator::new(module).compile(); document.to_pretty_string(80) } fn fun_arguments(arguments: &'_ [TypedArg], tail_recursion_used: bool) -> Document<'_> { let mut discards = 0; wrap_arguments( arguments .iter() .map(|argument| match argument.get_variable_name() { None => { let doc = if discards == 0 { "_".to_doc() } else { eco_format!("_{discards}").to_doc() }; discards += 1; doc } Some(name) if tail_recursion_used => eco_format!("loop${name}").to_doc(), Some(name) => maybe_escape_identifier(name).to_doc(), }), ) } fn wrap_arguments<'a, I>(arguments: I) -> Document<'a> where I: IntoIterator>, { break_("", "") .append(join(arguments, break_(",", ", "))) .nest(INDENT) .append(break_("", "")) .surround("(", ")") .group() } fn wrap_object<'a>( items: impl IntoIterator, Option>)>, ) -> Document<'a> { let mut empty = true; let fields = items.into_iter().map(|(key, value)| { empty = false; match value { Some(value) => docvec![key, ": ", value], None => key.to_doc(), } }); let fields = join(fields, break_(",", ", ")); if empty { "{}".to_doc() } else { docvec![ docvec!["{", break_("", " "), fields] .nest(INDENT) .append(break_("", " ")) .group(), "}" ] } } fn is_usable_js_identifier(word: &str) -> bool { !matches!( word, // Keywords and reserved words // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar "await" | "arguments" | "break" | "case" | "catch" | "class" | "const" | "continue" | "debugger" | "default" | "delete" | "do" | "else" | "enum" | "export" | "extends" | "eval" | "false" | "finally" | "for" | "function" | "if" | "implements" | "import" | "in" | "instanceof" | "interface" | "let" | "new" | "null" | "package" | "private" | "protected" | "public" | "return" | "static" | "super" | "switch" | "this" | "throw" | "true" | "try" | "typeof" | "var" | "void" | "while" | "with" | "yield" // `undefined` to avoid any unintentional overriding. | "undefined" // `then` to avoid a module that defines a `then` function being // used as a `thenable` in JavaScript when the module is imported // dynamically, which results in unexpected behaviour. // It is rather unfortunate that we have to do this. | "then" ) } fn is_usable_js_property(label: &str) -> bool { match label { // `then` to avoid a custom type that defines a `then` function being // used as a `thenable` in Javascript. "then" // `constructor` to avoid unintentional overriding of the constructor of // records, leading to potential runtime crashes while using `withFields`. | "constructor" // `prototype` and `__proto__` to avoid unintentionally overriding the // prototype chain. | "prototype" | "__proto__" => false, _ => true } } fn maybe_escape_identifier_string(word: &str) -> EcoString { if is_usable_js_identifier(word) { EcoString::from(word) } else { escape_identifier(word) } } fn escape_identifier(word: &str) -> EcoString { eco_format!("{word}$") } fn maybe_escape_identifier(word: &str) -> EcoString { if is_usable_js_identifier(word) { EcoString::from(word) } else { escape_identifier(word) } } fn maybe_escape_property(label: &str) -> EcoString { if is_usable_js_property(label) { EcoString::from(label) } else { escape_identifier(label) } } #[derive(Debug, Default)] pub(crate) struct UsageTracker { pub ok_used: bool, pub list_used: bool, pub list_empty_class_used: bool, pub list_non_empty_class_used: bool, pub prepend_used: bool, pub error_used: bool, pub int_remainder_used: bool, pub make_error_used: bool, pub custom_type_used: bool, pub int_division_used: bool, pub float_division_used: bool, pub object_equality_used: bool, pub bit_array_literal_used: bool, pub bit_array_slice_used: bool, pub bit_array_slice_to_float_used: bool, pub bit_array_slice_to_int_used: bool, pub sized_integer_segment_used: bool, pub string_bit_array_segment_used: bool, pub string_utf16_bit_array_segment_used: bool, pub string_utf32_bit_array_segment_used: bool, pub codepoint_bit_array_segment_used: bool, pub codepoint_utf16_bit_array_segment_used: bool, pub codepoint_utf32_bit_array_segment_used: bool, pub float_bit_array_segment_used: bool, pub echo_used: bool, } fn bool(bool: bool) -> Document<'static> { match bool { true => "true".to_doc(), false => "false".to_doc(), } } /// Int segments <= 48 bits wide in bit arrays are within JavaScript's safe range and are evaluated /// at compile time when all inputs are known. This is done for both bit array expressions and /// pattern matching. /// /// Int segments of any size could be evaluated at compile time, but currently aren't due to the /// potential for causing large generated JS for inputs such as `<<0:8192>>`. /// pub(crate) const SAFE_INT_SEGMENT_MAX_SIZE: usize = 48; /// Evaluates the value of an Int segment in a bit array into its corresponding bytes. This avoids /// needing to do the evaluation at runtime when all inputs are known at compile-time. /// pub(crate) fn bit_array_segment_int_value_to_bytes( mut value: BigInt, size: BigInt, endianness: Endianness, ) -> Vec { // Clamp negative sizes to zero let size = size.max(BigInt::ZERO); // Convert size to u32. This is safe because this function isn't called with a size greater // than `SAFE_INT_SEGMENT_MAX_SIZE`. let size = size .to_u32() .expect("bit array segment size to be a valid u32"); // Convert negative number to two's complement representation if value < BigInt::ZERO { let value_modulus = BigInt::from(2).pow(size); value = &value_modulus + (value % &value_modulus); } // Convert value to the desired number of bytes let mut bytes = vec![0u8; size as usize / 8]; for byte in bytes.iter_mut() { *byte = (&value % BigInt::from(256)) .to_u8() .expect("modulo result to be a valid u32"); value /= BigInt::from(256); } if endianness.is_big() { bytes.reverse(); } bytes } ================================================ FILE: compiler-core/src/lib.rs ================================================ #![warn( clippy::all, clippy::dbg_macro, clippy::todo, clippy::mem_forget, // TODO: enable once the false positive bug is solved // clippy::use_self, clippy::filter_map_next, clippy::needless_continue, clippy::needless_borrow, clippy::match_wildcard_for_single_variants, clippy::imprecise_flops, clippy::suboptimal_flops, clippy::lossy_float_literal, clippy::rest_pat_in_fully_bound_structs, clippy::fn_params_excessive_bools, clippy::inefficient_to_string, clippy::linkedlist, clippy::macro_use_imports, clippy::option_option, clippy::verbose_file_reads, clippy::unnested_or_patterns, rust_2018_idioms, missing_debug_implementations, missing_copy_implementations, trivial_casts, trivial_numeric_casts, nonstandard_style, unexpected_cfgs, unused_import_braces, unused_qualifications, clippy::wildcard_enum_match_arm )] #![deny( clippy::await_holding_lock, clippy::disallowed_methods, clippy::if_let_mutex, clippy::indexing_slicing, clippy::mem_forget, clippy::ok_expect, clippy::unimplemented, clippy::unwrap_used, unsafe_code, unstable_features, unused_results )] #![allow( clippy::assign_op_pattern, clippy::to_string_trait_impl, clippy::match_single_binding, clippy::match_like_matches_macro, clippy::inconsistent_struct_constructor, clippy::len_without_is_empty, // TODO: fix clippy::arc_with_non_send_sync, )] #[cfg(test)] #[macro_use] extern crate pretty_assertions; pub mod analyse; pub mod ast; pub mod bit_array; pub mod build; pub mod codegen; pub mod config; pub mod dependency; pub mod diagnostic; pub mod docs; pub mod encryption; pub mod erlang; pub mod error; pub mod fix; pub mod format; pub mod hex; pub mod io; pub mod javascript; pub mod line_numbers; pub mod manifest; pub mod metadata; pub mod package_interface; pub mod parse; pub mod paths; pub mod pretty; pub mod requirement; pub mod strings; pub mod type_; pub mod uid; pub mod version; pub mod warning; pub(crate) mod ast_folder; mod call_graph; mod dep_tree; pub(crate) mod derivation_tree; pub mod exhaustiveness; pub(crate) mod graph; pub(crate) mod inline; pub mod reference; pub use error::{Error, Result}; pub use warning::Warning; const GLEAM_CORE_PACKAGE_NAME: &str = ""; pub const STDLIB_PACKAGE_NAME: &str = "gleam_stdlib"; ================================================ FILE: compiler-core/src/line_numbers.rs ================================================ use crate::ast::SrcSpan; use lsp_types::Position; use std::collections::HashMap; /// A struct which contains information about line numbers of a source file, /// and can convert between byte offsets that are used in the compiler and /// line-column pairs used in LSP. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)] pub struct LineNumbers { /// The byte offsets of the start of each line of the source file pub line_starts: Vec, /// The total length of the source file pub length: u32, /// A mapping of byte offsets to character length information. This is used /// when converting between byte indices and line-column numbers, because /// LSP uses UTF-16, while Rust encodes strings as UTF-8. /// /// This only contains characters which are more than one byte in UTF-8, /// because one byte UTF-8 characters are one UTF-16 segment also, so no /// translation is needed. /// /// We could store the whole source file here instead, however that would /// be quite wasteful. Most Gleam programs use only ASCII characters, meaning /// UTF-8 offsets are the same as UTF-16 ones. With this representation, we /// only need to store a few characters. /// /// In most programs this will be empty because they will only be using /// ASCII characters. pub mapping: HashMap, } /// Information about how a character is encoded in UTF-8 and UTF-16. #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq)] pub struct Character { /// The number of bytes needed to encode this in UTF-8. pub length_utf8: u8, /// The number of 16-bit segments needed to encode this in UTF-16. pub length_utf16: u8, } impl LineNumbers { pub fn new(src: &str) -> Self { Self { length: src.len() as u32, line_starts: std::iter::once(0) .chain(src.match_indices('\n').map(|(i, _)| i as u32 + 1)) .collect(), mapping: Self::mapping(src), } } fn mapping(src: &str) -> HashMap { let mut map = HashMap::new(); for (i, char) in src.char_indices() { let length = char.len_utf8(); if length != 1 { _ = map.insert( i, Character { length_utf8: length as u8, length_utf16: char.len_utf16() as u8, }, ); } } map } /// Returns the 1-indexed line number of a given byte index pub fn line_number(&self, byte_index: u32) -> u32 { self.line_starts .binary_search(&byte_index) .unwrap_or_else(|next_line| next_line - 1) as u32 + 1 } /// Returns the 1-indexed line and column number of a given byte index, /// using a UTF-16 character offset. pub fn line_and_column_number(&self, byte_index: u32) -> LineColumn { let line = self.line_number(byte_index); let line_start = self .line_starts .get(line as usize - 1) .copied() .unwrap_or_default(); let mut u8_offset = line_start; let mut u16_offset = 0; loop { if u8_offset >= byte_index { break; } if let Some(length) = self.mapping.get(&(u8_offset as usize)) { u8_offset += length.length_utf8 as u32; u16_offset += length.length_utf16 as u32; } else { u16_offset += 1; u8_offset += 1; } } LineColumn { line, column: u16_offset + 1, } } /// Returns the byte index of the corresponding LSP line-column `Position`, /// translating from a UTF-16 character index to a UTF-8 byte index. pub fn byte_index(&self, position: Position) -> u32 { let line_start = match self.line_starts.get(position.line as usize) { Some(&line_start) => line_start, None => return self.length, }; let mut u8_offset = line_start; let mut u16_offset = 0; loop { if u16_offset >= position.character { break; } if let Some(length) = self.mapping.get(&(u8_offset as usize)) { u8_offset += length.length_utf8 as u32; u16_offset += length.length_utf16 as u32; } else { u16_offset += 1; u8_offset += 1; } } u8_offset } /// Checks if the given span spans an entire line (excluding the newline /// character itself). pub fn spans_entire_line(&self, span: &SrcSpan) -> bool { self.line_starts.iter().any(|&line_start| { line_start == span.start && self.line_starts.contains(&(span.end + 1)) }) } } #[test] fn byte_index() { let src = r#"import gleam/io pub fn main() { io.println("Hello, world!") } "#; let line_numbers = LineNumbers::new(src); assert_eq!( line_numbers.byte_index(Position { line: 0, character: 0 }), 0 ); assert_eq!( line_numbers.byte_index(Position { line: 0, character: 4 }), 4 ); assert_eq!( line_numbers.byte_index(Position { line: 100, character: 0 }), src.len() as u32 ); assert_eq!( line_numbers.byte_index(Position { line: 2, character: 1 }), 18 ); } // https://github.com/gleam-lang/gleam/issues/3628 #[test] fn byte_index_with_multibyte_characters() { let src = r#"fn wibble(_a, _b, _c) { todo } pub fn main() { wibble("क्षि", 10, <<"abc">>) } "#; let line_numbers = LineNumbers::new(src); assert_eq!( line_numbers.byte_index(Position { line: 1, character: 6 }), 30 ); assert_eq!( line_numbers.byte_index(Position { line: 5, character: 2 }), 52 ); assert_eq!( line_numbers.byte_index(Position { line: 5, character: 17 }), 75 ); assert_eq!( line_numbers.byte_index(Position { line: 6, character: 1 }), 91 ); } // https://github.com/gleam-lang/gleam/issues/3628 #[test] fn line_and_column_with_multibyte_characters() { let src = r#"fn wibble(_a, _b, _c) { todo } pub fn main() { wibble("क्षि", 10, <<"abc">>) } "#; let line_numbers = LineNumbers::new(src); assert_eq!( line_numbers.line_and_column_number(30), LineColumn { line: 2, column: 7 } ); assert_eq!( line_numbers.line_and_column_number(52), LineColumn { line: 6, column: 3 } ); assert_eq!( line_numbers.line_and_column_number(75), LineColumn { line: 6, column: 18 } ); assert_eq!( line_numbers.line_and_column_number(91), LineColumn { line: 7, column: 2 } ); } /// A 1-index line and column position #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct LineColumn { pub line: u32, pub column: u32, } ================================================ FILE: compiler-core/src/manifest.rs ================================================ use std::collections::HashMap; use std::fmt; use crate::Result; use crate::io::{make_relative, ordered_map}; use crate::requirement::Requirement; use camino::{Utf8Path, Utf8PathBuf}; use ecow::EcoString; use hexpm::version::Version; use itertools::Itertools; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)] pub struct Manifest { #[serde(serialize_with = "ordered_map")] pub requirements: HashMap, #[serde(serialize_with = "sorted_vec")] pub packages: Vec, } impl Manifest { // Rather than using the toml library to do serialization we implement it // manually so that we can control the formatting. // We want to keep entries on a single line each so that they are more // resistant to merge conflicts and are easier to fix when it does happen. pub fn to_toml(&self, root_path: &Utf8Path) -> String { let mut buffer = String::new(); let Self { requirements, packages, } = self; buffer.push_str( "# This file was generated by Gleam # You typically do not need to edit this file ", ); // Packages buffer.push_str("packages = [\n"); for ManifestPackage { name, source, version, otp_app, build_tools, requirements, } in packages.iter().sorted_by(|a, b| a.name.cmp(&b.name)) { buffer.push_str(r#" {"#); buffer.push_str(r#" name = ""#); buffer.push_str(name); buffer.push_str(r#"", version = ""#); buffer.push_str(&version.to_string()); buffer.push_str(r#"", build_tools = ["#); for (i, tool) in build_tools.iter().enumerate() { if i != 0 { buffer.push_str(", "); } buffer.push('"'); buffer.push_str(tool); buffer.push('"'); } buffer.push_str("], requirements = ["); for (i, package) in requirements.iter().sorted_by(|a, b| a.cmp(b)).enumerate() { if i != 0 { buffer.push_str(", "); } buffer.push('"'); buffer.push_str(package); buffer.push('"'); } buffer.push(']'); if let Some(app) = otp_app { buffer.push_str(", otp_app = \""); buffer.push_str(app); buffer.push('"'); } match source { ManifestPackageSource::Hex { outer_checksum } => { buffer.push_str(r#", source = "hex", outer_checksum = ""#); buffer.push_str(&outer_checksum.to_string()); buffer.push('"'); } ManifestPackageSource::Git { repo, commit } => { buffer.push_str(r#", source = "git", repo = ""#); buffer.push_str(repo); buffer.push_str(r#"", commit = ""#); buffer.push_str(commit); buffer.push('"'); } ManifestPackageSource::Local { path } => { buffer.push_str(r#", source = "local", path = ""#); buffer.push_str(&make_relative(root_path, path).as_str().replace('\\', "/")); buffer.push('"'); } }; buffer.push_str(" },\n"); } buffer.push_str("]\n\n"); // Requirements buffer.push_str("[requirements]\n"); for (name, requirement) in requirements.iter().sorted_by(|a, b| a.0.cmp(b.0)) { buffer.push_str(name); buffer.push_str(" = "); buffer.push_str(&requirement.to_toml(root_path)); buffer.push('\n'); } buffer } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Base16Checksum(pub Vec); impl ToString for Base16Checksum { fn to_string(&self) -> String { base16::encode_upper(&self.0) } } impl serde::Serialize for Base16Checksum { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.serialize_str(&base16::encode_upper(&self.0)) } } impl<'de> serde::Deserialize<'de> for Base16Checksum { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { deserializer.deserialize_str(Base16ChecksumVisitor) } } struct Base16ChecksumVisitor; impl<'de> serde::de::Visitor<'de> for Base16ChecksumVisitor { type Value = Base16Checksum; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("a base 16 checksum") } fn visit_str(self, value: &str) -> Result where E: serde::de::Error, { base16::decode(value) .map(Base16Checksum) .map_err(serde::de::Error::custom) } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)] pub struct ManifestPackage { pub name: EcoString, pub version: Version, pub build_tools: Vec, #[serde(default)] pub otp_app: Option, #[serde(serialize_with = "sorted_vec")] pub requirements: Vec, #[serde(flatten)] pub source: ManifestPackageSource, } impl ManifestPackage { pub fn with_build_tools(mut self, build_tools: &'static [&'static str]) -> Self { self.build_tools = build_tools.iter().map(|s| (*s).into()).collect(); self } pub fn application_name(&self) -> &EcoString { match self.otp_app { Some(ref app) => app, None => &self.name, } } #[inline] pub fn is_hex(&self) -> bool { matches!(self.source, ManifestPackageSource::Hex { .. }) } #[inline] pub fn is_local(&self) -> bool { matches!(self.source, ManifestPackageSource::Local { .. }) } #[inline] pub fn is_git(&self) -> bool { matches!(self.source, ManifestPackageSource::Git { .. }) } } #[cfg(test)] impl Default for ManifestPackage { fn default() -> Self { Self { name: Default::default(), build_tools: Default::default(), otp_app: Default::default(), requirements: Default::default(), version: Version::new(1, 0, 0), source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![]), }, } } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)] #[serde(tag = "source")] pub enum ManifestPackageSource { #[serde(rename = "hex")] Hex { outer_checksum: Base16Checksum }, #[serde(rename = "git")] Git { repo: EcoString, commit: EcoString }, #[serde(rename = "local")] Local { path: Utf8PathBuf }, // should be the canonical path } impl ManifestPackageSource { pub fn kind(&self) -> ManifestPackageSourceKind { match self { ManifestPackageSource::Hex { .. } => ManifestPackageSourceKind::Hex, ManifestPackageSource::Git { .. } => ManifestPackageSourceKind::Git, ManifestPackageSource::Local { .. } => ManifestPackageSourceKind::Local, } } } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum ManifestPackageSourceKind { Hex, Git, Local, } fn sorted_vec(value: &[T], serializer: S) -> Result where S: serde::Serializer, T: serde::Serialize + Ord, { use serde::Serialize; let mut value: Vec<&T> = value.iter().collect(); value.sort(); value.serialize(serializer) } #[cfg(test)] mod tests { use super::*; #[cfg(windows)] const HOME: &'static str = "C:\\home\\louis\\packages\\some_folder"; #[cfg(windows)] const PACKAGE: &'static str = "C:\\home\\louis\\packages\\path\\to\\package"; #[cfg(windows)] const PACKAGE_WITH_UNC: &'static str = "\\\\?\\C:\\home\\louis\\packages\\path\\to\\package"; #[cfg(not(windows))] const HOME: &str = "/home/louis/packages/some_folder"; #[cfg(not(windows))] const PACKAGE: &str = "/home/louis/packages/path/to/package"; #[test] fn manifest_toml_format() { let manifest = Manifest { requirements: [ ("zzz".into(), Requirement::hex("> 0.0.0").unwrap()), ("aaa".into(), Requirement::hex("> 0.0.0").unwrap()), ( "awsome_local2".into(), Requirement::git("https://github.com/gleam-lang/gleam.git", "bd9fe02f"), ), ( "awsome_local1".into(), Requirement::path("../path/to/package"), ), ("gleam_stdlib".into(), Requirement::hex("~> 0.17").unwrap()), ("gleeunit".into(), Requirement::hex("~> 0.1").unwrap()), ] .into(), packages: vec![ ManifestPackage { name: "gleam_stdlib".into(), version: Version::new(0, 17, 1), build_tools: ["gleam".into()].into(), otp_app: None, requirements: vec![], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![1, 22]), }, }, ManifestPackage { name: "aaa".into(), version: Version::new(0, 4, 0), build_tools: ["rebar3".into(), "make".into()].into(), otp_app: Some("aaa_app".into()), requirements: vec!["zzz".into(), "gleam_stdlib".into()], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![3, 22]), }, }, ManifestPackage { name: "zzz".into(), version: Version::new(0, 4, 0), build_tools: ["mix".into()].into(), otp_app: None, requirements: vec![], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![3, 22]), }, }, ManifestPackage { name: "awsome_local2".into(), version: Version::new(1, 2, 3), build_tools: ["gleam".into()].into(), otp_app: None, requirements: vec![], source: ManifestPackageSource::Git { repo: "https://github.com/gleam-lang/gleam.git".into(), commit: "bd9fe02f72250e6a136967917bcb1bdccaffa3c8".into(), }, }, ManifestPackage { name: "awsome_local1".into(), version: Version::new(1, 2, 3), build_tools: ["gleam".into()].into(), otp_app: None, requirements: vec![], source: ManifestPackageSource::Local { path: PACKAGE.into(), }, }, ManifestPackage { name: "gleeunit".into(), version: Version::new(0, 4, 0), build_tools: ["gleam".into()].into(), otp_app: None, requirements: vec!["gleam_stdlib".into()], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![3, 46]), }, }, ], }; let buffer = manifest.to_toml(HOME.into()); assert_eq!( buffer, r#"# This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "aaa", version = "0.4.0", build_tools = ["rebar3", "make"], requirements = ["gleam_stdlib", "zzz"], otp_app = "aaa_app", source = "hex", outer_checksum = "0316" }, { name = "awsome_local1", version = "1.2.3", build_tools = ["gleam"], requirements = [], source = "local", path = "../path/to/package" }, { name = "awsome_local2", version = "1.2.3", build_tools = ["gleam"], requirements = [], source = "git", repo = "https://github.com/gleam-lang/gleam.git", commit = "bd9fe02f72250e6a136967917bcb1bdccaffa3c8" }, { name = "gleam_stdlib", version = "0.17.1", build_tools = ["gleam"], requirements = [], source = "hex", outer_checksum = "0116" }, { name = "gleeunit", version = "0.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], source = "hex", outer_checksum = "032E" }, { name = "zzz", version = "0.4.0", build_tools = ["mix"], requirements = [], source = "hex", outer_checksum = "0316" }, ] [requirements] aaa = { version = "> 0.0.0" } awsome_local1 = { path = "../path/to/package" } awsome_local2 = { git = "https://github.com/gleam-lang/gleam.git", ref = "bd9fe02f" } gleam_stdlib = { version = "~> 0.17" } gleeunit = { version = "~> 0.1" } zzz = { version = "> 0.0.0" } "# ); } #[cfg(windows)] #[test] fn manifest_toml_format_with_unc() { let manifest = Manifest { requirements: [ ("zzz".into(), Requirement::hex("> 0.0.0").unwrap()), ("aaa".into(), Requirement::hex("> 0.0.0").unwrap()), ( "awsome_local2".into(), Requirement::git("https://github.com/gleam-lang/gleam.git", "main"), ), ( "awsome_local1".into(), Requirement::path("../path/to/package"), ), ("gleam_stdlib".into(), Requirement::hex("~> 0.17").unwrap()), ("gleeunit".into(), Requirement::hex("~> 0.1").unwrap()), ] .into(), packages: vec![ ManifestPackage { name: "gleam_stdlib".into(), version: Version::new(0, 17, 1), build_tools: ["gleam".into()].into(), otp_app: None, requirements: vec![], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![1, 22]), }, }, ManifestPackage { name: "aaa".into(), version: Version::new(0, 4, 0), build_tools: ["rebar3".into(), "make".into()].into(), otp_app: Some("aaa_app".into()), requirements: vec!["zzz".into(), "gleam_stdlib".into()], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![3, 22]), }, }, ManifestPackage { name: "zzz".into(), version: Version::new(0, 4, 0), build_tools: ["mix".into()].into(), otp_app: None, requirements: vec![], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![3, 22]), }, }, ManifestPackage { name: "awsome_local2".into(), version: Version::new(1, 2, 3), build_tools: ["gleam".into()].into(), otp_app: None, requirements: vec![], source: ManifestPackageSource::Git { repo: "https://github.com/gleam-lang/gleam.git".into(), commit: "bd9fe02f72250e6a136967917bcb1bdccaffa3c8".into(), }, }, ManifestPackage { name: "awsome_local1".into(), version: Version::new(1, 2, 3), build_tools: ["gleam".into()].into(), otp_app: None, requirements: vec![], source: ManifestPackageSource::Local { path: PACKAGE_WITH_UNC.into(), }, }, ManifestPackage { name: "gleeunit".into(), version: Version::new(0, 4, 0), build_tools: ["gleam".into()].into(), otp_app: None, requirements: vec!["gleam_stdlib".into()], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![3, 46]), }, }, ], }; let buffer = manifest.to_toml(HOME.into()); assert_eq!( buffer, r#"# This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "aaa", version = "0.4.0", build_tools = ["rebar3", "make"], requirements = ["gleam_stdlib", "zzz"], otp_app = "aaa_app", source = "hex", outer_checksum = "0316" }, { name = "awsome_local1", version = "1.2.3", build_tools = ["gleam"], requirements = [], source = "local", path = "../path/to/package" }, { name = "awsome_local2", version = "1.2.3", build_tools = ["gleam"], requirements = [], source = "git", repo = "https://github.com/gleam-lang/gleam.git", commit = "bd9fe02f72250e6a136967917bcb1bdccaffa3c8" }, { name = "gleam_stdlib", version = "0.17.1", build_tools = ["gleam"], requirements = [], source = "hex", outer_checksum = "0116" }, { name = "gleeunit", version = "0.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], source = "hex", outer_checksum = "032E" }, { name = "zzz", version = "0.4.0", build_tools = ["mix"], requirements = [], source = "hex", outer_checksum = "0316" }, ] [requirements] aaa = { version = "> 0.0.0" } awsome_local1 = { path = "../path/to/package" } awsome_local2 = { git = "https://github.com/gleam-lang/gleam.git", ref = "main" } gleam_stdlib = { version = "~> 0.17" } gleeunit = { version = "~> 0.1" } zzz = { version = "> 0.0.0" } "# ); } } #[derive(Debug)] pub struct Resolved { pub manifest: Manifest, pub package_changes: PackageChanges, pub requirements_changed: bool, } #[derive(Debug)] pub struct PackageChanges { pub added: Vec<(EcoString, Version)>, pub changed: Vec, /// When updating git dependencies, it is possible to update to a newer commit /// without updating the version of the package (which is specified in /// `gleam.toml`). In this case, we still want to record the change, but it /// must be stored differently. pub changed_git: Vec, pub removed: Vec, } #[derive(Debug, PartialEq)] pub struct Changed { pub name: EcoString, pub old: Version, pub new: Version, } #[derive(Debug, PartialEq)] pub struct ChangedGit { pub name: EcoString, pub old_hash: EcoString, pub new_hash: EcoString, } impl Resolved { pub fn any_changes(&self) -> bool { self.requirements_changed || self.package_changes.any_changes() } pub fn all_added(manifest: Manifest) -> Resolved { let added = manifest .packages .iter() .map(|package| (package.name.clone(), package.version.clone())) .collect(); Self { manifest, requirements_changed: true, package_changes: PackageChanges { added, changed: vec![], changed_git: vec![], removed: vec![], }, } } pub fn no_change(manifest: Manifest) -> Self { Self { manifest, requirements_changed: false, package_changes: PackageChanges { added: vec![], changed: vec![], changed_git: vec![], removed: vec![], }, } } } impl PackageChanges { pub fn any_changes(&self) -> bool { !self.added.is_empty() || !self.changed.is_empty() || !self.changed_git.is_empty() || !self.removed.is_empty() } /// Compare the old and new versions of the manifest and determine the package changes pub fn between_manifests(old: &Manifest, new: &Manifest) -> Self { let mut added = vec![]; let mut changed = vec![]; let mut changed_git = vec![]; let mut removed = vec![]; let mut old: HashMap<_, _> = old .packages .iter() .map(|package| (&package.name, package)) .collect(); for new in &new.packages { match old.remove(&new.name) { // If the kind of source changed, the packages bear essentially no connection Some(old) if new.source.kind() != old.source.kind() => { removed.push(old.name.clone()); added.push((new.name.clone(), new.version.clone())); } Some(old) if old.version == new.version => match (&old.source, &new.source) { ( ManifestPackageSource::Git { commit: old_hash, .. }, ManifestPackageSource::Git { commit: new_hash, .. }, ) if old_hash != new_hash => changed_git.push(ChangedGit { name: new.name.clone(), old_hash: old_hash.clone(), new_hash: new_hash.clone(), }), ( ManifestPackageSource::Hex { .. } | ManifestPackageSource::Local { .. } | ManifestPackageSource::Git { .. }, _, ) => {} }, Some(old) => { changed.push(Changed { name: new.name.clone(), old: old.version.clone(), new: new.version.clone(), }); } None => { added.push((new.name.clone(), new.version.clone())); } } } removed.extend(old.into_keys().cloned()); Self { added, changed, changed_git, removed, } } } #[cfg(test)] mod manifest_update_tests { use std::collections::HashMap; use ecow::EcoString; use hexpm::version::Version; use crate::manifest::{Base16Checksum, ManifestPackage, ManifestPackageSource, PackageChanges}; use crate::manifest::{Changed, Manifest}; #[test] fn resolved_with_updated() { let package = |name: &str, version| ManifestPackage { name: EcoString::from(name), version, build_tools: vec![], otp_app: None, requirements: vec![], source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![]), }, }; let old = Manifest { requirements: HashMap::new(), packages: vec![ package("unchanged1", Version::new(3, 0, 0)), package("unchanged2", Version::new(0, 1, 0)), package("changed1", Version::new(3, 0, 0)), package("changed2", Version::new(0, 1, 0)), package("removed1", Version::new(10, 0, 0)), package("removed2", Version::new(20, 1, 0)), ], }; let new = Manifest { requirements: HashMap::new(), packages: vec![ package("new1", Version::new(1, 0, 0)), package("new2", Version::new(2, 1, 0)), package("unchanged1", Version::new(3, 0, 0)), package("unchanged2", Version::new(0, 1, 0)), package("changed1", Version::new(5, 0, 0)), package("changed2", Version::new(3, 0, 0)), ], }; let mut changes = PackageChanges::between_manifests(&old, &new); changes.added.sort(); changes.changed.sort_by(|a, b| a.name.cmp(&b.name)); changes.removed.sort(); assert_eq!( changes.added, vec![ ("new1".into(), Version::new(1, 0, 0)), ("new2".into(), Version::new(2, 1, 0)), ] ); assert_eq!( changes.changed, vec![ Changed { name: "changed1".into(), old: Version::new(3, 0, 0), new: Version::new(5, 0, 0) }, Changed { name: "changed2".into(), old: Version::new(0, 1, 0), new: Version::new(3, 0, 0) }, ] ); assert_eq!( changes.removed, vec![EcoString::from("removed1"), EcoString::from("removed2")] ); } #[test] fn resolved_with_source_type_change() { let name = EcoString::from("wibble"); let version = Version::new(1, 0, 0); let package = |source| ManifestPackage { name: name.clone(), version: version.clone(), build_tools: vec![], otp_app: None, requirements: vec![], source, }; let old = Manifest { requirements: HashMap::new(), packages: vec![package(ManifestPackageSource::Local { path: "wibble".into(), })], }; let new = Manifest { requirements: HashMap::new(), packages: vec![package(ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![]), })], }; let changes = PackageChanges::between_manifests(&old, &new); assert!(changes.changed.is_empty()); assert_eq!(changes.removed, vec![name.clone()]); assert_eq!(changes.added, vec![(name.clone(), version.clone())]); } } ================================================ FILE: compiler-core/src/metadata/tests.rs ================================================ use hexpm::version::Version; use rand::Rng; use type_::{AccessorsMap, FieldMap, RecordAccessor}; use super::*; use crate::{ analyse::Inferred, ast::{ BitArrayOption, BitArraySegment, CallArg, Constant, Publicity, SrcSpan, TypedConstant, TypedConstantBitArraySegmentOption, }, build::Origin, line_numbers::LineNumbers, parse::LiteralFloatValue, reference::{Reference, ReferenceKind}, type_::{ self, Deprecation, ModuleInterface, Opaque, References, Type, TypeAliasConstructor, TypeConstructor, TypeValueConstructor, TypeValueConstructorField, TypeVariantConstructors, ValueConstructor, ValueConstructorVariant, expression::{Implementations, Purity}, prelude, }, uid::UniqueIdGenerator, }; use std::{collections::HashMap, sync::Arc}; use pretty_assertions::assert_eq; fn roundtrip(input: &ModuleInterface) -> ModuleInterface { let buffer = encode(input).unwrap(); let ids = UniqueIdGenerator::new(); decode(buffer.as_slice(), ids).unwrap() } fn constant_module(constant: TypedConstant) -> ModuleInterface { ModuleInterface { warnings: vec![], is_internal: true, package: "some_package".into(), origin: Origin::Src, name: "a".into(), types: HashMap::new(), types_value_constructors: HashMap::new(), accessors: HashMap::new(), values: [( "one".into(), ValueConstructor { publicity: Publicity::Public, deprecation: Deprecation::NotDeprecated, type_: type_::int(), variant: ValueConstructorVariant::ModuleConstant { documentation: Some("Some documentation".into()), literal: constant, location: SrcSpan::default(), module: "one/two".into(), implementations: Implementations { gleam: true, uses_erlang_externals: false, uses_javascript_externals: false, can_run_on_erlang: true, can_run_on_javascript: true, }, name: "one".into(), }, }, )] .into(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), } } fn bit_array_segment_option_module(option: TypedConstantBitArraySegmentOption) -> ModuleInterface { constant_module(Constant::BitArray { location: Default::default(), segments: vec![BitArraySegment { location: Default::default(), value: Box::new(Constant::Int { location: Default::default(), value: "1".into(), int_value: 1.into(), }), options: vec![option], type_: type_::int(), }], }) } #[test] fn empty_module() { let module = ModuleInterface { warnings: vec![], is_internal: true, package: "some_package".into(), origin: Origin::Src, name: "one/two".into(), types: HashMap::new(), types_value_constructors: HashMap::new(), values: HashMap::new(), accessors: HashMap::new(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } #[test] fn with_line_numbers() { let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "one/two".into(), types: HashMap::new(), types_value_constructors: HashMap::new(), values: HashMap::new(), accessors: HashMap::new(), line_numbers: LineNumbers::new( "const a = 1 const b = 2 const c = 3", ), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } #[test] fn module_with_private_type() { let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a/b".into(), types: [( "ListIntType".into(), TypeConstructor { type_: type_::list(type_::int()), publicity: Publicity::Private, origin: Default::default(), module: "the/module".into(), parameters: vec![], deprecation: Deprecation::NotDeprecated, documentation: None, }, )] .into(), types_value_constructors: HashMap::new(), values: HashMap::new(), accessors: HashMap::new(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } #[test] fn module_with_app_type() { let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a/b".into(), types: [( "ListIntType".into(), TypeConstructor { type_: type_::list(type_::int()), publicity: Publicity::Public, origin: Default::default(), module: "the/module".into(), parameters: vec![], deprecation: Deprecation::NotDeprecated, documentation: None, }, )] .into(), types_value_constructors: HashMap::new(), values: HashMap::new(), accessors: HashMap::new(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } #[test] fn module_with_fn_type() { let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a/b".into(), types: [( "FnType".into(), TypeConstructor { type_: type_::fn_(vec![type_::nil(), type_::float()], type_::int()), publicity: Publicity::Public, origin: Default::default(), module: "the/module".into(), parameters: vec![], deprecation: Deprecation::NotDeprecated, documentation: None, }, )] .into(), types_value_constructors: HashMap::new(), values: HashMap::new(), accessors: HashMap::new(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } #[test] fn module_with_tuple_type() { let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a/b".into(), types: [( "TupleType".into(), TypeConstructor { type_: type_::tuple(vec![type_::nil(), type_::float(), type_::int()]), publicity: Publicity::Public, origin: Default::default(), module: "the/module".into(), parameters: vec![], deprecation: Deprecation::NotDeprecated, documentation: None, }, )] .into(), types_value_constructors: HashMap::new(), values: HashMap::new(), accessors: HashMap::new(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } #[test] fn module_with_generic_type() { let t0 = type_::generic_var(0); let t1 = type_::generic_var(1); let t7 = type_::generic_var(7); let t8 = type_::generic_var(8); fn make(t1: Arc, t2: Arc) -> ModuleInterface { ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a/b".into(), types: [( "TupleType".into(), TypeConstructor { type_: type_::tuple(vec![t1.clone(), t1.clone(), t2.clone()]), publicity: Publicity::Public, origin: Default::default(), module: "the/module".into(), parameters: vec![t1, t2], deprecation: Deprecation::NotDeprecated, documentation: None, }, )] .into(), types_value_constructors: HashMap::new(), values: HashMap::new(), accessors: HashMap::new(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), } } assert_eq!(roundtrip(&make(t7, t8)), make(t0, t1)); } #[test] fn module_with_type_links() { let linked_type = type_::link(type_::int()); let type_ = type_::int(); fn make(type_: Arc) -> ModuleInterface { ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a".into(), types: [( "SomeType".into(), TypeConstructor { type_, publicity: Publicity::Public, origin: Default::default(), module: "a".into(), parameters: vec![], deprecation: Deprecation::NotDeprecated, documentation: None, }, )] .into(), types_value_constructors: HashMap::new(), values: HashMap::new(), accessors: HashMap::new(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), } } assert_eq!(roundtrip(&make(linked_type)), make(type_)); } #[test] fn module_with_type_constructor_documentation() { let linked_type = type_::link(type_::int()); let type_ = type_::int(); fn make(type_: Arc) -> ModuleInterface { ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a".into(), types: [( "SomeType".into(), TypeConstructor { type_, publicity: Publicity::Public, origin: Default::default(), module: "a".into(), parameters: vec![], deprecation: Deprecation::NotDeprecated, documentation: Some("type documentation".into()), }, )] .into(), types_value_constructors: HashMap::new(), values: HashMap::new(), accessors: HashMap::new(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), } } assert_eq!(roundtrip(&make(linked_type)), make(type_)); } #[test] fn module_with_type_constructor_origin() { let linked_type = type_::link(type_::int()); let type_ = type_::int(); fn make(type_: Arc) -> ModuleInterface { ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a".into(), types: [( "SomeType".into(), TypeConstructor { type_, publicity: Publicity::Public, origin: SrcSpan { start: 535, end: 543, }, module: "a".into(), parameters: vec![], deprecation: Deprecation::NotDeprecated, documentation: None, }, )] .into(), types_value_constructors: HashMap::new(), values: HashMap::new(), accessors: HashMap::new(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), } } assert_eq!(roundtrip(&make(linked_type)), make(type_)); } #[test] fn module_type_to_constructors_mapping() { let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a".into(), types: HashMap::new(), types_value_constructors: [( "SomeType".into(), TypeVariantConstructors { type_parameters_ids: vec![0, 1, 2], variants: vec![TypeValueConstructor { name: "One".into(), parameters: vec![], documentation: Some("Some documentation".into()), }], opaque: Opaque::NotOpaque, }, )] .into(), accessors: HashMap::new(), values: HashMap::new(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } #[test] fn module_fn_value() { let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a".into(), types: HashMap::new(), types_value_constructors: HashMap::new(), accessors: HashMap::new(), values: [( "one".into(), ValueConstructor { publicity: Publicity::Public, deprecation: Deprecation::NotDeprecated, type_: type_::int(), variant: ValueConstructorVariant::ModuleFn { documentation: Some("wobble!".into()), name: "one".into(), field_map: None, module: "a".into(), arity: 5, location: SrcSpan { start: 535, end: 1100, }, external_erlang: None, external_javascript: None, implementations: Implementations { gleam: true, uses_erlang_externals: false, uses_javascript_externals: false, can_run_on_erlang: true, can_run_on_javascript: true, }, purity: Purity::Pure, }, }, )] .into(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } #[test] fn deprecated_module_fn_value() { let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a".into(), types: HashMap::new(), types_value_constructors: HashMap::new(), accessors: HashMap::new(), values: [( "one".into(), ValueConstructor { publicity: Publicity::Public, deprecation: Deprecation::Deprecated { message: "wibble wobble".into(), }, type_: type_::int(), variant: ValueConstructorVariant::ModuleFn { documentation: Some("wobble!".into()), name: "one".into(), field_map: None, module: "a".into(), arity: 5, location: SrcSpan { start: 535, end: 1100, }, external_erlang: None, external_javascript: None, implementations: Implementations { gleam: true, uses_erlang_externals: false, uses_javascript_externals: false, can_run_on_erlang: true, can_run_on_javascript: true, }, purity: Purity::Pure, }, }, )] .into(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } #[test] fn private_module_fn_value() { let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a".into(), types: HashMap::new(), types_value_constructors: HashMap::new(), accessors: HashMap::new(), values: [( "one".into(), ValueConstructor { publicity: Publicity::Private, deprecation: Deprecation::NotDeprecated, type_: type_::int(), variant: ValueConstructorVariant::ModuleFn { documentation: Some("wobble!".into()), name: "one".into(), field_map: None, module: "a".into(), arity: 5, location: SrcSpan { start: 535, end: 1100, }, external_erlang: None, external_javascript: None, implementations: Implementations { gleam: true, uses_erlang_externals: false, uses_javascript_externals: false, can_run_on_erlang: true, can_run_on_javascript: true, }, purity: Purity::Pure, }, }, )] .into(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } // https://github.com/gleam-lang/gleam/commit/c8f3bd0ddbf61c27ea35f37297058ecca7515f6c #[test] fn module_fn_value_regression() { let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a/b/c".into(), types: HashMap::new(), types_value_constructors: HashMap::new(), accessors: HashMap::new(), values: [( "one".into(), ValueConstructor { publicity: Publicity::Public, deprecation: Deprecation::NotDeprecated, type_: type_::int(), variant: ValueConstructorVariant::ModuleFn { documentation: Some("wabble!".into()), name: "one".into(), field_map: None, module: "a".into(), arity: 5, location: SrcSpan { start: 52, end: 1100, }, external_erlang: None, external_javascript: None, implementations: Implementations { gleam: true, uses_erlang_externals: false, uses_javascript_externals: false, can_run_on_erlang: true, can_run_on_javascript: true, }, purity: Purity::TrustedPure, }, }, )] .into(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } #[test] fn module_fn_value_with_field_map() { let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a".into(), types: HashMap::new(), types_value_constructors: HashMap::new(), accessors: HashMap::new(), values: [( "one".into(), ValueConstructor { publicity: Publicity::Public, deprecation: Deprecation::NotDeprecated, type_: type_::int(), variant: ValueConstructorVariant::ModuleFn { documentation: Some("wubble!".into()), name: "one".into(), field_map: Some(FieldMap { arity: 20, fields: [("ok".into(), 5), ("ko".into(), 7)].into(), }), external_erlang: None, external_javascript: None, module: "a".into(), arity: 5, location: SrcSpan { start: 2, end: 11 }, implementations: Implementations { gleam: true, uses_erlang_externals: false, uses_javascript_externals: false, can_run_on_erlang: true, can_run_on_javascript: true, }, purity: Purity::Pure, }, }, )] .into(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } #[test] fn record_value() { let mut random = rand::rng(); let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a".into(), types: HashMap::new(), types_value_constructors: HashMap::new(), accessors: HashMap::new(), values: [( "one".into(), ValueConstructor { publicity: Publicity::Public, deprecation: Deprecation::NotDeprecated, type_: type_::int(), variant: ValueConstructorVariant::Record { documentation: Some("webble!".into()), name: "one".into(), module: "themodule".into(), field_map: None, arity: random.random(), variants_count: random.random(), location: SrcSpan { start: random.random(), end: random.random(), }, variant_index: random.random(), }, }, )] .into(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } #[test] fn record_value_with_field_map() { let mut random = rand::rng(); let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a".into(), types: HashMap::new(), types_value_constructors: HashMap::new(), accessors: HashMap::new(), values: [( "one".into(), ValueConstructor { publicity: Publicity::Public, deprecation: Deprecation::NotDeprecated, type_: type_::int(), variant: ValueConstructorVariant::Record { documentation: Some("wybble!".into()), module: "themodule".into(), name: "one".into(), field_map: Some(FieldMap { arity: random.random(), fields: [ ("ok".into(), random.random()), ("ko".into(), random.random()), ] .into(), }), arity: random.random(), variants_count: random.random(), variant_index: random.random(), location: SrcSpan { start: random.random(), end: random.random(), }, }, }, )] .into(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } #[test] fn accessors() { let accessors1 = [ ( "a".into(), RecordAccessor { index: 6, label: "siiixxx".into(), type_: type_::nil(), documentation: Some("Here is some documentation".into()), }, ), ( "a".into(), RecordAccessor { index: 5, label: "fiveee".into(), type_: type_::float(), documentation: None, }, ), ]; let accessors2 = [( "a".into(), RecordAccessor { index: 1, label: "ok".into(), type_: type_::float(), documentation: Some("Documentation for the ok field".into()), }, )]; let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a".into(), types: HashMap::new(), types_value_constructors: HashMap::new(), values: HashMap::new(), accessors: [ ( "one".into(), AccessorsMap { publicity: Publicity::Public, type_: type_::int(), shared_accessors: accessors1.clone().into(), variant_specific_accessors: vec![accessors1.into()], variant_positional_accessors: vec![vec![type_::int(), type_::float()]], }, ), ( "two".into(), AccessorsMap { publicity: Publicity::Public, type_: type_::int(), shared_accessors: accessors2.clone().into(), variant_specific_accessors: vec![accessors2.into()], variant_positional_accessors: vec![vec![]], }, ), ] .into(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } #[test] fn private_accessors() { let accessors1 = [ ( "a".into(), RecordAccessor { index: 6, label: "siiixxx".into(), type_: type_::nil(), documentation: None, }, ), ( "a".into(), RecordAccessor { index: 5, label: "fiveee".into(), type_: type_::float(), documentation: None, }, ), ]; let accessors2 = [( "a".into(), RecordAccessor { index: 1, label: "ok".into(), type_: type_::float(), documentation: None, }, )]; let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a".into(), types: HashMap::new(), types_value_constructors: HashMap::new(), contains_echo: false, values: HashMap::new(), accessors: [ ( "one".into(), AccessorsMap { publicity: Publicity::Private, type_: type_::int(), shared_accessors: accessors1.clone().into(), variant_specific_accessors: vec![accessors1.into()], variant_positional_accessors: vec![vec![type_::int(), type_::float()]], }, ), ( "two".into(), AccessorsMap { publicity: Publicity::Public, type_: type_::int(), shared_accessors: accessors2.clone().into(), variant_specific_accessors: vec![accessors2.into()], variant_positional_accessors: vec![vec![]], }, ), ] .into(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } #[test] fn constant_int() { let module = constant_module(Constant::Int { location: Default::default(), value: "100".into(), int_value: 100.into(), }); assert_eq!(roundtrip(&module), module); } #[test] fn constant_float() { let module = constant_module(Constant::Float { location: Default::default(), value: "1.0".into(), float_value: LiteralFloatValue::ONE, }); assert_eq!(roundtrip(&module), module); } #[test] fn constant_string() { let module = constant_module(Constant::String { location: Default::default(), value: "hello".into(), }); assert_eq!(roundtrip(&module), module); } #[test] fn constant_tuple() { let int_float_tuple_type = type_::tuple(vec![type_::int(), type_::float()]); let module = constant_module(Constant::Tuple { location: Default::default(), type_: type_::tuple(vec![ type_::int(), type_::float(), int_float_tuple_type.clone(), ]), elements: vec![ Constant::Int { location: Default::default(), value: "1".into(), int_value: 1.into(), }, Constant::Float { location: Default::default(), value: "1.0".into(), float_value: LiteralFloatValue::ONE, }, Constant::Tuple { location: Default::default(), type_: int_float_tuple_type, elements: vec![ Constant::Int { location: Default::default(), value: "1".into(), int_value: 1.into(), }, Constant::Float { location: Default::default(), value: "1.0".into(), float_value: LiteralFloatValue::ONE, }, ], }, ], }); assert_eq!(roundtrip(&module), module); } #[test] fn constant_list() { let module = constant_module(Constant::List { location: Default::default(), type_: type_::int(), elements: vec![ Constant::Int { location: Default::default(), value: "1".into(), int_value: 1.into(), }, Constant::Int { location: Default::default(), value: "2".into(), int_value: 2.into(), }, Constant::Int { location: Default::default(), value: "3".into(), int_value: 3.into(), }, ], tail: Some(Box::new(Constant::List { location: Default::default(), type_: type_::list(type_::int()), elements: vec![ Constant::Int { location: Default::default(), value: "4".into(), int_value: 4.into(), }, Constant::Int { location: Default::default(), value: "5".into(), int_value: 5.into(), }, ], tail: None, })), }); assert_eq!(roundtrip(&module), module); } #[test] fn constant_record() { let module = constant_module(Constant::Record { location: Default::default(), module: None, name: "".into(), arguments: vec![ CallArg { implicit: None, label: None, location: Default::default(), value: Constant::Float { location: Default::default(), value: "0.0".into(), float_value: LiteralFloatValue::ZERO, }, }, CallArg { implicit: None, label: None, location: Default::default(), value: Constant::Int { location: Default::default(), value: "1".into(), int_value: 1.into(), }, }, ], tag: "thetag".into(), type_: type_::int(), field_map: Inferred::Unknown, record_constructor: None, }); assert_eq!(roundtrip(&module), module); } #[test] fn constant_var() { let one_original = Constant::Int { location: Default::default(), value: "1".into(), int_value: 1.into(), }; let one = Constant::Var { location: Default::default(), module: None, name: "one_original".into(), type_: type_::int(), constructor: Some(Box::from(ValueConstructor { publicity: Publicity::Public, deprecation: Deprecation::NotDeprecated, type_: type_::int(), variant: ValueConstructorVariant::ModuleConstant { documentation: Some("some doc".into()), literal: one_original.clone(), location: SrcSpan::default(), module: "one/two".into(), implementations: Implementations { gleam: true, uses_erlang_externals: false, uses_javascript_externals: false, can_run_on_erlang: true, can_run_on_javascript: true, }, name: "one_original".into(), }, })), }; let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a".into(), types: HashMap::new(), types_value_constructors: HashMap::new(), accessors: HashMap::new(), values: [ ( "one".into(), ValueConstructor { publicity: Publicity::Public, deprecation: Deprecation::NotDeprecated, type_: type_::int(), variant: ValueConstructorVariant::ModuleConstant { documentation: Some("some doc!!!!!!!!!".into()), literal: one, location: SrcSpan::default(), module: "one/two".into(), implementations: Implementations { gleam: true, uses_erlang_externals: false, uses_javascript_externals: false, can_run_on_erlang: true, can_run_on_javascript: true, }, name: "one".into(), }, }, ), ( "one_original".into(), ValueConstructor { publicity: Publicity::Public, deprecation: Deprecation::NotDeprecated, type_: type_::int(), variant: ValueConstructorVariant::ModuleConstant { documentation: Some("some doc yeah".into()), literal: one_original, location: SrcSpan::default(), module: "one/two".into(), implementations: Implementations { gleam: true, uses_erlang_externals: false, uses_javascript_externals: false, can_run_on_erlang: true, can_run_on_javascript: true, }, name: "one_original".into(), }, }, ), ] .into(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } #[test] fn constant_bit_array() { let module = constant_module(Constant::BitArray { location: Default::default(), segments: vec![], }); assert_eq!(roundtrip(&module), module); } #[test] fn constant_bit_array_unit() { let module = bit_array_segment_option_module(BitArrayOption::Unit { location: Default::default(), value: 234, }); assert_eq!(roundtrip(&module), module); } #[test] fn constant_bit_array_float() { let module = bit_array_segment_option_module(BitArrayOption::Float { location: Default::default(), }); assert_eq!(roundtrip(&module), module); } #[test] fn constant_bit_array_int() { let module = bit_array_segment_option_module(BitArrayOption::Int { location: Default::default(), }); assert_eq!(roundtrip(&module), module); } #[test] fn constant_bit_array_size() { let module = bit_array_segment_option_module(BitArrayOption::Size { location: Default::default(), value: Box::new(Constant::Int { location: Default::default(), value: "1".into(), int_value: 1.into(), }), short_form: false, }); assert_eq!(roundtrip(&module), module); } #[test] fn constant_bit_array_size_short_form() { let module = bit_array_segment_option_module(BitArrayOption::Size { location: Default::default(), value: Box::new(Constant::Int { location: Default::default(), value: "1".into(), int_value: 1.into(), }), short_form: true, }); assert_eq!(roundtrip(&module), module); } #[test] fn constant_bit_array_bit_arry() { let module = bit_array_segment_option_module(BitArrayOption::Bits { location: Default::default(), }); assert_eq!(roundtrip(&module), module); } #[test] fn constant_bit_array_utf8() { let module = bit_array_segment_option_module(BitArrayOption::Utf8 { location: Default::default(), }); assert_eq!(roundtrip(&module), module); } #[test] fn constant_bit_array_utf16() { let module = bit_array_segment_option_module(BitArrayOption::Utf16 { location: Default::default(), }); assert_eq!(roundtrip(&module), module); } #[test] fn constant_bit_array_utf32() { let module = bit_array_segment_option_module(BitArrayOption::Utf32 { location: Default::default(), }); assert_eq!(roundtrip(&module), module); } #[test] fn constant_bit_array_utf8codepoint() { let module = bit_array_segment_option_module(BitArrayOption::Utf8Codepoint { location: Default::default(), }); assert_eq!(roundtrip(&module), module); } #[test] fn constant_bit_array_utf16codepoint() { let module = bit_array_segment_option_module(BitArrayOption::Utf16Codepoint { location: Default::default(), }); assert_eq!(roundtrip(&module), module); } #[test] fn constant_bit_array_utf32codepoint() { let module = bit_array_segment_option_module(BitArrayOption::Utf32Codepoint { location: Default::default(), }); assert_eq!(roundtrip(&module), module); } #[test] fn constant_bit_array_signed() { let module = bit_array_segment_option_module(BitArrayOption::Signed { location: Default::default(), }); assert_eq!(roundtrip(&module), module); } #[test] fn constant_bit_array_unsigned() { let module = bit_array_segment_option_module(BitArrayOption::Unsigned { location: Default::default(), }); assert_eq!(roundtrip(&module), module); } #[test] fn constant_bit_array_big() { let module = bit_array_segment_option_module(BitArrayOption::Big { location: Default::default(), }); assert_eq!(roundtrip(&module), module); } #[test] fn constant_bit_array_little() { let module = bit_array_segment_option_module(BitArrayOption::Little { location: Default::default(), }); assert_eq!(roundtrip(&module), module); } #[test] fn constant_bit_array_native() { let module = bit_array_segment_option_module(BitArrayOption::Native { location: Default::default(), }); assert_eq!(roundtrip(&module), module); } #[test] fn deprecated_type() { let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a/b".into(), types: [( "ListIntType".into(), TypeConstructor { type_: type_::list(type_::int()), publicity: Publicity::Public, origin: Default::default(), module: "the/module".into(), parameters: vec![], deprecation: Deprecation::Deprecated { message: "oh no".into(), }, documentation: None, }, )] .into(), types_value_constructors: HashMap::new(), values: HashMap::new(), accessors: HashMap::new(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } #[test] fn module_fn_value_with_external_implementations() { let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a/b/c".into(), types: HashMap::new(), types_value_constructors: HashMap::new(), accessors: HashMap::new(), values: [( "one".into(), ValueConstructor { publicity: Publicity::Public, deprecation: Deprecation::NotDeprecated, type_: type_::int(), variant: ValueConstructorVariant::ModuleFn { documentation: Some("wabble!".into()), name: "one".into(), field_map: None, module: "a".into(), arity: 5, location: SrcSpan { start: 52, end: 1100, }, external_erlang: Some(("wibble".into(), "wobble".into())), external_javascript: Some(("wobble".into(), "wibble".into())), implementations: Implementations { gleam: false, uses_erlang_externals: true, uses_javascript_externals: true, can_run_on_erlang: false, can_run_on_javascript: true, }, purity: Purity::Impure, }, }, )] .into(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } #[test] fn module_containing_echo() { let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a/b/c".into(), types: HashMap::new(), types_value_constructors: HashMap::new(), accessors: HashMap::new(), values: [].into(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: true, references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } #[test] fn internal_module_fn() { let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a/b/c".into(), types: HashMap::new(), types_value_constructors: HashMap::new(), accessors: HashMap::new(), values: [( "one".into(), ValueConstructor { publicity: Publicity::Internal { attribute_location: None, }, deprecation: Deprecation::NotDeprecated, type_: type_::int(), variant: ValueConstructorVariant::ModuleFn { documentation: Some("wabble!".into()), name: "one".into(), field_map: None, module: "a".into(), arity: 5, location: SrcSpan { start: 52, end: 1100, }, external_erlang: Some(("wibble".into(), "wobble".into())), external_javascript: Some(("wobble".into(), "wibble".into())), implementations: Implementations { gleam: false, uses_erlang_externals: true, uses_javascript_externals: true, can_run_on_erlang: true, can_run_on_javascript: true, }, purity: Purity::Unknown, }, }, )] .into(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } #[test] fn internal_annotated_module_fn() { let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a/b/c".into(), types: HashMap::new(), types_value_constructors: HashMap::new(), accessors: HashMap::new(), values: [( "one".into(), ValueConstructor { publicity: Publicity::Internal { attribute_location: Some(SrcSpan { start: 11, end: 111, }), }, deprecation: Deprecation::NotDeprecated, type_: type_::int(), variant: ValueConstructorVariant::ModuleFn { documentation: Some("wabble!".into()), name: "one".into(), field_map: None, module: "a".into(), arity: 5, location: SrcSpan { start: 52, end: 1100, }, external_erlang: Some(("wibble".into(), "wobble".into())), external_javascript: Some(("wobble".into(), "wibble".into())), implementations: Implementations { gleam: false, uses_erlang_externals: true, uses_javascript_externals: true, can_run_on_erlang: true, can_run_on_javascript: true, }, purity: Purity::Impure, }, }, )] .into(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } // https://github.com/gleam-lang/gleam/issues/2599 #[test] fn type_variable_ids_in_constructors_are_shared() { let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a/b/c".into(), types: HashMap::new(), types_value_constructors: HashMap::from([( "SomeType".into(), TypeVariantConstructors { type_parameters_ids: vec![4, 5, 6], variants: vec![TypeValueConstructor { name: "One".into(), parameters: vec![ TypeValueConstructorField { type_: type_::generic_var(6), label: None, documentation: Some("Here's some documentation".into()), }, TypeValueConstructorField { type_: type_::int(), label: None, documentation: None, }, TypeValueConstructorField { type_: type_::tuple(vec![type_::generic_var(4), type_::generic_var(5)]), label: None, documentation: None, }, ], documentation: None, }], opaque: Opaque::NotOpaque, }, )]), accessors: HashMap::new(), values: [].into(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), }; let expected = HashMap::from([( "SomeType".into(), TypeVariantConstructors { type_parameters_ids: vec![0, 1, 2], variants: vec![TypeValueConstructor { name: "One".into(), parameters: vec![ TypeValueConstructorField { type_: type_::generic_var(2), label: None, documentation: Some("Here's some documentation".into()), }, TypeValueConstructorField { type_: type_::int(), label: None, documentation: None, }, TypeValueConstructorField { type_: type_::tuple(vec![type_::generic_var(0), type_::generic_var(1)]), label: None, documentation: None, }, ], documentation: None, }], opaque: Opaque::NotOpaque, }, )]); assert_eq!(roundtrip(&module).types_value_constructors, expected); } #[test] fn type_with_inferred_variant() { let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), contains_echo: false, origin: Origin::Src, name: "a/b".into(), types: [( "Wibble".into(), TypeConstructor { type_: Arc::new(Type::Named { publicity: Publicity::Internal { attribute_location: None, }, package: "some_package".into(), module: "the/module".into(), name: "Wibble".into(), arguments: Vec::new(), inferred_variant: Some(1), }), publicity: Publicity::Internal { attribute_location: Some(SrcSpan::new(0, 10)), }, origin: Default::default(), module: "the/module".into(), parameters: vec![], deprecation: Deprecation::NotDeprecated, documentation: None, }, )] .into(), types_value_constructors: HashMap::new(), values: HashMap::new(), accessors: HashMap::new(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } #[test] fn module_with_type_aliases() { let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "some_module".into(), types: HashMap::new(), types_value_constructors: HashMap::new(), values: HashMap::new(), accessors: HashMap::new(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: [( "MyAlias".into(), TypeAliasConstructor { publicity: Publicity::Public, module: "some_module".into(), type_: prelude::int(), arity: 1, deprecation: Deprecation::NotDeprecated, documentation: Some("Some documentation".into()), origin: Default::default(), parameters: vec![type_::generic_var(0)], }, )] .into(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } #[test] fn module_with_documentation() { let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "some_module".into(), types: HashMap::new(), types_value_constructors: HashMap::new(), values: HashMap::new(), accessors: HashMap::new(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: vec![ "This is a line of documentation".into(), "And here is another".into(), "And finally, a third".into(), ], contains_echo: false, references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } #[test] fn module_with_opaque_type() { let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a".into(), types: HashMap::new(), types_value_constructors: [( "SomeType".into(), TypeVariantConstructors { type_parameters_ids: vec![0, 1, 2], variants: vec![TypeValueConstructor { name: "One".into(), parameters: vec![], documentation: Some("Some documentation".into()), }], opaque: Opaque::Opaque, }, )] .into(), accessors: HashMap::new(), values: HashMap::new(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } #[test] fn module_with_references() { let module = ModuleInterface { warnings: vec![], is_internal: false, package: "some_package".into(), origin: Origin::Src, name: "a".into(), types: HashMap::new(), types_value_constructors: HashMap::new(), accessors: HashMap::new(), values: HashMap::new(), line_numbers: LineNumbers::new(""), src_path: "some_path".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References { imported_modules: ["some_module".into(), "some_other_module".into()].into(), value_references: [ ( ("some_module".into(), "some_function".into()), vec![ Reference { location: SrcSpan::new(1, 6), kind: ReferenceKind::Definition, }, Reference { location: SrcSpan::new(7, 11), kind: ReferenceKind::Unqualified, }, ], ), ( ("some_other_module".into(), "some_constant".into()), vec![ Reference { location: SrcSpan::new(6, 9), kind: ReferenceKind::Import, }, Reference { location: SrcSpan::new(90, 108), kind: ReferenceKind::Unqualified, }, ], ), ( ("some_other_module".into(), "SomeTypeVariant".into()), vec![ Reference { location: SrcSpan::new(26, 35), kind: ReferenceKind::Qualified, }, Reference { location: SrcSpan::new(152, 204), kind: ReferenceKind::Qualified, }, Reference { location: SrcSpan::new(0, 8), kind: ReferenceKind::Qualified, }, ], ), ] .into(), type_references: [( ("some_other_module".into(), "TypeVariant".into()), vec![ Reference { location: SrcSpan::new(26, 35), kind: ReferenceKind::Qualified, }, Reference { location: SrcSpan::new(152, 204), kind: ReferenceKind::Unqualified, }, Reference { location: SrcSpan::new(0, 8), kind: ReferenceKind::Definition, }, ], )] .into(), }, inline_functions: HashMap::new(), }; assert_eq!(roundtrip(&module), module); } ================================================ FILE: compiler-core/src/metadata.rs ================================================ //! Seriaisation and deserialisation of Gleam compiler metadata into binary files //! using serde. #[cfg(test)] mod tests; use std::{collections::HashMap, sync::Arc}; use crate::{ ast::{ BitArrayOption, BitArraySegment, CallArg, Constant, RecordBeingUpdated, RecordUpdateArg, TypedConstant, }, type_::{ self, AccessorsMap, ModuleInterface, RecordAccessor, Type, TypeAliasConstructor, TypeConstructor, TypeValueConstructor, TypeValueConstructorField, TypeVar, TypeVariantConstructors, ValueConstructor, ValueConstructorVariant, }, uid::UniqueIdGenerator, }; pub fn encode(module: &ModuleInterface) -> Result, bincode::error::EncodeError> { bincode::serde::encode_to_vec(module, bincode::config::legacy()) } pub fn decode( bytes: &[u8], ids: UniqueIdGenerator, ) -> Result { bincode::serde::decode_from_slice(bytes, bincode::config::legacy()) .map(|(module, _)| remap_type_variable_ids(module, ids)) } fn remap_type_variable_ids(module: ModuleInterface, ids: UniqueIdGenerator) -> ModuleInterface { RemapIds::new(ids).module(module) } struct RemapIds { ids: UniqueIdGenerator, remapped_variable_ids: HashMap, } impl RemapIds { fn new(ids: UniqueIdGenerator) -> Self { Self { ids, remapped_variable_ids: HashMap::new(), } } fn module(&mut self, module: ModuleInterface) -> ModuleInterface { let ModuleInterface { name, origin, package, types, types_value_constructors, values, accessors, line_numbers, src_path, is_internal, warnings, minimum_required_version, type_aliases, documentation, contains_echo, references, inline_functions, } = module; let types = types .into_iter() .map(|(name, type_)| (name, self.type_constructor(type_))) .collect(); let types_value_constructors = types_value_constructors .into_iter() .map(|(name, type_)| (name, self.type_variant_constructors(type_))) .collect(); let values = values .into_iter() .map(|(name, value)| (name, self.value_constructor(value))) .collect(); let accessors = accessors .into_iter() .map(|(name, accessors)| (name, self.accessors_map(accessors))) .collect(); let type_aliases = type_aliases .into_iter() .map(|(name, type_)| (name, self.type_alias(type_))) .collect(); ModuleInterface { name, origin, package, types, types_value_constructors, values, accessors, line_numbers, src_path, is_internal, warnings, minimum_required_version, type_aliases, documentation, contains_echo, references, inline_functions, } } fn type_constructor(&mut self, type_: TypeConstructor) -> TypeConstructor { let TypeConstructor { publicity, origin, module, parameters, type_, deprecation, documentation, } = type_; let parameters = parameters .into_iter() .map(|parameter| self.type_(parameter)) .collect(); let type_ = self.type_(type_); TypeConstructor { publicity, origin, module, parameters, type_, deprecation, documentation, } } fn type_variant_constructors( &mut self, type_: TypeVariantConstructors, ) -> TypeVariantConstructors { let TypeVariantConstructors { type_parameters_ids, opaque, variants, } = type_; let type_parameters_ids = type_parameters_ids .into_iter() .map(|id| self.id(id)) .collect(); let variants = variants .into_iter() .map(|variant| self.type_value_constructor(variant)) .collect(); TypeVariantConstructors { type_parameters_ids, opaque, variants, } } fn type_value_constructor(&mut self, variant: TypeValueConstructor) -> TypeValueConstructor { let TypeValueConstructor { name, parameters, documentation, } = variant; let parameters = parameters .into_iter() .map( |TypeValueConstructorField { type_, label, documentation, }| TypeValueConstructorField { type_: self.type_(type_), label, documentation, }, ) .collect(); TypeValueConstructor { name, parameters, documentation, } } fn value_constructor(&mut self, value: ValueConstructor) -> ValueConstructor { let ValueConstructor { publicity, deprecation, variant, type_, } = value; let variant = self.value_constructor_variant(variant); let type_ = self.type_(type_); ValueConstructor { publicity, deprecation, variant, type_, } } fn value_constructor_variant( &mut self, variant: ValueConstructorVariant, ) -> ValueConstructorVariant { match variant { ValueConstructorVariant::LocalVariable { location, origin } => { ValueConstructorVariant::LocalVariable { location, origin } } ValueConstructorVariant::ModuleConstant { documentation, location, module, name, literal, implementations, } => ValueConstructorVariant::ModuleConstant { documentation, location, module, name, literal: self.constant(literal), implementations, }, ValueConstructorVariant::ModuleFn { name, field_map, module, arity, location, documentation, implementations, external_erlang, external_javascript, purity, } => ValueConstructorVariant::ModuleFn { name, field_map, module, arity, location, documentation, implementations, external_erlang, external_javascript, purity, }, ValueConstructorVariant::Record { name, arity, field_map, location, module, variants_count, variant_index, documentation, } => ValueConstructorVariant::Record { name, arity, field_map, location, module, variants_count, variant_index, documentation, }, } } fn constant(&mut self, constant: TypedConstant) -> TypedConstant { match constant { Constant::Int { location, value, int_value, } => Constant::Int { location, value, int_value, }, Constant::Float { location, value, float_value, } => Constant::Float { location, value, float_value, }, Constant::String { location, value } => Constant::String { location, value }, Constant::Tuple { location, elements, type_, } => Constant::Tuple { location, elements: elements .into_iter() .map(|element| self.constant(element)) .collect(), type_: self.type_(type_), }, Constant::List { location, elements, type_, tail, } => Constant::List { location, elements: elements .into_iter() .map(|element| self.constant(element)) .collect(), type_: self.type_(type_), tail: tail.map(|tail| Box::new(self.constant(*tail))), }, Constant::Record { location, module, name, arguments, tag, type_, field_map, record_constructor, } => Constant::Record { location, module, name, arguments: arguments .into_iter() .map( |CallArg { label, location, value, implicit, }| CallArg { label, location, value: self.constant(value), implicit, }, ) .collect(), tag, type_: self.type_(type_), field_map, record_constructor: record_constructor .map(|constructor| Box::new(self.value_constructor(*constructor))), }, Constant::RecordUpdate { location, constructor_location, module, name, record: RecordBeingUpdated { base: record, location: record_location, }, arguments, tag, type_, field_map, } => Constant::RecordUpdate { location, constructor_location, module, name, record: RecordBeingUpdated { base: Box::new(self.constant(*record)), location: record_location, }, arguments: arguments .into_iter() .map( |RecordUpdateArg { label, location, value, }| RecordUpdateArg { label, location, value: self.constant(value), }, ) .collect(), tag, type_: self.type_(type_), field_map, }, Constant::BitArray { location, segments } => Constant::BitArray { location, segments: segments .into_iter() .map(|segment| self.bit_array_segment(segment)) .collect(), }, Constant::Var { location, module, name, constructor, type_, } => Constant::Var { location, module, name, constructor: constructor .map(|constructor| Box::new(self.value_constructor(*constructor))), type_: self.type_(type_), }, Constant::StringConcatenation { location, left, right, } => Constant::StringConcatenation { location, left: Box::new(self.constant(*left)), right: Box::new(self.constant(*right)), }, Constant::Invalid { location, type_, extra_information, } => Constant::Invalid { location, type_: self.type_(type_), extra_information, }, } } fn bit_array_segment( &mut self, segment: BitArraySegment>, ) -> BitArraySegment> { let BitArraySegment { location, value, options, type_, } = segment; let value = Box::new(self.constant(*value)); let options = options .into_iter() .map(|option| self.bit_array_option(option)) .collect(); let type_ = self.type_(type_); BitArraySegment { location, value, options, type_, } } fn bit_array_option( &mut self, option: BitArrayOption, ) -> BitArrayOption { match option { BitArrayOption::Bytes { .. } | BitArrayOption::Int { .. } | BitArrayOption::Float { .. } | BitArrayOption::Bits { .. } | BitArrayOption::Utf8 { .. } | BitArrayOption::Utf16 { .. } | BitArrayOption::Utf32 { .. } | BitArrayOption::Utf8Codepoint { .. } | BitArrayOption::Utf16Codepoint { .. } | BitArrayOption::Utf32Codepoint { .. } | BitArrayOption::Signed { .. } | BitArrayOption::Unsigned { .. } | BitArrayOption::Big { .. } | BitArrayOption::Little { .. } | BitArrayOption::Native { .. } | BitArrayOption::Unit { .. } => option, BitArrayOption::Size { location, value, short_form, } => BitArrayOption::Size { location, value: Box::new(self.constant(*value)), short_form, }, } } fn accessors_map(&mut self, accessors: AccessorsMap) -> AccessorsMap { let AccessorsMap { publicity, type_, shared_accessors, variant_specific_accessors, variant_positional_accessors, } = accessors; let type_ = self.type_(type_); let shared_accessors = shared_accessors .into_iter() .map(|(label, accessor)| (label, self.record_accessor(accessor))) .collect(); let variant_specific_accessors = variant_specific_accessors .into_iter() .map(|variant_accessors| { variant_accessors .into_iter() .map(|(label, accessor)| (label, self.record_accessor(accessor))) .collect() }) .collect(); let variant_positional_accessors = variant_positional_accessors .into_iter() .map(|variant_accessors| { variant_accessors .into_iter() .map(|type_| self.type_(type_)) .collect() }) .collect(); AccessorsMap { publicity, type_, shared_accessors, variant_specific_accessors, variant_positional_accessors, } } fn record_accessor(&mut self, accessor: RecordAccessor) -> RecordAccessor { let RecordAccessor { index, label, type_, documentation, } = accessor; RecordAccessor { index, label, type_: self.type_(type_), documentation, } } fn type_alias(&mut self, type_: TypeAliasConstructor) -> TypeAliasConstructor { let TypeAliasConstructor { publicity, module, type_, arity, deprecation, documentation, origin, parameters, } = type_; let type_ = self.type_(type_); let parameters = parameters .into_iter() .map(|parameter| self.type_(parameter)) .collect(); TypeAliasConstructor { publicity, module, type_, arity, deprecation, documentation, origin, parameters, } } fn type_(&mut self, type_: Arc) -> Arc { let type_ = match Arc::unwrap_or_clone(type_) { Type::Named { publicity, package, module, name, arguments, inferred_variant, } => Type::Named { publicity, package, module, name, arguments: arguments .into_iter() .map(|argument| self.type_(argument)) .collect(), inferred_variant, }, Type::Fn { arguments, return_ } => Type::Fn { arguments: arguments .into_iter() .map(|argument| self.type_(argument)) .collect(), return_: self.type_(return_), }, Type::Var { type_ } => return self.type_var(&type_.borrow()), Type::Tuple { elements } => Type::Tuple { elements: elements .into_iter() .map(|element| self.type_(element)) .collect(), }, }; Arc::new(type_) } fn type_var(&mut self, variable: &TypeVar) -> Arc { match variable { TypeVar::Link { type_ } => self.type_(type_.clone()), TypeVar::Unbound { id } => type_::prelude::unbound_var(self.id(*id)), TypeVar::Generic { id } => type_::prelude::generic_var(self.id(*id)), } } fn id(&mut self, id: u64) -> u64 { match self.remapped_variable_ids.get(&id) { Some(new_id) => *new_id, None => { let new_id = self.ids.next(); _ = self.remapped_variable_ids.insert(id, new_id); new_id } } } } ================================================ FILE: compiler-core/src/package_interface/snapshots/gleam_core__package_interface__tests__constructors_with_documentation.snap ================================================ --- source: compiler-core/src/package_interface/tests.rs expression: "\npub type Wibble {\n /// This is the Wibble variant. It contains some example data.\n Wibble(Int)\n /// This is the Wobble variant. It is a recursive type.\n Wobble(Wibble)\n}\n" --- { "name": "my_package", "version": "11.10.9-1.wibble+build", "gleam-version-constraint": "1.0.0", "modules": { "my/module": { "documentation": [], "type-aliases": {}, "types": { "Wibble": { "documentation": null, "deprecation": null, "parameters": 0, "constructors": [ { "documentation": " This is the Wibble variant. It contains some example data.\n", "name": "Wibble", "parameters": [ { "label": null, "type": { "kind": "named", "name": "Int", "package": "", "module": "gleam", "parameters": [] } } ] }, { "documentation": " This is the Wobble variant. It is a recursive type.\n", "name": "Wobble", "parameters": [ { "label": null, "type": { "kind": "named", "name": "Wibble", "package": "my_package", "module": "my/module", "parameters": [] } } ] } ] } }, "constants": {}, "functions": {} } } } ================================================ FILE: compiler-core/src/package_interface/snapshots/gleam_core__package_interface__tests__generic_function.snap ================================================ --- source: compiler-core/src/package_interface/tests.rs expression: "\npub type Wob(a) { Wob }\n@deprecated(\"deprecation message\")\npub fn main() { Wob }\n" --- { "name": "my_package", "version": "11.10.9-1.wibble+build", "gleam-version-constraint": "1.0.0", "modules": { "my/module": { "documentation": [], "type-aliases": {}, "types": { "Wob": { "documentation": null, "deprecation": null, "parameters": 1, "constructors": [ { "documentation": null, "name": "Wob", "parameters": [] } ] } }, "constants": {}, "functions": { "main": { "documentation": null, "deprecation": { "message": "deprecation message" }, "implementations": { "gleam": true, "uses-erlang-externals": false, "uses-javascript-externals": false, "can-run-on-erlang": true, "can-run-on-javascript": true }, "parameters": [], "return": { "kind": "named", "name": "Wob", "package": "my_package", "module": "my/module", "parameters": [ { "kind": "variable", "id": 0 } ] } } } } } } ================================================ FILE: compiler-core/src/package_interface/snapshots/gleam_core__package_interface__tests__imported_aliased_type_keeps_original_name.snap ================================================ --- source: compiler-core/src/package_interface/tests.rs expression: "\nimport other_module.{type Element as Alias} as module_alias\npub fn main() -> Alias(module_alias.Element(a)) {}\n" --- { "name": "my_package", "version": "11.10.9-1.wibble+build", "gleam-version-constraint": "1.0.0", "modules": { "my/module": { "documentation": [], "type-aliases": {}, "types": {}, "constants": {}, "functions": { "main": { "documentation": null, "deprecation": null, "implementations": { "gleam": true, "uses-erlang-externals": false, "uses-javascript-externals": false, "can-run-on-erlang": true, "can-run-on-javascript": true }, "parameters": [], "return": { "kind": "named", "name": "Element", "package": "other_package", "module": "other_module", "parameters": [ { "kind": "named", "name": "Element", "package": "other_package", "module": "other_module", "parameters": [ { "kind": "variable", "id": 0 } ] } ] } } } } } } ================================================ FILE: compiler-core/src/package_interface/snapshots/gleam_core__package_interface__tests__imported_type.snap ================================================ --- source: compiler-core/src/package_interface/tests.rs expression: "\nimport other_module.{type Element}\npub fn main() -> Element(Int) {}\n" --- { "name": "my_package", "version": "11.10.9-1.wibble+build", "gleam-version-constraint": "1.0.0", "modules": { "my/module": { "documentation": [], "type-aliases": {}, "types": {}, "constants": {}, "functions": { "main": { "documentation": null, "deprecation": null, "implementations": { "gleam": true, "uses-erlang-externals": false, "uses-javascript-externals": false, "can-run-on-erlang": true, "can-run-on-javascript": true }, "parameters": [], "return": { "kind": "named", "name": "Element", "package": "other_package", "module": "other_module", "parameters": [ { "kind": "named", "name": "Int", "package": "", "module": "gleam", "parameters": [] } ] } } } } } } ================================================ FILE: compiler-core/src/package_interface/snapshots/gleam_core__package_interface__tests__internal_definitions_are_not_included.snap ================================================ --- source: compiler-core/src/package_interface/tests.rs expression: "\n@internal pub const float = 1.1\n@internal pub fn main() {}\n@internal pub type Wibble\n@internal pub type Wobble = Int\n" --- { "name": "my_package", "version": "11.10.9-1.wibble+build", "gleam-version-constraint": "1.0.0", "modules": { "my/module": { "documentation": [], "type-aliases": {}, "types": {}, "constants": {}, "functions": {} } } } ================================================ FILE: compiler-core/src/package_interface/snapshots/gleam_core__package_interface__tests__internal_modules_are_not_exported.snap ================================================ --- source: compiler-core/src/package_interface/tests.rs expression: "pub fn main() { 1 }" --- { "name": "my_package", "version": "11.10.9-1.wibble+build", "gleam-version-constraint": "1.0.0", "modules": {} } ================================================ FILE: compiler-core/src/package_interface/snapshots/gleam_core__package_interface__tests__labelled_function_parameters.snap ================================================ --- source: compiler-core/src/package_interface/tests.rs expression: "\npub fn fold(list: List(a), from acc: b, with f: fn(a, b) -> b) -> b {\n todo\n}\n" --- { "name": "my_package", "version": "11.10.9-1.wibble+build", "gleam-version-constraint": "1.0.0", "modules": { "my/module": { "documentation": [], "type-aliases": {}, "types": {}, "constants": {}, "functions": { "fold": { "documentation": null, "deprecation": null, "implementations": { "gleam": true, "uses-erlang-externals": false, "uses-javascript-externals": false, "can-run-on-erlang": true, "can-run-on-javascript": true }, "parameters": [ { "label": null, "type": { "kind": "named", "name": "List", "package": "", "module": "gleam", "parameters": [ { "kind": "variable", "id": 0 } ] } }, { "label": "from", "type": { "kind": "variable", "id": 1 } }, { "label": "with", "type": { "kind": "fn", "parameters": [ { "kind": "variable", "id": 0 }, { "kind": "variable", "id": 1 } ], "return": { "kind": "variable", "id": 1 } } } ], "return": { "kind": "variable", "id": 1 } } } } } } ================================================ FILE: compiler-core/src/package_interface/snapshots/gleam_core__package_interface__tests__multiple_type_variables.snap ================================================ --- source: compiler-core/src/package_interface/tests.rs expression: "\npub type Box(a, b)\npub fn some_type_variables(a: a, b: b, c: Box(c, d)) -> Box(a, d) {}\n" --- { "name": "my_package", "version": "11.10.9-1.wibble+build", "gleam-version-constraint": "1.0.0", "modules": { "my/module": { "documentation": [], "type-aliases": {}, "types": { "Box": { "documentation": null, "deprecation": null, "parameters": 2, "constructors": [] } }, "constants": {}, "functions": { "some_type_variables": { "documentation": null, "deprecation": null, "implementations": { "gleam": true, "uses-erlang-externals": false, "uses-javascript-externals": false, "can-run-on-erlang": true, "can-run-on-javascript": true }, "parameters": [ { "label": null, "type": { "kind": "variable", "id": 0 } }, { "label": null, "type": { "kind": "variable", "id": 1 } }, { "label": null, "type": { "kind": "named", "name": "Box", "package": "my_package", "module": "my/module", "parameters": [ { "kind": "variable", "id": 2 }, { "kind": "variable", "id": 3 } ] } } ], "return": { "kind": "named", "name": "Box", "package": "my_package", "module": "my/module", "parameters": [ { "kind": "variable", "id": 0 }, { "kind": "variable", "id": 3 } ] } } } } } } ================================================ FILE: compiler-core/src/package_interface/snapshots/gleam_core__package_interface__tests__opaque_constructors_are_not_exposed.snap ================================================ --- source: compiler-core/src/package_interface/tests.rs expression: "pub opaque type Wibble { Wob }" --- { "name": "my_package", "version": "11.10.9-1.wibble+build", "gleam-version-constraint": "1.0.0", "modules": { "my/module": { "documentation": [], "type-aliases": {}, "types": { "Wibble": { "documentation": null, "deprecation": null, "parameters": 0, "constructors": [] } }, "constants": {}, "functions": {} } } } ================================================ FILE: compiler-core/src/package_interface/snapshots/gleam_core__package_interface__tests__package_documentation_is_included.snap ================================================ --- source: compiler-core/src/package_interface/tests.rs expression: "\n//// Some package\n//// documentation!\n\npub fn main() { 1 }\n" --- { "name": "my_package", "version": "11.10.9-1.wibble+build", "gleam-version-constraint": "1.0.0", "modules": { "my/module": { "documentation": [ " Some package", " documentation!" ], "type-aliases": {}, "types": {}, "constants": {}, "functions": { "main": { "documentation": null, "deprecation": null, "implementations": { "gleam": true, "uses-erlang-externals": false, "uses-javascript-externals": false, "can-run-on-erlang": true, "can-run-on-javascript": true }, "parameters": [], "return": { "kind": "named", "name": "Int", "package": "", "module": "gleam", "parameters": [] } } } } } } ================================================ FILE: compiler-core/src/package_interface/snapshots/gleam_core__package_interface__tests__prelude_types.snap ================================================ --- source: compiler-core/src/package_interface/tests.rs expression: "\npub const float = 1.1\npub const string = \"\"\npub const int = 1\npub const bool = True\n" --- { "name": "my_package", "version": "11.10.9-1.wibble+build", "gleam-version-constraint": "1.0.0", "modules": { "my/module": { "documentation": [], "type-aliases": {}, "types": {}, "constants": { "bool": { "documentation": null, "deprecation": null, "implementations": { "gleam": true, "uses-erlang-externals": false, "uses-javascript-externals": false, "can-run-on-erlang": true, "can-run-on-javascript": true }, "type": { "kind": "named", "name": "Bool", "package": "", "module": "gleam", "parameters": [] } }, "float": { "documentation": null, "deprecation": null, "implementations": { "gleam": true, "uses-erlang-externals": false, "uses-javascript-externals": false, "can-run-on-erlang": true, "can-run-on-javascript": true }, "type": { "kind": "named", "name": "Float", "package": "", "module": "gleam", "parameters": [] } }, "int": { "documentation": null, "deprecation": null, "implementations": { "gleam": true, "uses-erlang-externals": false, "uses-javascript-externals": false, "can-run-on-erlang": true, "can-run-on-javascript": true }, "type": { "kind": "named", "name": "Int", "package": "", "module": "gleam", "parameters": [] } }, "string": { "documentation": null, "deprecation": null, "implementations": { "gleam": true, "uses-erlang-externals": false, "uses-javascript-externals": false, "can-run-on-erlang": true, "can-run-on-javascript": true }, "type": { "kind": "named", "name": "String", "package": "", "module": "gleam", "parameters": [] } } }, "functions": {} } } } ================================================ FILE: compiler-core/src/package_interface/snapshots/gleam_core__package_interface__tests__private_definitions_are_not_included.snap ================================================ --- source: compiler-core/src/package_interface/tests.rs expression: "\nconst float = 1.1\nfn main() {}\ntype Wibble\ntype Wob = Int\n" --- { "name": "my_package", "version": "11.10.9-1.wibble+build", "gleam-version-constraint": "1.0.0", "modules": { "my/module": { "documentation": [], "type-aliases": {}, "types": {}, "constants": {}, "functions": {} } } } ================================================ FILE: compiler-core/src/package_interface/snapshots/gleam_core__package_interface__tests__type_aliases.snap ================================================ --- source: compiler-core/src/package_interface/tests.rs expression: pub type Wibble(a) = List(a) --- { "name": "my_package", "version": "11.10.9-1.wibble+build", "gleam-version-constraint": "1.0.0", "modules": { "my/module": { "documentation": [], "type-aliases": { "Wibble": { "documentation": null, "deprecation": null, "parameters": 1, "alias": { "kind": "named", "name": "List", "package": "", "module": "gleam", "parameters": [ { "kind": "variable", "id": 0 } ] } } }, "types": {}, "constants": {}, "functions": {} } } } ================================================ FILE: compiler-core/src/package_interface/snapshots/gleam_core__package_interface__tests__type_constructors.snap ================================================ --- source: compiler-core/src/package_interface/tests.rs expression: "\npub type Box(a, b) {\n Box(b, Int)\n OtherBox(message: String, a: a)\n}\n" --- { "name": "my_package", "version": "11.10.9-1.wibble+build", "gleam-version-constraint": "1.0.0", "modules": { "my/module": { "documentation": [], "type-aliases": {}, "types": { "Box": { "documentation": null, "deprecation": null, "parameters": 2, "constructors": [ { "documentation": null, "name": "Box", "parameters": [ { "label": null, "type": { "kind": "variable", "id": 1 } }, { "label": null, "type": { "kind": "named", "name": "Int", "package": "", "module": "gleam", "parameters": [] } } ] }, { "documentation": null, "name": "OtherBox", "parameters": [ { "label": "message", "type": { "kind": "named", "name": "String", "package": "", "module": "gleam", "parameters": [] } }, { "label": "a", "type": { "kind": "variable", "id": 0 } } ] } ] } }, "constants": {}, "functions": {} } } } ================================================ FILE: compiler-core/src/package_interface/snapshots/gleam_core__package_interface__tests__type_definition.snap ================================================ --- source: compiler-core/src/package_interface/tests.rs expression: "\n/// Wibble's documentation\npub type Wibble(a, b) {\n Wibble\n Wobble\n}\n" --- { "name": "my_package", "version": "11.10.9-1.wibble+build", "gleam-version-constraint": "1.0.0", "modules": { "my/module": { "documentation": [], "type-aliases": {}, "types": { "Wibble": { "documentation": " Wibble's documentation\n", "deprecation": null, "parameters": 2, "constructors": [ { "documentation": null, "name": "Wibble", "parameters": [] }, { "documentation": null, "name": "Wobble", "parameters": [] } ] } }, "constants": {}, "functions": {} } } } ================================================ FILE: compiler-core/src/package_interface/tests.rs ================================================ use std::time::SystemTime; use camino::Utf8PathBuf; use ecow::EcoString; use globset::GlobBuilder; use hexpm::version::Identifier; use crate::{ analyse::TargetSupport, build::{Module, Origin, Package, Target}, config::{Docs, ErlangConfig, GleamVersion, JavaScriptConfig, PackageConfig}, line_numbers::LineNumbers, type_::PRELUDE_MODULE_NAME, uid::UniqueIdGenerator, warning::{TypeWarningEmitter, WarningEmitter}, }; use super::PackageInterface; #[macro_export] macro_rules! assert_package_interface_with_name { ($module_name:expr, $src:expr) => { let output = $crate::package_interface::tests::compile_package(Some($module_name), $src, None); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }; } #[macro_export] macro_rules! assert_package_interface { (($dep_package:expr, $dep_name:expr, $dep_src:expr), $src:expr $(,)?) => {{ let output = $crate::package_interface::tests::compile_package( None, $src, Some(($dep_package, $dep_name, $dep_src)), ); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }}; (($dep_package:expr, $dep_name:expr, $dep_src:expr), $src:expr $(,)?) => {{ let output = $crate::package_interface::tests::compile_package( None, $src, Some(($dep_package, $dep_name, $dep_src)), ); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }}; ($src:expr) => {{ let output = $crate::package_interface::tests::compile_package(None, $src, None); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }}; } pub fn compile_package( module_name: Option<&str>, src: &str, dep: Option<(&str, &str, &str)>, ) -> String { let mut modules = im::HashMap::new(); let ids = UniqueIdGenerator::new(); // DUPE: preludeinsertion // TODO: Currently we do this here and also in the tests. It would be better // to have one place where we create all this required state for use in each // place. let _ = modules.insert( PRELUDE_MODULE_NAME.into(), crate::type_::build_prelude(&ids), ); let mut direct_dependencies = std::collections::HashMap::from_iter(vec![]); if let Some((dep_package, dep_name, dep_src)) = dep { let parsed = crate::parse::parse_module( Utf8PathBuf::from("test/path"), dep_src, &WarningEmitter::null(), ) .expect("dep syntax error"); let mut ast = parsed.module; ast.name = dep_name.into(); let line_numbers = LineNumbers::new(dep_src); let mut config = PackageConfig::default(); config.name = dep_package.into(); let dep = crate::analyse::ModuleAnalyzerConstructor::<()> { target: Target::Erlang, ids: &ids, origin: Origin::Src, importable_modules: &modules, warnings: &TypeWarningEmitter::null(), direct_dependencies: &std::collections::HashMap::new(), dev_dependencies: &std::collections::HashSet::new(), target_support: TargetSupport::Enforced, package_config: &config, } .infer_module(ast, line_numbers, "".into()) .expect("should successfully infer"); let _ = modules.insert(dep_name.into(), dep.type_info); let _ = direct_dependencies.insert(dep_package.into(), ()); } let parsed = crate::parse::parse_module(Utf8PathBuf::from("test/path"), src, &WarningEmitter::null()) .expect("syntax error"); let mut ast = parsed.module; let module_name = module_name .map(EcoString::from) .unwrap_or("my/module".into()); ast.name = module_name.clone(); let mut config = PackageConfig::default(); config.name = "my_package".into(); let ast = crate::analyse::ModuleAnalyzerConstructor { target: Target::Erlang, ids: &ids, origin: Origin::Src, importable_modules: &modules, warnings: &TypeWarningEmitter::null(), direct_dependencies: &direct_dependencies, dev_dependencies: &std::collections::HashSet::new(), target_support: TargetSupport::Enforced, package_config: &config, } .infer_module(ast, LineNumbers::new(src), "".into()) .expect("should successfully infer"); // TODO: all the bits above are basically copy pasted from the javascript // and erlang test helpers. A refactor might be due here. let mut module = Module { name: module_name, code: src.into(), mtime: SystemTime::UNIX_EPOCH, input_path: "wibble".into(), origin: Origin::Src, ast, extra: parsed.extra, dependencies: vec![], }; module.attach_doc_and_module_comments(); let package: Package = package_from_module(module); serde_json::to_string_pretty(&PackageInterface::from_package( &package, &Default::default(), )) .expect("to json") } fn package_from_module(module: Module) -> Package { Package { config: PackageConfig { name: "my_package".into(), version: hexpm::version::Version { major: 11, minor: 10, patch: 9, pre: vec![ Identifier::Numeric(1), Identifier::AlphaNumeric("wibble".into()), ], build: Some("build".into()), }, gleam_version: Some(GleamVersion::new("1.0.0".to_string()).unwrap()), licences: vec![], description: "description".into(), documentation: Docs { pages: vec![] }, dependencies: std::collections::HashMap::new(), dev_dependencies: std::collections::HashMap::new(), repository: None, links: vec![], erlang: ErlangConfig::default(), javascript: JavaScriptConfig::default(), target: Target::Erlang, internal_modules: Some(vec![ GlobBuilder::new("internals/*") .build() .expect("internals glob"), ]), }, cached_module_names: Vec::new(), modules: vec![module], } } #[test] pub fn package_documentation_is_included() { assert_package_interface!( " //// Some package //// documentation! pub fn main() { 1 } " ); } #[test] pub fn private_definitions_are_not_included() { assert_package_interface!( " const float = 1.1 fn main() {} type Wibble type Wob = Int " ); } #[test] pub fn internal_definitions_are_not_included() { assert_package_interface!( " @internal pub const float = 1.1 @internal pub fn main() {} @internal pub type Wibble @internal pub type Wobble = Int " ); } #[test] pub fn opaque_constructors_are_not_exposed() { assert_package_interface!("pub opaque type Wibble { Wob }") } #[test] pub fn type_aliases() { assert_package_interface!("pub type Wibble(a) = List(a)") } #[test] pub fn type_definition() { assert_package_interface!( " /// Wibble's documentation pub type Wibble(a, b) { Wibble Wobble } " ) } #[test] pub fn prelude_types() { assert_package_interface!( r#" pub const float = 1.1 pub const string = "" pub const int = 1 pub const bool = True "# ); } #[test] pub fn generic_function() { assert_package_interface!( r#" pub type Wob(a) { Wob } @deprecated("deprecation message") pub fn main() { Wob } "# ); } #[test] pub fn imported_type() { assert_package_interface!( ("other_package", "other_module", "pub type Element(a)"), r#" import other_module.{type Element} pub fn main() -> Element(Int) {} "# ); } #[test] pub fn imported_aliased_type_keeps_original_name() { assert_package_interface!( ("other_package", "other_module", "pub type Element(a)"), r#" import other_module.{type Element as Alias} as module_alias pub fn main() -> Alias(module_alias.Element(a)) {} "# ); } #[test] pub fn multiple_type_variables() { assert_package_interface!( r#" pub type Box(a, b) pub fn some_type_variables(a: a, b: b, c: Box(c, d)) -> Box(a, d) {} "# ); } #[test] pub fn type_constructors() { assert_package_interface!( r#" pub type Box(a, b) { Box(b, Int) OtherBox(message: String, a: a) } "# ); } #[test] pub fn internal_modules_are_not_exported() { assert_package_interface_with_name!("internals/internal_module", "pub fn main() { 1 }"); } #[test] pub fn labelled_function_parameters() { assert_package_interface!( r#" pub fn fold(list: List(a), from acc: b, with f: fn(a, b) -> b) -> b { todo } "# ); } #[test] pub fn constructors_with_documentation() { assert_package_interface!( r#" pub type Wibble { /// This is the Wibble variant. It contains some example data. Wibble(Int) /// This is the Wobble variant. It is a recursive type. Wobble(Wibble) } "# ); } ================================================ FILE: compiler-core/src/package_interface.rs ================================================ use std::{collections::HashMap, ops::Deref}; use ecow::EcoString; use serde::Serialize; #[cfg(test)] mod tests; use crate::{ io::ordered_map, type_::{ self, Deprecation, Opaque, Type, TypeConstructor, TypeVar, TypeVariantConstructors, ValueConstructorVariant, expression::Implementations, }, }; use crate::build::Package; /// The public interface of a package that gets serialised as a json object. #[derive(Serialize, Debug)] #[serde(rename_all = "kebab-case")] pub struct PackageInterface { name: EcoString, version: EcoString, /// The Gleam version constraint that the package specifies in its `gleam.toml`. gleam_version_constraint: Option, /// A map from module name to its interface. #[serde(serialize_with = "ordered_map")] modules: HashMap, } #[derive(Serialize, Debug)] #[serde(rename_all = "kebab-case")] pub struct ModuleInterface { /// A vector with the lines composing the module's documentation (that is /// every line preceded by a `////`). documentation: Vec, /// A map from type alias name to its interface. #[serde(serialize_with = "ordered_map")] type_aliases: HashMap, /// A map from type name to its interface. #[serde(serialize_with = "ordered_map")] types: HashMap, /// A map from constant name to its interface. #[serde(serialize_with = "ordered_map")] constants: HashMap, /// A map from function name to its interface. #[serde(serialize_with = "ordered_map")] functions: HashMap, } #[derive(Serialize, Debug)] #[serde(rename_all = "kebab-case")] pub struct TypeDefinitionInterface { /// The definition's documentation comment (that is every line preceded by /// `///`). documentation: Option, /// If the definition has a deprecation annotation `@deprecated("...")` /// this field will hold the reason of the deprecation. deprecation: Option, /// The number of type variables in the type definition. /// ```gleam /// /// This type has 2 type variables. /// type Result(a, b) { /// Ok(a) /// Error(b) /// } /// ``` parameters: usize, /// A list of the type constructors. If the type is marked as opaque it /// won't have any visible constructors. constructors: Vec, } #[derive(Serialize, Debug)] #[serde(rename_all = "kebab-case")] pub struct TypeConstructorInterface { /// The constructor's documentation comment (that is every line preceded by /// `///`). documentation: Option, /// The name of the type constructor. /// ```gleam /// pub type Box(a) { /// MyBox(value: a) /// //^^^^^ This is the constructor's name /// } /// ``` name: EcoString, /// A list of the parameters needed by the constructor. /// ```gleam /// pub type Box(a) { /// MyBox(value: a) /// // ^^^^^^^^ This is the constructor's parameter. /// } /// ``` parameters: Vec, } #[derive(Serialize, Debug)] #[serde(rename_all = "kebab-case")] pub struct TypeAliasInterface { /// The constructor's documentation comment (that is every line preceded by /// `///`). documentation: Option, /// If the alias has a deprecation annotation `@deprecated("...")` /// this field will hold the reason of the deprecation. deprecation: Option, /// The number of type variables in the type alias definition. /// ```gleam /// /// This type alias has 2 type variables. /// type Results(a, b) = List(Restul(a, b)) /// ``` parameters: usize, /// The aliased type. /// ```gleam /// type Ints = List(Int) /// // ^^^^^^^^^ This is the aliased type in a type alias. /// ``` alias: TypeInterface, } #[derive(Serialize, Debug)] #[serde(rename_all = "kebab-case")] pub struct ConstantInterface { /// The constant's documentation comment (that is every line preceded by /// `///`). documentation: Option, /// If the constant has a deprecation annotation `@deprecated("...")` /// this field will hold the reason of the deprecation. deprecation: Option, implementations: ImplementationsInterface, /// The constant's type. #[serde(rename = "type")] type_: TypeInterface, } /// A module's function. This differs from a simple `Fn` type as its arguments /// can be labelled. #[derive(Serialize, Debug)] #[serde(rename_all = "kebab-case")] pub struct FunctionInterface { /// The function's documentation comment (that is every line preceded by /// `///`). documentation: Option, /// If the constant has a deprecation annotation `@deprecated("...")` /// this field will hold the reason of the deprecation. deprecation: Option, implementations: ImplementationsInterface, parameters: Vec, #[serde(rename = "return")] return_: TypeInterface, } /// Informations about how a value is implemented. #[derive(Debug, Serialize, Copy, Clone)] #[serde(rename_all = "kebab-case")] pub struct ImplementationsInterface { /// Set to `true` if the const/function has a pure Gleam implementation /// (that is, it never uses external code). /// Being pure Gleam means that the function will support all Gleam /// targets, even future ones that are not present to this day. /// /// Consider the following function: /// /// ```gleam /// @external(erlang, "wibble", "wobble") /// pub fn a_random_number() -> Int { /// 4 /// // This is a default implementation. /// } /// ``` /// /// The implementations for this function will look like this: /// /// ```json /// { /// gleam: true, /// can_run_on_erlang: true, /// can_run_on_javascript: true, /// uses_erlang_externals: true, /// uses_javascript_externals: false, /// } /// ``` /// /// - `gleam: true` means that the function has a pure Gleam implementation /// and thus it can be used on all Gleam targets with no problems. /// - `can_run_on_erlang: false` the function can be called on the Erlang /// target. /// - `can_run_on_javascript: true` the function can be called on the JavaScript /// target. /// - `uses_erlang_externals: true` means that the function will use Erlang /// external code when compiled to the Erlang target. /// - `uses_javascript_externals: false` means that the function won't use /// JavaScript external code when compiled to JavaScript. The function can /// still be used on the JavaScript target since it has a pure Gleam /// implementation. gleam: bool, /// Set to `true` if the const/function is defined using Erlang external /// code. That means that the function will use Erlang code through FFI when /// compiled for the Erlang target. uses_erlang_externals: bool, /// Set to `true` if the const/function is defined using JavaScript external /// code. That means that the function will use JavaScript code through FFI /// when compiled for the JavaScript target. /// /// Let's have a look at an example: /// /// ```gleam /// @external(javascript, "wibble", "wobble") /// pub fn javascript_only() -> Int /// ``` /// /// It's implementations field will look like this: /// /// ```json /// { /// gleam: false, /// can_run_on_erlang: false, /// can_run_on_javascript: true, /// uses_erlang_externals: false, /// uses_javascript_externals: true, /// } /// ``` /// /// - `gleam: false` means that the function doesn't have a pure Gleam /// implementations. This means that the function is only defined using /// externals and can only be used on some targets. /// - `can_run_on_erlang: false` the function cannot be called on the Erlang /// target. /// - `can_run_on_javascript: true` the function can be called on the JavaScript /// target. /// - `uses_erlang_externals: false` the function is not using external /// Erlang code. /// - `uses_javascript_externals: true` the function is using JavaScript /// external code. uses_javascript_externals: bool, /// Whether the function can be called on the Erlang target, either due to a /// pure Gleam implementation or an implementation that uses some Erlang /// externals. can_run_on_erlang: bool, /// Whether the function can be called on the JavaScript target, either due /// to a pure Gleam implementation or an implementation that uses some /// JavaScript externals. can_run_on_javascript: bool, } impl ImplementationsInterface { fn from_implementations(implementations: &Implementations) -> ImplementationsInterface { // It might look a bit silly to just recreate an identical structure with // a different name. However, this way we won't inadvertently cause breaking // changes if we were to change the names used by the `Implementations` struct // that is used by the target tracking algorithm. // By doing this we can change the target tracking and package interface // separately! // // This pattern matching makes sure we will remember to handle any change // in the `Implementations` struct. let Implementations { gleam, uses_erlang_externals, uses_javascript_externals, can_run_on_erlang, can_run_on_javascript, } = implementations; ImplementationsInterface { gleam: *gleam, uses_erlang_externals: *uses_erlang_externals, uses_javascript_externals: *uses_javascript_externals, can_run_on_erlang: *can_run_on_erlang, can_run_on_javascript: *can_run_on_javascript, } } } #[derive(Serialize, Debug)] #[serde(rename_all = "kebab-case")] pub struct DeprecationInterface { /// The reason for the deprecation. message: EcoString, } impl DeprecationInterface { fn from_deprecation(deprecation: &Deprecation) -> Option { match deprecation { Deprecation::NotDeprecated => None, Deprecation::Deprecated { message } => Some(DeprecationInterface { message: message.clone(), }), } } } #[derive(Serialize, Debug)] #[serde(tag = "kind")] #[serde(rename_all = "kebab-case")] pub enum TypeInterface { /// A tuple type like `#(Int, Float)`. Tuple { /// The types composing the tuple. elements: Vec, }, /// A function type like `fn(Int, String) -> String`. Fn { parameters: Vec, #[serde(rename = "return")] return_: Box, }, /// A type variable. /// ```gleam /// pub fn wibble(value: a) -> a {} /// // ^ This is a type variable. /// ``` Variable { id: u64 }, /// A custom named type. /// ```gleam /// let value: Bool = True /// ^^^^ This is a named type. /// ``` /// /// All prelude types - like Bool, String, etc. - are named types as well. /// In that case their package is an empty string `""` and their module /// name is the string `"gleam"`. /// Named { name: EcoString, /// The package the type is defined in. package: EcoString, /// The module the type is defined in. module: EcoString, /// The type parameters that might be needed to define a named type. /// ```gleam /// let result: Result(Int, e) = Ok(1) /// // ^^^^^^ The `Result` named type has 2 parameters. /// // In this case it's the Int type and a type /// // variable. /// ``` parameters: Vec, }, } #[derive(Serialize, Debug)] #[serde(rename_all = "kebab-case")] pub struct ParameterInterface { /// If the parameter is labelled this will hold the label's name. /// ```gleam /// pub fn repeat(times n: Int) -> List(Int) /// // ^^^^^ This is the parameter's label. /// ``` label: Option, /// The parameter's type. /// ```gleam /// pub fn repeat(times n: Int) -> List(Int) /// // ^^^ This is the parameter's type. /// ``` #[serde(rename = "type")] type_: TypeInterface, } impl PackageInterface { pub fn from_package( package: &Package, cached_modules: &im::HashMap, ) -> PackageInterface { PackageInterface { name: package.config.name.clone(), version: package.config.version.to_string().into(), gleam_version_constraint: package .config .gleam_version .clone() .map(|version| EcoString::from(version.hex().to_string())), modules: package .modules .iter() .map(|module| &module.ast.type_info) .chain( package .cached_module_names .iter() .filter_map(|name| cached_modules.get(name)), ) .filter(|module| !package.config.is_internal_module(module.name.as_str())) .map(|module| (module.name.clone(), ModuleInterface::from_interface(module))) .collect(), } } } impl ModuleInterface { fn from_interface(interface: &type_::ModuleInterface) -> ModuleInterface { let mut types = HashMap::new(); let mut type_aliases = HashMap::new(); let mut constants = HashMap::new(); let mut functions = HashMap::new(); for (name, constructor) in interface.types.iter().filter(|(name, c)| { // Aliases are stored separately c.publicity.is_public() && !interface.type_aliases.contains_key(*name) }) { let mut id_map = IdMap::new(); let TypeConstructor { deprecation, documentation, .. } = constructor; for typed_parameter in &constructor.parameters { id_map.add_type_variable_id(typed_parameter.as_ref()); } let _ = types.insert( name.clone(), TypeDefinitionInterface { documentation: documentation.clone(), deprecation: DeprecationInterface::from_deprecation(deprecation), parameters: interface .types .get(&name.clone()) .map_or(vec![], |t| t.parameters.clone()) .len(), constructors: match interface.types_value_constructors.get(&name.clone()) { Some(TypeVariantConstructors { variants, opaque: Opaque::NotOpaque, .. }) => variants .iter() .map(|constructor| TypeConstructorInterface { documentation: constructor.documentation.clone(), name: constructor.name.clone(), parameters: constructor .parameters .iter() .map(|arg| ParameterInterface { label: arg.label.clone(), // We share the same id_map between each step so that the // incremental ids assigned are consisten with each other type_: from_type_helper(arg.type_.as_ref(), &mut id_map), }) .collect(), }) .collect(), Some(_) | None => Vec::new(), }, }, ); } for (name, alias) in interface .type_aliases .iter() .filter(|(_, v)| v.publicity.is_public()) { let _ = type_aliases.insert( name.clone(), TypeAliasInterface { documentation: alias.documentation.clone(), deprecation: DeprecationInterface::from_deprecation(&alias.deprecation), parameters: alias.arity, alias: TypeInterface::from_type(&alias.type_), }, ); } for (name, value) in interface .values .iter() .filter(|(_, v)| v.publicity.is_public()) { match (value.type_.as_ref(), value.variant.clone()) { ( Type::Fn { arguments, return_: return_type, }, ValueConstructorVariant::ModuleFn { documentation, implementations, field_map, .. }, ) => { let mut id_map = IdMap::new(); let reverse_field_map = field_map .as_ref() .map(|field_map| field_map.indices_to_labels()) .unwrap_or_default(); let _ = functions.insert( name.clone(), FunctionInterface { implementations: ImplementationsInterface::from_implementations( &implementations, ), deprecation: DeprecationInterface::from_deprecation(&value.deprecation), documentation, parameters: arguments .iter() .enumerate() .map(|(index, type_)| ParameterInterface { label: reverse_field_map .get(&(index as u32)) .map(|label| (*label).clone()), type_: from_type_helper(type_, &mut id_map), }) .collect(), return_: from_type_helper(return_type, &mut id_map), }, ); } ( type_, ValueConstructorVariant::ModuleConstant { documentation, implementations, .. }, ) => { let _ = constants.insert( name.clone(), ConstantInterface { implementations: ImplementationsInterface::from_implementations( &implementations, ), type_: TypeInterface::from_type(type_), deprecation: DeprecationInterface::from_deprecation(&value.deprecation), documentation, }, ); } _ => {} } } ModuleInterface { documentation: interface.documentation.clone(), types, type_aliases, constants, functions, } } } impl TypeInterface { fn from_type(type_: &Type) -> TypeInterface { from_type_helper(type_, &mut IdMap::new()) } } /// Turns a type into its interface, an `IdMap` is needed to make sure that all /// the type variables' ids that appear in the type are mapped to an incremental /// number and consistent with each other (that is, two types variables that /// have the same id will also have the same incremental number in the end). fn from_type_helper(type_: &Type, id_map: &mut IdMap) -> TypeInterface { match type_ { Type::Fn { arguments, return_ } => TypeInterface::Fn { parameters: arguments .iter() .map(|argument| from_type_helper(argument.as_ref(), id_map)) .collect(), return_: Box::new(from_type_helper(return_, id_map)), }, Type::Tuple { elements } => TypeInterface::Tuple { elements: elements .iter() .map(|element| from_type_helper(element.as_ref(), id_map)) .collect(), }, Type::Var { type_ } => match type_ .as_ref() .try_borrow() .expect("borrow type after inference") .deref() { TypeVar::Link { type_ } => from_type_helper(type_, id_map), // Since package serialisation happens after inference there // should be no unbound type variables. // TODO: This branch should be `unreachable!()` but because of // https://github.com/gleam-lang/gleam/issues/2533 // we sometimes end up with those in top level // definitions. // However, `Unbound` and `Generic` ids are generated // using the same generator so we have no problem treating // unbound variables as generic ones since ids will never // overlap. // Once #2533 is closed this branch can be turned back to // be unreachable!(). TypeVar::Unbound { id } | TypeVar::Generic { id } => TypeInterface::Variable { id: id_map.map_id(*id), }, }, Type::Named { name, module, arguments, package, .. } => TypeInterface::Named { name: name.clone(), package: package.clone(), module: module.clone(), parameters: arguments .iter() .map(|argument| from_type_helper(argument.as_ref(), id_map)) .collect(), }, } } /// This is a map that is used to map type variable id's to progressive numbers /// starting from 0. /// After type inference the ids associated with type variables can be quite /// high and are not the best to produce a human/machine readable output. /// /// Imagine a function like this one: `pub fn wibble(item: a, rest: b) -> c` /// What we want here is for type variables to have increasing ids starting from /// 0: `a` with id `0`, `b` with id `1` and `c` with id `2`. /// /// This map allows us to keep track of the ids we've run into and map those to /// their incremental counterpart starting from 0. struct IdMap { next_id: u64, ids: HashMap, } impl IdMap { /// Create a new map that will assign id numbers starting from 0. fn new() -> IdMap { IdMap { next_id: 0, ids: HashMap::new(), } } /// Map an id to its mapped counterpart starting from 0. If an id has never /// been seen before it will be assigned a new incremental number. fn map_id(&mut self, id: u64) -> u64 { match self.ids.get(&id) { Some(mapped_id) => *mapped_id, None => { let mapped_id = self.next_id; let _ = self.ids.insert(id, mapped_id); self.next_id += 1; mapped_id } } } /// If the type is a type variable, and has not been seen before, it will /// be assigned to a new incremental number. fn add_type_variable_id(&mut self, type_: &Type) { match type_ { // These types have no id to add to the map. Type::Named { .. } | Type::Fn { .. } | Type::Tuple { .. } => (), // If the type is actually a type variable whose id needs to be mapped. Type::Var { type_ } => match type_ .as_ref() .try_borrow() .expect("borrow type after inference") .deref() { TypeVar::Link { .. } => (), TypeVar::Unbound { id } | TypeVar::Generic { id } => { let _ = self.map_id(*id); } }, } } } ================================================ FILE: compiler-core/src/parse/error.rs ================================================ use crate::ast::{SrcSpan, TypeAst}; use crate::diagnostic::{ExtraLabel, Label}; use crate::error::wrap; use crate::parse::Token; use ecow::EcoString; use itertools::Itertools; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct LexicalError { pub error: LexicalErrorType, pub location: SrcSpan, } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum InvalidUnicodeEscapeError { MissingOpeningBrace, // Expected '{' ExpectedHexDigitOrCloseBrace, // Expected hex digit or '}' InvalidNumberOfHexDigits, // Expected between 1 and 6 hex digits InvalidCodepoint, // Invalid Unicode codepoint } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum LexicalErrorType { BadStringEscape, // string contains an unescaped slash InvalidUnicodeEscape(InvalidUnicodeEscapeError), // \u{...} escape sequence is invalid DigitOutOfRadix, // 0x012 , 2 is out of radix NumTrailingUnderscore, // 1_000_ is not allowed RadixIntNoValue, // 0x, 0b, 0o without a value MissingExponent, // 1.0e, for example, where there is no exponent UnexpectedStringEnd, // Unterminated string literal UnrecognizedToken { tok: char }, InvalidTripleEqual, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct ParseError { pub error: ParseErrorType, pub location: SrcSpan, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ParseErrorType { ExpectedEqual, // expect "=" ExpectedExpr, // after "->" in a case clause ExpectedName, // any token used when a Name was expected ExpectedPattern, // after ':' where a pattern is expected ExpectedType, // after ':' or '->' where a type annotation is expected ExpectedUpName, // any token used when a UpName was expected ExpectedValue, // no value after "=" ExpectedDefinition, // after attributes ExpectedDeprecationMessage, // after "deprecated" ExpectedFunctionDefinition, // after function-only attributes ExpectedTargetName, // after "@target(" ExprLparStart, // it seems "(" was used to start an expression ExtraSeparator, // #(1,,) <- the 2nd comma is an extra separator IncorrectName, // UpName or DiscardName used when Name was expected IncorrectUpName, // Name or DiscardName used when UpName was expected InvalidBitArraySegment, // <<7:hello>> `hello` is an invalid BitArray segment InvalidBitArrayUnit, // in <<1:unit(x)>> x must be 1 <= x <= 256 InvalidTailPattern, // only name and _name are allowed after ".." in list pattern InvalidTupleAccess, // only positive int literals for tuple access LexError { error: LexicalError, }, NestedBitArrayPattern, // <<<<1>>, 2>>, <<1>> is not allowed in there NoLetBinding, // Bindings and rebinds always require let and must always bind to a value. NoValueAfterEqual, // = NotConstType, // :fn(), name, _ are not valid const types OpNakedRight, // Operator with no value to the right OpaqueTypeAlias, // Type aliases cannot be opaque TooManyArgHoles, // a function call can have at most 1 arg hole DuplicateAttribute, // an attribute was used more than once UnknownAttribute, // an attribute was used that is not known UnknownTarget, // an unknown target was used ListSpreadWithoutElements, // Pointless spread: `[..xs]` ListSpreadFollowedByElements, // trying to append something after the spread: `[..xs, x]` ListSpreadWithAnotherSpread { first_spread_location: SrcSpan, }, // trying to use multiple spreads: `[..xs, ..ys]` UnexpectedLabel, // argument labels were provided, but are not supported in this context UnexpectedEof, UnexpectedReservedWord, // reserved word used when a name was expected UnexpectedToken { token: Token, expected: Vec, hint: Option, }, UnexpectedFunction, // a function was used called outside of another function // A variable was assigned or discarded on the left hand side of a <> pattern ConcatPatternVariableLeftHandSide, ListSpreadWithoutTail, // let x = [1, ..] ExpectedFunctionBody, // let x = fn() RedundantInternalAttribute, // for a private definition marked as internal InvalidModuleTypePattern, // for patterns that have a dot like: `name.thing` ListPatternSpreadFollowedByElements, // When there is a pattern after a spread [..rest, pattern] ExpectedRecordConstructor { name: EcoString, public: bool, opaque: bool, field: EcoString, field_type: Option>, }, CallInClauseGuard, // case x { _ if f() -> 1 } IfExpression, ConstantRecordConstructorNoArguments, // const x = Record() TypeDefinitionNoArguments, // pub type Wibble() { ... } UnknownAttributeRecordVariant, // an attribute was used that is not know for a custom type variant // a Python-like import was written, such as `import gleam.io`, instead of `import gleam/io` IncorrectImportModuleSeparator { module: EcoString, item: EcoString, }, /// This can happen when there's an empty block in a case clause guard. /// For example: `_ if a == {}` EmptyGuardBlock, // When the use tries to define a constant inside a function ConstantInsideFunction, FunctionDefinitionAngleGenerics, // fn something() { ... } // let a: List = [] TypeUsageAngleGenerics { module: Option, name: EcoString, arguments: Vec, }, // type Something { TypeDefinitionAngleGenerics { name: EcoString, arguments: Vec, }, } pub(crate) struct ParseErrorDetails { pub text: String, pub label_text: EcoString, pub extra_labels: Vec, pub hint: Option, } impl ParseErrorType { pub(crate) fn details(&self) -> ParseErrorDetails { match self { ParseErrorType::ExpectedEqual => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting a '=' after this".into(), extra_labels: vec![], }, ParseErrorType::ExpectedExpr => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting an expression after this".into(), extra_labels: vec![], }, ParseErrorType::ExpectedName => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting a name here".into(), extra_labels: vec![], }, ParseErrorType::ExpectedPattern => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting a pattern after this".into(), extra_labels: vec![], }, ParseErrorType::ExpectedType => ParseErrorDetails { text: "See: https://tour.gleam.run/basics/assignments/".into(), hint: None, label_text: "I was expecting a type after this".into(), extra_labels: vec![], }, ParseErrorType::ExpectedUpName => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting a type name here".into(), extra_labels: vec![], }, ParseErrorType::ExpectedValue => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting a value after this".into(), extra_labels: vec![], }, ParseErrorType::ExpectedDefinition => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting a definition after this".into(), extra_labels: vec![], }, ParseErrorType::ExpectedDeprecationMessage => ParseErrorDetails { text: "".into(), hint: None, label_text: "A deprecation attribute must have a string message.".into(), extra_labels: vec![], }, ParseErrorType::ExpectedFunctionDefinition => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting a function definition after this".into(), extra_labels: vec![], }, ParseErrorType::ExpectedTargetName => ParseErrorDetails { text: "Try `erlang`, `javascript`.".into(), hint: None, label_text: "I was expecting a target name after this".into(), extra_labels: vec![], }, ParseErrorType::ExtraSeparator => ParseErrorDetails { text: "".into(), hint: Some("Try removing it?".into()), label_text: "This is an extra delimiter".into(), extra_labels: vec![], }, ParseErrorType::ExprLparStart => ParseErrorDetails { text: "".into(), hint: Some( "To group expressions in Gleam, use \"{\" and \"}\"; \ tuples are created with `#(` and `)`." .into(), ), label_text: "This parenthesis cannot be understood here".into(), extra_labels: vec![], }, ParseErrorType::IncorrectName => ParseErrorDetails { text: "".into(), hint: Some(wrap( "Variable and module names start with a lowercase letter, \ and can contain a-z, 0-9, or _.", )), label_text: "I'm expecting a lowercase name here".into(), extra_labels: vec![], }, ParseErrorType::IncorrectUpName => ParseErrorDetails { text: "".into(), hint: Some(wrap( "Type names start with a uppercase letter, and can \ contain a-z, A-Z, or 0-9.", )), label_text: "I'm expecting a type name here".into(), extra_labels: vec![], }, ParseErrorType::InvalidBitArraySegment => ParseErrorDetails { text: "See: https://tour.gleam.run/data-types/bit-arrays/".into(), hint: Some(format!( "Valid BitArray segment options are:\n{}", wrap( "bits, bytes, int, float, utf8, utf16, utf32, utf8_codepoint, \ utf16_codepoint, utf32_codepoint, signed, unsigned, big, little, native, size, unit.", ) )), label_text: "This is not a valid BitArray segment option".into(), extra_labels: vec![], }, ParseErrorType::InvalidBitArrayUnit => ParseErrorDetails { text: "See: https://tour.gleam.run/data-types/bit-arrays/".into(), hint: Some("Unit must be an integer literal >= 1 and <= 256.".into()), label_text: "This is not a valid BitArray unit value".into(), extra_labels: vec![], }, ParseErrorType::InvalidTailPattern => ParseErrorDetails { text: "".into(), hint: None, label_text: "This part of a list pattern can only be a name or a discard".into(), extra_labels: vec![], }, ParseErrorType::InvalidTupleAccess => ParseErrorDetails { text: "".into(), hint: Some( "Only non negative integer literals like 0, or 1_000 can be used.".into(), ), label_text: "This integer is not valid for tuple access".into(), extra_labels: vec![], }, ParseErrorType::LexError { error: lex_err } => { let (label_text, text_lines) = lex_err.to_parse_error_info(); let text = text_lines.join("\n"); ParseErrorDetails { text, hint: None, label_text: label_text.into(), extra_labels: vec![], } } ParseErrorType::NestedBitArrayPattern => ParseErrorDetails { text: "".into(), hint: None, label_text: "BitArray patterns cannot be nested".into(), extra_labels: vec![], }, ParseErrorType::NotConstType => ParseErrorDetails { text: "See: https://tour.gleam.run/basics/constants/".into(), hint: None, label_text: "This type is not allowed in module constants".into(), extra_labels: vec![], }, ParseErrorType::NoLetBinding => ParseErrorDetails { text: "See: https://tour.gleam.run/basics/assignments/".into(), hint: Some("Use let for binding.".into()), label_text: "There must be a 'let' to bind variable to value".into(), extra_labels: vec![], }, ParseErrorType::NoValueAfterEqual => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting to see a value after this equals sign".into(), extra_labels: vec![], }, ParseErrorType::OpaqueTypeAlias => ParseErrorDetails { text: "See: https://tour.gleam.run/basics/type-aliases/".into(), hint: None, label_text: "Type Aliases cannot be opaque".into(), extra_labels: vec![], }, ParseErrorType::OpNakedRight => ParseErrorDetails { text: "".into(), hint: Some("Remove it or put a value after it.".into()), label_text: "This operator has no value on its right side".into(), extra_labels: vec![], }, ParseErrorType::TooManyArgHoles => ParseErrorDetails { text: "See: https://tour.gleam.run/functions/functions/".into(), hint: Some("Function calls can have at most one argument hole.".into()), label_text: "There is more than 1 argument hole in this function call".into(), extra_labels: vec![], }, ParseErrorType::UnexpectedEof => ParseErrorDetails { text: "".into(), hint: None, label_text: "The module ended unexpectedly".into(), extra_labels: vec![], }, ParseErrorType::ListSpreadWithoutElements => ParseErrorDetails { text: "See: https://tour.gleam.run/basics/lists/".into(), hint: Some("Try prepending some elements [1, 2, ..list].".into()), label_text: "This spread does nothing".into(), extra_labels: vec![], }, ParseErrorType::ListSpreadWithAnotherSpread { first_spread_location, } => ParseErrorDetails { text: [ "Lists are immutable and singly-linked, so to join two or more lists", "all the elements of the lists would need to be copied into a new list.", "This would be slow, so there is no built-in syntax for it.", ] .join("\n"), hint: None, label_text: "I wasn't expecting a second list here".into(), extra_labels: vec![ExtraLabel { src_info: None, label: Label { text: Some("You're using a list here".into()), span: *first_spread_location, }, }], }, ParseErrorType::ListSpreadFollowedByElements => ParseErrorDetails { text: [ "Lists are immutable and singly-linked, so to append items to them", "all the elements of a list would need to be copied into a new list.", "This would be slow, so there is no built-in syntax for it.", "", ] .join("\n"), hint: Some( "Prepend items to the list and then reverse it once you are done.".into(), ), label_text: "I wasn't expecting elements after this".into(), extra_labels: vec![], }, ParseErrorType::ListPatternSpreadFollowedByElements => ParseErrorDetails { text: [ "Lists are immutable and singly-linked, so to match on the end", "of a list would require the whole list to be traversed. This", "would be slow, so there is no built-in syntax for it. Pattern", "match on the start of the list instead.", ] .join("\n"), hint: None, label_text: "I wasn't expecting elements after this".into(), extra_labels: vec![], }, ParseErrorType::UnexpectedReservedWord => ParseErrorDetails { text: "".into(), hint: Some("I was expecting to see a name here.".into()), label_text: "This is a reserved word".into(), extra_labels: vec![], }, ParseErrorType::UnexpectedLabel => ParseErrorDetails { text: "Please remove the argument label.".into(), hint: None, label_text: "Argument labels are not allowed for anonymous functions".into(), extra_labels: vec![], }, ParseErrorType::UnexpectedToken { token, expected, hint, } => { let found = match token { Token::Int { .. } => "an Int".to_string(), Token::Float { .. } => "a Float".to_string(), Token::String { .. } => "a String".to_string(), Token::CommentDoc { .. } => "a comment".to_string(), Token::DiscardName { .. } => "a discard name".to_string(), Token::Name { .. } | Token::UpName { .. } => "a name".to_string(), _ if token.is_reserved_word() => format!("the keyword {token}"), Token::LeftParen | Token::RightParen | Token::LeftSquare | Token::RightSquare | Token::LeftBrace | Token::RightBrace | Token::Plus | Token::Minus | Token::Star | Token::Slash | Token::Less | Token::Greater | Token::LessEqual | Token::GreaterEqual | Token::Percent | Token::PlusDot | Token::MinusDot | Token::StarDot | Token::SlashDot | Token::LessDot | Token::GreaterDot | Token::LessEqualDot | Token::GreaterEqualDot | Token::Concatenate | Token::Colon | Token::Comma | Token::Hash | Token::Bang | Token::Equal | Token::EqualEqual | Token::NotEqual | Token::Vbar | Token::VbarVbar | Token::AmperAmper | Token::LtLt | Token::GtGt | Token::Pipe | Token::Dot | Token::RArrow | Token::LArrow | Token::DotDot | Token::At | Token::EndOfFile | Token::CommentNormal | Token::CommentModule | Token::NewLine | Token::As | Token::Assert | Token::Auto | Token::Case | Token::Const | Token::Delegate | Token::Derive | Token::Echo | Token::Else | Token::Fn | Token::If | Token::Implement | Token::Import | Token::Let | Token::Macro | Token::Opaque | Token::Panic | Token::Pub | Token::Test | Token::Todo | Token::Type | Token::Use => token.to_string(), }; let messages = std::iter::once(format!("Found {found}, expected one of: ")) .chain(expected.iter().map(|s| format!("- {s}"))); let messages = match hint { Some(hint_text) => messages .chain(std::iter::once(format!("Hint: {hint_text}"))) .collect_vec(), _ => messages.collect(), }; ParseErrorDetails { text: messages.join("\n"), hint: None, label_text: "I was not expecting this".into(), extra_labels: vec![], } } ParseErrorType::ConcatPatternVariableLeftHandSide => ParseErrorDetails { text: [ "We can't tell what size this prefix should be so we don't know", "how to handle this pattern.", "", "If you want to match one character consider using `pop_grapheme`", "from the stdlib's `gleam/string` module.", ] .join("\n"), hint: None, label_text: "This must be a string literal".into(), extra_labels: vec![], }, ParseErrorType::UnexpectedFunction => ParseErrorDetails { text: "".into(), hint: None, label_text: "Functions can only be called within other functions".into(), extra_labels: vec![], }, ParseErrorType::ListSpreadWithoutTail => ParseErrorDetails { text: "If a list expression has a spread then a tail must also be given.".into(), hint: None, label_text: "I was expecting a value after this spread".into(), extra_labels: vec![], }, ParseErrorType::UnknownAttribute => ParseErrorDetails { text: "".into(), hint: Some("Try `deprecated`, `external` or `internal` instead.".into()), label_text: "I don't recognise this attribute".into(), extra_labels: vec![], }, ParseErrorType::DuplicateAttribute => ParseErrorDetails { text: "This attribute has already been given.".into(), hint: None, label_text: "Duplicate attribute".into(), extra_labels: vec![], }, ParseErrorType::UnknownTarget => ParseErrorDetails { text: "Try `erlang`, `javascript`.".into(), hint: None, label_text: "I don't recognise this target".into(), extra_labels: vec![], }, ParseErrorType::ExpectedFunctionBody => ParseErrorDetails { text: "".into(), hint: None, label_text: "This function does not have a body".into(), extra_labels: vec![], }, ParseErrorType::RedundantInternalAttribute => ParseErrorDetails { text: "Only a public definition can be annotated as internal.".into(), hint: Some("Remove the `@internal` annotation.".into()), label_text: "Redundant internal attribute".into(), extra_labels: vec![], }, ParseErrorType::InvalidModuleTypePattern => ParseErrorDetails { text: [ "I'm expecting a pattern here", "Hint: A pattern can be a constructor name, a literal value", "or a variable to bind a value to, etc.", "See: https://tour.gleam.run/flow-control/case-expressions/", ] .join("\n"), hint: None, label_text: "Invalid pattern".into(), extra_labels: vec![], }, ParseErrorType::ExpectedRecordConstructor { name, public, opaque, field, field_type, } => { let (accessor, opaque) = match *public { true if *opaque => ("pub ", "opaque "), true => ("pub ", ""), false => ("", ""), }; let mut annotation = EcoString::new(); match field_type { Some(t) => t.print(&mut annotation), None => annotation.push_str("Type"), }; ParseErrorDetails { text: [ "Each custom type variant must have a constructor:\n".into(), format!("{accessor}{opaque}type {name} {{"), format!(" {name}("), format!(" {field}: {annotation},"), " )".into(), "}".into(), ] .join("\n"), hint: None, label_text: "I was not expecting this".into(), extra_labels: vec![], } } ParseErrorType::CallInClauseGuard => ParseErrorDetails { text: "Functions cannot be called in clause guards.".into(), hint: None, label_text: "Unsupported expression".into(), extra_labels: vec![], }, ParseErrorType::IfExpression => ParseErrorDetails { text: [ "If you want to write a conditional expression you can use a `case`:", "", " case condition {", " True -> todo", " False -> todo", " }", "", "See: https://tour.gleam.run/flow-control/case-expressions/", ] .join("\n"), hint: None, label_text: "Gleam doesn't have if expressions".into(), extra_labels: vec![], }, ParseErrorType::ConstantRecordConstructorNoArguments => ParseErrorDetails { text: "A record must be passed arguments when constructed.".into(), hint: None, label_text: "I was expecting arguments here".into(), extra_labels: vec![], }, ParseErrorType::TypeDefinitionNoArguments => ParseErrorDetails { text: "A generic type must have at least a generic parameter.".into(), hint: Some("If a type is not generic you should omit the `()`.".into()), label_text: "I was expecting generic parameters here".into(), extra_labels: vec![], }, ParseErrorType::UnknownAttributeRecordVariant => ParseErrorDetails { text: "".into(), hint: Some("Did you mean `@deprecated`?".into()), label_text: "This attribute cannot be used on a variant.".into(), extra_labels: vec![], }, ParseErrorType::IncorrectImportModuleSeparator { module, item } => ParseErrorDetails { text: [ "Perhaps you meant one of:".into(), "".into(), format!(" import {module}/{item}"), format!(" import {module}.{{item}}"), ] .join("\n"), hint: None, label_text: "I was expecting either `/` or `.{` here.".into(), extra_labels: vec![], }, ParseErrorType::EmptyGuardBlock => ParseErrorDetails { text: "".into(), hint: None, label_text: "A clause guard block cannot be empty".into(), extra_labels: vec![], }, ParseErrorType::ConstantInsideFunction => ParseErrorDetails { text: wrap( "All variables are immutable in Gleam, so constants inside \ functions are not necessary.", ), hint: Some( "Either move this into the global scope or use `let` binding instead.".into(), ), label_text: "Constants are not allowed inside functions".into(), extra_labels: vec![], }, ParseErrorType::FunctionDefinitionAngleGenerics => ParseErrorDetails { text: "\ Generic function type variables do not need to be predeclared like they would be in some other languages, instead they are written with lowercase names. fn example(argument: generic) -> generic See: https://tour.gleam.run/functions/generic-functions/" .into(), hint: None, label_text: "I was expecting `(` here.".into(), extra_labels: vec![], }, ParseErrorType::TypeUsageAngleGenerics { module, name, arguments, } => { let type_arguments = arguments .iter() .map(|argument| { let mut argument_string = EcoString::new(); argument.print(&mut argument_string); argument_string }) .join(", "); let replacement_type = match module { Some(module) => format!("{module}.{name}({type_arguments})"), None => format!("{name}({type_arguments})"), }; ParseErrorDetails { text: format!( "\ Type parameters use lowercase names and are surrounded by parentheses. {replacement_type} See: https://tour.gleam.run/data-types/generic-custom-types/" ), hint: None, label_text: "I was expecting `(` here.".into(), extra_labels: vec![], } } ParseErrorType::TypeDefinitionAngleGenerics { name, arguments } => { let comma_separated_arguments = arguments.join(", "); ParseErrorDetails { text: format!( "\ Type parameters use lowercase names and are surrounded by parentheses. type {name}({comma_separated_arguments}) {{ See: https://tour.gleam.run/data-types/generic-custom-types/" ), hint: None, label_text: "I was expecting `(` here.".into(), extra_labels: vec![], } } } } } impl LexicalError { pub fn to_parse_error_info(&self) -> (&'static str, Vec) { match &self.error { LexicalErrorType::BadStringEscape => ( "I don't understand this escape code", vec![ "Hint: Add another backslash before it.".into(), "See: https://tour.gleam.run/basics/strings".into(), ], ), LexicalErrorType::DigitOutOfRadix => { ("This digit is too big for the specified radix", vec![]) } LexicalErrorType::NumTrailingUnderscore => ( "Numbers cannot have a trailing underscore", vec!["Hint: remove it.".into()], ), LexicalErrorType::RadixIntNoValue => ("This integer has no value", vec![]), LexicalErrorType::MissingExponent => ( "This float is missing an exponent", vec!["Hint: Add an exponent or remove the trailing `e`".into()], ), LexicalErrorType::UnexpectedStringEnd => { ("The string starting here was left open", vec![]) } LexicalErrorType::UnrecognizedToken { tok } if *tok == ';' => ( "Remove this semicolon", vec![ "Hint: Semicolons used to be whitespace and did nothing.".into(), "You can safely remove them without your program changing.".into(), ], ), LexicalErrorType::UnrecognizedToken { tok } if *tok == '\'' => ( "Unexpected single quote", vec!["Hint: Strings are written with double quotes.".into()], ), LexicalErrorType::UnrecognizedToken { .. } => ( "I can't figure out what to do with this character", vec!["Hint: Is it a typo?".into()], ), LexicalErrorType::InvalidUnicodeEscape( InvalidUnicodeEscapeError::MissingOpeningBrace, ) => ( "Expected '{' in Unicode escape sequence", vec!["Hint: Add it.".into()], ), LexicalErrorType::InvalidUnicodeEscape( InvalidUnicodeEscapeError::ExpectedHexDigitOrCloseBrace, ) => ( "Expected hex digit or '}' in Unicode escape sequence", vec![ "Hint: Hex digits are digits from 0 to 9 and letters from a to f or A to F." .into(), ], ), LexicalErrorType::InvalidUnicodeEscape( InvalidUnicodeEscapeError::InvalidNumberOfHexDigits, ) => ( "Expected between 1 and 6 hex digits in Unicode escape sequence", vec![], ), LexicalErrorType::InvalidUnicodeEscape(InvalidUnicodeEscapeError::InvalidCodepoint) => { ("Invalid Unicode codepoint", vec![]) } LexicalErrorType::InvalidTripleEqual => ( "Did you mean `==`?", vec![ "Gleam uses `==` to check for equality between two values.".into(), "See: https://tour.gleam.run/basics/equality".into(), ], ), } } } ================================================ FILE: compiler-core/src/parse/extra.rs ================================================ use std::cmp::Ordering; use ecow::EcoString; use crate::ast::SrcSpan; #[derive(Debug, PartialEq, Eq, Default)] pub struct ModuleExtra { pub module_comments: Vec, pub doc_comments: Vec, pub comments: Vec, pub empty_lines: Vec, pub new_lines: Vec, pub trailing_commas: Vec, } impl ModuleExtra { pub fn new() -> Self { Default::default() } /// Detects if a byte index is in a comment context pub fn is_within_comment(&self, byte_index: u32) -> bool { let cmp = |span: &SrcSpan| { if byte_index < span.start { Ordering::Greater } else if byte_index > span.end { Ordering::Less } else { Ordering::Equal } }; self.comments.binary_search_by(cmp).is_ok() || self.doc_comments.binary_search_by(cmp).is_ok() || self.module_comments.binary_search_by(cmp).is_ok() } pub(crate) fn has_comment_between(&self, start: u32, end: u32) -> bool { self.first_comment_between(start, end).is_some() } pub fn first_comment_between(&self, start: u32, end: u32) -> Option { self.comments .binary_search_by(|comment| { if comment.end < start { Ordering::Less } else if comment.start > end { Ordering::Greater } else { Ordering::Equal } }) .ok() .and_then(|index| self.comments.get(index).copied()) } } #[derive(Debug, PartialEq, Eq)] pub struct Comment<'a> { pub start: u32, pub content: &'a str, } impl<'a> From<(&SrcSpan, &'a EcoString)> for Comment<'a> { fn from(value: (&SrcSpan, &'a EcoString)) -> Self { Self::from((value.0, value.1.as_str())) } } impl<'a> From<(&SrcSpan, &'a str)> for Comment<'a> { fn from(src: (&SrcSpan, &'a str)) -> Comment<'a> { let start = src.0.start; let end = src.0.end as usize; Comment { start, content: src .1 .get(start as usize..end) .expect("From span to comment"), } } } ================================================ FILE: compiler-core/src/parse/lexer.rs ================================================ use ecow::EcoString; use crate::ast::SrcSpan; use crate::parse::LiteralFloatValue; use crate::parse::error::{LexicalError, LexicalErrorType}; use crate::parse::token::Token; use std::char; use super::error::InvalidUnicodeEscapeError; #[derive(Debug)] pub struct Lexer> { chars: T, pending: Vec, chr0: Option, chr1: Option, loc0: u32, loc1: u32, } pub type Spanned = (u32, Token, u32); pub type LexResult = Result; pub fn str_to_keyword(word: &str) -> Option { // Alphabetical keywords: match word { "as" => Some(Token::As), "assert" => Some(Token::Assert), "auto" => Some(Token::Auto), "case" => Some(Token::Case), "const" => Some(Token::Const), "delegate" => Some(Token::Delegate), "derive" => Some(Token::Derive), "echo" => Some(Token::Echo), "else" => Some(Token::Else), "fn" => Some(Token::Fn), "if" => Some(Token::If), "implement" => Some(Token::Implement), "import" => Some(Token::Import), "let" => Some(Token::Let), "macro" => Some(Token::Macro), "opaque" => Some(Token::Opaque), "panic" => Some(Token::Panic), "pub" => Some(Token::Pub), "test" => Some(Token::Test), "todo" => Some(Token::Todo), "type" => Some(Token::Type), "use" => Some(Token::Use), _ => None, } } pub fn make_tokenizer(source: &str) -> impl Iterator + '_ { let chars = source.char_indices().map(|(i, c)| (i as u32, c)); let nlh = NewlineHandler::new(chars); Lexer::new(nlh) } // The newline handler is an iterator which collapses different newline // types into \n always. #[derive(Debug)] pub struct NewlineHandler> { source: T, chr0: Option<(u32, char)>, chr1: Option<(u32, char)>, } impl NewlineHandler where T: Iterator, { pub fn new(source: T) -> Self { let mut nlh = NewlineHandler { source, chr0: None, chr1: None, }; let _ = nlh.shift(); let _ = nlh.shift(); nlh } fn shift(&mut self) -> Option<(u32, char)> { let result = self.chr0; self.chr0 = self.chr1; self.chr1 = self.source.next(); result } } impl Iterator for NewlineHandler where T: Iterator, { type Item = (u32, char); fn next(&mut self) -> Option { // Collapse \r\n into \n if let Some((i, '\r')) = self.chr0 { if let Some((_, '\n')) = self.chr1 { // Transform windows EOL into \n let _ = self.shift(); // using the position from the \r self.chr0 = Some((i, '\n')) } else { // Transform MAC EOL into \n self.chr0 = Some((i, '\n')) } } self.shift() } } impl Lexer where T: Iterator, { pub fn new(input: T) -> Self { let mut lxr = Lexer { chars: input, pending: Vec::new(), chr0: None, chr1: None, loc0: 0, loc1: 0, }; let _ = lxr.next_char(); let _ = lxr.next_char(); // Check whether the first character is a UTF-8 byte order mark, and if so, consume it. if lxr.chr0 == Some('\u{feff}') { let _ = lxr.next_char(); } lxr } // This is the main entry point. Call this function to retrieve the next token. // This function is used by the iterator implementation. fn inner_next(&mut self) -> LexResult { // top loop, keep on processing, until we have something pending. while self.pending.is_empty() { self.consume_normal()?; } Ok(self.pending.remove(0)) } // Take a look at the next character, if any, and decide upon the next steps. fn consume_normal(&mut self) -> Result<(), LexicalError> { // Check if we have some character: if let Some(c) = self.chr0 { let mut check_for_minus = false; if self.is_upname_start(c) { let name = self.lex_upname()?; self.emit(name) } else if self.is_name_start(c) { check_for_minus = true; let name = self.lex_name()?; self.emit(name); } else if self.is_number_start(c, self.chr1) { check_for_minus = true; let num = self.lex_number()?; self.emit(num); } else { self.consume_character(c)?; } if check_for_minus { // We want to lex `1-1` and `x-1` as `1 - 1` and `x - 1` if Some('-') == self.chr0 && self.is_number_start('-', self.chr1) { self.eat_single_char(Token::Minus); } } } else { // We reached end of file. let tok_pos = self.get_pos(); self.emit((tok_pos, Token::EndOfFile, tok_pos)); } Ok(()) } fn consume_character(&mut self, c: char) -> Result<(), LexicalError> { match c { '@' => { self.eat_single_char(Token::At); } '"' => { let string = self.lex_string()?; self.emit(string); } '=' => { let tok_start = self.get_pos(); let _ = self.next_char(); match self.chr0 { Some('=') => { let _ = self.next_char(); let tok_end = self.get_pos(); if let Some('=') = self.chr0 { return Err(LexicalError { error: LexicalErrorType::InvalidTripleEqual, location: SrcSpan { start: tok_start, end: tok_end + 1, }, }); }; self.emit((tok_start, Token::EqualEqual, tok_end)); } _ => { let tok_end = self.get_pos(); self.emit((tok_start, Token::Equal, tok_end)); } } } '+' => { let tok_start = self.get_pos(); let _ = self.next_char(); if let Some('.') = self.chr0 { let _ = self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Token::PlusDot, tok_end)); } else { let tok_end = self.get_pos(); self.emit((tok_start, Token::Plus, tok_end)); } } '*' => { let tok_start = self.get_pos(); let _ = self.next_char(); match self.chr0 { Some('.') => { let _ = self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Token::StarDot, tok_end)); } _ => { let tok_end = self.get_pos(); self.emit((tok_start, Token::Star, tok_end)); } } } '/' => { let tok_start = self.get_pos(); let _ = self.next_char(); match self.chr0 { Some('.') => { let _ = self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Token::SlashDot, tok_end)); } Some('/') => { let _ = self.next_char(); let comment = self.lex_comment(); self.emit(comment); } _ => { let tok_end = self.get_pos(); self.emit((tok_start, Token::Slash, tok_end)); } } } '%' => { self.eat_single_char(Token::Percent); } '|' => { let tok_start = self.get_pos(); let _ = self.next_char(); if let Some('|') = self.chr0 { let _ = self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Token::VbarVbar, tok_end)); } else if let Some('>') = self.chr0 { let _ = self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Token::Pipe, tok_end)); } else { let tok_end = self.get_pos(); self.emit((tok_start, Token::Vbar, tok_end)); } } '&' => { let tok_start = self.get_pos(); let _ = self.next_char(); if let Some('&') = self.chr0 { let _ = self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Token::AmperAmper, tok_end)); } else { return Err(LexicalError { error: LexicalErrorType::UnrecognizedToken { tok: '&' }, location: SrcSpan { start: tok_start, end: tok_start, }, }); } } '-' => { let tok_start = self.get_pos(); let _ = self.next_char(); match self.chr0 { Some('.') => { let _ = self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Token::MinusDot, tok_end)); } Some('>') => { let _ = self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Token::RArrow, tok_end)); } _ => { let tok_end = self.get_pos(); self.emit((tok_start, Token::Minus, tok_end)); } } } '!' => { let tok_start = self.get_pos(); let _ = self.next_char(); if let Some('=') = self.chr0 { let _ = self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Token::NotEqual, tok_end)); } else { let tok_end = self.get_pos(); self.emit((tok_start, Token::Bang, tok_end)); } } '(' => { self.eat_single_char(Token::LeftParen); } ')' => { self.eat_single_char(Token::RightParen); } '[' => { self.eat_single_char(Token::LeftSquare); } ']' => { self.eat_single_char(Token::RightSquare); } '{' => { self.eat_single_char(Token::LeftBrace); } '}' => { self.eat_single_char(Token::RightBrace); } ':' => { self.eat_single_char(Token::Colon); } '<' => { let tok_start = self.get_pos(); let _ = self.next_char(); match self.chr0 { Some('>') => { let _ = self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Token::Concatenate, tok_end)); } Some('<') => { let _ = self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Token::LtLt, tok_end)); } Some('.') => { let _ = self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Token::LessDot, tok_end)); } Some('-') => { let _ = self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Token::LArrow, tok_end)); } Some('=') => { let _ = self.next_char(); match self.chr0 { Some('.') => { let _ = self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Token::LessEqualDot, tok_end)); } _ => { let tok_end = self.get_pos(); self.emit((tok_start, Token::LessEqual, tok_end)); } } } _ => { let tok_end = self.get_pos(); self.emit((tok_start, Token::Less, tok_end)); } } } '>' => { let tok_start = self.get_pos(); let _ = self.next_char(); match self.chr0 { Some('>') => { let _ = self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Token::GtGt, tok_end)); } Some('.') => { let _ = self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Token::GreaterDot, tok_end)); } Some('=') => { let _ = self.next_char(); match self.chr0 { Some('.') => { let _ = self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Token::GreaterEqualDot, tok_end)); } _ => { let tok_end = self.get_pos(); self.emit((tok_start, Token::GreaterEqual, tok_end)); } } } _ => { let tok_end = self.get_pos(); self.emit((tok_start, Token::Greater, tok_end)); } } } ',' => { self.eat_single_char(Token::Comma); } '.' => { let tok_start = self.get_pos(); let _ = self.next_char(); if let Some('.') = &self.chr0 { let _ = self.next_char(); let tok_end = self.get_pos(); self.emit((tok_start, Token::DotDot, tok_end)); } else { let tok_end = self.get_pos(); self.emit((tok_start, Token::Dot, tok_end)); self.maybe_lex_dot_access()?; } } '#' => { self.eat_single_char(Token::Hash); } '\n' | ' ' | '\t' | '\x0C' => { let tok_start = self.get_pos(); let _ = self.next_char(); let tok_end = self.get_pos(); if c == '\n' { self.emit((tok_start, Token::NewLine, tok_end)); } } c => { let location = self.get_pos(); return Err(LexicalError { error: LexicalErrorType::UnrecognizedToken { tok: c }, location: SrcSpan { start: location, end: location, }, }); } } Ok(()) } // Lexer helper functions: // this can be either a reserved word, or a name fn lex_name(&mut self) -> LexResult { let mut name = String::new(); let start_pos = self.get_pos(); while self.is_name_continuation() { name.push(self.next_char().expect("lex_name continue")) } let end_pos = self.get_pos(); match str_to_keyword(&name) { Some(tok) => Ok((start_pos, tok, end_pos)), _ => { if name.starts_with('_') { Ok((start_pos, Token::DiscardName { name: name.into() }, end_pos)) } else { Ok((start_pos, Token::Name { name: name.into() }, end_pos)) } } } } // A type name or constructor fn lex_upname(&mut self) -> LexResult { let mut name = String::new(); let start_pos = self.get_pos(); while self.is_name_continuation() { name.push(self.next_char().expect("lex_upname upname")); } let end_pos = self.get_pos(); match str_to_keyword(&name) { Some(tok) => Ok((start_pos, tok, end_pos)), _ => Ok((start_pos, Token::UpName { name: name.into() }, end_pos)), } } fn lex_number(&mut self) -> LexResult { let start_pos = self.get_pos(); let num = if self.chr0 == Some('0') { if self.chr1 == Some('x') || self.chr1 == Some('X') { // Hex! let _ = self.next_char(); let _ = self.next_char(); self.lex_number_radix(start_pos, 16, "0x")? } else if self.chr1 == Some('o') || self.chr1 == Some('O') { // Octal! let _ = self.next_char(); let _ = self.next_char(); self.lex_number_radix(start_pos, 8, "0o")? } else if self.chr1 == Some('b') || self.chr1 == Some('B') { // Binary! let _ = self.next_char(); let _ = self.next_char(); self.lex_number_radix(start_pos, 2, "0b")? } else { self.lex_decimal_number()? } } else { self.lex_decimal_number()? }; if Some('_') == self.chr0 { let location = self.get_pos(); Err(LexicalError { error: LexicalErrorType::NumTrailingUnderscore, location: SrcSpan { start: location, end: location, }, }) } else { Ok(num) } } // Lex a hex/octal/decimal/binary number without a decimal point. fn lex_number_radix(&mut self, start_pos: u32, radix: u32, prefix: &str) -> LexResult { let num = self.radix_run(radix); if num.is_empty() { let location = self.get_pos() - 1; Err(LexicalError { error: LexicalErrorType::RadixIntNoValue, location: SrcSpan { start: location, end: location, }, }) } else if radix < 16 && Lexer::::is_digit_of_radix(self.chr0, 16) { let location = self.get_pos(); Err(LexicalError { error: LexicalErrorType::DigitOutOfRadix, location: SrcSpan { start: location, end: location, }, }) } else { let value = format!("{prefix}{num}"); let int_value = super::parse_int_value(&value).expect("int value to parse as bigint"); let end_pos = self.get_pos(); Ok(( start_pos, Token::Int { value: value.into(), int_value, }, end_pos, )) } } // Lex a normal number, that is, no octal, hex or binary number. // This function cannot be reached without the head of the stream being either 0-9 or '-', 0-9 fn lex_decimal_number(&mut self) -> LexResult { self.lex_decimal_or_int_number(true) } fn lex_int_number(&mut self) -> LexResult { self.lex_decimal_or_int_number(false) } fn lex_decimal_or_int_number(&mut self, can_lex_decimal: bool) -> LexResult { let start_pos = self.get_pos(); let mut value = String::new(); // consume negative sign if self.chr0 == Some('-') { value.push(self.next_char().expect("lex_normal_number negative")); } // consume first run of digits value.push_str(&self.radix_run(10)); // If float: if can_lex_decimal && self.chr0 == Some('.') { value.push(self.next_char().expect("lex_normal_number float")); value.push_str(&self.radix_run(10)); // If scientific: if self.chr0 == Some('e') { value.push(self.next_char().expect("lex_normal_number scientific")); if self.chr0 == Some('-') { value.push( self.next_char() .expect("lex_normal_number scientific negative"), ); } let exponent_run = self.radix_run(10); if exponent_run.is_empty() { return Err(LexicalError { error: LexicalErrorType::MissingExponent, location: SrcSpan::new(start_pos, self.get_pos()), }); } value.push_str(&exponent_run); } let end_pos = self.get_pos(); let float_value = LiteralFloatValue::parse(&value).expect("float value to parse as non-NaN f64"); Ok(( start_pos, Token::Float { value: value.into(), float_value, }, end_pos, )) } else { let int_value = super::parse_int_value(&value).expect("int value to parse as bigint"); let end_pos = self.get_pos(); Ok(( start_pos, Token::Int { value: value.into(), int_value, }, end_pos, )) } } // Maybe lex dot access that comes after name token. fn maybe_lex_dot_access(&mut self) -> Result<(), LexicalError> { // It can be nested like: `tuple.1.2.3.4` loop { if matches!(self.chr0, Some('0'..='9')) { let number = self.lex_int_number()?; self.emit(number); } else { break; } } Ok(()) } // Consume a sequence of numbers with the given radix, // the digits can be decorated with underscores // like this: '1_2_3_4' == '1234' fn radix_run(&mut self, radix: u32) -> String { let mut value_text = String::new(); loop { if let Some(c) = self.take_number(radix) { value_text.push(c); } else if self.chr0 == Some('_') && Lexer::::is_digit_of_radix(self.chr1, radix) { value_text.push('_'); let _ = self.next_char(); } else { break; } } value_text } // Consume a single character with the given radix. fn take_number(&mut self, radix: u32) -> Option { let take_char = Lexer::::is_digit_of_radix(self.chr0, radix); if take_char { Some(self.next_char().expect("take_number next char")) } else { None } } // Test if a digit is of a certain radix. fn is_digit_of_radix(c: Option, radix: u32) -> bool { match radix { 2 | 8 | 10 | 16 => c.filter(|c| c.is_digit(radix)).is_some(), other => panic!("Radix not implemented: {other}"), } } // There are 3 kinds of comments // 2 slash, normal // 3 slash, document // 4 slash, module // this function is entered after 2 slashes fn lex_comment(&mut self) -> Spanned { enum Kind { Comment, Doc, ModuleDoc, } let kind = match (self.chr0, self.chr1) { (Some('/'), Some('/')) => { let _ = self.next_char(); let _ = self.next_char(); Kind::ModuleDoc } (Some('/'), _) => { let _ = self.next_char(); Kind::Doc } _ => Kind::Comment, }; let mut content = EcoString::new(); let start_pos = self.get_pos(); while Some('\n') != self.chr0 { match self.chr0 { Some(c) => content.push(c), None => break, } let _ = self.next_char(); } let end_pos = self.get_pos(); let token = match kind { Kind::Comment => Token::CommentNormal, Kind::Doc => Token::CommentDoc { content }, Kind::ModuleDoc => Token::CommentModule, }; (start_pos, token, end_pos) } fn lex_string(&mut self) -> LexResult { let start_pos = self.get_pos(); // advance past the first quote let _ = self.next_char(); let mut string_content = String::new(); loop { match self.next_char() { Some('\\') => { let slash_pos = self.get_pos() - 1; if let Some(c) = self.chr0 { match c { 'f' | 'n' | 'r' | 't' | '"' | '\\' => { let _ = self.next_char(); string_content.push('\\'); string_content.push(c); } 'u' => { let _ = self.next_char(); if self.chr0 != Some('{') { return Err(LexicalError { error: LexicalErrorType::InvalidUnicodeEscape( InvalidUnicodeEscapeError::MissingOpeningBrace, ), location: SrcSpan { start: self.get_pos() - 1, end: self.get_pos(), }, }); } // All digits inside \u{...}. let mut hex_digits = String::new(); loop { let _ = self.next_char(); let Some(chr) = self.chr0 else { break; }; // Don't break early when we've reached 6 digits to ensure a // useful error message if chr == '}' { break; } hex_digits.push(chr); if !chr.is_ascii_hexdigit() { return Err(LexicalError { error: LexicalErrorType::InvalidUnicodeEscape( InvalidUnicodeEscapeError::ExpectedHexDigitOrCloseBrace, ), location: SrcSpan { start: self.get_pos(), end: self.get_pos() + 1, }, }); } } if self.chr0 != Some('}') { return Err(LexicalError { error: LexicalErrorType::InvalidUnicodeEscape( InvalidUnicodeEscapeError::ExpectedHexDigitOrCloseBrace, ), location: SrcSpan { start: self.get_pos() - 1, end: self.get_pos(), }, }); } let _ = self.next_char(); if !(1..=6).contains(&hex_digits.len()) { return Err(LexicalError { error: LexicalErrorType::InvalidUnicodeEscape( InvalidUnicodeEscapeError::InvalidNumberOfHexDigits, ), location: SrcSpan { start: slash_pos, end: self.get_pos(), }, }); } // Checks for i >= 0x110000 || (i >= 0xD800 && i < 0xE000), // where i is the unicode codepoint. if char::from_u32(u32::from_str_radix(&hex_digits, 16).expect( "Cannot parse codepoint number in Unicode escape sequence", )) .is_none() { return Err(LexicalError { error: LexicalErrorType::InvalidUnicodeEscape( InvalidUnicodeEscapeError::InvalidCodepoint, ), location: SrcSpan { start: slash_pos, end: self.get_pos(), }, }); } string_content.push_str("\\u{"); string_content.push_str(&hex_digits); string_content.push('}'); } _ => { return Err(LexicalError { error: LexicalErrorType::BadStringEscape, location: SrcSpan { start: slash_pos, end: slash_pos + 1, }, }); } } } else { return Err(LexicalError { error: LexicalErrorType::BadStringEscape, location: SrcSpan { start: slash_pos, end: slash_pos, }, }); } } Some('"') => break, Some(c) => string_content.push(c), None => { return Err(LexicalError { error: LexicalErrorType::UnexpectedStringEnd, location: SrcSpan { start: start_pos, end: start_pos, }, }); } } } let end_pos = self.get_pos(); let tok = Token::String { value: string_content.into(), }; Ok((start_pos, tok, end_pos)) } fn is_name_start(&self, c: char) -> bool { matches!(c, '_' | 'a'..='z') } fn is_upname_start(&self, c: char) -> bool { c.is_ascii_uppercase() } fn is_number_start(&self, c: char, c1: Option) -> bool { match c { '0'..='9' => true, '-' => matches!(c1, Some('0'..='9')), _ => false, } } fn is_name_continuation(&self) -> bool { self.chr0 .map(|c| matches!(c, '_' | '0'..='9' | 'a'..='z' | 'A'..='Z')) .unwrap_or(false) } // advance the stream and emit a token fn eat_single_char(&mut self, ty: Token) { let tok_start = self.get_pos(); let _ = self.next_char().expect("eat_single_char"); let tok_end = self.get_pos(); self.emit((tok_start, ty, tok_end)); } // Helper function to go to the next character coming up. fn next_char(&mut self) -> Option { let c = self.chr0; let nxt = match self.chars.next() { Some((loc, c)) => { self.loc0 = self.loc1; self.loc1 = loc; Some(c) } None => { // EOF needs a single advance self.loc0 = self.loc1; self.loc1 += 1; None } }; self.chr0 = self.chr1; self.chr1 = nxt; c } // Helper function to retrieve the current position. fn get_pos(&self) -> u32 { self.loc0 } // Helper function to emit a lexed token to the queue of tokens. fn emit(&mut self, spanned: Spanned) { self.pending.push(spanned); } } impl Iterator for Lexer where T: Iterator, { type Item = LexResult; fn next(&mut self) -> Option { let token = self.inner_next(); match token { Ok((_, Token::EndOfFile, _)) => None, r => Some(r), } } } ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__append_to_const_list.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nconst wibble = [2, 3]\nconst wobble = [..wibble, 4, 5]\n" --- ----- SOURCE CODE const wibble = [2, 3] const wobble = [..wibble, 4, 5] ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:17 │ 3 │ const wobble = [..wibble, 4, 5] │ ^^^^^^^^ I wasn't expecting elements after this Lists are immutable and singly-linked, so to append items to them all the elements of a list would need to be copied into a new list. This would be slow, so there is no built-in syntax for it. Hint: Prepend items to the list and then reverse it once you are done. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__argument_scope.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\n1 + let a = 5\na\n" --- ----- SOURCE CODE 1 + let a = 5 a ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:3 │ 2 │ 1 + let a = 5 │ ^ This operator has no value on its right side Hint: Remove it or put a value after it. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__arithmetic_in_guards.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\ncase 2, 3 {\n x, y if x + y == 1 -> True\n}" --- [ Expression( Case { location: SrcSpan { start: 1, end: 45, }, subjects: [ Int { location: SrcSpan { start: 6, end: 7, }, value: "2", int_value: 2, }, Int { location: SrcSpan { start: 9, end: 10, }, value: "3", int_value: 3, }, ], clauses: Some( [ Clause { location: SrcSpan { start: 17, end: 43, }, pattern: [ Variable { location: SrcSpan { start: 17, end: 18, }, name: "x", type_: (), origin: VariableOrigin { syntax: Variable( "x", ), declaration: ClausePattern, }, }, Variable { location: SrcSpan { start: 20, end: 21, }, name: "y", type_: (), origin: VariableOrigin { syntax: Variable( "y", ), declaration: ClausePattern, }, }, ], alternative_patterns: [], guard: Some( BinaryOperator { location: SrcSpan { start: 25, end: 35, }, operator: Eq, left: BinaryOperator { location: SrcSpan { start: 25, end: 30, }, operator: AddInt, left: Var { location: SrcSpan { start: 25, end: 26, }, type_: (), name: "x", definition_location: SrcSpan { start: 0, end: 0, }, origin: VariableOrigin { syntax: Generated, declaration: Generated, }, }, right: Var { location: SrcSpan { start: 29, end: 30, }, type_: (), name: "y", definition_location: SrcSpan { start: 0, end: 0, }, origin: VariableOrigin { syntax: Generated, declaration: Generated, }, }, }, right: Constant( Int { location: SrcSpan { start: 34, end: 35, }, value: "1", int_value: 1, }, ), }, ), then: Var { location: SrcSpan { start: 39, end: 43, }, name: "True", }, }, ], ), }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__assert_statement.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: assert 10 != 11 --- [ Assert( Assert { location: SrcSpan { start: 0, end: 15, }, value: BinOp { location: SrcSpan { start: 7, end: 15, }, name: NotEq, name_location: SrcSpan { start: 10, end: 12, }, left: Int { location: SrcSpan { start: 7, end: 9, }, value: "10", int_value: 10, }, right: Int { location: SrcSpan { start: 13, end: 15, }, value: "11", int_value: 11, }, }, message: None, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__assert_statement_followed_by_statement.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: assert let a = 10 --- ----- SOURCE CODE assert let a = 10 ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:1:8 │ 1 │ assert let a = 10 │ ^^^ I was not expecting this Found the keyword `let`, expected one of: - An expression ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__assert_statement_with_message.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "assert False as \"Uh oh\"" --- [ Assert( Assert { location: SrcSpan { start: 0, end: 23, }, value: Var { location: SrcSpan { start: 7, end: 12, }, name: "False", }, message: Some( String { location: SrcSpan { start: 16, end: 23, }, value: "Uh oh", }, ), }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__assert_statement_without_expression.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: assert --- ----- SOURCE CODE assert ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:1:6 │ 1 │ assert │ ^ The module ended unexpectedly ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__assign_left_hand_side_of_concat_pattern.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\n case \"\" {\n first <> rest -> rest\n }\n " --- ----- SOURCE CODE case "" { first <> rest -> rest } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:11 │ 3 │ first <> rest -> rest │ ^^^^^ This must be a string literal We can't tell what size this prefix should be so we don't know how to handle this pattern. If you want to match one character consider using `pop_grapheme` from the stdlib's `gleam/string` module. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__assignment_pattern_invalid_bit_segment.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nfn main() {\n let <> = <<24, 3>>\n}\n" --- ----- SOURCE CODE fn main() { let <> = <<24, 3>> } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:15 │ 3 │ let <> = <<24, 3>> │ ^^^ I was not expecting this Found the keyword `pub`, expected one of: - `>>` - a bit array segment pattern ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__assignment_pattern_invalid_tuple.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nfn main() {\n let #(a, case, c) = #(1, 2, 3)\n}\n" --- ----- SOURCE CODE fn main() { let #(a, case, c) = #(1, 2, 3) } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:14 │ 3 │ let #(a, case, c) = #(1, 2, 3) │ ^^^^ I was not expecting this Found the keyword `case`, expected one of: - `)` - a pattern ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__attributes_with_improper_definition.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\n@deprecated(\"1\")\n@external(erlang, \"module\", \"fun\")\n" --- ----- SOURCE CODE @deprecated("1") @external(erlang, "module", "fun") ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:1 │ 2 │ ╭ @deprecated("1") 3 │ │ @external(erlang, "module", "fun") │ ╰──────────────────────────────────^ I was expecting a function definition after this ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__attributes_with_no_definition.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\n@deprecated(\"1\")\n@target(erlang)\n" --- ----- SOURCE CODE @deprecated("1") @target(erlang) ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:1 │ 2 │ ╭ @deprecated("1") 3 │ │ @target(erlang) │ ╰───────────────^ I was expecting a definition after this ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__bare_expression.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "1" --- [ Expression( Int { location: SrcSpan { start: 0, end: 1, }, value: "1", int_value: 1, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__bit_array_invalid_segment.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nfn main() {\n <<72, 101, 108, 108, 111, 44, 32, 74, 111, 101, const>>\n}\n" --- ----- SOURCE CODE fn main() { <<72, 101, 108, 108, 111, 44, 32, 74, 111, 101, const>> } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:53 │ 3 │ <<72, 101, 108, 108, 111, 44, 32, 74, 111, 101, const>> │ ^^^^^ I was not expecting this Found the keyword `const`, expected one of: - `>>` - a bit array segment ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__block_of_one.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "{ 1 }" --- [ Expression( Block { location: SrcSpan { start: 0, end: 5, }, statements: [ Expression( Int { location: SrcSpan { start: 2, end: 3, }, value: "1", int_value: 1, }, ), ], }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__block_of_two.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "{ 1 2 }" --- [ Expression( Block { location: SrcSpan { start: 0, end: 7, }, statements: [ Expression( Int { location: SrcSpan { start: 2, end: 3, }, value: "1", int_value: 1, }, ), Expression( Int { location: SrcSpan { start: 4, end: 5, }, value: "2", int_value: 2, }, ), ], }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__byte_order_mark.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: todo --- [ Expression( Todo { kind: Keyword, location: SrcSpan { start: 3, end: 7, }, message: None, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__byte_order_mark_module.snap ================================================ --- source: compiler-core/src/parse/tests.rs assertion_line: 2103 expression: "\nconst local_const = other.Record(..other.base, field: value)\n" snapshot_kind: text --- Parsed { module: Module { name: "", documentation: [], type_info: (), definitions: [ TargetedDefinition { definition: ModuleConstant( ModuleConstant { documentation: None, location: SrcSpan { start: 4, end: 21, }, publicity: Private, name: "local_const", name_location: SrcSpan { start: 10, end: 21, }, annotation: None, value: RecordUpdate { location: SrcSpan { start: 24, end: 64, }, constructor_location: SrcSpan { start: 24, end: 36, }, module: Some( ( "other", SrcSpan { start: 24, end: 29, }, ), ), name: "Record", record: RecordBeingUpdated { base: Var { location: SrcSpan { start: 39, end: 49, }, module: Some( ( "other", SrcSpan { start: 39, end: 44, }, ), ), name: "base", constructor: None, type_: (), }, location: SrcSpan { start: 39, end: 49, }, }, arguments: [ RecordUpdateArg { label: "field", location: SrcSpan { start: 51, end: 63, }, value: Var { location: SrcSpan { start: 58, end: 63, }, module: None, name: "value", constructor: None, type_: (), }, }, ], tag: (), type_: (), field_map: Unknown, }, type_: (), deprecation: NotDeprecated, implementations: Implementations { gleam: true, can_run_on_erlang: true, can_run_on_javascript: true, uses_erlang_externals: false, uses_javascript_externals: false, }, }, ), target: None, }, ], names: Names { local_types: {}, imported_modules: {}, type_variables: {}, local_value_constructors: {}, reexport_aliases: {}, }, unused_definition_positions: {}, }, extra: ModuleExtra { module_comments: [], doc_comments: [], comments: [], empty_lines: [], new_lines: [ 3, 64, ], trailing_commas: [], }, } ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__capture_with_name.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub fn main() {\n add(_name, 1)\n}\n\nfn add(x, y) {\n x + y\n}\n" --- ----- SOURCE CODE pub fn main() { add(_name, 1) } fn add(x, y) { x + y } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:7 │ 3 │ add(_name, 1) │ ^^^^^ I was not expecting this Found a name, expected one of: - An expression - An underscore ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__case_alternative_clause_no_subject.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nfn main() {\n case 1 {\n 1 | -> 1\n _ -> 1\n }\n}\n" --- ----- SOURCE CODE fn main() { case 1 { 1 | -> 1 _ -> 1 } } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:4:9 │ 4 │ 1 | -> 1 │ ^ I was expecting a pattern after this ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__case_clause_no_subject.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nfn main() {\n case 1 {\n -> 1\n _ -> 2\n }\n}\n" --- ----- SOURCE CODE fn main() { case 1 { -> 1 _ -> 2 } } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:4:7 │ 4 │ -> 1 │ ^^ I was not expecting this Found `->`, expected one of: - `}` - a case clause ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__case_expression_without_body.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: case a --- [ Expression( Case { location: SrcSpan { start: 0, end: 6, }, subjects: [ Var { location: SrcSpan { start: 5, end: 6, }, name: "a", }, ], clauses: None, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__case_guard_with_empty_block.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "case 1 {\n _ if a || {} -> 1\n}" --- ----- SOURCE CODE case 1 { _ if a || {} -> 1 } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:13 │ 2 │ _ if a || {} -> 1 │ ^^ A clause guard block cannot be empty ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__case_guard_with_nested_blocks.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "case 1 {\n _ if { 1 || { 1 || 1 } } || 1 -> 1\n}" --- [ Expression( Case { location: SrcSpan { start: 0, end: 48, }, subjects: [ Int { location: SrcSpan { start: 5, end: 6, }, value: "1", int_value: 1, }, ], clauses: Some( [ Clause { location: SrcSpan { start: 11, end: 46, }, pattern: [ Discard { name: "_", location: SrcSpan { start: 11, end: 12, }, type_: (), }, ], alternative_patterns: [], guard: Some( BinaryOperator { location: SrcSpan { start: 16, end: 40, }, operator: Or, left: Block { location: SrcSpan { start: 16, end: 35, }, value: BinaryOperator { location: SrcSpan { start: 18, end: 33, }, operator: Or, left: Constant( Int { location: SrcSpan { start: 18, end: 19, }, value: "1", int_value: 1, }, ), right: Block { location: SrcSpan { start: 23, end: 33, }, value: BinaryOperator { location: SrcSpan { start: 25, end: 31, }, operator: Or, left: Constant( Int { location: SrcSpan { start: 25, end: 26, }, value: "1", int_value: 1, }, ), right: Constant( Int { location: SrcSpan { start: 30, end: 31, }, value: "1", int_value: 1, }, ), }, }, }, }, right: Constant( Int { location: SrcSpan { start: 39, end: 40, }, value: "1", int_value: 1, }, ), }, ), then: Int { location: SrcSpan { start: 45, end: 46, }, value: "1", int_value: 1, }, }, ], ), }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__case_invalid_case_pattern.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nfn main() {\n case 1 {\n -> -> 0\n }\n}\n" --- ----- SOURCE CODE fn main() { case 1 { -> -> 0 } } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:4:9 │ 4 │ -> -> 0 │ ^^ I was not expecting this Found `->`, expected one of: - `}` - a case clause ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__case_invalid_expression.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nfn main() {\n case 1, type {\n _, _ -> 0\n }\n}\n" --- ----- SOURCE CODE fn main() { case 1, type { _, _ -> 0 } } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:13 │ 3 │ case 1, type { │ ^^^^ I was not expecting this Found the keyword `type`, expected one of: - `}` ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__case_list_pattern_after_spread.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nfn main() {\n case somelist {\n [..rest, last] -> 1\n _ -> 2\n }\n}\n" --- ----- SOURCE CODE fn main() { case somelist { [..rest, last] -> 1 _ -> 2 } } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:4:10 │ 4 │ [..rest, last] -> 1 │ ^^^^^^ I wasn't expecting elements after this Lists are immutable and singly-linked, so to match on the end of a list would require the whole list to be traversed. This would be slow, so there is no built-in syntax for it. Pattern match on the start of the list instead. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__const_invalid_bit_array_segment.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nconst a = <<1, 2, <->>\n" --- ----- SOURCE CODE const a = <<1, 2, <->> ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:19 │ 2 │ const a = <<1, 2, <->> │ ^^ I was not expecting this Found `<-`, expected one of: - `>>` - a bit array segment ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__const_invalid_list.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nconst a = [1, 2, <-]\n" --- ----- SOURCE CODE const a = [1, 2, <-] ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:18 │ 2 │ const a = [1, 2, <-] │ ^^ I was not expecting this Found `<-`, expected one of: - `]` - a constant value ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__const_invalid_record_constructor.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\ntype A {\n A(String, Int)\n}\nconst a = A(\"a\", let)\n" --- ----- SOURCE CODE type A { A(String, Int) } const a = A("a", let) ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:5:18 │ 5 │ const a = A("a", let) │ ^^^ I was not expecting this Found the keyword `let`, expected one of: - `)` - a constant record argument ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__const_invalid_tuple.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nconst a = #(1, 2, <-)\n" --- ----- SOURCE CODE const a = #(1, 2, <-) ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:19 │ 2 │ const a = #(1, 2, <-) │ ^^ I was not expecting this Found `<-`, expected one of: - `)` - a constant value ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__const_record_update_all_fields.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\ntype Person {\n Person(name: String, age: Int, city: String)\n}\n\nconst base = Person(\"Alice\", 30, \"London\")\nconst updated = Person(..base, name: \"Bob\", age: 25, city: \"Paris\")\n" --- Parsed { module: Module { name: "", documentation: [], type_info: (), definitions: [ TargetedDefinition { definition: CustomType( CustomType { location: SrcSpan { start: 1, end: 12, }, end_position: 63, name: "Person", name_location: SrcSpan { start: 6, end: 12, }, publicity: Private, constructors: [ RecordConstructor { location: SrcSpan { start: 17, end: 61, }, name_location: SrcSpan { start: 17, end: 23, }, name: "Person", arguments: [ RecordConstructorArg { label: Some( ( SrcSpan { start: 24, end: 28, }, "name", ), ), ast: Constructor( TypeAstConstructor { location: SrcSpan { start: 30, end: 36, }, name_location: SrcSpan { start: 30, end: 36, }, module: None, name: "String", arguments: [], start_parentheses: None, }, ), location: SrcSpan { start: 24, end: 36, }, type_: (), doc: None, }, RecordConstructorArg { label: Some( ( SrcSpan { start: 38, end: 41, }, "age", ), ), ast: Constructor( TypeAstConstructor { location: SrcSpan { start: 43, end: 46, }, name_location: SrcSpan { start: 43, end: 46, }, module: None, name: "Int", arguments: [], start_parentheses: None, }, ), location: SrcSpan { start: 38, end: 46, }, type_: (), doc: None, }, RecordConstructorArg { label: Some( ( SrcSpan { start: 48, end: 52, }, "city", ), ), ast: Constructor( TypeAstConstructor { location: SrcSpan { start: 54, end: 60, }, name_location: SrcSpan { start: 54, end: 60, }, module: None, name: "String", arguments: [], start_parentheses: None, }, ), location: SrcSpan { start: 48, end: 60, }, type_: (), doc: None, }, ], documentation: None, deprecation: NotDeprecated, }, ], documentation: None, deprecation: NotDeprecated, opaque: false, parameters: [], typed_parameters: [], external_erlang: None, external_javascript: None, }, ), target: None, }, TargetedDefinition { definition: ModuleConstant( ModuleConstant { documentation: None, location: SrcSpan { start: 65, end: 75, }, publicity: Private, name: "base", name_location: SrcSpan { start: 71, end: 75, }, annotation: None, value: Record { location: SrcSpan { start: 78, end: 107, }, module: None, name: "Person", arguments: [ CallArg { label: None, location: SrcSpan { start: 85, end: 92, }, value: String { location: SrcSpan { start: 85, end: 92, }, value: "Alice", }, implicit: None, }, CallArg { label: None, location: SrcSpan { start: 94, end: 96, }, value: Int { location: SrcSpan { start: 94, end: 96, }, value: "30", int_value: 30, }, implicit: None, }, CallArg { label: None, location: SrcSpan { start: 98, end: 106, }, value: String { location: SrcSpan { start: 98, end: 106, }, value: "London", }, implicit: None, }, ], tag: (), type_: (), field_map: Unknown, record_constructor: None, }, type_: (), deprecation: NotDeprecated, implementations: Implementations { gleam: true, can_run_on_erlang: true, can_run_on_javascript: true, uses_erlang_externals: false, uses_javascript_externals: false, }, }, ), target: None, }, TargetedDefinition { definition: ModuleConstant( ModuleConstant { documentation: None, location: SrcSpan { start: 108, end: 121, }, publicity: Private, name: "updated", name_location: SrcSpan { start: 114, end: 121, }, annotation: None, value: RecordUpdate { location: SrcSpan { start: 124, end: 175, }, constructor_location: SrcSpan { start: 124, end: 130, }, module: None, name: "Person", record: RecordBeingUpdated { base: Var { location: SrcSpan { start: 133, end: 137, }, module: None, name: "base", constructor: None, type_: (), }, location: SrcSpan { start: 133, end: 137, }, }, arguments: [ RecordUpdateArg { label: "name", location: SrcSpan { start: 139, end: 150, }, value: String { location: SrcSpan { start: 145, end: 150, }, value: "Bob", }, }, RecordUpdateArg { label: "age", location: SrcSpan { start: 152, end: 159, }, value: Int { location: SrcSpan { start: 157, end: 159, }, value: "25", int_value: 25, }, }, RecordUpdateArg { label: "city", location: SrcSpan { start: 161, end: 174, }, value: String { location: SrcSpan { start: 167, end: 174, }, value: "Paris", }, }, ], tag: (), type_: (), field_map: Unknown, }, type_: (), deprecation: NotDeprecated, implementations: Implementations { gleam: true, can_run_on_erlang: true, can_run_on_javascript: true, uses_erlang_externals: false, uses_javascript_externals: false, }, }, ), target: None, }, ], names: Names { local_types: {}, imported_modules: {}, type_variables: {}, local_value_constructors: {}, reexport_aliases: {}, }, unused_definition_positions: {}, }, extra: ModuleExtra { module_comments: [], doc_comments: [], comments: [], empty_lines: [ 64, ], new_lines: [ 0, 14, 61, 63, 64, 107, 175, ], trailing_commas: [], }, } ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__const_record_update_basic.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\ntype Person {\n Person(name: String, age: Int)\n}\n\nconst alice = Person(\"Alice\", 30)\nconst bob = Person(..alice, name: \"Bob\")\n" --- Parsed { module: Module { name: "", documentation: [], type_info: (), definitions: [ TargetedDefinition { definition: CustomType( CustomType { location: SrcSpan { start: 1, end: 12, }, end_position: 49, name: "Person", name_location: SrcSpan { start: 6, end: 12, }, publicity: Private, constructors: [ RecordConstructor { location: SrcSpan { start: 17, end: 47, }, name_location: SrcSpan { start: 17, end: 23, }, name: "Person", arguments: [ RecordConstructorArg { label: Some( ( SrcSpan { start: 24, end: 28, }, "name", ), ), ast: Constructor( TypeAstConstructor { location: SrcSpan { start: 30, end: 36, }, name_location: SrcSpan { start: 30, end: 36, }, module: None, name: "String", arguments: [], start_parentheses: None, }, ), location: SrcSpan { start: 24, end: 36, }, type_: (), doc: None, }, RecordConstructorArg { label: Some( ( SrcSpan { start: 38, end: 41, }, "age", ), ), ast: Constructor( TypeAstConstructor { location: SrcSpan { start: 43, end: 46, }, name_location: SrcSpan { start: 43, end: 46, }, module: None, name: "Int", arguments: [], start_parentheses: None, }, ), location: SrcSpan { start: 38, end: 46, }, type_: (), doc: None, }, ], documentation: None, deprecation: NotDeprecated, }, ], documentation: None, deprecation: NotDeprecated, opaque: false, parameters: [], typed_parameters: [], external_erlang: None, external_javascript: None, }, ), target: None, }, TargetedDefinition { definition: ModuleConstant( ModuleConstant { documentation: None, location: SrcSpan { start: 51, end: 62, }, publicity: Private, name: "alice", name_location: SrcSpan { start: 57, end: 62, }, annotation: None, value: Record { location: SrcSpan { start: 65, end: 84, }, module: None, name: "Person", arguments: [ CallArg { label: None, location: SrcSpan { start: 72, end: 79, }, value: String { location: SrcSpan { start: 72, end: 79, }, value: "Alice", }, implicit: None, }, CallArg { label: None, location: SrcSpan { start: 81, end: 83, }, value: Int { location: SrcSpan { start: 81, end: 83, }, value: "30", int_value: 30, }, implicit: None, }, ], tag: (), type_: (), field_map: Unknown, record_constructor: None, }, type_: (), deprecation: NotDeprecated, implementations: Implementations { gleam: true, can_run_on_erlang: true, can_run_on_javascript: true, uses_erlang_externals: false, uses_javascript_externals: false, }, }, ), target: None, }, TargetedDefinition { definition: ModuleConstant( ModuleConstant { documentation: None, location: SrcSpan { start: 85, end: 94, }, publicity: Private, name: "bob", name_location: SrcSpan { start: 91, end: 94, }, annotation: None, value: RecordUpdate { location: SrcSpan { start: 97, end: 125, }, constructor_location: SrcSpan { start: 97, end: 103, }, module: None, name: "Person", record: RecordBeingUpdated { base: Var { location: SrcSpan { start: 106, end: 111, }, module: None, name: "alice", constructor: None, type_: (), }, location: SrcSpan { start: 106, end: 111, }, }, arguments: [ RecordUpdateArg { label: "name", location: SrcSpan { start: 113, end: 124, }, value: String { location: SrcSpan { start: 119, end: 124, }, value: "Bob", }, }, ], tag: (), type_: (), field_map: Unknown, }, type_: (), deprecation: NotDeprecated, implementations: Implementations { gleam: true, can_run_on_erlang: true, can_run_on_javascript: true, uses_erlang_externals: false, uses_javascript_externals: false, }, }, ), target: None, }, ], names: Names { local_types: {}, imported_modules: {}, type_variables: {}, local_value_constructors: {}, reexport_aliases: {}, }, unused_definition_positions: {}, }, extra: ModuleExtra { module_comments: [], doc_comments: [], comments: [], empty_lines: [ 50, ], new_lines: [ 0, 14, 47, 49, 50, 84, 125, ], trailing_commas: [], }, } ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__const_record_update_only.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\ntype Person {\n Person(name: String, age: Int)\n}\n\nconst alice = Person(\"Alice\", 30)\nconst bob = Person(..alice)\n" --- Parsed { module: Module { name: "", documentation: [], type_info: (), definitions: [ TargetedDefinition { definition: CustomType( CustomType { location: SrcSpan { start: 1, end: 12, }, end_position: 49, name: "Person", name_location: SrcSpan { start: 6, end: 12, }, publicity: Private, constructors: [ RecordConstructor { location: SrcSpan { start: 17, end: 47, }, name_location: SrcSpan { start: 17, end: 23, }, name: "Person", arguments: [ RecordConstructorArg { label: Some( ( SrcSpan { start: 24, end: 28, }, "name", ), ), ast: Constructor( TypeAstConstructor { location: SrcSpan { start: 30, end: 36, }, name_location: SrcSpan { start: 30, end: 36, }, module: None, name: "String", arguments: [], start_parentheses: None, }, ), location: SrcSpan { start: 24, end: 36, }, type_: (), doc: None, }, RecordConstructorArg { label: Some( ( SrcSpan { start: 38, end: 41, }, "age", ), ), ast: Constructor( TypeAstConstructor { location: SrcSpan { start: 43, end: 46, }, name_location: SrcSpan { start: 43, end: 46, }, module: None, name: "Int", arguments: [], start_parentheses: None, }, ), location: SrcSpan { start: 38, end: 46, }, type_: (), doc: None, }, ], documentation: None, deprecation: NotDeprecated, }, ], documentation: None, deprecation: NotDeprecated, opaque: false, parameters: [], typed_parameters: [], external_erlang: None, external_javascript: None, }, ), target: None, }, TargetedDefinition { definition: ModuleConstant( ModuleConstant { documentation: None, location: SrcSpan { start: 51, end: 62, }, publicity: Private, name: "alice", name_location: SrcSpan { start: 57, end: 62, }, annotation: None, value: Record { location: SrcSpan { start: 65, end: 84, }, module: None, name: "Person", arguments: [ CallArg { label: None, location: SrcSpan { start: 72, end: 79, }, value: String { location: SrcSpan { start: 72, end: 79, }, value: "Alice", }, implicit: None, }, CallArg { label: None, location: SrcSpan { start: 81, end: 83, }, value: Int { location: SrcSpan { start: 81, end: 83, }, value: "30", int_value: 30, }, implicit: None, }, ], tag: (), type_: (), field_map: Unknown, record_constructor: None, }, type_: (), deprecation: NotDeprecated, implementations: Implementations { gleam: true, can_run_on_erlang: true, can_run_on_javascript: true, uses_erlang_externals: false, uses_javascript_externals: false, }, }, ), target: None, }, TargetedDefinition { definition: ModuleConstant( ModuleConstant { documentation: None, location: SrcSpan { start: 85, end: 94, }, publicity: Private, name: "bob", name_location: SrcSpan { start: 91, end: 94, }, annotation: None, value: RecordUpdate { location: SrcSpan { start: 97, end: 112, }, constructor_location: SrcSpan { start: 97, end: 103, }, module: None, name: "Person", record: RecordBeingUpdated { base: Var { location: SrcSpan { start: 106, end: 111, }, module: None, name: "alice", constructor: None, type_: (), }, location: SrcSpan { start: 106, end: 111, }, }, arguments: [], tag: (), type_: (), field_map: Unknown, }, type_: (), deprecation: NotDeprecated, implementations: Implementations { gleam: true, can_run_on_erlang: true, can_run_on_javascript: true, uses_erlang_externals: false, uses_javascript_externals: false, }, }, ), target: None, }, ], names: Names { local_types: {}, imported_modules: {}, type_variables: {}, local_value_constructors: {}, reexport_aliases: {}, }, unused_definition_positions: {}, }, extra: ModuleExtra { module_comments: [], doc_comments: [], comments: [], empty_lines: [ 50, ], new_lines: [ 0, 14, 47, 49, 50, 84, 112, ], trailing_commas: [], }, } ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__const_record_update_with_module.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nconst local_const = other.Record(..other.base, field: value)\n" --- Parsed { module: Module { name: "", documentation: [], type_info: (), definitions: [ TargetedDefinition { definition: ModuleConstant( ModuleConstant { documentation: None, location: SrcSpan { start: 1, end: 18, }, publicity: Private, name: "local_const", name_location: SrcSpan { start: 7, end: 18, }, annotation: None, value: RecordUpdate { location: SrcSpan { start: 21, end: 61, }, constructor_location: SrcSpan { start: 21, end: 33, }, module: Some( ( "other", SrcSpan { start: 21, end: 26, }, ), ), name: "Record", record: RecordBeingUpdated { base: Var { location: SrcSpan { start: 36, end: 46, }, module: Some( ( "other", SrcSpan { start: 36, end: 41, }, ), ), name: "base", constructor: None, type_: (), }, location: SrcSpan { start: 36, end: 46, }, }, arguments: [ RecordUpdateArg { label: "field", location: SrcSpan { start: 48, end: 60, }, value: Var { location: SrcSpan { start: 55, end: 60, }, module: None, name: "value", constructor: None, type_: (), }, }, ], tag: (), type_: (), field_map: Unknown, }, type_: (), deprecation: NotDeprecated, implementations: Implementations { gleam: true, can_run_on_erlang: true, can_run_on_javascript: true, uses_erlang_externals: false, uses_javascript_externals: false, }, }, ), target: None, }, ], names: Names { local_types: {}, imported_modules: {}, type_variables: {}, local_value_constructors: {}, reexport_aliases: {}, }, unused_definition_positions: {}, }, extra: ModuleExtra { module_comments: [], doc_comments: [], comments: [], empty_lines: [], new_lines: [ 0, 61, ], trailing_commas: [], }, } ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__const_string_concat.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nconst cute = \"cute\"\nconst cute_bee = cute <> \"bee\"\n" --- Parsed { module: Module { name: "", documentation: [], type_info: (), definitions: [ TargetedDefinition { definition: ModuleConstant( ModuleConstant { documentation: None, location: SrcSpan { start: 1, end: 11, }, publicity: Private, name: "cute", name_location: SrcSpan { start: 7, end: 11, }, annotation: None, value: String { location: SrcSpan { start: 14, end: 20, }, value: "cute", }, type_: (), deprecation: NotDeprecated, implementations: Implementations { gleam: true, can_run_on_erlang: true, can_run_on_javascript: true, uses_erlang_externals: false, uses_javascript_externals: false, }, }, ), target: None, }, TargetedDefinition { definition: ModuleConstant( ModuleConstant { documentation: None, location: SrcSpan { start: 21, end: 35, }, publicity: Private, name: "cute_bee", name_location: SrcSpan { start: 27, end: 35, }, annotation: None, value: StringConcatenation { location: SrcSpan { start: 38, end: 51, }, left: Var { location: SrcSpan { start: 38, end: 42, }, module: None, name: "cute", constructor: None, type_: (), }, right: String { location: SrcSpan { start: 46, end: 51, }, value: "bee", }, }, type_: (), deprecation: NotDeprecated, implementations: Implementations { gleam: true, can_run_on_erlang: true, can_run_on_javascript: true, uses_erlang_externals: false, uses_javascript_externals: false, }, }, ), target: None, }, ], names: Names { local_types: {}, imported_modules: {}, type_variables: {}, local_value_constructors: {}, reexport_aliases: {}, }, unused_definition_positions: {}, }, extra: ModuleExtra { module_comments: [], doc_comments: [], comments: [], empty_lines: [], new_lines: [ 0, 20, 51, ], trailing_commas: [], }, } ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__const_string_concat_naked_right.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nconst no_cute_bee = \"cute\" <>\n" --- ----- SOURCE CODE const no_cute_bee = "cute" <> ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:28 │ 2 │ const no_cute_bee = "cute" <> │ ^^ This operator has no value on its right side Hint: Remove it or put a value after it. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__const_with_function_call.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub fn wibble() { 123 }\nconst wib: Int = wibble()\n" --- ----- SOURCE CODE pub fn wibble() { 123 } const wib: Int = wibble() ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:18 │ 3 │ const wib: Int = wibble() │ ^^^^^^^ Functions can only be called within other functions ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__const_with_function_call_with_args.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub fn wibble() { 123 }\nconst wib: Int = wibble(1, \"wobble\")\n" --- ----- SOURCE CODE pub fn wibble() { 123 } const wib: Int = wibble(1, "wobble") ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:18 │ 3 │ const wib: Int = wibble(1, "wobble") │ ^^^^^^^ Functions can only be called within other functions ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__constant_inside_function.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub fn main() {\n const x = 10\n x\n}\n" --- ----- SOURCE CODE pub fn main() { const x = 10 x } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:3 │ 3 │ const x = 10 │ ^^^^^ Constants are not allowed inside functions All variables are immutable in Gleam, so constants inside functions are not necessary. Hint: Either move this into the global scope or use `let` binding instead. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__correct_precedence_in_pattern_size.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "let assert <> = <<>>" --- [ Assignment( Assignment { location: SrcSpan { start: 0, end: 54, }, value: BitArray { location: SrcSpan { start: 50, end: 54, }, segments: [], }, pattern: BitArray { location: SrcSpan { start: 11, end: 47, }, segments: [ BitArraySegment { location: SrcSpan { start: 13, end: 17, }, value: Variable { location: SrcSpan { start: 13, end: 17, }, name: "size", type_: (), origin: VariableOrigin { syntax: Variable( "size", ), declaration: LetPattern, }, }, options: [], type_: (), }, BitArraySegment { location: SrcSpan { start: 19, end: 45, }, value: Variable { location: SrcSpan { start: 19, end: 26, }, name: "payload", type_: (), origin: VariableOrigin { syntax: Variable( "payload", ), declaration: LetPattern, }, }, options: [ Size { location: SrcSpan { start: 27, end: 45, }, value: BitArraySize( BinaryOperator { location: SrcSpan { start: 32, end: 44, }, operator: Add, left: Variable { location: SrcSpan { start: 32, end: 36, }, name: "size", constructor: None, type_: (), }, right: BinaryOperator { location: SrcSpan { start: 39, end: 44, }, operator: Multiply, left: Int { location: SrcSpan { start: 39, end: 40, }, value: "2", int_value: 2, }, right: Int { location: SrcSpan { start: 43, end: 44, }, value: "8", int_value: 8, }, }, }, ), short_form: false, }, ], type_: (), }, ], }, kind: Assert { location: SrcSpan { start: 0, end: 10, }, assert_keyword_start: 4, message: None, }, compiled_case: CompiledCase { tree: Fail, subject_variables: [], }, annotation: None, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__deeply_nested_tuples.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nlet tup = #(#(#(#(4))))\n{{{tup.0}.0}.0}.0\n" --- [ Assignment( Assignment { location: SrcSpan { start: 1, end: 24, }, value: Tuple { location: SrcSpan { start: 11, end: 24, }, elements: [ Tuple { location: SrcSpan { start: 13, end: 23, }, elements: [ Tuple { location: SrcSpan { start: 15, end: 22, }, elements: [ Tuple { location: SrcSpan { start: 17, end: 21, }, elements: [ Int { location: SrcSpan { start: 19, end: 20, }, value: "4", int_value: 4, }, ], }, ], }, ], }, ], }, pattern: Variable { location: SrcSpan { start: 5, end: 8, }, name: "tup", type_: (), origin: VariableOrigin { syntax: Variable( "tup", ), declaration: LetPattern, }, }, kind: Let, compiled_case: CompiledCase { tree: Fail, subject_variables: [], }, annotation: None, }, ), Expression( TupleIndex { location: SrcSpan { start: 25, end: 42, }, index: 0, tuple: Block { location: SrcSpan { start: 25, end: 40, }, statements: [ Expression( TupleIndex { location: SrcSpan { start: 26, end: 39, }, index: 0, tuple: Block { location: SrcSpan { start: 26, end: 37, }, statements: [ Expression( TupleIndex { location: SrcSpan { start: 27, end: 36, }, index: 0, tuple: Block { location: SrcSpan { start: 27, end: 34, }, statements: [ Expression( TupleIndex { location: SrcSpan { start: 28, end: 33, }, index: 0, tuple: Var { location: SrcSpan { start: 28, end: 31, }, name: "tup", }, }, ), ], }, }, ), ], }, }, ), ], }, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__deeply_nested_tuples_no_block.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nlet tup = #(#(#(#(4))))\ntup.0.0.0.0\n" --- [ Assignment( Assignment { location: SrcSpan { start: 1, end: 24, }, value: Tuple { location: SrcSpan { start: 11, end: 24, }, elements: [ Tuple { location: SrcSpan { start: 13, end: 23, }, elements: [ Tuple { location: SrcSpan { start: 15, end: 22, }, elements: [ Tuple { location: SrcSpan { start: 17, end: 21, }, elements: [ Int { location: SrcSpan { start: 19, end: 20, }, value: "4", int_value: 4, }, ], }, ], }, ], }, ], }, pattern: Variable { location: SrcSpan { start: 5, end: 8, }, name: "tup", type_: (), origin: VariableOrigin { syntax: Variable( "tup", ), declaration: LetPattern, }, }, kind: Let, compiled_case: CompiledCase { tree: Fail, subject_variables: [], }, annotation: None, }, ), Expression( TupleIndex { location: SrcSpan { start: 25, end: 36, }, index: 0, tuple: TupleIndex { location: SrcSpan { start: 25, end: 34, }, index: 0, tuple: TupleIndex { location: SrcSpan { start: 25, end: 32, }, index: 0, tuple: TupleIndex { location: SrcSpan { start: 25, end: 30, }, index: 0, tuple: Var { location: SrcSpan { start: 25, end: 28, }, name: "tup", }, }, }, }, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__deprecation_attribute_on_type_variant.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\ntype Wibble {\n @deprecated(\"1\")\n Wibble1\n Wibble2\n}\n" --- Parsed { module: Module { name: "", documentation: [], type_info: (), definitions: [ TargetedDefinition { definition: CustomType( CustomType { location: SrcSpan { start: 1, end: 12, }, end_position: 61, name: "Wibble", name_location: SrcSpan { start: 6, end: 12, }, publicity: Private, constructors: [ RecordConstructor { location: SrcSpan { start: 40, end: 47, }, name_location: SrcSpan { start: 40, end: 47, }, name: "Wibble1", arguments: [], documentation: None, deprecation: Deprecated { message: "1", }, }, RecordConstructor { location: SrcSpan { start: 52, end: 59, }, name_location: SrcSpan { start: 52, end: 59, }, name: "Wibble2", arguments: [], documentation: None, deprecation: NotDeprecated, }, ], documentation: None, deprecation: NotDeprecated, opaque: false, parameters: [], typed_parameters: [], external_erlang: None, external_javascript: None, }, ), target: None, }, ], names: Names { local_types: {}, imported_modules: {}, type_variables: {}, local_value_constructors: {}, reexport_aliases: {}, }, unused_definition_positions: {}, }, extra: ModuleExtra { module_comments: [], doc_comments: [], comments: [], empty_lines: [], new_lines: [ 0, 14, 35, 47, 59, 61, ], trailing_commas: [], }, } ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__deprecation_without_message.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\n@deprecated()\npub fn main() -> Nil {\n Nil\n}\n" --- ----- SOURCE CODE @deprecated() pub fn main() -> Nil { Nil } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:1 │ 2 │ @deprecated() │ ^^^^^^^^^^^ A deprecation attribute must have a string message. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__discard_left_hand_side_of_concat_pattern.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\n case \"\" {\n _ <> rest -> rest\n }\n " --- ----- SOURCE CODE case "" { _ <> rest -> rest } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:11 │ 3 │ _ <> rest -> rest │ ^ This must be a string literal We can't tell what size this prefix should be so we don't know how to handle this pattern. If you want to match one character consider using `pop_grapheme` from the stdlib's `gleam/string` module. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__doesnt_issue_special_error_for_pythonic_import_if_slash.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: import one/two.three --- ----- SOURCE CODE import one/two.three ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:1:16 │ 1 │ import one/two.three │ ^^^^^ I was not expecting this Found a name, expected one of: - `{` ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__dot_access_function_call_in_case_clause_guard.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nlet my_string = \"hello\"\ncase my_string {\n _ if string.length(my_string) > 2 -> io.debug(\"doesn't work')\n}" --- ----- SOURCE CODE let my_string = "hello" case my_string { _ if string.length(my_string) > 2 -> io.debug("doesn't work') } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:4:10 │ 4 │ _ if string.length(my_string) > 2 -> io.debug("doesn't work') │ ^^^^^^^^^^^^^^^^^^^^^^^^ Unsupported expression Functions cannot be called in clause guards. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__echo_at_start_of_pipeline_wraps_the_whole_thing.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: echo 1 |> wibble |> wobble --- [ Expression( Echo { location: SrcSpan { start: 0, end: 26, }, keyword_end: 4, expression: Some( PipeLine { expressions: [ Int { location: SrcSpan { start: 5, end: 6, }, value: "1", int_value: 1, }, Var { location: SrcSpan { start: 10, end: 16, }, name: "wibble", }, Var { location: SrcSpan { start: 20, end: 26, }, name: "wobble", }, ], }, ), message: None, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__echo_cannot_have_an_expression_in_a_pipeline.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "[] |> echo fun |> wibble" --- [ Expression( PipeLine { expressions: [ List { location: SrcSpan { start: 0, end: 2, }, elements: [], tail: None, }, Echo { location: SrcSpan { start: 6, end: 10, }, keyword_end: 10, expression: None, message: None, }, ], }, ), Expression( PipeLine { expressions: [ Var { location: SrcSpan { start: 11, end: 14, }, name: "fun", }, Var { location: SrcSpan { start: 18, end: 24, }, name: "wibble", }, ], }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__echo_followed_by_expression_ends_where_expression_ends.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: echo wibble --- [ Expression( Echo { location: SrcSpan { start: 0, end: 11, }, keyword_end: 4, expression: Some( Var { location: SrcSpan { start: 5, end: 11, }, name: "wibble", }, ), message: None, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__echo_has_lower_precedence_than_binop.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: echo 1 + 1 --- [ Expression( Echo { location: SrcSpan { start: 0, end: 10, }, keyword_end: 4, expression: Some( BinOp { location: SrcSpan { start: 5, end: 10, }, name: AddInt, name_location: SrcSpan { start: 7, end: 8, }, left: Int { location: SrcSpan { start: 5, end: 6, }, value: "1", int_value: 1, }, right: Int { location: SrcSpan { start: 9, end: 10, }, value: "1", int_value: 1, }, }, ), message: None, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__echo_has_lower_precedence_than_pipeline.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: echo wibble |> wobble |> woo --- [ Expression( Echo { location: SrcSpan { start: 0, end: 28, }, keyword_end: 4, expression: Some( PipeLine { expressions: [ Var { location: SrcSpan { start: 5, end: 11, }, name: "wibble", }, Var { location: SrcSpan { start: 15, end: 21, }, name: "wobble", }, Var { location: SrcSpan { start: 25, end: 28, }, name: "woo", }, ], }, ), message: None, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__echo_in_a_pipeline.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "[] |> echo |> wibble" --- [ Expression( PipeLine { expressions: [ List { location: SrcSpan { start: 0, end: 2, }, elements: [], tail: None, }, Echo { location: SrcSpan { start: 6, end: 10, }, keyword_end: 10, expression: None, message: None, }, Var { location: SrcSpan { start: 14, end: 20, }, name: "wibble", }, ], }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__echo_with_assert_and_message_1.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: assert 1 == echo 2 as this_belongs_to_echo --- [ Assert( Assert { location: SrcSpan { start: 0, end: 42, }, value: BinOp { location: SrcSpan { start: 7, end: 42, }, name: Eq, name_location: SrcSpan { start: 9, end: 11, }, left: Int { location: SrcSpan { start: 7, end: 8, }, value: "1", int_value: 1, }, right: Echo { location: SrcSpan { start: 12, end: 42, }, keyword_end: 16, expression: Some( Int { location: SrcSpan { start: 17, end: 18, }, value: "2", int_value: 2, }, ), message: Some( Var { location: SrcSpan { start: 22, end: 42, }, name: "this_belongs_to_echo", }, ), }, }, message: None, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__echo_with_assert_and_message_2.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: assert echo True as this_belongs_to_echo --- [ Assert( Assert { location: SrcSpan { start: 0, end: 40, }, value: Echo { location: SrcSpan { start: 7, end: 40, }, keyword_end: 11, expression: Some( Var { location: SrcSpan { start: 12, end: 16, }, name: "True", }, ), message: Some( Var { location: SrcSpan { start: 20, end: 40, }, name: "this_belongs_to_echo", }, ), }, message: None, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__echo_with_assert_and_messages_1.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: assert 1 == echo 2 as this_belongs_to_echo as this_belongs_to_assert --- [ Assert( Assert { location: SrcSpan { start: 0, end: 68, }, value: BinOp { location: SrcSpan { start: 7, end: 42, }, name: Eq, name_location: SrcSpan { start: 9, end: 11, }, left: Int { location: SrcSpan { start: 7, end: 8, }, value: "1", int_value: 1, }, right: Echo { location: SrcSpan { start: 12, end: 42, }, keyword_end: 16, expression: Some( Int { location: SrcSpan { start: 17, end: 18, }, value: "2", int_value: 2, }, ), message: Some( Var { location: SrcSpan { start: 22, end: 42, }, name: "this_belongs_to_echo", }, ), }, }, message: Some( Var { location: SrcSpan { start: 46, end: 68, }, name: "this_belongs_to_assert", }, ), }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__echo_with_assert_and_messages_2.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: assert echo True as this_belongs_to_echo as this_belongs_to_assert --- [ Assert( Assert { location: SrcSpan { start: 0, end: 66, }, value: Echo { location: SrcSpan { start: 7, end: 40, }, keyword_end: 11, expression: Some( Var { location: SrcSpan { start: 12, end: 16, }, name: "True", }, ), message: Some( Var { location: SrcSpan { start: 20, end: 40, }, name: "this_belongs_to_echo", }, ), }, message: Some( Var { location: SrcSpan { start: 44, end: 66, }, name: "this_belongs_to_assert", }, ), }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__echo_with_assert_and_messages_3.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: assert echo 1 == 2 as this_belongs_to_echo as this_belongs_to_assert --- [ Assert( Assert { location: SrcSpan { start: 0, end: 68, }, value: Echo { location: SrcSpan { start: 7, end: 42, }, keyword_end: 11, expression: Some( BinOp { location: SrcSpan { start: 12, end: 18, }, name: Eq, name_location: SrcSpan { start: 14, end: 16, }, left: Int { location: SrcSpan { start: 12, end: 13, }, value: "1", int_value: 1, }, right: Int { location: SrcSpan { start: 17, end: 18, }, value: "2", int_value: 2, }, }, ), message: Some( Var { location: SrcSpan { start: 22, end: 42, }, name: "this_belongs_to_echo", }, ), }, message: Some( Var { location: SrcSpan { start: 46, end: 68, }, name: "this_belongs_to_assert", }, ), }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__echo_with_block.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "echo { 1 + 1 }" --- [ Expression( Echo { location: SrcSpan { start: 0, end: 14, }, keyword_end: 4, expression: Some( Block { location: SrcSpan { start: 5, end: 14, }, statements: [ Expression( BinOp { location: SrcSpan { start: 7, end: 12, }, name: AddInt, name_location: SrcSpan { start: 9, end: 10, }, left: Int { location: SrcSpan { start: 7, end: 8, }, value: "1", int_value: 1, }, right: Int { location: SrcSpan { start: 11, end: 12, }, value: "1", int_value: 1, }, }, ), ], }, ), message: None, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__echo_with_complex_expression.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "echo wibble as { this <> complex }" --- [ Expression( Echo { location: SrcSpan { start: 0, end: 34, }, keyword_end: 4, expression: Some( Var { location: SrcSpan { start: 5, end: 11, }, name: "wibble", }, ), message: Some( Block { location: SrcSpan { start: 15, end: 34, }, statements: [ Expression( BinOp { location: SrcSpan { start: 17, end: 32, }, name: Concatenate, name_location: SrcSpan { start: 22, end: 24, }, left: Var { location: SrcSpan { start: 17, end: 21, }, name: "this", }, right: Var { location: SrcSpan { start: 25, end: 32, }, name: "complex", }, }, ), ], }, ), }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__echo_with_let_assert_and_message.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: let assert 1 = echo 2 as this_belongs_to_echo --- [ Assignment( Assignment { location: SrcSpan { start: 0, end: 45, }, value: Echo { location: SrcSpan { start: 15, end: 45, }, keyword_end: 19, expression: Some( Int { location: SrcSpan { start: 20, end: 21, }, value: "2", int_value: 2, }, ), message: Some( Var { location: SrcSpan { start: 25, end: 45, }, name: "this_belongs_to_echo", }, ), }, pattern: Int { location: SrcSpan { start: 11, end: 12, }, value: "1", int_value: 1, }, kind: Assert { location: SrcSpan { start: 0, end: 10, }, assert_keyword_start: 4, message: None, }, compiled_case: CompiledCase { tree: Fail, subject_variables: [], }, annotation: None, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__echo_with_let_assert_and_messages.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: let assert 1 = echo 1 as this_belongs_to_echo as this_belongs_to_assert --- [ Assignment( Assignment { location: SrcSpan { start: 0, end: 71, }, value: Echo { location: SrcSpan { start: 15, end: 45, }, keyword_end: 19, expression: Some( Int { location: SrcSpan { start: 20, end: 21, }, value: "1", int_value: 1, }, ), message: Some( Var { location: SrcSpan { start: 25, end: 45, }, name: "this_belongs_to_echo", }, ), }, pattern: Int { location: SrcSpan { start: 11, end: 12, }, value: "1", int_value: 1, }, kind: Assert { location: SrcSpan { start: 0, end: 10, }, assert_keyword_start: 4, message: Some( Var { location: SrcSpan { start: 49, end: 71, }, name: "this_belongs_to_assert", }, ), }, compiled_case: CompiledCase { tree: Fail, subject_variables: [], }, annotation: None, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__echo_with_no_expressions_after_it.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: echo --- [ Expression( Echo { location: SrcSpan { start: 0, end: 4, }, keyword_end: 4, expression: None, message: None, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__echo_with_no_expressions_after_it_but_a_message.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: echo as message --- [ Expression( Echo { location: SrcSpan { start: 0, end: 15, }, keyword_end: 4, expression: None, message: Some( Var { location: SrcSpan { start: 8, end: 15, }, name: "message", }, ), }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__echo_with_panic.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "echo panic as \"a\"" --- [ Expression( Echo { location: SrcSpan { start: 0, end: 17, }, keyword_end: 4, expression: Some( Panic { location: SrcSpan { start: 5, end: 17, }, message: Some( String { location: SrcSpan { start: 14, end: 17, }, value: "a", }, ), }, ), message: None, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__echo_with_panic_and_message.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "echo panic as \"a\"" --- [ Expression( Echo { location: SrcSpan { start: 0, end: 17, }, keyword_end: 4, expression: Some( Panic { location: SrcSpan { start: 5, end: 17, }, message: Some( String { location: SrcSpan { start: 14, end: 17, }, value: "a", }, ), }, ), message: None, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__echo_with_panic_and_messages.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "echo panic as \"a\" as \"b\"" --- [ Expression( Echo { location: SrcSpan { start: 0, end: 24, }, keyword_end: 4, expression: Some( Panic { location: SrcSpan { start: 5, end: 17, }, message: Some( String { location: SrcSpan { start: 14, end: 17, }, value: "a", }, ), }, ), message: Some( String { location: SrcSpan { start: 21, end: 24, }, value: "b", }, ), }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__echo_with_simple_expression_1.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: echo wibble as message --- [ Expression( Echo { location: SrcSpan { start: 0, end: 22, }, keyword_end: 4, expression: Some( Var { location: SrcSpan { start: 5, end: 11, }, name: "wibble", }, ), message: Some( Var { location: SrcSpan { start: 15, end: 22, }, name: "message", }, ), }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__echo_with_simple_expression_2.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "echo wibble as \"message\"" --- [ Expression( Echo { location: SrcSpan { start: 0, end: 24, }, keyword_end: 4, expression: Some( Var { location: SrcSpan { start: 5, end: 11, }, name: "wibble", }, ), message: Some( String { location: SrcSpan { start: 15, end: 24, }, value: "message", }, ), }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__error_message_on_variable_starting_with_underscore.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\n pub fn main() {\n let val = _func_starting_with_underscore(1)\n }" --- ----- SOURCE CODE pub fn main() { let val = _func_starting_with_underscore(1) } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:15 │ 3 │ let val = _func_starting_with_underscore(1) │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ I'm expecting a lowercase name here Hint: Variable and module names start with a lowercase letter, and can contain a-z, 0-9, or _. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__error_message_on_variable_starting_with_underscore2.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\n pub fn main() {\n case 1 {\n 1 -> _with_underscore(1)\n }\n }" --- ----- SOURCE CODE pub fn main() { case 1 { 1 -> _with_underscore(1) } } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:4:12 │ 4 │ 1 -> _with_underscore(1) │ ^^^^^^^^^^^^^^^^ I'm expecting a lowercase name here Hint: Variable and module names start with a lowercase letter, and can contain a-z, 0-9, or _. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__external_attribute_on_type_variant.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\ntype Wibble {\n @external(erlang, \"one\", \"two\")\n Wibble1\n}\n" --- ----- SOURCE CODE type Wibble { @external(erlang, "one", "two") Wibble1 } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:5 │ 3 │ @external(erlang, "one", "two") │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This attribute cannot be used on a variant. Hint: Did you mean `@deprecated`? ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__external_attribute_with_custom_type.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\n@external(erlang, \"gleam_stdlib\", \"dict\")\n@external(javascript, \"./gleam_stdlib.d.ts\", \"Dict\")\npub type Dict(key, value)\n" --- Parsed { module: Module { name: "", documentation: [], type_info: (), definitions: [ TargetedDefinition { definition: CustomType( CustomType { location: SrcSpan { start: 96, end: 121, }, end_position: 121, name: "Dict", name_location: SrcSpan { start: 105, end: 109, }, publicity: Public, constructors: [], documentation: None, deprecation: NotDeprecated, opaque: false, parameters: [ ( SrcSpan { start: 110, end: 113, }, "key", ), ( SrcSpan { start: 115, end: 120, }, "value", ), ], typed_parameters: [], external_erlang: Some( ( "gleam_stdlib", "dict", SrcSpan { start: 1, end: 42, }, ), ), external_javascript: Some( ( "./gleam_stdlib.d.ts", "Dict", SrcSpan { start: 43, end: 95, }, ), ), }, ), target: None, }, ], names: Names { local_types: {}, imported_modules: {}, type_variables: {}, local_value_constructors: {}, reexport_aliases: {}, }, unused_definition_positions: {}, }, extra: ModuleExtra { module_comments: [], doc_comments: [], comments: [], empty_lines: [], new_lines: [ 0, 42, 95, 121, ], trailing_commas: [], }, } ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__external_attribute_with_non_fn_definition.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\n@external(erlang, \"module\", \"fun\")\npub type Fun = Fun\n" --- ----- SOURCE CODE @external(erlang, "module", "fun") pub type Fun = Fun ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:1 │ 2 │ @external(erlang, "module", "fun") │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ I was expecting a function definition after this ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__float_empty_exponent.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: 1.32e --- ----- SOURCE CODE 1.32e ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:1:1 │ 1 │ 1.32e │ ^^^^^ This float is missing an exponent Hint: Add an exponent or remove the trailing `e` ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__function_call_in_case_clause_guard.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nlet my_string = \"hello\"\ncase my_string {\n _ if length(my_string) > 2 -> io.debug(\"doesn't work')\n}" --- ----- SOURCE CODE let my_string = "hello" case my_string { _ if length(my_string) > 2 -> io.debug("doesn't work') } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:4:10 │ 4 │ _ if length(my_string) > 2 -> io.debug("doesn't work') │ ^^^^^^^^^^^^^^^^^ Unsupported expression Functions cannot be called in clause guards. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__function_definition_angle_generics_error.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "fn id(x: T) { x }" --- ----- SOURCE CODE fn id(x: T) { x } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:1:6 │ 1 │ fn id(x: T) { x } │ ^ I was expecting `(` here. Generic function type variables do not need to be predeclared like they would be in some other languages, instead they are written with lowercase names. fn example(argument: generic) -> generic See: https://tour.gleam.run/functions/generic-functions/ ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__function_inside_a_type.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\ntype Wibble {\n fn wobble() {}\n}\n" --- ----- SOURCE CODE type Wibble { fn wobble() {} } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:3 │ 3 │ fn wobble() {} │ ^^ I was not expecting this Found the keyword `fn`, expected one of: - `}` - a record constructor Hint: Gleam is not an object oriented programming language so functions are declared separately from types. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__function_invalid_signature.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nfn f(a, \"b\") -> String {\n a <> b\n}\n" --- ----- SOURCE CODE fn f(a, "b") -> String { a <> b } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:9 │ 2 │ fn f(a, "b") -> String { │ ^^^ I was not expecting this Found a String, expected one of: - `)` - a function parameter ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__function_type_invalid_param_type.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nfn f(g: fn(Int, 1) -> Int) -> Int {\n g(0, 1)\n}\n" --- ----- SOURCE CODE fn f(g: fn(Int, 1) -> Int) -> Int { g(0, 1) } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:17 │ 2 │ fn f(g: fn(Int, 1) -> Int) -> Int { │ ^ I was not expecting this Found an Int, expected one of: - `)` - a type ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__if_like_expression.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub fn main() {\n let a = if wibble {\n wobble\n }\n}\n" --- ----- SOURCE CODE pub fn main() { let a = if wibble { wobble } } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:11 │ 3 │ let a = if wibble { │ ^^ Gleam doesn't have if expressions If you want to write a conditional expression you can use a `case`: case condition { True -> todo False -> todo } See: https://tour.gleam.run/flow-control/case-expressions/ ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__import_type.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "import wibble.{type Wobble, Wobble, type Wabble}" --- Parsed { module: Module { name: "", documentation: [], type_info: (), definitions: [ TargetedDefinition { definition: Import( Import { documentation: None, location: SrcSpan { start: 0, end: 48, }, module_location: SrcSpan { start: 7, end: 13, }, module: "wibble", as_name: None, unqualified_values: [ UnqualifiedImport { location: SrcSpan { start: 28, end: 34, }, imported_name_location: SrcSpan { start: 28, end: 34, }, name: "Wobble", as_name: None, }, ], unqualified_types: [ UnqualifiedImport { location: SrcSpan { start: 15, end: 26, }, imported_name_location: SrcSpan { start: 20, end: 26, }, name: "Wobble", as_name: None, }, UnqualifiedImport { location: SrcSpan { start: 36, end: 47, }, imported_name_location: SrcSpan { start: 41, end: 47, }, name: "Wabble", as_name: None, }, ], package: (), }, ), target: None, }, ], names: Names { local_types: {}, imported_modules: {}, type_variables: {}, local_value_constructors: {}, reexport_aliases: {}, }, unused_definition_positions: {}, }, extra: ModuleExtra { module_comments: [], doc_comments: [], comments: [], empty_lines: [], new_lines: [], trailing_commas: [], }, } ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__incomplete_function.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: fn() --- ----- SOURCE CODE fn() ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:1:1 │ 1 │ fn() │ ^^^^ This function does not have a body ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__inner_single_quote_parses.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nlet a = \"inner 'quotes'\"\n" --- [ Assignment( Assignment { location: SrcSpan { start: 1, end: 25, }, value: String { location: SrcSpan { start: 9, end: 25, }, value: "inner 'quotes'", }, pattern: Variable { location: SrcSpan { start: 5, end: 6, }, name: "a", type_: (), origin: VariableOrigin { syntax: Variable( "a", ), declaration: LetPattern, }, }, kind: Let, compiled_case: CompiledCase { tree: Fail, subject_variables: [], }, annotation: None, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__internal_attribute_on_type_variant.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\ntype Wibble {\n @internal\n Wibble1\n}\n" --- ----- SOURCE CODE type Wibble { @internal Wibble1 } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:5 │ 3 │ @internal │ ^^^^^^^^^ This attribute cannot be used on a variant. Hint: Did you mean `@deprecated`? ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__invalid_label_shorthand.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub fn main() {\n wibble(:)\n}\n" --- ----- SOURCE CODE pub fn main() { wibble(:) } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:10 │ 3 │ wibble(:) │ ^ I was not expecting this Found `:`, expected one of: - `)` ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__invalid_label_shorthand_2.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub fn main() {\n wibble(:,)\n}\n" --- ----- SOURCE CODE pub fn main() { wibble(:,) } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:10 │ 3 │ wibble(:,) │ ^ I was not expecting this Found `:`, expected one of: - `)` ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__invalid_label_shorthand_3.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub fn main() {\n wibble(:arg)\n}\n" --- ----- SOURCE CODE pub fn main() { wibble(:arg) } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:10 │ 3 │ wibble(:arg) │ ^ I was not expecting this Found `:`, expected one of: - `)` ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__invalid_label_shorthand_4.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub fn main() {\n wibble(arg::)\n}\n" --- ----- SOURCE CODE pub fn main() { wibble(arg::) } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:14 │ 3 │ wibble(arg::) │ ^ I was not expecting this Found `:`, expected one of: - `)` ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__invalid_label_shorthand_5.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub fn main() {\n wibble(arg::arg)\n}\n" --- ----- SOURCE CODE pub fn main() { wibble(arg::arg) } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:14 │ 3 │ wibble(arg::arg) │ ^ I was not expecting this Found `:`, expected one of: - `)` ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__invalid_left_paren_in_case_clause_guard.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nlet my_string = \"hello\"\ncase my_string {\n _ if string.length( > 2 -> io.debug(\"doesn't work')\n}" --- ----- SOURCE CODE let my_string = "hello" case my_string { _ if string.length( > 2 -> io.debug("doesn't work') } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:4:23 │ 4 │ _ if string.length( > 2 -> io.debug("doesn't work') │ ^ I was not expecting this Found `(`, expected one of: - `->` Hint: Did you mean to wrap a multi line clause in curly braces? ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__invalid_pattern_label_shorthand.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub fn main() {\n let Wibble(:) = todo\n}\n" --- ----- SOURCE CODE pub fn main() { let Wibble(:) = todo } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:14 │ 3 │ let Wibble(:) = todo │ ^ I was not expecting this Found `:`, expected one of: - `)` ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__invalid_pattern_label_shorthand_2.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub fn main() {\n let Wibble(:arg) = todo\n}\n" --- ----- SOURCE CODE pub fn main() { let Wibble(:arg) = todo } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:14 │ 3 │ let Wibble(:arg) = todo │ ^ I was not expecting this Found `:`, expected one of: - `)` ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__invalid_pattern_label_shorthand_3.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub fn main() {\n let Wibble(arg::) = todo\n}\n" --- ----- SOURCE CODE pub fn main() { let Wibble(arg::) = todo } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:18 │ 3 │ let Wibble(arg::) = todo │ ^ I was not expecting this Found `:`, expected one of: - `)` ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__invalid_pattern_label_shorthand_4.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub fn main() {\n let Wibble(arg: arg:) = todo\n}\n" --- ----- SOURCE CODE pub fn main() { let Wibble(arg: arg:) = todo } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:22 │ 3 │ let Wibble(arg: arg:) = todo │ ^ I was not expecting this Found `:`, expected one of: - `)` ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__invalid_pattern_label_shorthand_5.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub fn main() {\n let Wibble(arg1: arg2:) = todo\n}\n" --- ----- SOURCE CODE pub fn main() { let Wibble(arg1: arg2:) = todo } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:24 │ 3 │ let Wibble(arg1: arg2:) = todo │ ^ I was not expecting this Found `:`, expected one of: - `)` ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__list_spread_as_first_item_followed_by_other_items.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub fn main() -> Nil {\n let xs = [1, 2, 3]\n [..xs, 3 + 3, 4]\n}\n" --- ----- SOURCE CODE pub fn main() -> Nil { let xs = [1, 2, 3] [..xs, 3 + 3, 4] } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:4:4 │ 4 │ [..xs, 3 + 3, 4] │ ^^^^ I wasn't expecting elements after this Lists are immutable and singly-linked, so to append items to them all the elements of a list would need to be copied into a new list. This would be slow, so there is no built-in syntax for it. Hint: Prepend items to the list and then reverse it once you are done. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__list_spread_followed_by_extra_item_and_another_spread.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub fn main() -> Nil {\n let xs = [1, 2, 3]\n let ys = [5, 6, 7]\n [..xs, 4, ..ys]\n}\n" --- ----- SOURCE CODE pub fn main() -> Nil { let xs = [1, 2, 3] let ys = [5, 6, 7] [..xs, 4, ..ys] } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:5:4 │ 5 │ [..xs, 4, ..ys] │ ^^^^ I wasn't expecting elements after this Lists are immutable and singly-linked, so to append items to them all the elements of a list would need to be copied into a new list. This would be slow, so there is no built-in syntax for it. Hint: Prepend items to the list and then reverse it once you are done. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__list_spread_followed_by_extra_items.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub fn main() -> Nil {\n let xs = [1, 2, 3]\n [1, 2, ..xs, 3 + 3, 4]\n}\n" --- ----- SOURCE CODE pub fn main() -> Nil { let xs = [1, 2, 3] [1, 2, ..xs, 3 + 3, 4] } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:4:10 │ 4 │ [1, 2, ..xs, 3 + 3, 4] │ ^^^^ I wasn't expecting elements after this Lists are immutable and singly-linked, so to append items to them all the elements of a list would need to be copied into a new list. This would be slow, so there is no built-in syntax for it. Hint: Prepend items to the list and then reverse it once you are done. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__list_spread_followed_by_other_spread.snap ================================================ --- source: compiler-core/src/parse/tests.rs assertion_line: 959 expression: "\npub fn main() -> Nil {\n let xs = [1, 2, 3]\n let ys = [5, 6, 7]\n [1, ..xs, ..ys]\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn main() -> Nil { let xs = [1, 2, 3] let ys = [5, 6, 7] [1, ..xs, ..ys] } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:5:13 │ 5 │ [1, ..xs, ..ys] │ -- ^^ I wasn't expecting a second list here │ │ │ You're using a list here Lists are immutable and singly-linked, so to join two or more lists all the elements of the lists would need to be copied into a new list. This would be slow, so there is no built-in syntax for it. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__list_spread_with_no_tail_in_the_middle_of_a_list.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub fn main() -> Nil {\n let xs = [1, 2, 3]\n [1, 2, .., 3 + 3, 4]\n}\n" --- ----- SOURCE CODE pub fn main() -> Nil { let xs = [1, 2, 3] [1, 2, .., 3 + 3, 4] } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:4:10 │ 4 │ [1, 2, .., 3 + 3, 4] │ ^^ I was expecting a value after this spread If a list expression has a spread then a tail must also be given. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__missing_constructor_arguments.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub type A {\n A(Int)\n}\n\nconst a = A()\n" --- ----- SOURCE CODE pub type A { A(Int) } const a = A() ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:6:12 │ 6 │ const a = A() │ ^^ I was expecting arguments here A record must be passed arguments when constructed. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__missing_target.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\n@target()\npub fn one() {}" --- ----- SOURCE CODE @target() pub fn one() {} ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:8 │ 2 │ @target() │ ^ I was expecting a target name after this Try `erlang`, `javascript`. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__missing_target_and_bracket.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\n@target(\npub fn one() {}" --- ----- SOURCE CODE @target( pub fn one() {} ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:8 │ 2 │ @target( │ ^ I was expecting a target name after this Try `erlang`, `javascript`. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__missing_type_constructor_arguments_in_type_definition.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub type A() {\n A(Int)\n}\n" --- ----- SOURCE CODE pub type A() { A(Int) } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:11 │ 2 │ pub type A() { │ ^^ I was expecting generic parameters here A generic type must have at least a generic parameter. Hint: If a type is not generic you should omit the `()`. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__multiple_deprecation_attribute_on_type_variant.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\ntype Wibble {\n @deprecated(\"1\")\n @deprecated(\"2\")\n Wibble1\n Wibble2\n}\n" --- ----- SOURCE CODE type Wibble { @deprecated("1") @deprecated("2") Wibble1 Wibble2 } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:4:5 │ 4 │ @deprecated("2") │ ^^^^^^^^^^^ Duplicate attribute This attribute has already been given. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__multiple_deprecation_attributes.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\n@deprecated(\"1\")\n@deprecated(\"2\")\npub fn main() -> Nil {\n Nil\n}\n" --- ----- SOURCE CODE @deprecated("1") @deprecated("2") pub fn main() -> Nil { Nil } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:1 │ 3 │ @deprecated("2") │ ^^^^^^^^^^^ Duplicate attribute This attribute has already been given. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__multiple_external_for_same_project_erlang.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\n@external(erlang, \"one\", \"two\")\n@external(erlang, \"three\", \"four\")\npub fn one(x: Int) -> Int {\n todo\n}\n" --- ----- SOURCE CODE @external(erlang, "one", "two") @external(erlang, "three", "four") pub fn one(x: Int) -> Int { todo } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:1 │ 3 │ @external(erlang, "three", "four") │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Duplicate attribute This attribute has already been given. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__multiple_external_for_same_project_javascript.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\n@external(javascript, \"one\", \"two\")\n@external(javascript, \"three\", \"four\")\npub fn one(x: Int) -> Int {\n todo\n}\n" --- ----- SOURCE CODE @external(javascript, "one", "two") @external(javascript, "three", "four") pub fn one(x: Int) -> Int { todo } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:1 │ 3 │ @external(javascript, "three", "four") │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Duplicate attribute This attribute has already been given. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__multiple_internal_attributes.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\n@internal\n@internal\npub fn main() -> Nil {\n Nil\n}\n" --- ----- SOURCE CODE @internal @internal pub fn main() -> Nil { Nil } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:1 │ 3 │ @internal │ ^^^^^^^^^ Duplicate attribute This attribute has already been given. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__multiple_unsupported_attributes_on_type_variant.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\ntype Wibble {\n @external(erlang, \"one\", \"two\")\n @target(erlang)\n @internal\n Wibble1\n}\n" --- ----- SOURCE CODE type Wibble { @external(erlang, "one", "two") @target(erlang) @internal Wibble1 } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:5 │ 3 │ ╭ @external(erlang, "one", "two") 4 │ │ @target(erlang) 5 │ │ @internal │ ╰─────────────^ This attribute cannot be used on a variant. Hint: Did you mean `@deprecated`? ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__nested_block.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "{ 1 { 1.0 2.0 } 3 }" --- [ Expression( Block { location: SrcSpan { start: 0, end: 19, }, statements: [ Expression( Int { location: SrcSpan { start: 2, end: 3, }, value: "1", int_value: 1, }, ), Expression( Block { location: SrcSpan { start: 4, end: 15, }, statements: [ Expression( Float { location: SrcSpan { start: 6, end: 9, }, value: "1.0", float_value: LiteralFloatValue( 1.0, ), }, ), Expression( Float { location: SrcSpan { start: 10, end: 13, }, value: "2.0", float_value: LiteralFloatValue( 2.0, ), }, ), ], }, ), Expression( Int { location: SrcSpan { start: 16, end: 17, }, value: "3", int_value: 3, }, ), ], }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__nested_tuple_access_after_function.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: tuple().0.1 --- [ Expression( TupleIndex { location: SrcSpan { start: 0, end: 11, }, index: 1, tuple: TupleIndex { location: SrcSpan { start: 0, end: 9, }, index: 0, tuple: Call { location: SrcSpan { start: 0, end: 7, }, fun: Var { location: SrcSpan { start: 0, end: 5, }, name: "tuple", }, arguments: [], }, }, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__nested_tuples.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nlet tup = #(#(5, 6))\n{tup.0}.1\n" --- [ Assignment( Assignment { location: SrcSpan { start: 1, end: 21, }, value: Tuple { location: SrcSpan { start: 11, end: 21, }, elements: [ Tuple { location: SrcSpan { start: 13, end: 20, }, elements: [ Int { location: SrcSpan { start: 15, end: 16, }, value: "5", int_value: 5, }, Int { location: SrcSpan { start: 18, end: 19, }, value: "6", int_value: 6, }, ], }, ], }, pattern: Variable { location: SrcSpan { start: 5, end: 8, }, name: "tup", type_: (), origin: VariableOrigin { syntax: Variable( "tup", ), declaration: LetPattern, }, }, kind: Let, compiled_case: CompiledCase { tree: Fail, subject_variables: [], }, annotation: None, }, ), Expression( TupleIndex { location: SrcSpan { start: 22, end: 31, }, index: 1, tuple: Block { location: SrcSpan { start: 22, end: 29, }, statements: [ Expression( TupleIndex { location: SrcSpan { start: 23, end: 28, }, index: 0, tuple: Var { location: SrcSpan { start: 23, end: 26, }, name: "tup", }, }, ), ], }, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__nested_tuples_no_block.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nlet tup = #(#(5, 6))\ntup.0.1\n" --- [ Assignment( Assignment { location: SrcSpan { start: 1, end: 21, }, value: Tuple { location: SrcSpan { start: 11, end: 21, }, elements: [ Tuple { location: SrcSpan { start: 13, end: 20, }, elements: [ Int { location: SrcSpan { start: 15, end: 16, }, value: "5", int_value: 5, }, Int { location: SrcSpan { start: 18, end: 19, }, value: "6", int_value: 6, }, ], }, ], }, pattern: Variable { location: SrcSpan { start: 5, end: 8, }, name: "tup", type_: (), origin: VariableOrigin { syntax: Variable( "tup", ), declaration: LetPattern, }, }, kind: Let, compiled_case: CompiledCase { tree: Fail, subject_variables: [], }, annotation: None, }, ), Expression( TupleIndex { location: SrcSpan { start: 22, end: 29, }, index: 1, tuple: TupleIndex { location: SrcSpan { start: 22, end: 27, }, index: 0, tuple: Var { location: SrcSpan { start: 22, end: 25, }, name: "tup", }, }, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__no_eq_after_binding_snapshot_1.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: let wibble --- ----- SOURCE CODE let wibble ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:1:5 │ 1 │ let wibble │ ^^^^^^ I was expecting a '=' after this ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__no_eq_after_binding_snapshot_2.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "let wibble\n wibble = 4" --- ----- SOURCE CODE let wibble wibble = 4 ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:1:5 │ 1 │ let wibble │ ^^^^^^ I was expecting a '=' after this ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__no_let_binding_snapshot_1.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: wibble = 4 --- ----- SOURCE CODE wibble = 4 ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:1:8 │ 1 │ wibble = 4 │ ^ There must be a 'let' to bind variable to value See: https://tour.gleam.run/basics/assignments/ Hint: Use let for binding. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__no_let_binding_snapshot_2.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "wibble:Int = 4" --- ----- SOURCE CODE wibble:Int = 4 ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:1:7 │ 1 │ wibble:Int = 4 │ ^ There must be a 'let' to bind variable to value See: https://tour.gleam.run/basics/assignments/ Hint: Use let for binding. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__no_let_binding_snapshot_3.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "let wobble:Int = 32\n wobble = 42" --- ----- SOURCE CODE let wobble:Int = 32 wobble = 42 ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:16 │ 2 │ wobble = 42 │ ^ There must be a 'let' to bind variable to value See: https://tour.gleam.run/basics/assignments/ Hint: Use let for binding. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__non_module_level_function_with_a_name.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub fn main() {\n fn my() { 1 }\n}\n" --- ----- SOURCE CODE pub fn main() { fn my() { 1 } } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:6 │ 3 │ fn my() { 1 } │ ^^ I was not expecting this Found a name, expected one of: - `(` Hint: Only module-level functions can be named. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__non_module_level_function_with_not_a_name.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub fn main() {\n fn @() { 1 } // wrong token and not a name\n}\n" --- ----- SOURCE CODE pub fn main() { fn @() { 1 } // wrong token and not a name } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:6 │ 3 │ fn @() { 1 } // wrong token and not a name │ ^ I was not expecting this Found `@`, expected one of: - `(` ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__operator_in_pattern_size.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "let assert <> = <<>>" --- [ Assignment( Assignment { location: SrcSpan { start: 0, end: 50, }, value: BitArray { location: SrcSpan { start: 46, end: 50, }, segments: [], }, pattern: BitArray { location: SrcSpan { start: 11, end: 43, }, segments: [ BitArraySegment { location: SrcSpan { start: 13, end: 17, }, value: Variable { location: SrcSpan { start: 13, end: 17, }, name: "size", type_: (), origin: VariableOrigin { syntax: Variable( "size", ), declaration: LetPattern, }, }, options: [], type_: (), }, BitArraySegment { location: SrcSpan { start: 19, end: 41, }, value: Variable { location: SrcSpan { start: 19, end: 26, }, name: "payload", type_: (), origin: VariableOrigin { syntax: Variable( "payload", ), declaration: LetPattern, }, }, options: [ Size { location: SrcSpan { start: 27, end: 41, }, value: BitArraySize( BinaryOperator { location: SrcSpan { start: 32, end: 40, }, operator: Subtract, left: Variable { location: SrcSpan { start: 32, end: 36, }, name: "size", constructor: None, type_: (), }, right: Int { location: SrcSpan { start: 39, end: 40, }, value: "1", int_value: 1, }, }, ), short_form: false, }, ], type_: (), }, ], }, kind: Assert { location: SrcSpan { start: 0, end: 10, }, assert_keyword_start: 4, message: None, }, compiled_case: CompiledCase { tree: Fail, subject_variables: [], }, annotation: None, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__panic_with_echo.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "panic as echo \"string\"" --- [ Expression( Panic { location: SrcSpan { start: 0, end: 22, }, message: Some( Echo { location: SrcSpan { start: 9, end: 22, }, keyword_end: 13, expression: Some( String { location: SrcSpan { start: 14, end: 22, }, value: "string", }, ), message: None, }, ), }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__panic_with_echo_and_message.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: panic as echo wibble as message --- [ Expression( Panic { location: SrcSpan { start: 0, end: 31, }, message: Some( Echo { location: SrcSpan { start: 9, end: 31, }, keyword_end: 13, expression: Some( Var { location: SrcSpan { start: 14, end: 20, }, name: "wibble", }, ), message: Some( Var { location: SrcSpan { start: 24, end: 31, }, name: "message", }, ), }, ), }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__prepend_no_elements_to_const_list.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nconst wibble = [2, 3]\nconst wobble = [..wibble]\n" --- ----- SOURCE CODE const wibble = [2, 3] const wobble = [..wibble] ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:16 │ 3 │ const wobble = [..wibble] │ ^^^^^^^^^^ This spread does nothing See: https://tour.gleam.run/basics/lists/ Hint: Try prepending some elements [1, 2, ..list]. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__prepend_to_const_list_with_multiple_spreads.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nconst wibble = [2, 3]\nconst wobble = [0, 1]\nconst wubble = [..wobble, ..wibble]\n" --- ----- SOURCE CODE const wibble = [2, 3] const wobble = [0, 1] const wubble = [..wobble, ..wibble] ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:4:27 │ 4 │ const wubble = [..wobble, ..wibble] │ -- ^^ I wasn't expecting a second list here │ │ │ You're using a list here Lists are immutable and singly-linked, so to join two or more lists all the elements of the lists would need to be copied into a new list. This would be slow, so there is no built-in syntax for it. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__prepend_to_const_list_with_no_tail.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nconst wibble = [1, 2, ..]\n" --- ----- SOURCE CODE const wibble = [1, 2, ..] ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:23 │ 2 │ const wibble = [1, 2, ..] │ ^^ I was expecting a value after this spread If a list expression has a spread then a tail must also be given. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__prepend_to_const_list_without_comma.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nconst wibble = [2, 3]\nconst wobble = [1 ..wibble]\n" --- ----- SOURCE CODE const wibble = [2, 3] const wobble = [1 ..wibble] ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:19 │ 3 │ const wobble = [1 ..wibble] │ ^^ I was not expecting this Found `..`, expected one of: - `]` - a constant value ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__private_internal_const.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\n@internal\nconst wibble = 1\n" --- ----- SOURCE CODE @internal const wibble = 1 ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:1 │ 2 │ @internal │ ^^^^^^^^^ Redundant internal attribute Only a public definition can be annotated as internal. Hint: Remove the `@internal` annotation. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__private_internal_function.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\n@internal\nfn wibble() { todo }\n" --- ----- SOURCE CODE @internal fn wibble() { todo } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:1 │ 2 │ @internal │ ^^^^^^^^^ Redundant internal attribute Only a public definition can be annotated as internal. Hint: Remove the `@internal` annotation. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__private_internal_type.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\n@internal\ntype Wibble {\n Wibble\n}\n" --- ----- SOURCE CODE @internal type Wibble { Wibble } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:1 │ 2 │ @internal │ ^^^^^^^^^ Redundant internal attribute Only a public definition can be annotated as internal. Hint: Remove the `@internal` annotation. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__private_internal_type_alias.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\n@internal\ntype Alias = Int\n" --- ----- SOURCE CODE @internal type Alias = Int ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:1 │ 2 │ @internal │ ^^^^^^^^^ Redundant internal attribute Only a public definition can be annotated as internal. Hint: Remove the `@internal` annotation. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__private_opaque_type_is_parsed.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "opaque type Wibble { Wobble }" --- Parsed { module: Module { name: "", documentation: [], type_info: (), definitions: [ TargetedDefinition { definition: CustomType( CustomType { location: SrcSpan { start: 0, end: 18, }, end_position: 29, name: "Wibble", name_location: SrcSpan { start: 12, end: 18, }, publicity: Private, constructors: [ RecordConstructor { location: SrcSpan { start: 21, end: 27, }, name_location: SrcSpan { start: 21, end: 27, }, name: "Wobble", arguments: [], documentation: None, deprecation: NotDeprecated, }, ], documentation: None, deprecation: NotDeprecated, opaque: true, parameters: [], typed_parameters: [], external_erlang: None, external_javascript: None, }, ), target: None, }, ], names: Names { local_types: {}, imported_modules: {}, type_variables: {}, local_value_constructors: {}, reexport_aliases: {}, }, unused_definition_positions: {}, }, extra: ModuleExtra { module_comments: [], doc_comments: [], comments: [], empty_lines: [], new_lines: [], trailing_commas: [], }, } ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__pub_function_inside_a_type.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\ntype Wibble {\n pub fn wobble() {}\n}\n" --- ----- SOURCE CODE type Wibble { pub fn wobble() {} } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:3 │ 3 │ pub fn wobble() {} │ ^^^ I was not expecting this Found the keyword `pub`, expected one of: - `}` - a record constructor Hint: Gleam is not an object oriented programming language so functions are declared separately from types. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__record_access_no_label.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\ntype Wibble {\n Wibble(wibble: String)\n}\n\nfn wobble() {\n Wibble(\"a\").\n}\n" --- Parsed { module: Module { name: "", documentation: [], type_info: (), definitions: [ TargetedDefinition { definition: CustomType( CustomType { location: SrcSpan { start: 1, end: 12, }, end_position: 43, name: "Wibble", name_location: SrcSpan { start: 6, end: 12, }, publicity: Private, constructors: [ RecordConstructor { location: SrcSpan { start: 19, end: 41, }, name_location: SrcSpan { start: 19, end: 25, }, name: "Wibble", arguments: [ RecordConstructorArg { label: Some( ( SrcSpan { start: 26, end: 32, }, "wibble", ), ), ast: Constructor( TypeAstConstructor { location: SrcSpan { start: 34, end: 40, }, name_location: SrcSpan { start: 34, end: 40, }, module: None, name: "String", arguments: [], start_parentheses: None, }, ), location: SrcSpan { start: 26, end: 40, }, type_: (), doc: None, }, ], documentation: None, deprecation: NotDeprecated, }, ], documentation: None, deprecation: NotDeprecated, opaque: false, parameters: [], typed_parameters: [], external_erlang: None, external_javascript: None, }, ), target: None, }, TargetedDefinition { definition: Function( Function { location: SrcSpan { start: 45, end: 56, }, body_start: Some( 57, ), end_position: 75, name: Some( ( SrcSpan { start: 48, end: 54, }, "wobble", ), ), arguments: [], body: [ Expression( FieldAccess { location: SrcSpan { start: 61, end: 73, }, label_location: SrcSpan { start: 72, end: 73, }, label: "", container: Call { location: SrcSpan { start: 61, end: 72, }, fun: Var { location: SrcSpan { start: 61, end: 67, }, name: "Wibble", }, arguments: [ CallArg { label: None, location: SrcSpan { start: 68, end: 71, }, value: String { location: SrcSpan { start: 68, end: 71, }, value: "a", }, implicit: None, }, ], }, }, ), ], publicity: Private, deprecation: NotDeprecated, return_annotation: None, return_type: (), documentation: None, external_erlang: None, external_javascript: None, implementations: Implementations { gleam: true, can_run_on_erlang: true, can_run_on_javascript: true, uses_erlang_externals: false, uses_javascript_externals: false, }, purity: Pure, }, ), target: None, }, ], names: Names { local_types: {}, imported_modules: {}, type_variables: {}, local_value_constructors: {}, reexport_aliases: {}, }, unused_definition_positions: {}, }, extra: ModuleExtra { module_comments: [], doc_comments: [], comments: [], empty_lines: [ 44, ], new_lines: [ 0, 14, 41, 43, 44, 58, 73, 75, ], trailing_commas: [], }, } ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__repeated_echos.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: echo echo echo 1 --- [ Expression( Echo { location: SrcSpan { start: 0, end: 16, }, keyword_end: 4, expression: Some( Echo { location: SrcSpan { start: 5, end: 16, }, keyword_end: 9, expression: Some( Echo { location: SrcSpan { start: 10, end: 16, }, keyword_end: 14, expression: Some( Int { location: SrcSpan { start: 15, end: 16, }, value: "1", int_value: 1, }, ), message: None, }, ), message: None, }, ), message: None, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__reserved_auto.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: const auto = 1 --- ----- SOURCE CODE const auto = 1 ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:1:7 │ 1 │ const auto = 1 │ ^^^^ This is a reserved word Hint: I was expecting to see a name here. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__reserved_delegate.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: const delegate = 1 --- ----- SOURCE CODE const delegate = 1 ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:1:7 │ 1 │ const delegate = 1 │ ^^^^^^^^ This is a reserved word Hint: I was expecting to see a name here. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__reserved_derive.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: const derive = 1 --- ----- SOURCE CODE const derive = 1 ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:1:7 │ 1 │ const derive = 1 │ ^^^^^^ This is a reserved word Hint: I was expecting to see a name here. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__reserved_echo.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: const echo = 1 --- ----- SOURCE CODE const echo = 1 ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:1:7 │ 1 │ const echo = 1 │ ^^^^ This is a reserved word Hint: I was expecting to see a name here. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__reserved_else.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: const else = 1 --- ----- SOURCE CODE const else = 1 ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:1:7 │ 1 │ const else = 1 │ ^^^^ This is a reserved word Hint: I was expecting to see a name here. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__reserved_implement.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: const implement = 1 --- ----- SOURCE CODE const implement = 1 ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:1:7 │ 1 │ const implement = 1 │ ^^^^^^^^^ This is a reserved word Hint: I was expecting to see a name here. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__reserved_macro.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: const macro = 1 --- ----- SOURCE CODE const macro = 1 ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:1:7 │ 1 │ const macro = 1 │ ^^^^^ This is a reserved word Hint: I was expecting to see a name here. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__reserved_test.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: const test = 1 --- ----- SOURCE CODE const test = 1 ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:1:7 │ 1 │ const test = 1 │ ^^^^ This is a reserved word Hint: I was expecting to see a name here. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__semicolons.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "{ 2 + 3; - -5; }" --- ----- SOURCE CODE { 2 + 3; - -5; } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:1:8 │ 1 │ { 2 + 3; - -5; } │ ^ Remove this semicolon Hint: Semicolons used to be whitespace and did nothing. You can safely remove them without your program changing. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__special_error_for_pythonic_import.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: import gleam.io --- ----- SOURCE CODE import gleam.io ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:1:13 │ 1 │ import gleam.io │ ^ I was expecting either `/` or `.{` here. Perhaps you meant one of: import gleam/io import gleam.{item} ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__special_error_for_pythonic_neste_import.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: import one.two.three --- ----- SOURCE CODE import one.two.three ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:1:11 │ 1 │ import one.two.three │ ^ I was expecting either `/` or `.{` here. Perhaps you meant one of: import one/two import one.{item} ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__string_concatenation_in_case_clause_guard.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nlet my_string = \"hello \"\ncase my_string {\n _ if my_string <> \"world\" == \"hello world\" -> io.debug(\"ok\")\n}" --- [ Assignment( Assignment { location: SrcSpan { start: 1, end: 25, }, value: String { location: SrcSpan { start: 17, end: 25, }, value: "hello ", }, pattern: Variable { location: SrcSpan { start: 5, end: 14, }, name: "my_string", type_: (), origin: VariableOrigin { syntax: Variable( "my_string", ), declaration: LetPattern, }, }, kind: Let, compiled_case: CompiledCase { tree: Fail, subject_variables: [], }, annotation: None, }, ), Expression( Case { location: SrcSpan { start: 26, end: 109, }, subjects: [ Var { location: SrcSpan { start: 31, end: 40, }, name: "my_string", }, ], clauses: Some( [ Clause { location: SrcSpan { start: 47, end: 107, }, pattern: [ Discard { name: "_", location: SrcSpan { start: 47, end: 48, }, type_: (), }, ], alternative_patterns: [], guard: Some( BinaryOperator { location: SrcSpan { start: 52, end: 89, }, operator: Eq, left: BinaryOperator { location: SrcSpan { start: 52, end: 72, }, operator: Concatenate, left: Var { location: SrcSpan { start: 52, end: 61, }, type_: (), name: "my_string", definition_location: SrcSpan { start: 0, end: 0, }, origin: VariableOrigin { syntax: Generated, declaration: Generated, }, }, right: Constant( String { location: SrcSpan { start: 65, end: 72, }, value: "world", }, ), }, right: Constant( String { location: SrcSpan { start: 76, end: 89, }, value: "hello world", }, ), }, ), then: Call { location: SrcSpan { start: 93, end: 107, }, fun: FieldAccess { location: SrcSpan { start: 93, end: 101, }, label_location: SrcSpan { start: 96, end: 101, }, label: "debug", container: Var { location: SrcSpan { start: 93, end: 95, }, name: "io", }, }, arguments: [ CallArg { label: None, location: SrcSpan { start: 102, end: 106, }, value: String { location: SrcSpan { start: 102, end: 106, }, value: "ok", }, implicit: None, }, ], }, }, ], ), }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__string_single_char_suggestion.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\n pub fn main() {\n let a = 'example'\n }\n " --- ----- SOURCE CODE pub fn main() { let a = 'example' } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:17 │ 3 │ let a = 'example' │ ^ Unexpected single quote Hint: Strings are written with double quotes. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__target_attribute_on_type_variant.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\ntype Wibble {\n @target(erlang)\n Wibble2\n}\n" --- ----- SOURCE CODE type Wibble { @target(erlang) Wibble2 } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:5 │ 3 │ @target(erlang) │ ^^^^^^^^^^^^^^^ This attribute cannot be used on a variant. Hint: Did you mean `@deprecated`? ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__tuple_invalid_expr.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nfn main() {\n #(1, 2, const)\n}\n" --- ----- SOURCE CODE fn main() { #(1, 2, const) } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:13 │ 3 │ #(1, 2, const) │ ^^^^^ I was not expecting this Found the keyword `const`, expected one of: - `)` - an expression ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__tuple_without_hash.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub fn main() {\n let triple = (1, 2.2, \"three\")\n io.debug(triple)\n let (a, *, *) = triple\n io.debug(a)\n io.debug(triple.1)\n}\n" --- ----- SOURCE CODE pub fn main() { let triple = (1, 2.2, "three") io.debug(triple) let (a, *, *) = triple io.debug(a) io.debug(triple.1) } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:18 │ 3 │ let triple = (1, 2.2, "three") │ ^ This parenthesis cannot be understood here Hint: To group expressions in Gleam, use "{" and "}"; tuples are created with `#(` and `)`. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__type_angle_generics_definition_error.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\ntype Either {\n This(a)\n That(b)\n}\n" --- ----- SOURCE CODE type Either { This(a) That(b) } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:12 │ 2 │ type Either { │ ^ I was expecting `(` here. Type parameters use lowercase names and are surrounded by parentheses. type Either(a, b) { See: https://tour.gleam.run/data-types/generic-custom-types/ ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__type_angle_generics_definition_error_fallback.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\ntype Either {\n This(A)\n That(B)\n}\n" --- ----- SOURCE CODE type Either { This(A) That(B) } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:12 │ 2 │ type Either { │ ^ I was expecting `(` here. Type parameters use lowercase names and are surrounded by parentheses. type Either(value) { See: https://tour.gleam.run/data-types/generic-custom-types/ ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__type_angle_generics_definition_with_upname_error.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\ntype Either {\n This(A)\n That(B)\n}\n" --- ----- SOURCE CODE type Either { This(A) That(B) } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:12 │ 2 │ type Either { │ ^ I was expecting `(` here. Type parameters use lowercase names and are surrounded by parentheses. type Either(a, b) { See: https://tour.gleam.run/data-types/generic-custom-types/ ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__type_angle_generics_usage_with_module_error.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "let set: set.Set = set.new()" --- ----- SOURCE CODE let set: set.Set = set.new() ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:1:17 │ 1 │ let set: set.Set = set.new() │ ^ I was expecting `(` here. Type parameters use lowercase names and are surrounded by parentheses. set.Set(Int) See: https://tour.gleam.run/data-types/generic-custom-types/ ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__type_angle_generics_usage_without_module_error.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "let list: List = []" --- ----- SOURCE CODE let list: List = [] ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:1:15 │ 1 │ let list: List = [] │ ^ I was expecting `(` here. Type parameters use lowercase names and are surrounded by parentheses. List(Int, String) See: https://tour.gleam.run/data-types/generic-custom-types/ ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__type_invalid_constructor.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\ntype A {\n A(String)\n type\n}\n" --- ----- SOURCE CODE type A { A(String) type } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:4:5 │ 4 │ type │ ^^^^ I was not expecting this Found the keyword `type`, expected one of: - `}` - a record constructor ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__type_invalid_constructor_arg.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\ntype A {\n A(type: String)\n}\n" --- ----- SOURCE CODE type A { A(type: String) } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:7 │ 3 │ A(type: String) │ ^^^^ I was not expecting this Found the keyword `type`, expected one of: - `)` - a constructor argument name ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__type_invalid_record.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\ntype A {\n One\n Two\n 3\n}\n" --- ----- SOURCE CODE type A { One Two 3 } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:5:5 │ 5 │ 3 │ ^ I was not expecting this Found an Int, expected one of: - `}` - a record constructor ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__type_invalid_record_constructor.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub type User {\n name: String,\n}\n" --- ----- SOURCE CODE pub type User { name: String, } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:5 │ 3 │ name: String, │ ^^^^ I was not expecting this Each custom type variant must have a constructor: pub type User { User( name: String, ) } ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__type_invalid_record_constructor_invalid_field_type.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\ntype User {\n name: \"Test User\",\n}\n" --- ----- SOURCE CODE type User { name: "Test User", } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:5 │ 3 │ name: "Test User", │ ^^^^ I was not expecting this Each custom type variant must have a constructor: type User { User( name: Type, ) } ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__type_invalid_record_constructor_without_field_type.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub opaque type User {\n name\n}\n" --- ----- SOURCE CODE pub opaque type User { name } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:5 │ 3 │ name │ ^^^^ I was not expecting this Each custom type variant must have a constructor: pub opaque type User { User( name: Type, ) } ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__type_invalid_type_name.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\ntype A(a, type) {\n A\n}\n" --- ----- SOURCE CODE type A(a, type) { A } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:11 │ 2 │ type A(a, type) { │ ^^^^ I was not expecting this Found the keyword `type`, expected one of: - `)` - a name ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__unknown_attribute.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "@go_faster()\npub fn main() { 1 }" --- ----- SOURCE CODE @go_faster() pub fn main() { 1 } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:1:1 │ 1 │ @go_faster() │ ^^^^^^^^^^ I don't recognise this attribute Hint: Try `deprecated`, `external` or `internal` instead. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__unknown_external_target.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\n@external(erl, \"one\", \"two\")\npub fn one(x: Int) -> Int {\n todo\n}" --- ----- SOURCE CODE @external(erl, "one", "two") pub fn one(x: Int) -> Int { todo } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:1 │ 2 │ @external(erl, "one", "two") │ ^^^^^^^^^ I don't recognise this target Try `erlang`, `javascript`. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__unknown_target.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\n@target(abc)\npub fn one() {}" --- ----- SOURCE CODE @target(abc) pub fn one() {} ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:9 │ 2 │ @target(abc) │ ^^^ I don't recognise this target Try `erlang`, `javascript`. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__use_invalid_assignments.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\nfn main() {\n use fn <- result.try(get_username())\n}\n" --- ----- SOURCE CODE fn main() { use fn <- result.try(get_username()) } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:9 │ 3 │ use fn <- result.try(get_username()) │ ^ I was expecting a pattern after this ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__valueless_list_spread_expression.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "let x = [1, 2, 3, ..]" --- ----- SOURCE CODE let x = [1, 2, 3, ..] ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:1:19 │ 1 │ let x = [1, 2, 3, ..] │ ^^ I was expecting a value after this spread If a list expression has a spread then a tail must also be given. ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__with_let_binding3.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "let assert [x] = [2]" --- [ Assignment( Assignment { location: SrcSpan { start: 0, end: 20, }, value: List { location: SrcSpan { start: 17, end: 20, }, elements: [ Int { location: SrcSpan { start: 18, end: 19, }, value: "2", int_value: 2, }, ], tail: None, }, pattern: List { location: SrcSpan { start: 11, end: 14, }, elements: [ Variable { location: SrcSpan { start: 12, end: 13, }, name: "x", type_: (), origin: VariableOrigin { syntax: Variable( "x", ), declaration: LetPattern, }, }, ], tail: None, type_: (), }, kind: Assert { location: SrcSpan { start: 0, end: 10, }, assert_keyword_start: 4, message: None, }, compiled_case: CompiledCase { tree: Fail, subject_variables: [], }, annotation: None, }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__with_let_binding3_and_annotation.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "let assert [x]: List(Int) = [2]" --- [ Assignment( Assignment { location: SrcSpan { start: 0, end: 31, }, value: List { location: SrcSpan { start: 28, end: 31, }, elements: [ Int { location: SrcSpan { start: 29, end: 30, }, value: "2", int_value: 2, }, ], tail: None, }, pattern: List { location: SrcSpan { start: 11, end: 14, }, elements: [ Variable { location: SrcSpan { start: 12, end: 13, }, name: "x", type_: (), origin: VariableOrigin { syntax: Variable( "x", ), declaration: LetPattern, }, }, ], tail: None, type_: (), }, kind: Assert { location: SrcSpan { start: 0, end: 10, }, assert_keyword_start: 4, message: None, }, compiled_case: CompiledCase { tree: Fail, subject_variables: [], }, annotation: Some( Constructor( TypeAstConstructor { location: SrcSpan { start: 16, end: 25, }, name_location: SrcSpan { start: 16, end: 20, }, module: None, name: "List", arguments: [ Constructor( TypeAstConstructor { location: SrcSpan { start: 21, end: 24, }, name_location: SrcSpan { start: 21, end: 24, }, module: None, name: "Int", arguments: [], start_parentheses: None, }, ), ], start_parentheses: Some( 20, ), }, ), ), }, ), ] ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__wrong_function_return_type_declaration_using_colon_instead_of_right_arrow.snap ================================================ --- source: compiler-core/src/parse/tests.rs assertion_line: 2025 expression: "\npub fn main(): Nil {}\n " snapshot_kind: text --- ----- SOURCE CODE pub fn main(): Nil {} ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:2:14 │ 2 │ pub fn main(): Nil {} │ ^ I was not expecting this Found `:`, expected one of: - `->` Hint: Return type annotations are written using `->`, not `:` ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__wrong_record_access_pattern.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub fn main() {\n case wibble {\n wibble.thing -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case wibble { wibble.thing -> 1 } } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:4:5 │ 4 │ wibble.thing -> 1 │ ^^^^^^^^^^^^ Invalid pattern I'm expecting a pattern here Hint: A pattern can be a constructor name, a literal value or a variable to bind a value to, etc. See: https://tour.gleam.run/flow-control/case-expressions/ ================================================ FILE: compiler-core/src/parse/snapshots/gleam_core__parse__tests__wrong_type_of_comments_with_hash.snap ================================================ --- source: compiler-core/src/parse/tests.rs expression: "\npub fn main() {\n # a python-style comment\n}\n" --- ----- SOURCE CODE pub fn main() { # a python-style comment } ----- ERROR error: Syntax error ┌─ /src/parse/error.gleam:3:5 │ 3 │ # a python-style comment │ ^ I was not expecting this Found a name, expected one of: - `(` Hint: Maybe you meant to create a comment? Comments in Gleam start with `//`, not `#` ================================================ FILE: compiler-core/src/parse/tests.rs ================================================ use crate::ast::SrcSpan; use crate::parse::error::{ InvalidUnicodeEscapeError, LexicalError, LexicalErrorType, ParseError, ParseErrorType, }; use crate::parse::lexer::make_tokenizer; use crate::parse::token::Token; use crate::warning::WarningEmitter; use camino::Utf8PathBuf; use ecow::EcoString; use itertools::Itertools; use pretty_assertions::assert_eq; macro_rules! assert_error { ($src:expr, $error:expr $(,)?) => { let result = crate::parse::parse_statement_sequence($src).expect_err("should not parse"); assert_eq!(($src, $error), ($src, result),); }; ($src:expr) => { let error = $crate::parse::tests::expect_error($src); let output = format!("----- SOURCE CODE\n{}\n\n----- ERROR\n{}", $src, error); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }; } macro_rules! assert_module_error { ($src:expr) => { let error = $crate::parse::tests::expect_module_error($src); let output = format!("----- SOURCE CODE\n{}\n\n----- ERROR\n{}", $src, error); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }; } macro_rules! assert_parse_module { ($src:expr) => { let result = crate::parse::parse_module( camino::Utf8PathBuf::from("test/path"), $src, &crate::warning::WarningEmitter::null(), ) .expect("should parse"); insta::assert_snapshot!(insta::internals::AutoName, &format!("{:#?}", result), $src); }; } macro_rules! assert_parse { ($src:expr) => { let result = crate::parse::parse_statement_sequence($src).expect("should parse"); insta::assert_snapshot!(insta::internals::AutoName, &format!("{:#?}", result), $src); }; } pub fn expect_module_error(src: &str) -> String { let result = crate::parse::parse_module(Utf8PathBuf::from("test/path"), src, &WarningEmitter::null()) .expect_err("should not parse"); let error = crate::error::Error::Parse { src: src.into(), path: Utf8PathBuf::from("/src/parse/error.gleam"), error: Box::new(result), }; error.pretty_string() } pub fn expect_error(src: &str) -> String { let result = crate::parse::parse_statement_sequence(src).expect_err("should not parse"); let error = crate::error::Error::Parse { src: src.into(), path: Utf8PathBuf::from("/src/parse/error.gleam"), error: Box::new(result), }; error.pretty_string() } #[test] fn int_tests() { // bad binary digit assert_error!( "0b012", ParseError { error: ParseErrorType::LexError { error: LexicalError { error: LexicalErrorType::DigitOutOfRadix, location: SrcSpan { start: 4, end: 4 }, } }, location: SrcSpan { start: 4, end: 4 }, } ); // bad octal digit assert_error!( "0o12345678", ParseError { error: ParseErrorType::LexError { error: LexicalError { error: LexicalErrorType::DigitOutOfRadix, location: SrcSpan { start: 9, end: 9 }, } }, location: SrcSpan { start: 9, end: 9 }, } ); // no int value assert_error!( "0x", ParseError { error: ParseErrorType::LexError { error: LexicalError { error: LexicalErrorType::RadixIntNoValue, location: SrcSpan { start: 1, end: 1 }, } }, location: SrcSpan { start: 1, end: 1 }, } ); // trailing underscore assert_error!( "1_000_", ParseError { error: ParseErrorType::LexError { error: LexicalError { error: LexicalErrorType::NumTrailingUnderscore, location: SrcSpan { start: 5, end: 5 }, } }, location: SrcSpan { start: 5, end: 5 }, } ); } #[test] fn string_bad_character_escape() { assert_error!( r#""\g""#, ParseError { error: ParseErrorType::LexError { error: LexicalError { error: LexicalErrorType::BadStringEscape, location: SrcSpan { start: 1, end: 2 }, } }, location: SrcSpan { start: 1, end: 2 }, } ); } #[test] fn string_bad_character_escape_leading_backslash() { assert_error!( r#""\\\g""#, ParseError { error: ParseErrorType::LexError { error: LexicalError { error: LexicalErrorType::BadStringEscape, location: SrcSpan { start: 3, end: 4 }, } }, location: SrcSpan { start: 3, end: 4 }, } ); } #[test] fn string_freestanding_unicode_escape_sequence() { assert_error!( r#""\u""#, ParseError { error: ParseErrorType::LexError { error: LexicalError { error: LexicalErrorType::InvalidUnicodeEscape( InvalidUnicodeEscapeError::MissingOpeningBrace, ), location: SrcSpan { start: 2, end: 3 }, } }, location: SrcSpan { start: 2, end: 3 }, } ); } #[test] fn string_unicode_escape_sequence_no_braces() { assert_error!( r#""\u65""#, ParseError { error: ParseErrorType::LexError { error: LexicalError { error: LexicalErrorType::InvalidUnicodeEscape( InvalidUnicodeEscapeError::MissingOpeningBrace, ), location: SrcSpan { start: 2, end: 3 }, } }, location: SrcSpan { start: 2, end: 3 }, } ); } #[test] fn string_unicode_escape_sequence_invalid_hex() { assert_error!( r#""\u{z}""#, ParseError { error: ParseErrorType::LexError { error: LexicalError { error: LexicalErrorType::InvalidUnicodeEscape( InvalidUnicodeEscapeError::ExpectedHexDigitOrCloseBrace, ), location: SrcSpan { start: 4, end: 5 }, } }, location: SrcSpan { start: 4, end: 5 }, } ); } #[test] fn string_unclosed_unicode_escape_sequence() { assert_error!( r#""\u{039a""#, ParseError { error: ParseErrorType::LexError { error: LexicalError { error: LexicalErrorType::InvalidUnicodeEscape( InvalidUnicodeEscapeError::ExpectedHexDigitOrCloseBrace, ), location: SrcSpan { start: 8, end: 9 }, } }, location: SrcSpan { start: 8, end: 9 }, } ); } #[test] fn string_empty_unicode_escape_sequence() { assert_error!( r#""\u{}""#, ParseError { error: ParseErrorType::LexError { error: LexicalError { error: LexicalErrorType::InvalidUnicodeEscape( InvalidUnicodeEscapeError::InvalidNumberOfHexDigits, ), location: SrcSpan { start: 1, end: 5 }, } }, location: SrcSpan { start: 1, end: 5 }, } ); } #[test] fn string_overlong_unicode_escape_sequence() { assert_error!( r#""\u{0011f601}""#, ParseError { error: ParseErrorType::LexError { error: LexicalError { error: LexicalErrorType::InvalidUnicodeEscape( InvalidUnicodeEscapeError::InvalidNumberOfHexDigits, ), location: SrcSpan { start: 1, end: 13 }, } }, location: SrcSpan { start: 1, end: 13 }, } ); } #[test] fn string_invalid_unicode_escape_sequence() { assert_error!( r#""\u{110000}""#, ParseError { error: ParseErrorType::LexError { error: LexicalError { error: LexicalErrorType::InvalidUnicodeEscape( InvalidUnicodeEscapeError::InvalidCodepoint, ), location: SrcSpan { start: 1, end: 11 }, } }, location: SrcSpan { start: 1, end: 11 }, } ); } #[test] fn bit_array() { // non int value in bit array unit option assert_error!( "let x = <<1:unit(0)>> x", ParseError { error: ParseErrorType::InvalidBitArrayUnit, location: SrcSpan { start: 17, end: 18 } } ); } #[test] fn bit_array1() { assert_error!( "let x = <<1:unit(257)>> x", ParseError { error: ParseErrorType::InvalidBitArrayUnit, location: SrcSpan { start: 17, end: 20 } } ); } #[test] fn bit_array2() { // patterns cannot be nested assert_error!( "case <<>> { <<<<1>>:bits>> -> 1 }", ParseError { error: ParseErrorType::NestedBitArrayPattern, location: SrcSpan { start: 14, end: 19 } } ); } // https://github.com/gleam-lang/gleam/issues/3125 #[test] fn triple_equals() { assert_error!( "let wobble:Int = 32 wobble === 42", ParseError { error: ParseErrorType::LexError { error: LexicalError { error: LexicalErrorType::InvalidTripleEqual, location: SrcSpan { start: 35, end: 38 }, } }, location: SrcSpan { start: 35, end: 38 }, } ); } #[test] fn triple_equals_with_whitespace() { assert_error!( "let wobble:Int = 32 wobble == = 42", ParseError { error: ParseErrorType::OpNakedRight, location: SrcSpan { start: 35, end: 37 }, } ); } // https://github.com/gleam-lang/gleam/issues/1231 #[test] fn pointless_spread() { assert_error!( "let xs = [] [..xs]", ParseError { error: ParseErrorType::ListSpreadWithoutElements, location: SrcSpan { start: 12, end: 18 }, } ); } // https://github.com/gleam-lang/gleam/issues/1613 #[test] fn anonymous_function_labeled_arguments() { assert_error!( "let anon_subtract = fn (minuend a: Int, subtrahend b: Int) -> Int { a - b }", ParseError { location: SrcSpan { start: 24, end: 31 }, error: ParseErrorType::UnexpectedLabel } ); } #[test] fn no_let_binding() { assert_error!( "wibble = 32", ParseError { location: SrcSpan { start: 7, end: 8 }, error: ParseErrorType::NoLetBinding } ); } #[test] fn no_let_binding1() { assert_error!( "wibble:Int = 32", ParseError { location: SrcSpan { start: 6, end: 7 }, error: ParseErrorType::NoLetBinding } ); } #[test] fn no_let_binding2() { assert_error!( "let wobble:Int = 32 wobble = 42", ParseError { location: SrcSpan { start: 35, end: 36 }, error: ParseErrorType::NoLetBinding } ); } #[test] fn no_let_binding3() { assert_error!( "[x] = [2]", ParseError { location: SrcSpan { start: 4, end: 5 }, error: ParseErrorType::NoLetBinding } ); } #[test] fn with_let_binding3() { // The same with `let assert` must parse: assert_parse!("let assert [x] = [2]"); } #[test] fn with_let_binding3_and_annotation() { assert_parse!("let assert [x]: List(Int) = [2]"); } #[test] fn no_eq_after_binding() { assert_error!( "let wibble", ParseError { location: SrcSpan { start: 4, end: 10 }, error: ParseErrorType::ExpectedEqual } ); } #[test] fn no_eq_after_binding1() { assert_error!( "let wibble wibble = 4", ParseError { location: SrcSpan { start: 4, end: 10 }, error: ParseErrorType::ExpectedEqual } ); } #[test] fn echo_followed_by_expression_ends_where_expression_ends() { assert_parse!("echo wibble"); } #[test] fn echo_with_simple_expression_1() { assert_parse!("echo wibble as message"); } #[test] fn echo_with_simple_expression_2() { assert_parse!("echo wibble as \"message\""); } #[test] fn echo_with_complex_expression() { assert_parse!("echo wibble as { this <> complex }"); } #[test] fn echo_with_no_expressions_after_it() { assert_parse!("echo"); } #[test] fn echo_with_no_expressions_after_it_but_a_message() { assert_parse!("echo as message"); } #[test] fn echo_with_block() { assert_parse!("echo { 1 + 1 }"); } #[test] fn echo_has_lower_precedence_than_binop() { assert_parse!("echo 1 + 1"); } #[test] fn echo_in_a_pipeline() { assert_parse!("[] |> echo |> wibble"); } #[test] fn echo_has_lower_precedence_than_pipeline() { assert_parse!("echo wibble |> wobble |> woo"); } #[test] fn echo_cannot_have_an_expression_in_a_pipeline() { // So this is actually two pipelines! assert_parse!("[] |> echo fun |> wibble"); } #[test] fn panic_with_echo() { assert_parse!("panic as echo \"string\""); } #[test] fn panic_with_echo_and_message() { assert_parse!("panic as echo wibble as message"); } #[test] fn echo_with_panic() { assert_parse!("echo panic as \"a\""); } #[test] fn echo_with_panic_and_message() { assert_parse!("echo panic as \"a\""); } #[test] fn echo_with_panic_and_messages() { assert_parse!("echo panic as \"a\" as \"b\""); } #[test] fn echo_with_assert_and_message_1() { assert_parse!("assert 1 == echo 2 as this_belongs_to_echo"); } #[test] fn echo_with_assert_and_message_2() { assert_parse!("assert echo True as this_belongs_to_echo"); } #[test] fn echo_with_assert_and_messages_1() { assert_parse!("assert 1 == echo 2 as this_belongs_to_echo as this_belongs_to_assert"); } #[test] fn echo_with_assert_and_messages_2() { assert_parse!("assert echo True as this_belongs_to_echo as this_belongs_to_assert"); } #[test] fn echo_with_assert_and_messages_3() { assert_parse!("assert echo 1 == 2 as this_belongs_to_echo as this_belongs_to_assert"); } #[test] fn echo_with_let_assert_and_message() { assert_parse!("let assert 1 = echo 2 as this_belongs_to_echo"); } #[test] fn echo_with_let_assert_and_messages() { assert_parse!("let assert 1 = echo 1 as this_belongs_to_echo as this_belongs_to_assert"); } #[test] fn repeated_echos() { assert_parse!("echo echo echo 1"); } #[test] fn echo_at_start_of_pipeline_wraps_the_whole_thing() { assert_parse!("echo 1 |> wibble |> wobble"); } #[test] fn no_let_binding_snapshot_1() { assert_error!("wibble = 4"); } #[test] fn no_let_binding_snapshot_2() { assert_error!("wibble:Int = 4"); } #[test] fn no_let_binding_snapshot_3() { assert_error!( "let wobble:Int = 32 wobble = 42" ); } #[test] fn no_eq_after_binding_snapshot_1() { assert_error!("let wibble"); } #[test] fn no_eq_after_binding_snapshot_2() { assert_error!( "let wibble wibble = 4" ); } #[test] fn discard_left_hand_side_of_concat_pattern() { assert_error!( r#" case "" { _ <> rest -> rest } "# ); } #[test] fn assign_left_hand_side_of_concat_pattern() { assert_error!( r#" case "" { first <> rest -> rest } "# ); } // https://github.com/gleam-lang/gleam/issues/1890 #[test] fn valueless_list_spread_expression() { assert_error!(r#"let x = [1, 2, 3, ..]"#); } // https://github.com/gleam-lang/gleam/issues/2035 #[test] fn semicolons() { assert_error!(r#"{ 2 + 3; - -5; }"#); } #[test] fn bare_expression() { assert_parse!(r#"1"#); } // https://github.com/gleam-lang/gleam/issues/1991 #[test] fn block_of_one() { assert_parse!(r#"{ 1 }"#); } // https://github.com/gleam-lang/gleam/issues/1991 #[test] fn block_of_two() { assert_parse!(r#"{ 1 2 }"#); } // https://github.com/gleam-lang/gleam/issues/1991 #[test] fn nested_block() { assert_parse!(r#"{ 1 { 1.0 2.0 } 3 }"#); } // https://github.com/gleam-lang/gleam/issues/1831 #[test] fn argument_scope() { assert_error!( " 1 + let a = 5 a " ); } #[test] fn multiple_external_for_same_project_erlang() { assert_module_error!( r#" @external(erlang, "one", "two") @external(erlang, "three", "four") pub fn one(x: Int) -> Int { todo } "# ); } #[test] fn multiple_external_for_same_project_javascript() { assert_module_error!( r#" @external(javascript, "one", "two") @external(javascript, "three", "four") pub fn one(x: Int) -> Int { todo } "# ); } #[test] fn unknown_external_target() { assert_module_error!( r#" @external(erl, "one", "two") pub fn one(x: Int) -> Int { todo }"# ); } #[test] fn unknown_target() { assert_module_error!( r#" @target(abc) pub fn one() {}"# ); } #[test] fn missing_target() { assert_module_error!( r#" @target() pub fn one() {}"# ); } #[test] fn missing_target_and_bracket() { assert_module_error!( r#" @target( pub fn one() {}"# ); } #[test] fn unknown_attribute() { assert_module_error!( r#"@go_faster() pub fn main() { 1 }"# ); } #[test] fn incomplete_function() { assert_error!("fn()"); } #[test] fn multiple_deprecation_attributes() { assert_module_error!( r#" @deprecated("1") @deprecated("2") pub fn main() -> Nil { Nil } "# ); } #[test] fn deprecation_without_message() { assert_module_error!( r#" @deprecated() pub fn main() -> Nil { Nil } "# ); } #[test] fn multiple_internal_attributes() { assert_module_error!( r#" @internal @internal pub fn main() -> Nil { Nil } "# ); } #[test] fn attributes_with_no_definition() { assert_module_error!( r#" @deprecated("1") @target(erlang) "# ); } #[test] fn external_attribute_with_custom_type() { assert_parse_module!( r#" @external(erlang, "gleam_stdlib", "dict") @external(javascript, "./gleam_stdlib.d.ts", "Dict") pub type Dict(key, value) "# ); } #[test] fn external_attribute_with_non_fn_definition() { assert_module_error!( r#" @external(erlang, "module", "fun") pub type Fun = Fun "# ); } #[test] fn attributes_with_improper_definition() { assert_module_error!( r#" @deprecated("1") @external(erlang, "module", "fun") "# ); } #[test] fn const_with_function_call() { assert_module_error!( r#" pub fn wibble() { 123 } const wib: Int = wibble() "# ); } #[test] fn const_with_function_call_with_args() { assert_module_error!( r#" pub fn wibble() { 123 } const wib: Int = wibble(1, "wobble") "# ); } #[test] fn import_type() { assert_parse_module!(r#"import wibble.{type Wobble, Wobble, type Wabble}"#); } #[test] fn reserved_auto() { assert_module_error!(r#"const auto = 1"#); } #[test] fn reserved_delegate() { assert_module_error!(r#"const delegate = 1"#); } #[test] fn reserved_derive() { assert_module_error!(r#"const derive = 1"#); } #[test] fn reserved_else() { assert_module_error!(r#"const else = 1"#); } #[test] fn reserved_implement() { assert_module_error!(r#"const implement = 1"#); } #[test] fn reserved_macro() { assert_module_error!(r#"const macro = 1"#); } #[test] fn reserved_test() { assert_module_error!(r#"const test = 1"#); } #[test] fn reserved_echo() { assert_module_error!(r#"const echo = 1"#); } #[test] fn capture_with_name() { assert_module_error!( r#" pub fn main() { add(_name, 1) } fn add(x, y) { x + y } "# ); } #[test] fn list_spread_with_no_tail_in_the_middle_of_a_list() { assert_module_error!( r#" pub fn main() -> Nil { let xs = [1, 2, 3] [1, 2, .., 3 + 3, 4] } "# ); } #[test] fn list_spread_followed_by_extra_items() { assert_module_error!( r#" pub fn main() -> Nil { let xs = [1, 2, 3] [1, 2, ..xs, 3 + 3, 4] } "# ); } #[test] fn list_spread_followed_by_extra_item_and_another_spread() { assert_module_error!( r#" pub fn main() -> Nil { let xs = [1, 2, 3] let ys = [5, 6, 7] [..xs, 4, ..ys] } "# ); } #[test] fn list_spread_followed_by_other_spread() { assert_module_error!( r#" pub fn main() -> Nil { let xs = [1, 2, 3] let ys = [5, 6, 7] [1, ..xs, ..ys] } "# ); } #[test] fn list_spread_as_first_item_followed_by_other_items() { assert_module_error!( r#" pub fn main() -> Nil { let xs = [1, 2, 3] [..xs, 3 + 3, 4] } "# ); } // Tests for nested tuples and structs in tuples // https://github.com/gleam-lang/gleam/issues/1980 #[test] fn nested_tuples() { assert_parse!( r#" let tup = #(#(5, 6)) {tup.0}.1 "# ); } #[test] fn nested_tuples_no_block() { assert_parse!( r#" let tup = #(#(5, 6)) tup.0.1 "# ); } #[test] fn deeply_nested_tuples() { assert_parse!( r#" let tup = #(#(#(#(4)))) {{{tup.0}.0}.0}.0 "# ); } #[test] fn deeply_nested_tuples_no_block() { assert_parse!( r#" let tup = #(#(#(#(4)))) tup.0.0.0.0 "# ); } #[test] fn inner_single_quote_parses() { assert_parse!( r#" let a = "inner 'quotes'" "# ); } #[test] fn string_single_char_suggestion() { assert_module_error!( " pub fn main() { let a = 'example' } " ); } #[test] fn private_internal_const() { assert_module_error!( " @internal const wibble = 1 " ); } #[test] fn private_internal_type_alias() { assert_module_error!( " @internal type Alias = Int " ); } #[test] fn private_internal_function() { assert_module_error!( " @internal fn wibble() { todo } " ); } #[test] fn private_internal_type() { assert_module_error!( " @internal type Wibble { Wibble } " ); } #[test] fn wrong_record_access_pattern() { assert_module_error!( " pub fn main() { case wibble { wibble.thing -> 1 } } " ); } #[test] fn tuple_invalid_expr() { assert_module_error!( " fn main() { #(1, 2, const) } " ); } #[test] fn bit_array_invalid_segment() { assert_module_error!( " fn main() { <<72, 101, 108, 108, 111, 44, 32, 74, 111, 101, const>> } " ); } #[test] fn case_invalid_expression() { assert_module_error!( " fn main() { case 1, type { _, _ -> 0 } } " ); } #[test] fn case_invalid_case_pattern() { assert_module_error!( " fn main() { case 1 { -> -> 0 } } " ); } #[test] fn case_clause_no_subject() { assert_module_error!( " fn main() { case 1 { -> 1 _ -> 2 } } " ); } #[test] fn case_alternative_clause_no_subject() { assert_module_error!( " fn main() { case 1 { 1 | -> 1 _ -> 1 } } " ); } #[test] fn use_invalid_assignments() { assert_module_error!( " fn main() { use fn <- result.try(get_username()) } " ); } #[test] fn assignment_pattern_invalid_tuple() { assert_module_error!( " fn main() { let #(a, case, c) = #(1, 2, 3) } " ); } #[test] fn assignment_pattern_invalid_bit_segment() { assert_module_error!( " fn main() { let <> = <<24, 3>> } " ); } #[test] fn case_list_pattern_after_spread() { assert_module_error!( " fn main() { case somelist { [..rest, last] -> 1 _ -> 2 } } " ); } #[test] fn type_invalid_constructor() { assert_module_error!( " type A { A(String) type } " ); } // Tests whether diagnostic presents an example of how to formulate a proper // record constructor based off a common user error pattern. // https://github.com/gleam-lang/gleam/issues/3324 #[test] fn type_invalid_record_constructor() { assert_module_error!( " pub type User { name: String, } " ); } #[test] fn type_invalid_record_constructor_without_field_type() { assert_module_error!( " pub opaque type User { name } " ); } #[test] fn type_invalid_record_constructor_invalid_field_type() { assert_module_error!( r#" type User { name: "Test User", } "# ); } #[test] fn type_invalid_type_name() { assert_module_error!( " type A(a, type) { A } " ); } #[test] fn type_invalid_constructor_arg() { assert_module_error!( " type A { A(type: String) } " ); } #[test] fn type_invalid_record() { assert_module_error!( " type A { One Two 3 } " ); } #[test] fn function_type_invalid_param_type() { assert_module_error!( " fn f(g: fn(Int, 1) -> Int) -> Int { g(0, 1) } " ); } #[test] fn function_invalid_signature() { assert_module_error!( r#" fn f(a, "b") -> String { a <> b } "# ); } #[test] fn const_invalid_tuple() { assert_module_error!( " const a = #(1, 2, <-) " ); } #[test] fn const_invalid_list() { assert_module_error!( " const a = [1, 2, <-] " ); } #[test] fn const_invalid_bit_array_segment() { assert_module_error!( " const a = <<1, 2, <->> " ); } #[test] fn const_invalid_record_constructor() { assert_module_error!( " type A { A(String, Int) } const a = A(\"a\", let) " ); } // record access should parse even if there is no label written #[test] fn record_access_no_label() { assert_parse_module!( " type Wibble { Wibble(wibble: String) } fn wobble() { Wibble(\"a\"). } " ); } #[test] fn newline_tokens() { assert_eq!( make_tokenizer("1\n\n2\n").collect_vec(), [ Ok(( 0, Token::Int { value: "1".into(), int_value: 1.into() }, 1 )), Ok((1, Token::NewLine, 2)), Ok((2, Token::NewLine, 3)), Ok(( 3, Token::Int { value: "2".into(), int_value: 2.into() }, 4 )), Ok((4, Token::NewLine, 5)) ] ); } // https://github.com/gleam-lang/gleam/issues/1756 #[test] fn arithmetic_in_guards() { assert_parse!( " case 2, 3 { x, y if x + y == 1 -> True }" ); } #[test] fn const_string_concat() { assert_parse_module!( " const cute = \"cute\" const cute_bee = cute <> \"bee\" " ); } #[test] fn const_string_concat_naked_right() { assert_module_error!( " const no_cute_bee = \"cute\" <> " ); } #[test] fn function_call_in_case_clause_guard() { assert_error!( r#" let my_string = "hello" case my_string { _ if length(my_string) > 2 -> io.debug("doesn't work') }"# ); } #[test] fn dot_access_function_call_in_case_clause_guard() { assert_error!( r#" let my_string = "hello" case my_string { _ if string.length(my_string) > 2 -> io.debug("doesn't work') }"# ); } #[test] fn invalid_left_paren_in_case_clause_guard() { assert_error!( r#" let my_string = "hello" case my_string { _ if string.length( > 2 -> io.debug("doesn't work') }"# ); } #[test] fn string_concatenation_in_case_clause_guard() { assert_parse!( r#" let my_string = "hello " case my_string { _ if my_string <> "world" == "hello world" -> io.debug("ok") }"# ); } #[test] fn invalid_label_shorthand() { assert_module_error!( " pub fn main() { wibble(:) } " ); } #[test] fn invalid_label_shorthand_2() { assert_module_error!( " pub fn main() { wibble(:,) } " ); } #[test] fn invalid_label_shorthand_3() { assert_module_error!( " pub fn main() { wibble(:arg) } " ); } #[test] fn invalid_label_shorthand_4() { assert_module_error!( " pub fn main() { wibble(arg::) } " ); } #[test] fn invalid_label_shorthand_5() { assert_module_error!( " pub fn main() { wibble(arg::arg) } " ); } #[test] fn invalid_pattern_label_shorthand() { assert_module_error!( " pub fn main() { let Wibble(:) = todo } " ); } #[test] fn invalid_pattern_label_shorthand_2() { assert_module_error!( " pub fn main() { let Wibble(:arg) = todo } " ); } #[test] fn invalid_pattern_label_shorthand_3() { assert_module_error!( " pub fn main() { let Wibble(arg::) = todo } " ); } #[test] fn invalid_pattern_label_shorthand_4() { assert_module_error!( " pub fn main() { let Wibble(arg: arg:) = todo } " ); } #[test] fn invalid_pattern_label_shorthand_5() { assert_module_error!( " pub fn main() { let Wibble(arg1: arg2:) = todo } " ); } fn first_parsed_docstring(src: &str) -> EcoString { let parsed = crate::parse::parse_module(Utf8PathBuf::from("test/path"), src, &WarningEmitter::null()) .expect("should parse"); parsed .module .definitions .first() .expect("parsed a definition") .definition .get_doc() .expect("definition without doc") } #[test] fn doc_comment_before_comment_is_not_attached_to_following_function() { assert_eq!( first_parsed_docstring( r#" /// Not included! // pub fn call() /// Doc! pub fn wibble() {} "# ), " Doc!\n" ) } #[test] fn doc_comment_before_comment_is_not_attached_to_following_type() { assert_eq!( first_parsed_docstring( r#" /// Not included! // pub fn call() /// Doc! pub type Wibble "# ), " Doc!\n" ) } #[test] fn doc_comment_before_comment_is_not_attached_to_following_type_alias() { assert_eq!( first_parsed_docstring( r#" /// Not included! // pub fn call() /// Doc! pub type Wibble = Int "# ), " Doc!\n" ) } #[test] fn doc_comment_before_comment_is_not_attached_to_following_constant() { assert_eq!( first_parsed_docstring( r#" /// Not included! // pub fn call() /// Doc! pub const wibble = 1 "# ), " Doc!\n" ); } #[test] fn non_module_level_function_with_a_name() { assert_module_error!( r#" pub fn main() { fn my() { 1 } } "# ); } #[test] fn error_message_on_variable_starting_with_underscore() { // https://github.com/gleam-lang/gleam/issues/3504 assert_module_error!( " pub fn main() { let val = _func_starting_with_underscore(1) }" ); } #[test] fn non_module_level_function_with_not_a_name() { assert_module_error!( r#" pub fn main() { fn @() { 1 } // wrong token and not a name } "# ); } #[test] fn error_message_on_variable_starting_with_underscore2() { // https://github.com/gleam-lang/gleam/issues/3504 assert_module_error!( " pub fn main() { case 1 { 1 -> _with_underscore(1) } }" ); } #[test] fn function_inside_a_type() { assert_module_error!( r#" type Wibble { fn wobble() {} } "# ); } #[test] fn pub_function_inside_a_type() { assert_module_error!( r#" type Wibble { pub fn wobble() {} } "# ); } #[test] fn if_like_expression() { assert_module_error!( r#" pub fn main() { let a = if wibble { wobble } } "# ); } // https://github.com/gleam-lang/gleam/issues/3730 #[test] fn missing_constructor_arguments() { assert_module_error!( " pub type A { A(Int) } const a = A() " ); } // https://github.com/gleam-lang/gleam/issues/3796 #[test] fn missing_type_constructor_arguments_in_type_definition() { assert_module_error!( " pub type A() { A(Int) } " ); } #[test] fn tuple_without_hash() { assert_module_error!( r#" pub fn main() { let triple = (1, 2.2, "three") io.debug(triple) let (a, *, *) = triple io.debug(a) io.debug(triple.1) } "# ); } #[test] fn deprecation_attribute_on_type_variant() { assert_parse_module!( r#" type Wibble { @deprecated("1") Wibble1 Wibble2 } "# ); } #[test] fn float_empty_exponent() { assert_error!("1.32e"); } #[test] fn multiple_deprecation_attribute_on_type_variant() { assert_module_error!( r#" type Wibble { @deprecated("1") @deprecated("2") Wibble1 Wibble2 } "# ); } #[test] fn target_attribute_on_type_variant() { assert_module_error!( r#" type Wibble { @target(erlang) Wibble2 } "# ); } #[test] fn internal_attribute_on_type_variant() { assert_module_error!( r#" type Wibble { @internal Wibble1 } "# ); } #[test] fn external_attribute_on_type_variant() { assert_module_error!( r#" type Wibble { @external(erlang, "one", "two") Wibble1 } "# ); } #[test] fn multiple_unsupported_attributes_on_type_variant() { assert_module_error!( r#" type Wibble { @external(erlang, "one", "two") @target(erlang) @internal Wibble1 } "# ); } #[test] // https://github.com/gleam-lang/gleam/issues/3870 fn nested_tuple_access_after_function() { assert_parse!("tuple().0.1"); } #[test] fn case_expression_without_body() { assert_parse!("case a"); } #[test] fn assert_statement() { assert_parse!("assert 10 != 11"); } #[test] fn assert_statement_with_message() { assert_parse!(r#"assert False as "Uh oh""#); } #[test] fn assert_statement_without_expression() { assert_error!("assert"); } #[test] fn assert_statement_followed_by_statement() { assert_error!("assert let a = 10"); } #[test] fn special_error_for_pythonic_import() { assert_module_error!("import gleam.io"); } #[test] fn special_error_for_pythonic_neste_import() { assert_module_error!("import one.two.three"); } #[test] fn doesnt_issue_special_error_for_pythonic_import_if_slash() { assert_module_error!("import one/two.three"); } #[test] fn operator_in_pattern_size() { assert_parse!("let assert <> = <<>>"); } #[test] fn correct_precedence_in_pattern_size() { assert_parse!("let assert <> = <<>>"); } #[test] fn private_opaque_type_is_parsed() { assert_parse_module!("opaque type Wibble { Wobble }"); } #[test] fn case_guard_with_nested_blocks() { assert_parse!( "case 1 { _ if { 1 || { 1 || 1 } } || 1 -> 1 }" ); } #[test] fn case_guard_with_empty_block() { assert_error!( "case 1 { _ if a || {} -> 1 }" ); } #[test] fn constant_inside_function() { assert_module_error!( " pub fn main() { const x = 10 x } " ); } #[test] fn function_definition_angle_generics_error() { assert_module_error!("fn id(x: T) { x }"); } #[test] fn type_angle_generics_usage_without_module_error() { assert_error!("let list: List = []"); } #[test] fn type_angle_generics_usage_with_module_error() { assert_error!("let set: set.Set = set.new()"); } #[test] fn type_angle_generics_definition_error() { assert_module_error!( r#" type Either { This(a) That(b) } "# ); } #[test] fn type_angle_generics_definition_with_upname_error() { assert_module_error!( r#" type Either { This(A) That(B) } "# ); } #[test] fn type_angle_generics_definition_error_fallback() { // Example of a more incorrect syntax, where Gleam doesn't make a suggestion. // In this case, an example type argument is used instead. assert_module_error!( r#" type Either { This(A) That(B) } "# ); } #[test] fn wrong_type_of_comments_with_hash() { assert_module_error!( r#" pub fn main() { # a python-style comment } "# ); } #[test] fn wrong_function_return_type_declaration_using_colon_instead_of_right_arrow() { assert_module_error!( r#" pub fn main(): Nil {} "# ); } #[test] fn const_record_update_basic() { assert_parse_module!( r#" type Person { Person(name: String, age: Int) } const alice = Person("Alice", 30) const bob = Person(..alice, name: "Bob") "# ); } #[test] fn const_record_update_all_fields() { assert_parse_module!( r#" type Person { Person(name: String, age: Int, city: String) } const base = Person("Alice", 30, "London") const updated = Person(..base, name: "Bob", age: 25, city: "Paris") "# ); } #[test] fn const_record_update_only() { assert_parse_module!( r#" type Person { Person(name: String, age: Int) } const alice = Person("Alice", 30) const bob = Person(..alice) "# ); } #[test] fn const_record_update_with_module() { assert_parse_module!( r#" const local_const = other.Record(..other.base, field: value) "# ); } // https://github.com/gleam-lang/gleam/issues/5391 #[test] fn byte_order_mark() { assert_parse!("\u{feff}todo"); } // https://github.com/gleam-lang/gleam/issues/5391 #[test] fn byte_order_mark_module() { assert_parse_module!( "\u{feff} const local_const = other.Record(..other.base, field: value) " ); } #[test] fn prepend_to_const_list_without_comma() { // While this is valid (but deprecated) syntax for expressions, prepending to // constant lists wasn't added until after the deprecation, so it was never // valid in the first place. assert_module_error!( " const wibble = [2, 3] const wobble = [1 ..wibble] " ); } #[test] fn prepend_to_const_list_with_multiple_spreads() { assert_module_error!( " const wibble = [2, 3] const wobble = [0, 1] const wubble = [..wobble, ..wibble] " ); } #[test] fn prepend_to_const_list_with_no_tail() { assert_module_error!( " const wibble = [1, 2, ..] " ); } #[test] fn prepend_no_elements_to_const_list() { assert_module_error!( " const wibble = [2, 3] const wobble = [..wibble] " ); } #[test] fn append_to_const_list() { assert_module_error!( " const wibble = [2, 3] const wobble = [..wibble, 4, 5] " ); } ================================================ FILE: compiler-core/src/parse/token.rs ================================================ use num_bigint::BigInt; use std::fmt; use ecow::EcoString; use crate::parse::LiteralFloatValue; #[derive(Clone, Debug, PartialEq, Eq)] pub enum Token { Name { name: EcoString, }, UpName { name: EcoString, }, DiscardName { name: EcoString, }, Int { value: EcoString, int_value: BigInt, }, Float { value: EcoString, float_value: LiteralFloatValue, }, String { value: EcoString, }, CommentDoc { content: EcoString, }, // Groupings LeftParen, // ( RightParen, // ) LeftSquare, // [ RightSquare, // ] LeftBrace, // { RightBrace, // } // Int Operators Plus, Minus, Star, Slash, Less, Greater, LessEqual, GreaterEqual, Percent, // Float Operators PlusDot, // '+.' MinusDot, // '-.' StarDot, // '*.' SlashDot, // '/.' LessDot, // '<.' GreaterDot, // '>.' LessEqualDot, // '<=.' GreaterEqualDot, // '>=.' // String Operators Concatenate, // '<>' // Other Punctuation Colon, Comma, Hash, // '#' Bang, // '!' Equal, EqualEqual, // '==' NotEqual, // '!=' Vbar, // '|' VbarVbar, // '||' AmperAmper, // '&&' LtLt, // '<<' GtGt, // '>>' Pipe, // '|>' Dot, // '.' RArrow, // '->' LArrow, // '<-' DotDot, // '..' At, // '@' EndOfFile, // Extra CommentNormal, CommentModule, NewLine, // Keywords (alphabetically): As, Assert, Auto, Case, Const, Delegate, Derive, Echo, Else, Fn, If, Implement, Import, Let, Macro, Opaque, Panic, Pub, Test, Todo, Type, Use, } impl Token { pub fn guard_precedence(&self) -> Option { match self { Self::VbarVbar => Some(1), Self::AmperAmper => Some(2), Self::EqualEqual | Self::NotEqual => Some(3), Self::Less | Self::LessEqual | Self::LessDot | Self::LessEqualDot | Self::GreaterEqual | Self::Greater | Self::GreaterEqualDot | Self::GreaterDot => Some(4), Self::Concatenate => Some(5), Self::Plus | Self::PlusDot | Self::Minus | Self::MinusDot => Some(6), Self::Star | Self::StarDot | Self::Slash | Self::SlashDot | Self::Percent => Some(7), Self::Name { .. } | Self::UpName { .. } | Self::DiscardName { .. } | Self::Int { .. } | Self::Float { .. } | Self::String { .. } | Self::CommentDoc { .. } | Self::LeftParen | Self::RightParen | Self::LeftSquare | Self::RightSquare | Self::LeftBrace | Self::RightBrace | Self::Colon | Self::Comma | Self::Hash | Self::Bang | Self::Equal | Self::Vbar | Self::LtLt | Self::GtGt | Self::Pipe | Self::Dot | Self::RArrow | Self::LArrow | Self::DotDot | Self::At | Self::EndOfFile | Self::CommentNormal | Self::CommentModule | Self::NewLine | Self::As | Self::Assert | Self::Auto | Self::Case | Self::Const | Self::Delegate | Self::Derive | Self::Echo | Self::Else | Self::Fn | Self::If | Self::Implement | Self::Import | Self::Let | Self::Macro | Self::Opaque | Self::Panic | Self::Pub | Self::Test | Self::Todo | Self::Type | Self::Use => None, } } pub fn is_reserved_word(&self) -> bool { match self { Token::As | Token::Assert | Token::Case | Token::Const | Token::Fn | Token::If | Token::Import | Token::Let | Token::Opaque | Token::Pub | Token::Todo | Token::Type | Token::Use | Token::Auto | Token::Delegate | Token::Derive | Token::Echo | Token::Else | Token::Implement | Token::Macro | Token::Panic | Token::Test => true, Token::Name { .. } | Token::UpName { .. } | Token::DiscardName { .. } | Token::Int { .. } | Token::Float { .. } | Token::String { .. } | Token::CommentDoc { .. } | Token::LeftParen | Token::RightParen | Token::LeftSquare | Token::RightSquare | Token::LeftBrace | Token::RightBrace | Token::Plus | Token::Minus | Token::Star | Token::Slash | Token::Less | Token::Greater | Token::LessEqual | Token::GreaterEqual | Token::Percent | Token::PlusDot | Token::MinusDot | Token::StarDot | Token::SlashDot | Token::LessDot | Token::GreaterDot | Token::LessEqualDot | Token::GreaterEqualDot | Token::Concatenate | Token::Colon | Token::Comma | Token::Hash | Token::Bang | Token::Equal | Token::EqualEqual | Token::NotEqual | Token::Vbar | Token::VbarVbar | Token::AmperAmper | Token::LtLt | Token::GtGt | Token::Pipe | Token::Dot | Token::RArrow | Token::LArrow | Token::DotDot | Token::At | Token::EndOfFile | Token::CommentNormal | Token::CommentModule | Token::NewLine => false, } } } impl fmt::Display for Token { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let s = match self { Token::Name { name } | Token::UpName { name } | Token::DiscardName { name } => { name.as_str() } Token::Int { value, int_value: _, } | Token::Float { value, float_value: _, } | Token::String { value } => value.as_str(), Token::AmperAmper => "&&", Token::As => "as", Token::Assert => "assert", Token::At => "@", Token::Auto => "auto", Token::Bang => "!", Token::Case => "case", Token::Colon => ":", Token::Comma => ",", Token::CommentDoc { .. } => "///", Token::CommentModule => "////", Token::CommentNormal => "//", Token::Const => "const", Token::Delegate => "delegate", Token::Derive => "derive", Token::Dot => ".", Token::DotDot => "..", Token::Echo => "echo", Token::Else => "else", Token::NewLine => "NEWLINE", Token::EndOfFile => "EOF", Token::Equal => "=", Token::EqualEqual => "==", Token::Fn => "fn", Token::Greater => ">", Token::GreaterDot => ">.", Token::GreaterEqual => ">=", Token::GreaterEqualDot => ">=.", Token::GtGt => ">>", Token::Hash => "#", Token::If => "if", Token::Implement => "implement", Token::Import => "import", Token::LArrow => "<-", Token::LeftBrace => "{", Token::LeftParen => "(", Token::LeftSquare => "[", Token::Less => "<", Token::LessDot => "<.", Token::LessEqual => "<=", Token::LessEqualDot => "<=.", Token::Let => "let", Token::Concatenate => "<>", Token::LtLt => "<<", Token::Macro => "macro", Token::Minus => "-", Token::MinusDot => "-.", Token::NotEqual => "!=", Token::Opaque => "opaque", Token::Panic => "panic", Token::Percent => "%", Token::Pipe => "|>", Token::Plus => "+", Token::PlusDot => "+.", Token::Pub => "pub", Token::RArrow => "->", Token::RightBrace => "}", Token::RightParen => ")", Token::RightSquare => "]", Token::Slash => "/", Token::SlashDot => "/.", Token::Star => "*", Token::StarDot => "*.", Token::Test => "test", Token::Todo => "todo", Token::Type => "type", Token::Use => "use", Token::Vbar => "|", Token::VbarVbar => "||", }; write!(f, "`{s}`") } } ================================================ FILE: compiler-core/src/parse.rs ================================================ // Gleam Parser // // Terminology: // Expression Unit: // Essentially a thing that goes between operators. // Int, Bool, function call, "{" expression-sequence "}", case x {}, ..etc // // Expression: // One or more Expression Units separated by an operator // // Binding: // (let|let assert|use) name (:TypeAnnotation)? = Expression // // Expression Sequence: // * One or more Expressions // * A Binding followed by at least one more Expression Sequences // // Naming Conventions: // parse_x // Parse a specific part of the grammar, not erroring if it cannot. // Generally returns `Result, ParseError>`, note the inner Option // // expect_x // Parse a generic or specific part of the grammar, erroring if it cannot. // Generally returns `Result`, note no inner Option // // maybe_x // Parse a generic part of the grammar. Returning `None` if it cannot. // Returns `Some(x)` and advances the token stream if it can. // // Operator Precedence Parsing: // Needs to take place in expressions and in clause guards. // It is accomplished using the Simple Precedence Parser algorithm. // See: https://en.wikipedia.org/wiki/Simple_precedence_parser // // It relies or the operator grammar being in the general form: // e ::= expr op expr | expr // Which just means that exprs and operators always alternate, starting with an expr // // The gist of the algorithm is: // Create 2 stacks, one to hold expressions, and one to hold un-reduced operators. // While consuming the input stream, if an expression is encountered add it to the top // of the expression stack. If an operator is encountered, compare its precedence to the // top of the operator stack and perform the appropriate action, which is either using an // operator to reduce 2 expressions on the top of the expression stack or put it on the top // of the operator stack. When the end of the input is reached, attempt to reduce all of the // expressions down to a single expression(or no expression) using the remaining operators // on the operator stack. If there are any operators left, or more than 1 expression left // this is a syntax error. But the implementation here shouldn't need to handle that case // as the outer parser ensures the correct structure. // pub mod error; pub mod extra; pub mod lexer; mod token; use crate::Warning; use crate::analyse::Inferred; use crate::ast::{ Arg, ArgNames, Assert, AssignName, Assignment, AssignmentKind, BinOp, BitArrayOption, BitArraySegment, BitArraySize, CAPTURE_VARIABLE, CallArg, Clause, ClauseGuard, Constant, CustomType, Definition, Function, FunctionLiteralKind, HasLocation, Import, IntOperator, Module, ModuleConstant, Pattern, Publicity, RecordBeingUpdated, RecordConstructor, RecordConstructorArg, RecordUpdateArg, SrcSpan, Statement, TailPattern, TargetedDefinition, TodoKind, TypeAlias, TypeAst, TypeAstConstructor, TypeAstFn, TypeAstHole, TypeAstTuple, TypeAstVar, UnqualifiedImport, UntypedArg, UntypedClause, UntypedClauseGuard, UntypedConstant, UntypedDefinition, UntypedExpr, UntypedModule, UntypedPattern, UntypedRecordUpdateArg, UntypedStatement, UntypedUseAssignment, Use, UseAssignment, }; use crate::build::Target; use crate::error::wrap; use crate::exhaustiveness::CompiledCase; use crate::parse::extra::ModuleExtra; use crate::type_::Deprecation; use crate::type_::error::{VariableDeclaration, VariableOrigin, VariableSyntax}; use crate::type_::expression::{Implementations, Purity}; use crate::warning::{DeprecatedSyntaxWarning, WarningEmitter}; use camino::Utf8PathBuf; use ecow::EcoString; use error::{LexicalError, ParseError, ParseErrorType}; use lexer::{LexResult, Spanned}; use num_bigint::BigInt; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; use std::collections::VecDeque; use std::hash::{Hash, Hasher}; use std::str::FromStr; pub use token::Token; use vec1::{Vec1, vec1}; #[cfg(test)] mod tests; #[derive(Debug)] pub struct Parsed { pub module: UntypedModule, pub extra: ModuleExtra, } /// We use this to keep track of the `@internal` annotation for top level /// definitions. Instead of using just a boolean we want to keep track of the /// source position of the annotation in case it is present. This way we can /// report a better error message highlighting the annotation in case it is /// used on a private definition (it doesn't make sense to mark something /// private as internal): /// /// ```txt /// @internal /// ^^^^^^^^^ we first get to the annotation /// fn wibble() {} /// ^^ and only later discover it's applied on a private definition /// so we have to keep track of the attribute's position to highlight it /// in the resulting error message. /// ``` #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] enum InternalAttribute { #[default] Missing, Present(SrcSpan), } #[derive(Debug, Default)] struct Attributes { target: Option, deprecated: Deprecation, external_erlang: Option<(EcoString, EcoString, SrcSpan)>, external_javascript: Option<(EcoString, EcoString, SrcSpan)>, internal: InternalAttribute, } impl Attributes { fn has_function_only(&self) -> bool { self.external_erlang.is_some() || self.external_javascript.is_some() } fn has_external_for(&self, target: Target) -> bool { match target { Target::Erlang => self.external_erlang.is_some(), Target::JavaScript => self.external_javascript.is_some(), } } fn set_external_for(&mut self, target: Target, ext: Option<(EcoString, EcoString, SrcSpan)>) { match target { Target::Erlang => self.external_erlang = ext, Target::JavaScript => self.external_javascript = ext, } } } // // Public Interface // pub type SpannedString = (SrcSpan, EcoString); pub fn parse_module( path: Utf8PathBuf, src: &str, warnings: &WarningEmitter, ) -> Result { let lex = lexer::make_tokenizer(src); let mut parser = Parser::new(lex); let mut parsed = parser.parse_module()?; parsed.extra = parser.extra; let src = EcoString::from(src); for warning in parser.warnings { warnings.emit(Warning::DeprecatedSyntax { path: path.clone(), src: src.clone(), warning, }); } for detached in parser.detached_doc_comments { warnings.emit(Warning::DetachedDocComment { path: path.clone(), src: src.clone(), location: detached, }); } Ok(parsed) } // // Test Interface // #[cfg(test)] pub fn parse_statement_sequence(src: &str) -> Result, ParseError> { let lex = lexer::make_tokenizer(src); let mut parser = Parser::new(lex); let expr = parser.parse_statement_seq(); let expr = parser.ensure_no_errors_or_remaining_input(expr)?; match expr { Some((e, _)) => Ok(e), _ => parse_error(ParseErrorType::ExpectedExpr, SrcSpan { start: 0, end: 0 }), } } // // Test Interface // #[cfg(test)] pub fn parse_const_value(src: &str) -> Result, ParseError> { let lex = lexer::make_tokenizer(src); let mut parser = Parser::new(lex); let expr = parser.parse_const_value(); let expr = parser.ensure_no_errors_or_remaining_input(expr)?; match expr { Some(e) => Ok(e), _ => parse_error(ParseErrorType::ExpectedExpr, SrcSpan { start: 0, end: 0 }), } } // // Parser // #[derive(Debug)] pub struct Parser> { tokens: T, lex_errors: Vec, warnings: Vec, tok0: Option, tok1: Option, extra: ModuleExtra, doc_comments: VecDeque<(u32, EcoString)>, detached_doc_comments: Vec, } impl Parser where T: Iterator, { pub fn new(input: T) -> Self { let mut parser = Parser { tokens: input, lex_errors: vec![], warnings: vec![], tok0: None, tok1: None, extra: ModuleExtra::new(), doc_comments: VecDeque::new(), detached_doc_comments: Vec::new(), }; parser.advance(); parser.advance(); parser } fn parse_module(&mut self) -> Result { let definitions = Parser::series_of(self, &Parser::parse_definition, None); let definitions = self.ensure_no_errors_or_remaining_input(definitions)?; let module = Module { name: "".into(), documentation: vec![], type_info: (), definitions, names: Default::default(), unused_definition_positions: Default::default(), }; Ok(Parsed { module, extra: Default::default(), }) } // The way the parser is currently implemented, it cannot exit immediately while advancing // the token stream upon seeing a LexError. That is to avoid having to put `?` all over the // place and instead we collect LexErrors in `self.lex_errors` and attempt to continue parsing. // Once parsing has returned we want to surface an error in the order: // 1) LexError, 2) ParseError, 3) More Tokens Left fn ensure_no_errors_or_remaining_input( &mut self, parse_result: Result, ) -> Result { let parse_result = self.ensure_no_errors(parse_result)?; if let Some((start, token, end)) = self.next_tok() { // there are still more tokens let expected = vec!["An import, const, type, or function.".into()]; return parse_error( ParseErrorType::UnexpectedToken { token, expected, hint: None, }, SrcSpan { start, end }, ); } // no errors Ok(parse_result) } // The way the parser is currently implemented, it cannot exit immediately // while advancing the token stream upon seeing a LexError. That is to avoid // having to put `?` all over the place and instead we collect LexErrors in // `self.lex_errors` and attempt to continue parsing. // Once parsing has returned we want to surface an error in the order: // 1) LexError, 2) ParseError fn ensure_no_errors( &mut self, parse_result: Result, ) -> Result { if let Some(error) = self.lex_errors.first() { // Lex errors first let location = error.location; let error = *error; parse_error(ParseErrorType::LexError { error }, location) } else { // Return any existing parse error parse_result } } fn parse_definition(&mut self) -> Result, ParseError> { let mut attributes = Attributes::default(); let location = self.parse_attributes(&mut attributes)?; let def = match (self.tok0.take(), self.tok1.as_ref()) { // Imports (Some((start, Token::Import, _)), _) => { self.advance(); self.parse_import(start) } // Module Constants (Some((start, Token::Const, _)), _) => { self.advance(); self.parse_module_const(start, false, &attributes) } (Some((start, Token::Pub, _)), Some((_, Token::Const, _))) => { self.advance(); self.advance(); self.parse_module_const(start, true, &attributes) } // Function (Some((start, Token::Fn, _)), _) => { self.advance(); self.parse_function(start, false, false, &mut attributes) } (Some((start, Token::Pub, _)), Some((_, Token::Fn, _))) => { self.advance(); self.advance(); self.parse_function(start, true, false, &mut attributes) } // Custom Types, and Type Aliases (Some((start, Token::Type, _)), _) => { self.advance(); self.parse_custom_type(start, false, false, &mut attributes) } (Some((start, Token::Pub, _)), Some((_, Token::Opaque, _))) => { self.advance(); self.advance(); let _ = self.expect_one(&Token::Type)?; self.parse_custom_type(start, true, true, &mut attributes) } (Some((start, Token::Pub, _)), Some((_, Token::Type, _))) => { self.advance(); self.advance(); self.parse_custom_type(start, true, false, &mut attributes) } (Some((start, Token::Opaque, _)), Some((_, Token::Type, _))) => { // A private opaque type makes no sense! We still want to parse it // and return an error later during the analysis phase. self.advance(); self.advance(); self.parse_custom_type(start, false, true, &mut attributes) } (t0, _) => { self.tok0 = t0; Ok(None) } }?; match (def, location) { (Some(definition), _) if definition.is_function() || definition.is_custom_type() => { Ok(Some(TargetedDefinition { definition, target: attributes.target, })) } (Some(definition), None) => Ok(Some(TargetedDefinition { definition, target: attributes.target, })), (_, Some(location)) if attributes.has_function_only() => { parse_error(ParseErrorType::ExpectedFunctionDefinition, location) } (Some(definition), _) => Ok(Some(TargetedDefinition { definition, target: attributes.target, })), (_, Some(location)) => parse_error(ParseErrorType::ExpectedDefinition, location), (None, None) => Ok(None), } } // // Parse Expressions // // examples: // unit // unit op unit // unit op unit pipe unit(call) // unit op unit pipe unit(call) pipe unit(call) fn parse_expression(&mut self) -> Result, ParseError> { self.parse_expression_inner(false) } fn parse_expression_inner( &mut self, is_let_binding: bool, ) -> Result, ParseError> { // uses the simple operator parser algorithm let mut opstack = vec![]; let mut estack = vec![]; let mut last_op_start = 0; let mut last_op_end = 0; // This is used to keep track if we've just ran into a `|>` operator in // order to properly parse an echo based on its position: if it is in a // pipeline then it isn't expected to be followed by an expression. // Otherwise, it's expected to be followed by an expression. let mut expression_unit_context = ExpressionUnitContext::Other; loop { match self.parse_expression_unit(expression_unit_context)? { Some(unit) => { self.post_process_expression_unit(&unit, is_let_binding)?; estack.push(unit) } _ if estack.is_empty() => return Ok(None), _ => { return parse_error( ParseErrorType::OpNakedRight, SrcSpan { start: last_op_start, end: last_op_end, }, ); } } let Some((op_s, t, op_e)) = self.tok0.take() else { break; }; let Some(p) = precedence(&t) else { self.tok0 = Some((op_s, t, op_e)); break; }; expression_unit_context = if t == Token::Pipe { ExpressionUnitContext::FollowingPipe } else { ExpressionUnitContext::Other }; // Is Op self.advance(); last_op_start = op_s; last_op_end = op_e; let _ = handle_op( Some(((op_s, t, op_e), p)), &mut opstack, &mut estack, &do_reduce_expression, ); } Ok(handle_op( None, &mut opstack, &mut estack, &do_reduce_expression, )) } fn post_process_expression_unit( &mut self, unit: &UntypedExpr, is_let_binding: bool, ) -> Result<(), ParseError> { // Produce better error message for `[x] = [1]` outside // of `let` statement. if !is_let_binding && let UntypedExpr::List { .. } = unit && let Some((start, Token::Equal, end)) = self.tok0 { return parse_error(ParseErrorType::NoLetBinding, SrcSpan { start, end }); } Ok(()) } // examples: // 1 // "one" // True // fn() { "hi" } // unit().unit().unit() // A(a.., label: tuple(1)) // { expression_sequence } fn parse_expression_unit( &mut self, context: ExpressionUnitContext, ) -> Result, ParseError> { let mut expr = match self.tok0.take() { Some((start, Token::String { value }, end)) => { self.advance(); UntypedExpr::String { location: SrcSpan { start, end }, value, } } Some((start, Token::Int { value, int_value }, end)) => { self.advance(); UntypedExpr::Int { location: SrcSpan { start, end }, value, int_value, } } Some((start, Token::Float { value, float_value }, end)) => { self.advance(); UntypedExpr::Float { location: SrcSpan { start, end }, value, float_value, } } // var lower_name and UpName Some((start, Token::Name { name } | Token::UpName { name }, end)) => { self.advance(); UntypedExpr::Var { location: SrcSpan { start, end }, name, } } Some((start, Token::Todo, end)) => { self.advance(); let message = self.maybe_parse_as_message()?; let end = message.as_ref().map_or(end, |m| m.location().end); UntypedExpr::Todo { location: SrcSpan { start, end }, kind: TodoKind::Keyword, message, } } Some((start, Token::Panic, end)) => { self.advance(); let message = self.maybe_parse_as_message()?; let end = message.as_ref().map_or(end, |m| m.location().end); UntypedExpr::Panic { location: SrcSpan { start, end }, message, } } Some((start, Token::Echo, echo_end)) => { self.advance(); if context == ExpressionUnitContext::FollowingPipe { // If an echo is used as a step in a pipeline (`|> echo`) // then it cannot be followed by an expression. let message = self.maybe_parse_as_message()?; let end = message.as_ref().map_or(echo_end, |m| m.location().end); UntypedExpr::Echo { location: SrcSpan { start, end }, keyword_end: echo_end, expression: None, message, } } else { // Otherwise it must be followed by an expression. // However, you might have noticed we're not erroring if the // expression is not there. Instead we move this error to // the analysis phase so that a wrong usage of echo won't // stop analysis from happening everywhere and be fault // tolerant like everything else. let expression = self.parse_expression()?; let end = expression.as_ref().map_or(echo_end, |e| e.location().end); let message = self.maybe_parse_as_message()?; let end = message.as_ref().map_or(end, |m| m.location().end); UntypedExpr::Echo { location: SrcSpan { start, end }, keyword_end: echo_end, expression: expression.map(Box::new), message, } } } Some((start, Token::Hash, _)) => { self.advance(); let _ = self .expect_one(&Token::LeftParen) .map_err(|e| self.add_comment_style_hint(e))?; let elements = Parser::series_of(self, &Parser::parse_expression, Some(&Token::Comma))?; let (_, end) = self.expect_one_following_series(&Token::RightParen, "an expression")?; UntypedExpr::Tuple { location: SrcSpan { start, end }, elements, } } // list Some((start, Token::LeftSquare, _)) => { self.advance(); let (elements, elements_end_with_comma) = self.series_of_has_trailing_separator( &Parser::parse_expression, Some(&Token::Comma), )?; // Parse an optional tail let mut tail = None; let mut elements_after_tail = None; let mut dot_dot_location = None; if let Some((start, end)) = self.maybe_one(&Token::DotDot) { dot_dot_location = Some((start, end)); tail = self.parse_expression()?.map(Box::new); if self.maybe_one(&Token::Comma).is_some() { // See if there's a list of items after the tail, // like `[..wibble, wobble, wabble]` let elements = self.series_of(&Parser::parse_expression, Some(&Token::Comma)); match elements { Err(_) => {} Ok(elements) => { elements_after_tail = Some(elements); } }; }; if tail.is_some() { if !elements_end_with_comma { self.warnings .push(DeprecatedSyntaxWarning::DeprecatedListPrepend { location: SrcSpan { start, end }, }); } // Give a better error when there is two consecutive spreads // like `[..wibble, ..wabble, woo]`. However, if there's other // elements after the tail of the list if let Some((second_start, second_end)) = self.maybe_one(&Token::DotDot) { let _second_tail = self.parse_expression(); if elements_after_tail.is_none() || elements_after_tail .as_ref() .is_some_and(|vec| vec.is_empty()) { return parse_error( ParseErrorType::ListSpreadWithAnotherSpread { first_spread_location: SrcSpan { start, end }, }, SrcSpan { start: second_start, end: second_end, }, ); } } } } let (_, end) = self.expect_one(&Token::RightSquare)?; // Return errors for malformed lists match dot_dot_location { Some((start, end)) if tail.is_none() => { return parse_error( ParseErrorType::ListSpreadWithoutTail, SrcSpan { start, end }, ); } _ => {} } if tail.is_some() && elements.is_empty() && elements_after_tail.as_ref().is_none_or(|e| e.is_empty()) { return parse_error( ParseErrorType::ListSpreadWithoutElements, SrcSpan { start, end }, ); } match elements_after_tail { Some(elements) if !elements.is_empty() => { let (start, end) = match (dot_dot_location, tail) { (Some((start, _)), Some(tail)) => (start, tail.location().end), (_, _) => (start, end), }; return parse_error( ParseErrorType::ListSpreadFollowedByElements, SrcSpan { start, end }, ); } _ => {} } UntypedExpr::List { location: SrcSpan { start, end }, elements, tail, } } // BitArray Some((start, Token::LtLt, _)) => { self.advance(); let segments = Parser::series_of( self, &|s| { Parser::parse_bit_array_segment( s, &(|this| this.parse_expression_unit(ExpressionUnitContext::Other)), &Parser::expect_expression, &bit_array_expr_int, ) }, Some(&Token::Comma), )?; let (_, end) = self.expect_one_following_series(&Token::GtGt, "a bit array segment")?; UntypedExpr::BitArray { location: SrcSpan { start, end }, segments, } } Some((start, Token::Fn, _)) => { self.advance(); let mut attributes = Attributes::default(); match self.parse_function(start, false, true, &mut attributes)? { Some(Definition::Function(Function { location, arguments, body, return_annotation, end_position, .. })) => { let Ok(body) = Vec1::try_from_vec(body) else { return parse_error(ParseErrorType::ExpectedFunctionBody, location); }; UntypedExpr::Fn { location: SrcSpan::new(location.start, end_position), end_of_head_byte_index: location.end, kind: FunctionLiteralKind::Anonymous { head: location }, arguments, body, return_annotation, } } _ => { // this isn't just none, it could also be Some(UntypedExpr::..) return self.next_tok_unexpected(vec!["An opening parenthesis.".into()]); } } } // expression block "{" "}" Some((start, Token::LeftBrace, _)) => { self.advance(); self.parse_block(start)? } // case Some((start, Token::Case, case_e)) => { self.advance(); let subjects = Parser::series_of(self, &Parser::parse_expression, Some(&Token::Comma))?; if self.maybe_one(&Token::LeftBrace).is_some() { let clauses = Parser::series_of(self, &Parser::parse_case_clause, None)?; let (_, end) = self.expect_one_following_series(&Token::RightBrace, "a case clause")?; if subjects.is_empty() { return parse_error( ParseErrorType::ExpectedExpr, SrcSpan { start, end: case_e }, ); } else { UntypedExpr::Case { location: SrcSpan { start, end }, subjects, clauses: Some(clauses), } } } else { UntypedExpr::Case { location: SrcSpan::new( start, subjects .last() .map(|subject| subject.location().end) .unwrap_or(case_e), ), subjects, clauses: None, } } } // Helpful error if trying to write an if expression instead of a // case. Some((start, Token::If, end)) => { return parse_error(ParseErrorType::IfExpression, SrcSpan { start, end }); } // Helpful error on possibly trying to group with "(". Some((start, Token::LeftParen, _)) => { return parse_error(ParseErrorType::ExprLparStart, SrcSpan { start, end: start }); } // Boolean negation Some((start, Token::Bang, _end)) => { self.advance(); match self.parse_expression_unit(ExpressionUnitContext::Other)? { Some(value) => UntypedExpr::NegateBool { location: SrcSpan { start, end: value.location().end, }, value: Box::from(value), }, None => { return parse_error( ParseErrorType::ExpectedExpr, SrcSpan { start, end: start }, ); } } } // Int negation Some((start, Token::Minus, _end)) => { self.advance(); match self.parse_expression_unit(ExpressionUnitContext::Other)? { Some(value) => UntypedExpr::NegateInt { location: SrcSpan { start, end: value.location().end, }, value: Box::from(value), }, None => { return parse_error( ParseErrorType::ExpectedExpr, SrcSpan { start, end: start }, ); } } } t0 => { self.tok0 = t0; return Ok(None); } }; // field access and call can stack up loop { match self.maybe_one(&Token::Dot) { Some((dot_start, _)) => { let start = expr.location().start; // field access match self.tok0.take() { // tuple access Some(( _, Token::Int { value, int_value: _, }, end, )) => { self.advance(); let v = value.replace("_", ""); match u64::from_str(&v) { Ok(index) => { expr = UntypedExpr::TupleIndex { location: SrcSpan { start, end }, index, tuple: Box::new(expr), } } _ => { return parse_error( ParseErrorType::InvalidTupleAccess, SrcSpan { start, end }, ); } } } Some((label_start, Token::Name { name: label }, end)) => { self.advance(); expr = UntypedExpr::FieldAccess { location: SrcSpan { start, end }, label_location: SrcSpan { start: label_start, end, }, label, container: Box::new(expr), } } Some((label_start, Token::UpName { name: label }, end)) => { self.advance(); expr = UntypedExpr::FieldAccess { location: SrcSpan { start, end }, label_location: SrcSpan { start: label_start, end, }, label, container: Box::new(expr), } } t0 => { // parse a field access with no label self.tok0 = t0; let end = dot_start + 1; expr = UntypedExpr::FieldAccess { location: SrcSpan { start, end }, label_location: SrcSpan { start: dot_start, end, }, label: "".into(), container: Box::new(expr), }; return Ok(Some(expr)); } } } _ => { if self.maybe_one(&Token::LeftParen).is_some() { let start = expr.location().start; match self.maybe_one(&Token::DotDot) { Some((dot_s, _)) => { // Record update let base = self.expect_expression()?; let base_e = base.location().end; let record = RecordBeingUpdated { base: Box::new(base), location: SrcSpan { start: dot_s, end: base_e, }, }; let mut arguments = vec![]; if self.maybe_one(&Token::Comma).is_some() { arguments = Parser::series_of( self, &Parser::parse_record_update_arg, Some(&Token::Comma), )?; } let (_, end) = self.expect_one(&Token::RightParen)?; expr = UntypedExpr::RecordUpdate { location: SrcSpan { start, end }, constructor: Box::new(expr), record, arguments, }; } _ => { // Call let arguments = self.parse_fn_arguments()?; let (_, end) = self.expect_one(&Token::RightParen)?; expr = make_call(expr, arguments, start, end)?; } } } else { // done break; } } } } Ok(Some(expr)) } fn add_comment_style_hint(&self, mut err: ParseError) -> ParseError { if let ParseErrorType::UnexpectedToken { ref mut hint, .. } = err.error { let text = "Maybe you meant to create a comment?\nComments in Gleam start with `//`, not `#`"; *hint = Some(text.into()); } err } // A `use` expression // use <- function // use <- function() // use <- function(a, b) // use <- module.function(a, b) // use a, b, c <- function(a, b) // use a, b, c, <- function(a, b) fn parse_use(&mut self, start: u32, end: u32) -> Result { let assignments = match self.tok0 { Some((_, Token::LArrow, _)) => { vec![] } _ => Parser::series_of(self, &Parser::parse_use_assignment, Some(&Token::Comma))?, }; _ = self.expect_one_following_series(&Token::LArrow, "a use variable assignment")?; let call = self.expect_expression()?; let assignments_location = match (assignments.first(), assignments.last()) { (Some(first), Some(last)) => SrcSpan { start: first.location.start, end: last.location.end, }, (_, _) => SrcSpan { start, end }, }; Ok(Statement::Use(Use { location: SrcSpan::new(start, call.location().end), assignments_location, right_hand_side_location: call.location(), assignments, call: Box::new(call), })) } fn parse_use_assignment(&mut self) -> Result, ParseError> { let start = self.tok0.as_ref().map(|t| t.0).unwrap_or(0); let pattern = self .parse_pattern(PatternPosition::UsePattern)? .ok_or_else(|| ParseError { error: ParseErrorType::ExpectedPattern, location: SrcSpan { start, end: start }, })?; let annotation = self.parse_type_annotation(&Token::Colon)?; let end = match annotation { Some(ref a) => a.location().end, None => pattern.location().end, }; Ok(Some(UseAssignment { location: SrcSpan { start, end }, pattern, annotation, })) } fn maybe_parse_as_message(&mut self) -> Result>, ParseError> { let message = if self.maybe_one(&Token::As).is_some() { let expression = self.expect_expression_unit(ExpressionUnitContext::Other)?; Some(Box::new(expression)) } else { None }; Ok(message) } // An assignment, with `Let` already consumed fn parse_assignment(&mut self, start: u32) -> Result { let mut kind = match self.tok0 { Some((assert_keyword_start, Token::Assert, assert_end)) => { _ = self.next_tok(); AssignmentKind::Assert { location: SrcSpan::new(start, assert_end), assert_keyword_start, message: None, } } _ => AssignmentKind::Let, }; let pattern = match self.parse_pattern(PatternPosition::LetAssignment)? { Some(p) => p, _ => { // DUPE: 62884 return self.next_tok_unexpected(vec!["A pattern".into()])?; } }; let annotation = self.parse_type_annotation(&Token::Colon)?; let (eq_s, eq_e) = self.maybe_one(&Token::Equal).ok_or(ParseError { error: ParseErrorType::ExpectedEqual, location: SrcSpan { start: pattern.location().start, end: pattern.location().end, }, })?; let value = self.parse_expression_inner(true)?.ok_or(match self.tok0 { Some((start, Token::DiscardName { .. }, end)) => ParseError { error: ParseErrorType::IncorrectName, location: SrcSpan { start, end }, }, _ => ParseError { error: ParseErrorType::ExpectedValue, location: SrcSpan { start: eq_s, end: eq_e, }, }, })?; let mut end = value.location().end; match &mut kind { AssignmentKind::Let | AssignmentKind::Generated => {} AssignmentKind::Assert { message, .. } => { if self.maybe_one(&Token::As).is_some() { let message_expression = self.expect_expression_unit(ExpressionUnitContext::Other)?; end = message_expression.location().end; *message = Some(message_expression); } } } Ok(Statement::Assignment(Box::new(Assignment { location: SrcSpan { start, end }, value, compiled_case: CompiledCase::failure(), pattern, annotation, kind, }))) } // An assert statement, with `Assert` already consumed fn parse_assert(&mut self, start: u32) -> Result { let value = self.expect_expression()?; let mut end = value.location().end; let message = if self.maybe_one(&Token::As).is_some() { let message_expression = self.expect_expression_unit(ExpressionUnitContext::Other)?; end = message_expression.location().end; Some(message_expression) } else { None }; Ok(Statement::Assert(Assert { location: SrcSpan { start, end }, value, message, })) } // examples: // expr // expr expr.. // expr assignment.. // assignment // assignment expr.. // assignment assignment.. fn parse_statement_seq(&mut self) -> Result, u32)>, ParseError> { let mut statements = vec![]; let mut start = None; let mut end = 0; // Try and parse as many expressions as possible while let Some(statement) = self.parse_statement()? { if start.is_none() { start = Some(statement.location().start); } end = statement.location().end; statements.push(statement); } match Vec1::try_from_vec(statements) { Ok(statements) => Ok(Some((statements, end))), Err(_) => Ok(None), } } fn parse_statement(&mut self) -> Result, ParseError> { match self.tok0.take() { Some((start, Token::Use, end)) => { self.advance(); Ok(Some(self.parse_use(start, end)?)) } Some((start, Token::Let, _)) => { self.advance(); Ok(Some(self.parse_assignment(start)?)) } Some((start, Token::Assert, _)) => { self.advance(); Ok(Some(self.parse_assert(start)?)) } // Helpful error when trying to define a constant inside a function. Some((start, Token::Const, end)) => parse_error( ParseErrorType::ConstantInsideFunction, SrcSpan { start, end }, ), token => { self.tok0 = token; self.parse_statement_errors()?; let expression = self.parse_expression()?.map(Statement::Expression); Ok(expression) } } } fn parse_statement_errors(&mut self) -> Result<(), ParseError> { // Better error: name definitions must start with `let` if let Some((_, Token::Name { .. }, _)) = self.tok0.as_ref() && let Some((start, Token::Equal | Token::Colon, end)) = self.tok1 { return parse_error(ParseErrorType::NoLetBinding, SrcSpan { start, end }); } Ok(()) } fn parse_block(&mut self, start: u32) -> Result { let body = self.parse_statement_seq()?; let (_, end) = self.expect_one(&Token::RightBrace)?; let location = SrcSpan { start, end }; let statements = match body { Some((statements, _)) => statements, None => vec1![Statement::Expression(UntypedExpr::Todo { kind: TodoKind::EmptyBlock, location, message: None })], }; Ok(UntypedExpr::Block { location, statements, }) } // The left side of an "=" or a "->" fn parse_pattern( &mut self, position: PatternPosition, ) -> Result, ParseError> { let pattern = match self.tok0.take() { // Pattern::Var or Pattern::Constructor start Some((start, Token::Name { name }, end)) => { self.advance(); // A variable is not permitted on the left hand side of a `<>` if let Some((_, Token::Concatenate, _)) = self.tok0.as_ref() { return concat_pattern_variable_left_hand_side_error(start, end); } if self.maybe_one(&Token::Dot).is_some() { // We're doing this to get a better error message instead of a generic // `I was expecting a type`, you can have a look at this issue to get // a better idea: https://github.com/gleam-lang/gleam/issues/2841. match self.expect_constructor_pattern(Some((start, name, end)), position) { Ok(result) => result, Err(ParseError { location: SrcSpan { end, .. }, .. }) => { return parse_error( ParseErrorType::InvalidModuleTypePattern, SrcSpan { start, end }, ); } } } else { Pattern::Variable { origin: VariableOrigin { syntax: VariableSyntax::Variable(name.clone()), declaration: position.to_declaration(), }, location: SrcSpan { start, end }, name, type_: (), } } } // Constructor Some((start, tok @ Token::UpName { .. }, end)) => { self.tok0 = Some((start, tok, end)); self.expect_constructor_pattern(None, position)? } Some((start, Token::DiscardName { name }, end)) => { self.advance(); // A discard is not permitted on the left hand side of a `<>` if let Some((_, Token::Concatenate, _)) = self.tok0.as_ref() { return concat_pattern_variable_left_hand_side_error(start, end); } Pattern::Discard { location: SrcSpan { start, end }, name, type_: (), } } Some((start, Token::String { value }, end)) => { self.advance(); match self.tok0 { // String matching with assignment, it could either be a // String prefix matching: "Hello, " as greeting <> name -> ... // or a full string matching: "Hello, World!" as greeting -> ... Some((_, Token::As, _)) => { self.advance(); let (name_start, name, name_end) = self.expect_name()?; let name_span = SrcSpan { start: name_start, end: name_end, }; match self.tok0 { // String prefix matching with assignment // "Hello, " as greeting <> name -> ... Some((_, Token::Concatenate, _)) => { self.advance(); let (r_start, right, r_end) = self.expect_assign_name()?; Pattern::StringPrefix { location: SrcSpan { start, end: r_end }, left_location: SrcSpan { start, end: name_end, }, right_location: SrcSpan { start: r_start, end: r_end, }, left_side_string: value, left_side_assignment: Some((name, name_span)), right_side_assignment: right, } } // Full string matching with assignment _ => { return Ok(Some(Pattern::Assign { name, location: name_span, pattern: Box::new(Pattern::String { location: SrcSpan { start, end }, value, }), })); } } } // String prefix matching with no left side assignment // "Hello, " <> name -> ... Some((_, Token::Concatenate, _)) => { self.advance(); let (r_start, right, r_end) = self.expect_assign_name()?; Pattern::StringPrefix { location: SrcSpan { start, end: r_end }, left_location: SrcSpan { start, end }, right_location: SrcSpan { start: r_start, end: r_end, }, left_side_string: value, left_side_assignment: None, right_side_assignment: right, } } // Full string matching // "Hello, World!" -> ... _ => Pattern::String { location: SrcSpan { start, end }, value, }, } } Some((start, Token::Int { value, int_value }, end)) => { self.advance(); Pattern::Int { location: SrcSpan { start, end }, value, int_value, } } Some((start, Token::Float { value, float_value }, end)) => { self.advance(); Pattern::Float { location: SrcSpan { start, end }, value, float_value, } } Some((start, Token::Hash, _)) => { self.advance(); let _ = self.expect_one(&Token::LeftParen)?; let elements = Parser::series_of( self, &|this| this.parse_pattern(position), Some(&Token::Comma), )?; let (_, end) = self.expect_one_following_series(&Token::RightParen, "a pattern")?; Pattern::Tuple { location: SrcSpan { start, end }, elements, } } // BitArray Some((start, Token::LtLt, _)) => { self.advance(); let segments = Parser::series_of( self, &|s| { Parser::parse_bit_array_segment( s, &|s| match s.parse_pattern(position) { Ok(Some(Pattern::BitArray { location, .. })) => { parse_error(ParseErrorType::NestedBitArrayPattern, location) } x => x, }, &Parser::expect_bit_array_pattern_segment_arg, &bit_array_size_int, ) }, Some(&Token::Comma), )?; let (_, end) = self.expect_one_following_series(&Token::GtGt, "a bit array segment pattern")?; Pattern::BitArray { location: SrcSpan { start, end }, segments, } } // List Some((start, Token::LeftSquare, _)) => { self.advance(); let (elements, elements_end_with_comma) = self.series_of_has_trailing_separator( &|this| this.parse_pattern(position), Some(&Token::Comma), )?; let mut elements_after_tail = None; let mut dot_dot_location = None; let tail = match self.tok0 { Some((dot_dot_start, Token::DotDot, dot_dot_end)) => { dot_dot_location = Some((dot_dot_start, dot_dot_end)); if !elements.is_empty() && !elements_end_with_comma { self.warnings .push(DeprecatedSyntaxWarning::DeprecatedListPattern { location: SrcSpan { start: dot_dot_start, end: dot_dot_end, }, }); } self.advance(); let tail = self.parse_pattern(position)?; if self.maybe_one(&Token::Comma).is_some() { // See if there's a list of items after the tail, // like `[..wibble, wobble, wabble]` let elements = Parser::series_of( self, &|this| this.parse_pattern(position), Some(&Token::Comma), ); match elements { Err(_) => {} Ok(elements) => { elements_after_tail = Some(elements); } }; }; Some(tail) } _ => None, }; let (end, closing_square_bracket_end) = self.expect_one_following_series(&Token::RightSquare, "a pattern")?; // If there are elements after the tail, return an error match elements_after_tail { Some(elements) if !elements.is_empty() => { let (start, end) = match (dot_dot_location, tail) { (Some((start, _)), Some(Some(tail))) => (start, tail.location().end), (Some((start, end)), Some(None)) => (start, end), (_, _) => (start, end), }; return parse_error( ParseErrorType::ListPatternSpreadFollowedByElements, SrcSpan { start, end }, ); } _ => {} } let tail = match tail { // There is a tail and it has a Pattern::Var or Pattern::Discard Some(Some(pattern @ (Pattern::Variable { .. } | Pattern::Discard { .. }))) => { Some(pattern) } // There is a tail and but it has no content, implicit discard Some(Some(pattern)) => { return parse_error(ParseErrorType::InvalidTailPattern, pattern.location()); } Some(None) => Some(Pattern::Discard { location: SrcSpan { start: closing_square_bracket_end - 1, end: closing_square_bracket_end, }, name: "_".into(), type_: (), }), // No tail specified None => None, }; if elements.is_empty() && tail.as_ref().is_some_and(|pattern| pattern.is_discard()) { self.warnings .push(DeprecatedSyntaxWarning::DeprecatedListCatchAllPattern { location: SrcSpan { start, end: closing_square_bracket_end, }, }) } Pattern::List { location: SrcSpan { start, end: closing_square_bracket_end, }, elements, tail: tail.map(|tail_pattern| { let dot_dot_start = dot_dot_location .expect("parsed tail with no preceding `..`") .0; Box::new(TailPattern { location: SrcSpan::new(dot_dot_start, tail_pattern.location().end), pattern: tail_pattern, }) }), type_: (), } } // No pattern t0 => { self.tok0 = t0; return Ok(None); } }; match self.tok0 { Some((_, Token::As, _)) => { self.advance(); let (start, name, end) = self.expect_name()?; Ok(Some(Pattern::Assign { name, location: SrcSpan { start, end }, pattern: Box::new(pattern), })) } _ => Ok(Some(pattern)), } } fn add_multi_line_clause_hint(&self, mut err: ParseError) -> ParseError { if let ParseErrorType::UnexpectedToken { ref mut hint, .. } = err.error { *hint = Some("Did you mean to wrap a multi line clause in curly braces?".into()); } err } // examples: // pattern -> expr // pattern, pattern if -> expr // pattern, pattern | pattern, pattern if -> expr fn parse_case_clause(&mut self) -> Result, ParseError> { let patterns = self.parse_patterns(PatternPosition::CaseClause)?; match &patterns.first() { Some(lead) => { let mut alternative_patterns = vec![]; while let Some((vbar_start, vbar_end)) = self.maybe_one(&Token::Vbar) { let patterns = self.parse_patterns(PatternPosition::CaseClause)?; if patterns.is_empty() { return parse_error( ParseErrorType::ExpectedPattern, SrcSpan { start: vbar_start, end: vbar_end, }, ); } alternative_patterns.push(patterns); } let guard = self.parse_case_clause_guard()?; let (arr_s, arr_e) = self .expect_one(&Token::RArrow) .map_err(|e| self.add_multi_line_clause_hint(e))?; let then = self.parse_expression()?; match then { Some(then) => Ok(Some(Clause { location: SrcSpan { start: lead.location().start, end: then.location().end, }, pattern: patterns, alternative_patterns, guard, then, })), _ => match self.tok0 { Some((start, Token::DiscardName { .. }, end)) => { parse_error(ParseErrorType::IncorrectName, SrcSpan { start, end }) } _ => parse_error( ParseErrorType::ExpectedExpr, SrcSpan { start: arr_s, end: arr_e, }, ), }, } } _ => Ok(None), } } fn parse_patterns( &mut self, position: PatternPosition, ) -> Result, ParseError> { Parser::series_of( self, &|this| this.parse_pattern(position), Some(&Token::Comma), ) } // examples: // if a // if a < b // if a < b || b < c fn parse_case_clause_guard(&mut self) -> Result, ParseError> { let Some((start, end)) = self.maybe_one(&Token::If) else { return Ok(None); }; let clause_guard_result = self.parse_clause_guard_inner(); // If inner clause is none, a warning should be shown to let the user // know that empty clauses in guards are deprecated. if let Ok(None) = clause_guard_result { self.warnings .push(DeprecatedSyntaxWarning::DeprecatedEmptyClauseGuard { location: SrcSpan { start, end }, }); } clause_guard_result } fn parse_clause_guard_inner(&mut self) -> Result, ParseError> { let mut opstack = vec![]; let mut estack = vec![]; let mut last_op_start = 0; let mut last_op_end = 0; loop { match self.parse_case_clause_guard_unit()? { Some(unit) => estack.push(unit), _ => { if estack.is_empty() { return Ok(None); } else { return parse_error( ParseErrorType::OpNakedRight, SrcSpan { start: last_op_start, end: last_op_end, }, ); } } } let Some((op_s, t, op_e)) = self.tok0.take() else { break; }; let Some(precedence) = t.guard_precedence() else { // Is not Op self.tok0 = Some((op_s, t, op_e)); break; }; // Is Op self.advance(); last_op_start = op_s; last_op_end = op_e; let _ = handle_op( Some(((op_s, t, op_e), precedence)), &mut opstack, &mut estack, &do_reduce_clause_guard, ); } Ok(handle_op( None, &mut opstack, &mut estack, &do_reduce_clause_guard, )) } /// Checks if we have an unexpected left parenthesis and returns appropriate /// error if it is a function call. fn parse_function_call_in_clause_guard(&mut self, start: u32) -> Result<(), ParseError> { if let Some((l_paren_start, l_paren_end)) = self.maybe_one(&Token::LeftParen) { if let Ok((_, end)) = self .parse_fn_arguments() .and(self.expect_one(&Token::RightParen)) { return parse_error(ParseErrorType::CallInClauseGuard, SrcSpan { start, end }); } return parse_error( ParseErrorType::UnexpectedToken { token: Token::LeftParen, expected: vec![Token::RArrow.to_string().into()], hint: None, }, SrcSpan { start: l_paren_start, end: l_paren_end, }, ) .map_err(|e| self.add_multi_line_clause_hint(e)); } Ok(()) } // examples // a // 1 // a.1 // { a } // a || b // a < b || b < c fn parse_case_clause_guard_unit(&mut self) -> Result, ParseError> { match self.tok0.take() { Some((start, Token::Bang, _)) => { self.advance(); match self.parse_case_clause_guard_unit()? { Some(unit) => Ok(Some(ClauseGuard::Not { location: SrcSpan { start, end: unit.location().end, }, expression: Box::new(unit), })), None => { parse_error(ParseErrorType::ExpectedValue, SrcSpan { start, end: start }) } } } Some((start, Token::Name { name }, end)) => { self.advance(); self.parse_function_call_in_clause_guard(start)?; let mut unit = match self.parse_record_in_clause_guard(&name, SrcSpan { start, end })? { Some(record) => record, _ => ClauseGuard::Var { location: SrcSpan { start, end }, type_: (), name, definition_location: SrcSpan::default(), // We don't know the origin until type analysis, so // we just put `Generated` here as a placeholder. origin: VariableOrigin { syntax: VariableSyntax::Generated, declaration: VariableDeclaration::Generated, }, }, }; loop { let dot_s = match self.maybe_one(&Token::Dot) { Some((dot_s, _)) => dot_s, None => return Ok(Some(unit)), }; match self.next_tok() { Some(( _, Token::Int { value, int_value: _, }, int_e, )) => { let v = value.replace("_", ""); match u64::from_str(&v) { Ok(index) => { unit = ClauseGuard::TupleIndex { location: SrcSpan { start: dot_s, end: int_e, }, index, type_: (), tuple: Box::new(unit), }; } _ => { return parse_error( ParseErrorType::InvalidTupleAccess, SrcSpan { start, end }, ); } } } Some((name_start, Token::Name { name: label }, name_end)) => { self.parse_function_call_in_clause_guard(start)?; unit = ClauseGuard::FieldAccess { label_location: SrcSpan { start: name_start, end: name_end, }, index: None, label, type_: (), container: Box::new(unit), }; } Some((start, _, end)) => { return parse_error( ParseErrorType::IncorrectName, SrcSpan { start, end }, ); } _ => return self.next_tok_unexpected(vec!["A positive integer".into()]), } } } Some((start, Token::LeftBrace, _)) => { self.advance(); Ok(Some(self.parse_case_clause_guard_block(start)?)) } t0 => { self.tok0 = t0; match self.parse_const_value()? { Some(const_val) => { // Constant Ok(Some(ClauseGuard::Constant(const_val))) } _ => Ok(None), } } } } fn parse_case_clause_guard_block( &mut self, start: u32, ) -> Result { let body = self.parse_clause_guard_inner()?; let Some(body) = body else { let location = match self.next_tok() { Some((_, Token::RightBrace, end)) => SrcSpan { start, end }, Some((_, _, _)) | None => SrcSpan { start, end: start + 1, }, }; return parse_error(ParseErrorType::EmptyGuardBlock, location); }; let (_, end) = self.expect_one(&Token::RightBrace)?; Ok(ClauseGuard::Block { location: SrcSpan { start, end }, value: Box::new(body), }) } fn parse_record_in_clause_guard( &mut self, module: &EcoString, module_location: SrcSpan, ) -> Result, ParseError> { let (name, end) = match (self.tok0.take(), self.peek_tok1()) { (Some((_, Token::Dot, _)), Some(Token::UpName { .. })) => { self.advance(); // dot let Some((_, Token::UpName { name }, end)) = self.next_tok() else { return Ok(None); }; (name, end) } (tok0, _) => { self.tok0 = tok0; return Ok(None); } }; match self.parse_const_record_finish( module_location.start, Some((module.clone(), module_location)), name, end, )? { Some(record) => Ok(Some(ClauseGuard::Constant(record))), _ => Ok(None), } } // examples: // UpName( args ) fn expect_constructor_pattern( &mut self, module: Option<(u32, EcoString, u32)>, position: PatternPosition, ) -> Result { let (name_start, name, name_end) = self.expect_upname()?; let mut start = name_start; let (arguments, spread, end) = self.parse_constructor_pattern_arguments(name_end, position)?; if let Some((s, _, _)) = module { start = s; } Ok(Pattern::Constructor { location: SrcSpan { start, end }, name_location: SrcSpan::new(name_start, name_end), arguments, module: module.map(|(start, n, end)| (n, SrcSpan { start, end })), name, spread, constructor: Inferred::Unknown, type_: (), }) } // examples: // ( args ) #[allow(clippy::type_complexity)] fn parse_constructor_pattern_arguments( &mut self, upname_end: u32, position: PatternPosition, ) -> Result<(Vec>, Option, u32), ParseError> { if self.maybe_one(&Token::LeftParen).is_some() { let (arguments, arguments_end_with_comma) = self.series_of_has_trailing_separator( &|this| this.parse_constructor_pattern_arg(position), Some(&Token::Comma), )?; let spread = self .maybe_one(&Token::DotDot) .map(|(start, end)| SrcSpan { start, end }); if let Some(spread_location) = spread { let _ = self.maybe_one(&Token::Comma); if !arguments.is_empty() && !arguments_end_with_comma { self.warnings .push(DeprecatedSyntaxWarning::DeprecatedRecordSpreadPattern { location: spread_location, }) } } let (_, end) = self.expect_one(&Token::RightParen)?; Ok((arguments, spread, end)) } else { Ok((vec![], None, upname_end)) } } // examples: // a: // a: // fn parse_constructor_pattern_arg( &mut self, position: PatternPosition, ) -> Result>, ParseError> { match (self.tok0.take(), self.tok1.take()) { // named arg (Some((start, Token::Name { name }, _)), Some((_, Token::Colon, end))) => { self.advance(); self.advance(); match self.parse_pattern(position)? { Some(value) => Ok(Some(CallArg { implicit: None, location: SrcSpan { start, end: value.location().end, }, label: Some(name), value, })), _ => { // Argument supplied with a label shorthand. Ok(Some(CallArg { implicit: None, location: SrcSpan { start, end }, label: Some(name.clone()), value: UntypedPattern::Variable { origin: VariableOrigin { syntax: VariableSyntax::LabelShorthand(name.clone()), declaration: position.to_declaration(), }, name, location: SrcSpan { start, end }, type_: (), }, })) } } } // unnamed arg (t0, t1) => { self.tok0 = t0; self.tok1 = t1; match self.parse_pattern(position)? { Some(value) => Ok(Some(CallArg { implicit: None, location: value.location(), label: None, value, })), _ => Ok(None), } } } } // examples: // a: expr // a: fn parse_record_update_arg(&mut self) -> Result, ParseError> { match self.maybe_name() { Some((start, label, _)) => { let (_, end) = self.expect_one(&Token::Colon)?; let value = self.parse_expression()?; match value { Some(value) => Ok(Some(UntypedRecordUpdateArg { label, location: SrcSpan { start, end: value.location().end, }, value, })), _ => { // Argument supplied with a label shorthand. Ok(Some(UntypedRecordUpdateArg { label: label.clone(), location: SrcSpan { start, end }, value: UntypedExpr::Var { name: label, location: SrcSpan { start, end }, }, })) } } } _ => Ok(None), } } // // Parse Functions // // Starts after "fn" // // examples: // fn a(name: String) -> String { .. } // pub fn a(name name: String) -> String { .. } fn parse_function( &mut self, start: u32, public: bool, is_anon: bool, attributes: &mut Attributes, ) -> Result, ParseError> { let documentation = if is_anon { None } else { self.take_documentation(start) }; let mut name = None; if !is_anon { let (name_start, n, name_end) = self.expect_name()?; name = Some(( SrcSpan { start: name_start, end: name_end, }, n, )); } if let Some((less_start, less_end)) = self.maybe_one(&Token::Less) { return Err(ParseError { error: ParseErrorType::FunctionDefinitionAngleGenerics, location: SrcSpan { start: less_start, end: less_end, }, }); } let _ = self .expect_one(&Token::LeftParen) .map_err(|e| self.add_anon_function_hint(e))?; let arguments = Parser::series_of( self, &|parser| Parser::parse_fn_param(parser, is_anon), Some(&Token::Comma), )?; let (_, rpar_e) = self.expect_one_following_series(&Token::RightParen, "a function parameter")?; // Check for TypeScript-style return type annotation (:) instead of arrow (->) if let Some((colon_start, colon_end)) = self.maybe_one(&Token::Colon) { return Err(ParseError { error: ParseErrorType::UnexpectedToken { token: Token::Colon, expected: vec!["`->`".into()], hint: Some("Return type annotations are written using `->`, not `:`".into()), }, location: SrcSpan { start: colon_start, end: colon_end, }, }); }; let return_annotation = self.parse_type_annotation(&Token::RArrow)?; let (body_start, body, end, end_position) = match self.maybe_one(&Token::LeftBrace) { Some((left_brace_start, _)) => { let some_body = self.parse_statement_seq()?; let (_, right_brace_end) = self.expect_one(&Token::RightBrace)?; let end = return_annotation .as_ref() .map(|l| l.location().end) .unwrap_or(rpar_e); let body = match some_body { None => vec![Statement::Expression(UntypedExpr::Todo { kind: TodoKind::EmptyFunction { function_location: SrcSpan { start, end }, }, location: SrcSpan { start: left_brace_start + 1, end: right_brace_end, }, message: None, })], Some((body, _)) => body.to_vec(), }; (Some(left_brace_start), body, end, right_brace_end) } None => (None, vec![], rpar_e, rpar_e), }; Ok(Some(Definition::Function(Function { documentation, location: SrcSpan { start, end }, end_position, body_start, publicity: self.publicity(public, attributes.internal)?, name, arguments, body, return_type: (), return_annotation, deprecation: std::mem::take(&mut attributes.deprecated), external_erlang: attributes.external_erlang.take(), external_javascript: attributes.external_javascript.take(), implementations: Implementations { gleam: true, can_run_on_erlang: true, can_run_on_javascript: true, uses_erlang_externals: false, uses_javascript_externals: false, }, purity: Purity::Pure, }))) } fn add_anon_function_hint(&self, mut err: ParseError) -> ParseError { if let ParseErrorType::UnexpectedToken { ref mut hint, token: Token::Name { .. }, .. } = err.error { *hint = Some("Only module-level functions can be named.".into()); } err } fn publicity( &self, public: bool, internal: InternalAttribute, ) -> Result { match (internal, public) { (InternalAttribute::Missing, true) => Ok(Publicity::Public), (InternalAttribute::Missing, false) => Ok(Publicity::Private), (InternalAttribute::Present(location), true) => Ok(Publicity::Internal { attribute_location: Some(location), }), (InternalAttribute::Present(location), false) => Err(ParseError { error: ParseErrorType::RedundantInternalAttribute, location, }), } } // Parse a single function definition param // // examples: // _ // a // a a // a _ // a _:A // a a:A fn parse_fn_param(&mut self, is_anon: bool) -> Result, ParseError> { let (start, names, mut end) = match (self.tok0.take(), self.tok1.take()) { // labeled discard ( Some((start, Token::Name { name: label }, tok0_end)), Some((name_start, Token::DiscardName { name }, end)), ) => { if is_anon { return parse_error( ParseErrorType::UnexpectedLabel, SrcSpan { start, end: tok0_end, }, ); } self.advance(); self.advance(); ( start, ArgNames::LabelledDiscard { name, name_location: SrcSpan::new(name_start, end), label, label_location: SrcSpan::new(start, tok0_end), }, end, ) } // discard (Some((start, Token::DiscardName { name }, end)), t1) => { self.tok1 = t1; self.advance(); ( start, ArgNames::Discard { name, location: SrcSpan { start, end }, }, end, ) } // labeled name ( Some((start, Token::Name { name: label }, tok0_end)), Some((name_start, Token::Name { name }, end)), ) => { if is_anon { return parse_error( ParseErrorType::UnexpectedLabel, SrcSpan { start, end: tok0_end, }, ); } self.advance(); self.advance(); ( start, ArgNames::NamedLabelled { name, name_location: SrcSpan::new(name_start, end), label, label_location: SrcSpan::new(start, tok0_end), }, end, ) } // name (Some((start, Token::Name { name }, end)), t1) => { self.tok1 = t1; self.advance(); ( start, ArgNames::Named { name, location: SrcSpan { start, end }, }, end, ) } (t0, t1) => { self.tok0 = t0; self.tok1 = t1; return Ok(None); } }; let annotation = match self.parse_type_annotation(&Token::Colon)? { Some(a) => { end = a.location().end; Some(a) } _ => None, }; Ok(Some(Arg { location: SrcSpan { start, end }, type_: (), names, annotation, })) } // Parse function call arguments, no parens // // examples: // _ // expr, expr // a: _, expr // a: expr, _, b: _ fn parse_fn_arguments(&mut self) -> Result, ParseError> { let arguments = Parser::series_of(self, &Parser::parse_fn_argument, Some(&Token::Comma))?; Ok(arguments) } // Parse a single function call arg // // examples: // _ // expr // a: _ // a: expr fn parse_fn_argument(&mut self) -> Result, ParseError> { let label = match (self.tok0.take(), self.tok1.take()) { (Some((start, Token::Name { name }, _)), Some((_, Token::Colon, end))) => { self.advance(); self.advance(); Some((start, name, end)) } (t0, t1) => { self.tok0 = t0; self.tok1 = t1; None } }; match self.parse_expression()? { Some(value) => { let arg = match label { Some((start, label, _)) => CallArg { implicit: None, label: Some(label), location: SrcSpan { start, end: value.location().end, }, value, }, _ => CallArg { implicit: None, label: None, location: value.location(), value, }, }; Ok(Some(ParserArg::Arg(Box::new(arg)))) } _ => { match self.maybe_discard_name() { Some((name_start, name, name_end)) => { let arg = match label { Some((label_start, label, _)) => ParserArg::Hole { label: Some(label), arg_location: SrcSpan { start: label_start, end: name_end, }, discard_location: SrcSpan { start: name_start, end: name_end, }, name, }, _ => ParserArg::Hole { label: None, arg_location: SrcSpan { start: name_start, end: name_end, }, discard_location: SrcSpan { start: name_start, end: name_end, }, name, }, }; Ok(Some(arg)) } _ => { match label { Some((start, label, end)) => { // Argument supplied with a label shorthand. Ok(Some(ParserArg::Arg(Box::new(CallArg { implicit: None, label: Some(label.clone()), location: SrcSpan { start, end }, value: UntypedExpr::Var { name: label, location: SrcSpan { start, end }, }, })))) } _ => Ok(None), } } } } } } // // Parse Custom Types // // examples: // type A { A } // type A { A(String) } // type Box(inner_type) { Box(inner: inner_type) } // type NamedBox(inner_type) { Box(String, inner: inner_type) } fn parse_custom_type( &mut self, start: u32, public: bool, opaque: bool, attributes: &mut Attributes, ) -> Result, ParseError> { let documentation = self.take_documentation(start); let (name_start, name, parameters, end, name_end) = self.expect_type_name()?; let name_location = SrcSpan::new(name_start, name_end); let (constructors, end_position) = if self.maybe_one(&Token::LeftBrace).is_some() { // Custom Type let constructors = Parser::series_of( self, &|p| { // The only attribute supported on constructors is @deprecated let mut attributes = Attributes::default(); let attr_loc = Parser::parse_attributes(p, &mut attributes)?; if let Some(attr_span) = attr_loc { // Expecting all but the deprecated atterbutes to be default if attributes.external_erlang.is_some() || attributes.external_javascript.is_some() || attributes.target.is_some() || attributes.internal != InternalAttribute::Missing { return parse_error( ParseErrorType::UnknownAttributeRecordVariant, attr_span, ); } } match Parser::maybe_upname(p) { Some((c_s, c_n, c_e)) => { let documentation = p.take_documentation(c_s); let (arguments, arguments_e) = Parser::parse_type_constructor_arguments(p)?; let end = arguments_e.max(c_e); Ok(Some(RecordConstructor { location: SrcSpan { start: c_s, end }, name_location: SrcSpan { start: c_s, end: c_e, }, name: c_n, arguments, documentation, deprecation: attributes.deprecated, })) } _ => Ok(None), } }, // No separator None, )?; let (_, close_end) = self.expect_custom_type_close(&name, public, opaque)?; (constructors, close_end) } else { match self.maybe_one(&Token::Equal) { Some((eq_s, eq_e)) => { // Type Alias if opaque { return parse_error( ParseErrorType::OpaqueTypeAlias, SrcSpan { start, end }, ); } match self.parse_type()? { Some(t) => { let type_end = t.location().end; return Ok(Some(Definition::TypeAlias(TypeAlias { documentation, location: SrcSpan::new(start, type_end), publicity: self.publicity(public, attributes.internal)?, alias: name, name_location, parameters, type_ast: t, type_: (), deprecation: std::mem::take(&mut attributes.deprecated), }))); } _ => { return parse_error( ParseErrorType::ExpectedType, SrcSpan::new(eq_s, eq_e), ); } } } _ => (vec![], end), } }; Ok(Some(Definition::CustomType(CustomType { documentation, location: SrcSpan { start, end }, end_position, publicity: self.publicity(public, attributes.internal)?, opaque, name, name_location, parameters, constructors, typed_parameters: vec![], deprecation: std::mem::take(&mut attributes.deprecated), external_erlang: std::mem::take(&mut attributes.external_erlang), external_javascript: std::mem::take(&mut attributes.external_javascript), }))) } // examples: // A // A(one, two) fn expect_type_name( &mut self, ) -> Result<(u32, EcoString, Vec, u32, u32), ParseError> { let (start, upname, end) = self.expect_upname()?; if let Some((par_s, _)) = self.maybe_one(&Token::LeftParen) { let arguments = Parser::series_of(self, &|p| Ok(Parser::maybe_name(p)), Some(&Token::Comma))?; let (_, par_e) = self.expect_one_following_series(&Token::RightParen, "a name")?; if arguments.is_empty() { return parse_error( ParseErrorType::TypeDefinitionNoArguments, SrcSpan::new(par_s, par_e), ); } let arguments2 = arguments .into_iter() .map(|(start, name, end)| (SrcSpan { start, end }, name)) .collect(); Ok((start, upname, arguments2, par_e, end)) } else if let Some((less_start, less_end)) = self.maybe_one(&Token::Less) { let mut arguments = Parser::series_of( self, &|p| // Permit either names (`a`) or upnames (`A`) in this error-handling mode, // as upnames are common in other languages. Convert to lowercase so the // example is correct whichever was used. Ok(Parser::maybe_name(p) .or_else(|| Parser::maybe_upname(p)) .map(|(_, name, _)| name.to_lowercase())), Some(&Token::Comma), )?; // If no type arguments were parsed, fall back to a dummy type argument as an example, // because `Type()` would be invalid if arguments.is_empty() { arguments = vec!["value".into()]; } Err(ParseError { error: ParseErrorType::TypeDefinitionAngleGenerics { name: upname, arguments, }, location: SrcSpan { start: less_start, end: less_end, }, }) } else { Ok((start, upname, vec![], end, end)) } } // examples: // *no args* // () // (a, b) fn parse_type_constructor_arguments( &mut self, ) -> Result<(Vec>, u32), ParseError> { if self.maybe_one(&Token::LeftParen).is_some() { let arguments = Parser::series_of( self, &|p| match (p.tok0.take(), p.tok1.take()) { ( Some((start, Token::Name { name }, name_end)), Some((_, Token::Colon, end)), ) => { let _ = Parser::next_tok(p); let _ = Parser::next_tok(p); let doc = p.take_documentation(start); match Parser::parse_type(p)? { Some(type_ast) => { let end = type_ast.location().end; Ok(Some(RecordConstructorArg { label: Some((SrcSpan::new(start, name_end), name)), ast: type_ast, location: SrcSpan { start, end }, type_: (), doc, })) } None => { parse_error(ParseErrorType::ExpectedType, SrcSpan { start, end }) } } } (t0, t1) => { p.tok0 = t0; p.tok1 = t1; match Parser::parse_type(p)? { Some(type_ast) => { let doc = match &p.tok0 { Some((start, _, _)) => p.take_documentation(*start), None => None, }; let type_location = type_ast.location(); Ok(Some(RecordConstructorArg { label: None, ast: type_ast, location: type_location, type_: (), doc, })) } None => Ok(None), } } }, Some(&Token::Comma), )?; let (_, end) = self .expect_one_following_series(&Token::RightParen, "a constructor argument name")?; Ok((arguments, end)) } else { Ok((vec![], 0)) } } // // Parse Type Annotations // // examples: // :a // :Int // :Result(a, _) // :Result(Result(a, e), #(_, String)) fn parse_type_annotation(&mut self, start_tok: &Token) -> Result, ParseError> { if let Some((start, end)) = self.maybe_one(start_tok) { match self.parse_type() { Ok(None) => parse_error(ParseErrorType::ExpectedType, SrcSpan { start, end }), other => other, } } else { Ok(None) } } // Parse the type part of a type annotation, same as `parse_type_annotation` minus the ":" fn parse_type(&mut self) -> Result, ParseError> { match self.tok0.take() { // Type hole Some((start, Token::DiscardName { name }, end)) => { self.advance(); Ok(Some(TypeAst::Hole(TypeAstHole { location: SrcSpan { start, end }, name, }))) } // Tuple Some((start, Token::Hash, _)) => { self.advance(); let _ = self.expect_one(&Token::LeftParen)?; let elements = self.parse_types()?; let (_, end) = self.expect_one(&Token::RightParen)?; Ok(Some(TypeAst::Tuple(TypeAstTuple { location: SrcSpan { start, end }, elements, }))) } // Function Some((start, Token::Fn, _)) => { self.advance(); let _ = self.expect_one(&Token::LeftParen)?; let arguments = Parser::series_of(self, &|x| Parser::parse_type(x), Some(&Token::Comma))?; let _ = self.expect_one_following_series(&Token::RightParen, "a type")?; let (arr_s, arr_e) = self.expect_one(&Token::RArrow)?; let return_ = self.parse_type()?; match return_ { Some(return_) => Ok(Some(TypeAst::Fn(TypeAstFn { location: SrcSpan { start, end: return_.location().end, }, return_: Box::new(return_), arguments, }))), _ => parse_error( ParseErrorType::ExpectedType, SrcSpan { start: arr_s, end: arr_e, }, ), } } // Constructor function Some((start, Token::UpName { name }, end)) => { self.advance(); self.parse_type_name_finish(start, start, None, name, end) } // Constructor Module or type Variable Some((start, Token::Name { name: mod_name }, end)) => { self.advance(); if self.maybe_one(&Token::Dot).is_some() { let (name_start, upname, upname_e) = self.expect_upname()?; self.parse_type_name_finish( start, name_start, Some((mod_name, SrcSpan { start, end })), upname, upname_e, ) } else { Ok(Some(TypeAst::Var(TypeAstVar { location: SrcSpan { start, end }, name: mod_name, }))) } } t0 => { self.tok0 = t0; Ok(None) } } } // Parse the '( ... )' of a type name fn parse_type_name_finish( &mut self, start: u32, name_start: u32, module: Option<(EcoString, SrcSpan)>, name: EcoString, end: u32, ) -> Result, ParseError> { if let Some((par_s, _)) = self.maybe_one(&Token::LeftParen) { let arguments = self.parse_types()?; let (_, par_e) = self.expect_one(&Token::RightParen)?; Ok(Some(TypeAst::Constructor(TypeAstConstructor { location: SrcSpan { start, end: par_e }, name_location: SrcSpan { start: name_start, end, }, module, name, arguments, start_parentheses: Some(par_s), }))) } else if let Some((less_start, less_end)) = self.maybe_one(&Token::Less) { let arguments = self.parse_types()?; Err(ParseError { error: ParseErrorType::TypeUsageAngleGenerics { name, module: module.map(|(module_name, _)| module_name), arguments, }, location: SrcSpan { start: less_start, end: less_end, }, }) } else { Ok(Some(TypeAst::Constructor(TypeAstConstructor { location: SrcSpan { start, end }, name_location: SrcSpan { start: name_start, end, }, module, name, arguments: vec![], start_parentheses: None, }))) } } // For parsing a comma separated "list" of types, for tuple, constructor, and function fn parse_types(&mut self) -> Result, ParseError> { let elements = Parser::series_of(self, &|p| Parser::parse_type(p), Some(&Token::Comma))?; Ok(elements) } // // Parse Imports // // examples: // import a // import a/b // import a/b.{c} // import a/b.{c as d} as e fn parse_import(&mut self, import_start: u32) -> Result, ParseError> { let mut start = 0; let mut end; let mut module = EcoString::new(); let mut last_segment_start; let mut last_segment_end; // Gather module names loop { let (s, name, e) = self.expect_name()?; if module.is_empty() { start = s; } else { module.push('/'); } module.push_str(&name); end = e; last_segment_start = s; last_segment_end = e; // Useful error for : import a/.{b} if let Some((s, _)) = self.maybe_one(&Token::SlashDot) { return parse_error( ParseErrorType::ExpectedName, SrcSpan { start: s + 1, end: s + 1, }, ); } // break if there's no trailing slash if self.maybe_one(&Token::Slash).is_none() { break; } } let (_, documentation) = self.take_documentation(start).unzip(); // Gather imports let mut unqualified_values = vec![]; let mut unqualified_types = vec![]; if let Some((dot_start, dot_end)) = self.maybe_one(&Token::Dot) { if let Err(e) = self.expect_one(&Token::LeftBrace) { // If the module does contain a '/', then it's unlikely that the user // intended for the import to be pythonic, so skip this. if module.contains('/') { return Err(e); } // Catch `import gleam.io` and provide a more helpful error... let ParseErrorType::UnexpectedToken { token: Token::Name { name } | Token::UpName { name }, .. } = &e.error else { return Err(e); }; return Err(ParseError { error: ParseErrorType::IncorrectImportModuleSeparator { module, item: name.clone(), }, location: SrcSpan::new(dot_start, dot_end), }); }; let parsed = self.parse_unqualified_imports()?; unqualified_types = parsed.types; unqualified_values = parsed.values; let (_, e) = self.expect_one(&Token::RightBrace)?; end = e; } // Parse as_name let mut as_name = None; if let Some((as_start, _)) = self.maybe_one(&Token::As) { let (_, name, e) = self.expect_assign_name()?; end = e; as_name = Some(( name, SrcSpan { start: as_start, end, }, )); } Ok(Some(Definition::Import(Import { documentation, location: SrcSpan { start: import_start, end, }, module_location: SrcSpan { start: last_segment_start, end: last_segment_end, }, unqualified_values, unqualified_types, module, as_name, package: (), }))) } // [Name (as Name)? | UpName (as Name)? ](, [Name (as Name)? | UpName (as Name)?])*,? fn parse_unqualified_imports(&mut self) -> Result { let mut imports = ParsedUnqualifiedImports::default(); loop { // parse imports match self.tok0.take() { Some((start, Token::Name { name }, end)) => { self.advance(); let location = SrcSpan { start, end }; let mut import = UnqualifiedImport { name, location, imported_name_location: location, as_name: None, }; if self.maybe_one(&Token::As).is_some() { let (_, as_name, end) = self.expect_name()?; import.as_name = Some(as_name); import.location.end = end; } imports.values.push(import) } Some((start, Token::UpName { name }, end)) => { self.advance(); let location = SrcSpan { start, end }; let mut import = UnqualifiedImport { name, location, imported_name_location: location, as_name: None, }; if self.maybe_one(&Token::As).is_some() { let (_, as_name, end) = self.expect_upname()?; import.as_name = Some(as_name); import.location.end = end; } imports.values.push(import) } Some((start, Token::Type, _)) => { self.advance(); let (name_start, name, end) = self.expect_upname()?; let location = SrcSpan { start, end }; let mut import = UnqualifiedImport { name, location, imported_name_location: SrcSpan::new(name_start, end), as_name: None, }; if self.maybe_one(&Token::As).is_some() { let (_, as_name, end) = self.expect_upname()?; import.as_name = Some(as_name); import.location.end = end; } imports.types.push(import) } t0 => { self.tok0 = t0; break; } } // parse comma match self.tok0 { Some((_, Token::Comma, _)) => { self.advance(); } _ => break, } } Ok(imports) } // // Parse Constants // // examples: // const a = 1 // const a:Int = 1 // pub const a:Int = 1 fn parse_module_const( &mut self, start: u32, public: bool, attributes: &Attributes, ) -> Result, ParseError> { let (name_start, name, name_end) = self.expect_name()?; let documentation = self.take_documentation(name_start); let annotation = self.parse_type_annotation(&Token::Colon)?; let (eq_s, eq_e) = self.expect_one(&Token::Equal)?; match self.parse_const_value()? { Some(value) => { Ok(Some(Definition::ModuleConstant(ModuleConstant { documentation, location: SrcSpan { start, // End after the type annotation if it's there, otherwise after the name end: annotation .as_ref() .map(|annotation| annotation.location().end) .unwrap_or(0) .max(name_end), }, publicity: self.publicity(public, attributes.internal)?, name, name_location: SrcSpan::new(name_start, name_end), annotation, value: Box::new(value), type_: (), deprecation: attributes.deprecated.clone(), implementations: Implementations { gleam: true, can_run_on_erlang: true, can_run_on_javascript: true, uses_erlang_externals: false, uses_javascript_externals: false, }, }))) } _ => parse_error( ParseErrorType::NoValueAfterEqual, SrcSpan { start: eq_s, end: eq_e, }, ), } } // examples: // 1 // "hi" // True // [1,2,3] // wibble <> "wobble" fn parse_const_value(&mut self) -> Result, ParseError> { let constant_result = self.parse_const_value_unit(); match constant_result { Ok(Some(constant)) => self.parse_const_maybe_concatenation(constant), _ => constant_result, } } fn parse_const_value_unit(&mut self) -> Result, ParseError> { match self.tok0.take() { Some((start, Token::String { value }, end)) => { self.advance(); Ok(Some(Constant::String { value, location: SrcSpan { start, end }, })) } Some((start, Token::Float { value, float_value }, end)) => { self.advance(); Ok(Some(Constant::Float { value, location: SrcSpan { start, end }, float_value, })) } Some((start, Token::Int { value, int_value }, end)) => { self.advance(); Ok(Some(Constant::Int { value, int_value, location: SrcSpan { start, end }, })) } Some((start, Token::Hash, _)) => { self.advance(); let _ = self.expect_one(&Token::LeftParen)?; let elements = Parser::series_of(self, &Parser::parse_const_value, Some(&Token::Comma))?; let (_, end) = self.expect_one_following_series(&Token::RightParen, "a constant value")?; Ok(Some(Constant::Tuple { elements, location: SrcSpan { start, end }, type_: (), })) } Some((start, Token::LeftSquare, _)) => { self.advance(); let (elements, elements_end_with_comma) = self.series_of_has_trailing_separator( &Parser::parse_const_value, Some(&Token::Comma), )?; // Parse an optional tail let mut tail = None; let mut elements_after_tail = None; let mut dot_dot_location = None; // If there are no elements, we still want to parse a tail so // that we can report a better error message. if (elements_end_with_comma || elements.is_empty()) && let Some((start, end)) = self.maybe_one(&Token::DotDot) { dot_dot_location = Some((start, end)); tail = self.parse_const_value()?.map(Box::new); if self.maybe_one(&Token::Comma).is_some() { // See if there's a list of items after the tail, // like `[..wibble, wobble, wabble]` let elements = self.series_of(&Parser::parse_const_value, Some(&Token::Comma)); match elements { Err(_) => {} Ok(elements) => { elements_after_tail = Some(elements); } }; }; if tail.is_some() { // Give a better error when there are two lists being // concatenated like `[..wibble, ..wabble, woo]`, or if // there are elements after the tail. if let Some((second_start, second_end)) = self.maybe_one(&Token::DotDot) { let _second_tail = self.parse_const_value(); if elements_after_tail.is_none() || elements_after_tail .as_ref() .is_some_and(|vec| vec.is_empty()) { return parse_error( ParseErrorType::ListSpreadWithAnotherSpread { first_spread_location: SrcSpan { start, end }, }, SrcSpan { start: second_start, end: second_end, }, ); } } } } let (_, end) = self.expect_one_following_series(&Token::RightSquare, "a constant value")?; // Return errors for malformed lists match dot_dot_location { Some((start, end)) if tail.is_none() => { return parse_error( ParseErrorType::ListSpreadWithoutTail, SrcSpan { start, end }, ); } _ => {} } if tail.is_some() && elements.is_empty() && elements_after_tail.as_ref().is_none_or(|e| e.is_empty()) { return parse_error( ParseErrorType::ListSpreadWithoutElements, SrcSpan { start, end }, ); } match elements_after_tail { Some(elements) if !elements.is_empty() => { let (start, end) = match (dot_dot_location, tail) { (Some((start, _)), Some(tail)) => (start, tail.location().end), (_, _) => (start, end), }; return parse_error( ParseErrorType::ListSpreadFollowedByElements, SrcSpan { start, end }, ); } _ => {} } Ok(Some(Constant::List { elements, location: SrcSpan { start, end }, type_: (), tail, })) } // BitArray Some((start, Token::LtLt, _)) => { self.advance(); let segments = Parser::series_of( self, &|s| { Parser::parse_bit_array_segment( s, &Parser::parse_const_value, &Parser::expect_const_int, &bit_array_const_int, ) }, Some(&Token::Comma), )?; let (_, end) = self.expect_one_following_series(&Token::GtGt, "a bit array segment")?; Ok(Some(Constant::BitArray { location: SrcSpan { start, end }, segments, })) } Some((start, Token::UpName { name }, end)) => { self.advance(); self.parse_const_record_finish(start, None, name, end) } Some((start, Token::Name { name }, module_end)) if self.peek_tok1() == Some(&Token::Dot) => { self.advance(); // name self.advance(); // dot match self.tok0.take() { Some((_, Token::UpName { name: upname }, end)) => { self.advance(); // upname self.parse_const_record_finish( start, Some((name, SrcSpan::new(start, module_end))), upname, end, ) } Some((_, Token::Name { name: end_name }, end)) => { self.advance(); // name match self.tok0 { Some((_, Token::LeftParen, _)) => parse_error( ParseErrorType::UnexpectedFunction, SrcSpan { start, end: end + 1, }, ), _ => Ok(Some(Constant::Var { location: SrcSpan { start, end }, module: Some((name, SrcSpan::new(start, module_end))), name: end_name, constructor: None, type_: (), })), } } Some((start, token, end)) => parse_error( ParseErrorType::UnexpectedToken { token, expected: vec!["UpName".into(), "Name".into()], hint: None, }, SrcSpan { start, end }, ), None => { parse_error(ParseErrorType::UnexpectedEof, SrcSpan { start: 0, end: 0 }) } } } Some((start, Token::Name { name }, end)) => { self.advance(); // name match self.tok0 { Some((_, Token::LeftParen, _)) => parse_error( ParseErrorType::UnexpectedFunction, SrcSpan { start, end: end + 1, }, ), _ => Ok(Some(Constant::Var { location: SrcSpan { start, end }, module: None, name, constructor: None, type_: (), })), } } // Helpful error for fn Some((start, Token::Fn, end)) => { parse_error(ParseErrorType::NotConstType, SrcSpan { start, end }) } t0 => { self.tok0 = t0; Ok(None) } } } fn parse_const_maybe_concatenation( &mut self, left: UntypedConstant, ) -> Result, ParseError> { match self.tok0.take() { Some((op_start, Token::Concatenate, op_end)) => { self.advance(); match self.parse_const_value() { Ok(Some(right_constant_value)) => Ok(Some(Constant::StringConcatenation { location: SrcSpan { start: left.location().start, end: right_constant_value.location().end, }, left: Box::new(left), right: Box::new(right_constant_value), })), _ => parse_error( ParseErrorType::OpNakedRight, SrcSpan { start: op_start, end: op_end, }, ), } } t0 => { self.tok0 = t0; Ok(Some(left)) } } } // Parse the '( .. )' of a const type constructor fn parse_const_record_finish( &mut self, start: u32, module: Option<(EcoString, SrcSpan)>, name: EcoString, end: u32, ) -> Result, ParseError> { match self.maybe_one(&Token::LeftParen) { Some((par_s, _)) => { if self.maybe_one(&Token::DotDot).is_some() { let record = match self.parse_const_value()? { Some(value) => RecordBeingUpdated { location: value.location(), base: Box::new(value), }, None => { return parse_error( ParseErrorType::UnexpectedEof, SrcSpan::new(par_s, par_s + 2), ); } }; let mut update_arguments = vec![]; if self.maybe_one(&Token::Comma).is_some() { update_arguments = Parser::series_of( self, &Parser::parse_const_record_update_arg, Some(&Token::Comma), )?; } let (_, par_e) = self.expect_one_following_series( &Token::RightParen, "a constant record update argument", )?; let constructor_location = SrcSpan { start, end }; Ok(Some(Constant::RecordUpdate { location: SrcSpan { start, end: par_e }, constructor_location, module, name, record, arguments: update_arguments, tag: (), type_: (), field_map: Inferred::Unknown, })) } else { let arguments = Parser::series_of( self, &Parser::parse_const_record_arg, Some(&Token::Comma), )?; let (_, par_e) = self.expect_one_following_series( &Token::RightParen, "a constant record argument", )?; if arguments.is_empty() { return parse_error( ParseErrorType::ConstantRecordConstructorNoArguments, SrcSpan::new(par_s, par_e), ); } Ok(Some(Constant::Record { location: SrcSpan { start, end: par_e }, module, name, arguments, tag: (), type_: (), field_map: Inferred::Unknown, record_constructor: None, })) } } _ => Ok(Some(Constant::Record { location: SrcSpan { start, end }, module, name, arguments: vec![], tag: (), type_: (), field_map: Inferred::Unknown, record_constructor: None, })), } } // examples: // name: const // const // name: fn parse_const_record_arg(&mut self) -> Result>, ParseError> { let label = match (self.tok0.take(), self.tok1.take()) { // Named arg (Some((start, Token::Name { name }, _)), Some((_, Token::Colon, end))) => { self.advance(); self.advance(); Some((start, name, end)) } // Unnamed arg (t0, t1) => { self.tok0 = t0; self.tok1 = t1; None } }; match self.parse_const_value()? { Some(value) => match label { Some((start, label, _)) => Ok(Some(CallArg { implicit: None, location: SrcSpan { start, end: value.location().end, }, value, label: Some(label), })), _ => Ok(Some(CallArg { implicit: None, location: value.location(), value, label: None, })), }, _ => { match label { Some((start, label, end)) => { // Argument supplied with a label shorthand. Ok(Some(CallArg { implicit: None, location: SrcSpan { start, end }, label: Some(label.clone()), value: UntypedConstant::Var { location: SrcSpan { start, end }, constructor: None, module: None, name: label, type_: (), }, })) } _ => Ok(None), } } } } fn parse_const_record_update_arg( &mut self, ) -> Result>, ParseError> { let (start, label, label_end) = match (self.tok0.take(), self.tok1.take()) { // Named arg - required for record updates (Some((start, Token::Name { name }, _)), Some((_, Token::Colon, end))) => { self.advance(); self.advance(); (start, name, end) } // Unnamed arg or other - return error since record updates require labels (Some((start, Token::Name { name }, end)), t1) => { self.tok0 = Some((start, Token::Name { name: name.clone() }, end)); self.tok1 = t1; // Check if this is label shorthand (name without colon) // In this case, use the name as both label and value match self.parse_const_value()? { Some(value) if value.location() == SrcSpan { start, end } => { return Ok(Some(RecordUpdateArg { label: name.clone(), location: SrcSpan { start, end }, value, })); } _ => { self.tok0 = Some((start, Token::Name { name }, end)); return parse_error(ParseErrorType::ExpectedName, SrcSpan { start, end }); } } } (t0, t1) => { self.tok0 = t0; self.tok1 = t1; return Ok(None); } }; match self.parse_const_value()? { Some(value) => Ok(Some(RecordUpdateArg { label, location: SrcSpan { start, end: value.location().end, }, value, })), _ => { // Label shorthand: field without value means field: field Ok(Some(RecordUpdateArg { label: label.clone(), location: SrcSpan { start, end: label_end, }, value: UntypedConstant::Var { location: SrcSpan { start, end: label_end, }, constructor: None, module: None, name: label, type_: (), }, })) } } } // // Bit String parsing // // The structure is roughly the same for pattern, const, and expr // that's why these functions take functions // // pattern (: option)? fn parse_bit_array_segment( &mut self, value_parser: &impl Fn(&mut Self) -> Result, ParseError>, arg_parser: &impl Fn(&mut Self) -> Result, to_int_segment: &impl Fn(EcoString, BigInt, u32, u32) -> A, ) -> Result>, ParseError> where A: HasLocation + std::fmt::Debug, { match value_parser(self)? { Some(value) => { let options = if self.maybe_one(&Token::Colon).is_some() { Parser::series_of( self, &|s| Parser::parse_bit_array_option(s, &arg_parser, &to_int_segment), Some(&Token::Minus), )? } else { vec![] }; let end = options .last() .map(|o| o.location().end) .unwrap_or_else(|| value.location().end); Ok(Some(BitArraySegment { location: SrcSpan { start: value.location().start, end, }, value: Box::new(value), type_: (), options, })) } _ => Ok(None), } } // examples: // 1 // size(1) // size(five) // utf8 fn parse_bit_array_option( &mut self, arg_parser: &impl Fn(&mut Self) -> Result, to_int_segment: &impl Fn(EcoString, BigInt, u32, u32) -> A, ) -> Result>, ParseError> { match self.next_tok() { // named segment Some((start, Token::Name { name }, end)) => { if self.maybe_one(&Token::LeftParen).is_some() { // named function segment match name.as_str() { "unit" => match self.next_tok() { Some((int_s, Token::Int { value, .. }, int_e)) => { let (_, end) = self.expect_one(&Token::RightParen)?; let v = value.replace("_", ""); match u8::from_str(&v) { Ok(units) if units > 0 => Ok(Some(BitArrayOption::Unit { location: SrcSpan { start, end }, value: units, })), _ => Err(ParseError { error: ParseErrorType::InvalidBitArrayUnit, location: SrcSpan { start: int_s, end: int_e, }, }), } } _ => self.next_tok_unexpected(vec!["positive integer".into()]), }, "size" => { let value = arg_parser(self)?; let (_, end) = self.expect_one(&Token::RightParen)?; Ok(Some(BitArrayOption::Size { location: SrcSpan { start, end }, value: Box::new(value), short_form: false, })) } _ => parse_error( ParseErrorType::InvalidBitArraySegment, SrcSpan { start, end }, ), } } else { str_to_bit_array_option(&name, SrcSpan { start, end }) .ok_or(ParseError { error: ParseErrorType::InvalidBitArraySegment, location: SrcSpan { start, end }, }) .map(Some) } } // int segment Some((start, Token::Int { value, int_value }, end)) => Ok(Some(BitArrayOption::Size { location: SrcSpan { start, end }, value: Box::new(to_int_segment(value, int_value, start, end)), short_form: true, })), // invalid _ => self.next_tok_unexpected(vec![ "A valid bit array segment type".into(), "See: https://tour.gleam.run/data-types/bit-arrays/".into(), ]), } } fn expect_bit_array_pattern_segment_arg(&mut self) -> Result { Ok(Pattern::BitArraySize(self.expect_bit_array_size()?)) } fn expect_bit_array_size(&mut self) -> Result, ParseError> { let left = match self.next_tok() { Some((start, Token::Name { name }, end)) => BitArraySize::Variable { location: SrcSpan { start, end }, name, constructor: None, type_: (), }, Some((start, Token::Int { value, int_value }, end)) => BitArraySize::Int { location: SrcSpan { start, end }, value, int_value, }, Some((start, Token::LeftBrace, _)) => { let inner = self.expect_bit_array_size()?; let (_, end) = self.expect_one(&Token::RightBrace)?; BitArraySize::Block { location: SrcSpan { start, end }, inner: Box::new(inner), } } _ => return self.next_tok_unexpected(vec!["A variable name or an integer".into()]), }; let Some((start, token, end)) = self.tok0.take() else { return Ok(left); }; match token { Token::Plus => { _ = self.next_tok(); let right = self.expect_bit_array_size()?; Ok(self.bit_array_size_binary_operator(left, right, IntOperator::Add)) } Token::Minus => { _ = self.next_tok(); let right = self.expect_bit_array_size()?; Ok(self.bit_array_size_binary_operator(left, right, IntOperator::Subtract)) } Token::Star => { _ = self.next_tok(); let right = self.expect_bit_array_size()?; Ok( self.bit_array_size_high_precedence_operator( left, right, IntOperator::Multiply, ), ) } Token::Slash => { _ = self.next_tok(); let right = self.expect_bit_array_size()?; Ok(self.bit_array_size_high_precedence_operator(left, right, IntOperator::Divide)) } Token::Percent => { _ = self.next_tok(); let right = self.expect_bit_array_size()?; Ok(self.bit_array_size_high_precedence_operator( left, right, IntOperator::Remainder, )) } Token::Name { .. } | Token::UpName { .. } | Token::DiscardName { .. } | Token::Int { .. } | Token::Float { .. } | Token::String { .. } | Token::CommentDoc { .. } | Token::LeftParen | Token::RightParen | Token::LeftSquare | Token::RightSquare | Token::LeftBrace | Token::RightBrace | Token::Less | Token::Greater | Token::LessEqual | Token::GreaterEqual | Token::PlusDot | Token::MinusDot | Token::StarDot | Token::SlashDot | Token::LessDot | Token::GreaterDot | Token::LessEqualDot | Token::GreaterEqualDot | Token::Concatenate | Token::Colon | Token::Comma | Token::Hash | Token::Bang | Token::Equal | Token::EqualEqual | Token::NotEqual | Token::Vbar | Token::VbarVbar | Token::AmperAmper | Token::LtLt | Token::GtGt | Token::Pipe | Token::Dot | Token::RArrow | Token::LArrow | Token::DotDot | Token::At | Token::EndOfFile | Token::CommentNormal | Token::CommentModule | Token::NewLine | Token::As | Token::Assert | Token::Auto | Token::Case | Token::Const | Token::Delegate | Token::Derive | Token::Echo | Token::Else | Token::Fn | Token::If | Token::Implement | Token::Import | Token::Let | Token::Macro | Token::Opaque | Token::Panic | Token::Pub | Token::Test | Token::Todo | Token::Type | Token::Use => { self.tok0 = Some((start, token, end)); Ok(left) } } } fn bit_array_size_binary_operator( &self, left: BitArraySize<()>, right: BitArraySize<()>, operator: IntOperator, ) -> BitArraySize<()> { let start = left.location().start; let end = right.location().end; BitArraySize::BinaryOperator { left: Box::new(left), right: Box::new(right), operator, location: SrcSpan { start, end }, } } fn bit_array_size_high_precedence_operator( &self, left: BitArraySize<()>, right: BitArraySize<()>, operator: IntOperator, ) -> BitArraySize<()> { match right { BitArraySize::Int { .. } | BitArraySize::Variable { .. } | BitArraySize::Block { .. } => { self.bit_array_size_binary_operator(left, right, operator) } // This operator has the highest precedence of the possible operators // for bit array size patterns. So, `a * b + c` should be parsed as // `(a * b) + c`. If the right-hand side of this operator is another // operator, we rearrange it to ensure correct precedence. BitArraySize::BinaryOperator { operator: right_operator, left: middle, right, .. } => self.bit_array_size_binary_operator( self.bit_array_size_binary_operator(left, *middle, operator), *right, right_operator, ), } } fn expect_const_int(&mut self) -> Result { match self.next_tok() { Some((start, Token::Int { value, int_value }, end)) => Ok(Constant::Int { location: SrcSpan { start, end }, value, int_value, }), _ => self.next_tok_unexpected(vec!["A variable name or an integer".into()]), } } fn expect_expression(&mut self) -> Result { match self.parse_expression()? { Some(e) => Ok(e), _ => self.next_tok_unexpected(vec!["An expression".into()]), } } fn expect_expression_unit( &mut self, context: ExpressionUnitContext, ) -> Result { if let Some(e) = self.parse_expression_unit(context)? { Ok(e) } else { self.next_tok_unexpected(vec!["An expression".into()]) } } // // Parse Helpers // /// Expect a particular token, advances the token stream fn expect_one(&mut self, wanted: &Token) -> Result<(u32, u32), ParseError> { match self.maybe_one(wanted) { Some((start, end)) => Ok((start, end)), None => self.next_tok_unexpected(vec![wanted.to_string().into()]), } } // Expect a particular token after having parsed a series, advances the token stream // Used for giving a clearer error message in cases where the series item is what failed to parse fn expect_one_following_series( &mut self, wanted: &Token, series: &'static str, ) -> Result<(u32, u32), ParseError> { match self.maybe_one(wanted) { Some((start, end)) => Ok((start, end)), None => self.next_tok_unexpected(vec![wanted.to_string().into(), series.into()]), } } /// Expect the end to a custom type definiton or handle an incorrect /// record constructor definition. /// /// Used for mapping to a more specific error type and message. fn expect_custom_type_close( &mut self, name: &EcoString, public: bool, opaque: bool, ) -> Result<(u32, u32), ParseError> { match self.maybe_one(&Token::RightBrace) { Some((start, end)) => Ok((start, end)), None => match self.next_tok() { None => parse_error(ParseErrorType::UnexpectedEof, SrcSpan { start: 0, end: 0 }), Some((start, token, end)) => { // If provided a Name, map to a more detailed error // message to nudge the user. // Else, handle as an unexpected token. let field = if let Token::Name { name } = token { name } else { let hint = match (&token, self.tok0.take()) { (&Token::Fn, _) | (&Token::Pub, Some((_, Token::Fn, _))) => { let text = "Gleam is not an object oriented programming language so functions are declared separately from types."; Some(wrap(text).into()) } (_, _) => None, }; return parse_error( ParseErrorType::UnexpectedToken { token, expected: vec![ Token::RightBrace.to_string().into(), "a record constructor".into(), ], hint, }, SrcSpan { start, end }, ); }; let field_type = match self.parse_type_annotation(&Token::Colon) { Ok(Some(annotation)) => Some(Box::new(annotation)), _ => None, }; parse_error( ParseErrorType::ExpectedRecordConstructor { name: name.clone(), public, opaque, field, field_type, }, SrcSpan { start, end }, ) } }, } } // Expect a Name else a token dependent helpful error fn expect_name(&mut self) -> Result<(u32, EcoString, u32), ParseError> { let (start, token, end) = self.expect_assign_name()?; match token { AssignName::Variable(name) => Ok((start, name, end)), AssignName::Discard(_) => { parse_error(ParseErrorType::IncorrectName, SrcSpan { start, end }) } } } fn expect_assign_name(&mut self) -> Result<(u32, AssignName, u32), ParseError> { let t = self.next_tok(); match t { Some((start, tok, end)) => match tok { Token::Name { name } => Ok((start, AssignName::Variable(name), end)), Token::DiscardName { name, .. } => Ok((start, AssignName::Discard(name), end)), Token::UpName { .. } => { parse_error(ParseErrorType::IncorrectName, SrcSpan { start, end }) } _ if tok.is_reserved_word() => parse_error( ParseErrorType::UnexpectedReservedWord, SrcSpan { start, end }, ), Token::Int { .. } | Token::Float { .. } | Token::String { .. } | Token::CommentDoc { .. } | Token::LeftParen | Token::RightParen | Token::LeftSquare | Token::RightSquare | Token::LeftBrace | Token::RightBrace | Token::Plus | Token::Minus | Token::Star | Token::Slash | Token::Less | Token::Greater | Token::LessEqual | Token::GreaterEqual | Token::Percent | Token::PlusDot | Token::MinusDot | Token::StarDot | Token::SlashDot | Token::LessDot | Token::GreaterDot | Token::LessEqualDot | Token::GreaterEqualDot | Token::Concatenate | Token::Colon | Token::Comma | Token::Hash | Token::Bang | Token::Equal | Token::EqualEqual | Token::NotEqual | Token::Vbar | Token::VbarVbar | Token::AmperAmper | Token::LtLt | Token::GtGt | Token::Pipe | Token::Dot | Token::RArrow | Token::LArrow | Token::DotDot | Token::At | Token::EndOfFile | Token::CommentNormal | Token::CommentModule | Token::NewLine | Token::As | Token::Assert | Token::Auto | Token::Case | Token::Const | Token::Delegate | Token::Derive | Token::Echo | Token::Else | Token::Fn | Token::If | Token::Implement | Token::Import | Token::Let | Token::Macro | Token::Opaque | Token::Panic | Token::Pub | Token::Test | Token::Todo | Token::Type | Token::Use => parse_error(ParseErrorType::ExpectedName, SrcSpan { start, end }), }, None => parse_error(ParseErrorType::UnexpectedEof, SrcSpan { start: 0, end: 0 }), } } // Expect an UpName else a token dependent helpful error fn expect_upname(&mut self) -> Result<(u32, EcoString, u32), ParseError> { let t = self.next_tok(); match t { Some((start, tok, end)) => match tok { Token::Name { .. } | Token::DiscardName { .. } => { parse_error(ParseErrorType::IncorrectUpName, SrcSpan { start, end }) } Token::UpName { name } => Ok((start, name, end)), Token::Int { .. } | Token::Float { .. } | Token::String { .. } | Token::CommentDoc { .. } | Token::LeftParen | Token::RightParen | Token::LeftSquare | Token::RightSquare | Token::LeftBrace | Token::RightBrace | Token::Plus | Token::Minus | Token::Star | Token::Slash | Token::Less | Token::Greater | Token::LessEqual | Token::GreaterEqual | Token::Percent | Token::PlusDot | Token::MinusDot | Token::StarDot | Token::SlashDot | Token::LessDot | Token::GreaterDot | Token::LessEqualDot | Token::GreaterEqualDot | Token::Concatenate | Token::Colon | Token::Comma | Token::Hash | Token::Bang | Token::Equal | Token::EqualEqual | Token::NotEqual | Token::Vbar | Token::VbarVbar | Token::AmperAmper | Token::LtLt | Token::GtGt | Token::Pipe | Token::Dot | Token::RArrow | Token::LArrow | Token::DotDot | Token::At | Token::EndOfFile | Token::CommentNormal | Token::CommentModule | Token::NewLine | Token::As | Token::Assert | Token::Auto | Token::Case | Token::Const | Token::Delegate | Token::Derive | Token::Echo | Token::Else | Token::Fn | Token::If | Token::Implement | Token::Import | Token::Let | Token::Macro | Token::Opaque | Token::Panic | Token::Pub | Token::Test | Token::Todo | Token::Type | Token::Use => parse_error(ParseErrorType::ExpectedUpName, SrcSpan { start, end }), }, None => parse_error(ParseErrorType::UnexpectedEof, SrcSpan { start: 0, end: 0 }), } } // Expect a target name. e.g. `javascript` or `erlang`. // The location of the preceding left parenthesis is required // to give the correct error span in case the target name is missing. fn expect_target(&mut self, paren_location: SrcSpan) -> Result { let (start, t, end) = match self.next_tok() { Some(t) => t, None => { return parse_error(ParseErrorType::UnexpectedEof, SrcSpan { start: 0, end: 0 }); } }; if let Token::Name { name } = t { match name.as_str() { "javascript" => Ok(Target::JavaScript), "erlang" => Ok(Target::Erlang), "js" => { self.warnings .push(DeprecatedSyntaxWarning::DeprecatedTargetShorthand { location: SrcSpan::new(start, end), target: Target::JavaScript, }); Ok(Target::JavaScript) } "erl" => { self.warnings .push(DeprecatedSyntaxWarning::DeprecatedTargetShorthand { location: SrcSpan::new(start, end), target: Target::Erlang, }); Ok(Target::Erlang) } _ => parse_error(ParseErrorType::UnknownTarget, SrcSpan::new(start, end)), } } else { parse_error(ParseErrorType::ExpectedTargetName, paren_location) } } // Expect a String else error fn expect_string(&mut self) -> Result<(u32, EcoString, u32), ParseError> { match self.next_tok() { Some((start, Token::String { value }, end)) => Ok((start, value, end)), _ => self.next_tok_unexpected(vec!["a string".into()]), } } fn peek_tok1(&mut self) -> Option<&Token> { self.tok1.as_ref().map(|(_, token, _)| token) } // If the next token matches the requested, consume it and return (start, end) fn maybe_one(&mut self, tok: &Token) -> Option<(u32, u32)> { match self.tok0.take() { Some((s, t, e)) if t == *tok => { self.advance(); Some((s, e)) } t0 => { self.tok0 = t0; None } } } // Parse a series by repeating a parser, and possibly a separator fn series_of( &mut self, parser: &impl Fn(&mut Self) -> Result, ParseError>, sep: Option<&Token>, ) -> Result, ParseError> { let (res, _) = self.series_of_has_trailing_separator(parser, sep)?; Ok(res) } /// Parse a series by repeating a parser, and a separator. Returns true if /// the series ends with the trailing separator. fn series_of_has_trailing_separator( &mut self, parser: &impl Fn(&mut Self) -> Result, ParseError>, sep: Option<&Token>, ) -> Result<(Vec, bool), ParseError> { let mut results = vec![]; let mut final_separator = None; while let Some(result) = parser(self)? { results.push(result); if let Some(sep) = sep { if let Some(separator) = self.maybe_one(sep) { final_separator = Some(separator); } else { final_separator = None; break; } // Helpful error if extra separator if let Some((start, end)) = self.maybe_one(sep) { return parse_error(ParseErrorType::ExtraSeparator, SrcSpan { start, end }); } } } // If the sequence ends with a trailing comma we want to keep track of // its position. if let (Some(Token::Comma), Some((_, end))) = (sep, final_separator) { self.extra.trailing_commas.push(end) }; Ok((results, final_separator.is_some())) } // If next token is a Name, consume it and return relevant info, otherwise, return none fn maybe_name(&mut self) -> Option<(u32, EcoString, u32)> { match self.tok0.take() { Some((s, Token::Name { name }, e)) => { self.advance(); Some((s, name, e)) } t0 => { self.tok0 = t0; None } } } // if next token is an UpName, consume it and return relevant info, otherwise, return none fn maybe_upname(&mut self) -> Option<(u32, EcoString, u32)> { match self.tok0.take() { Some((s, Token::UpName { name }, e)) => { self.advance(); Some((s, name, e)) } t0 => { self.tok0 = t0; None } } } // if next token is a DiscardName, consume it and return relevant info, otherwise, return none fn maybe_discard_name(&mut self) -> Option<(u32, EcoString, u32)> { match self.tok0.take() { Some((s, Token::DiscardName { name }, e)) => { self.advance(); Some((s, name, e)) } t0 => { self.tok0 = t0; None } } } // Unexpected token error on the next token or EOF fn next_tok_unexpected(&mut self, expected: Vec) -> Result { match self.next_tok() { None => parse_error(ParseErrorType::UnexpectedEof, SrcSpan { start: 0, end: 0 }), Some((start, token, end)) => parse_error( ParseErrorType::UnexpectedToken { token, expected, hint: None, }, SrcSpan { start, end }, ), } } // Moves the token stream forward fn advance(&mut self) { let _ = self.next_tok(); } // Moving the token stream forward // returns old tok0 fn next_tok(&mut self) -> Option { let t = self.tok0.take(); let mut previous_newline = None; let mut nxt; loop { match self.tokens.next() { // gather and skip extra Some(Ok((start, Token::CommentNormal, end))) => { self.extra.comments.push(SrcSpan { start, end }); previous_newline = None; } Some(Ok((start, Token::CommentDoc { content }, end))) => { self.extra.doc_comments.push(SrcSpan::new(start, end)); self.doc_comments.push_back((start, content)); previous_newline = None; } Some(Ok((start, Token::CommentModule, end))) => { self.extra.module_comments.push(SrcSpan { start, end }); previous_newline = None; } Some(Ok((start, Token::NewLine, _))) => { self.extra.new_lines.push(start); // If the previous token is a newline as well that means we // have run into an empty line. if let Some(start) = previous_newline { // We increase the byte position so that newline's start // doesn't overlap with the previous token's end. self.extra.empty_lines.push(start + 1); } previous_newline = Some(start); } // die on lex error Some(Err(err)) => { nxt = None; self.lex_errors.push(err); break; } Some(Ok(tok)) => { nxt = Some(tok); break; } None => { nxt = None; break; } } } self.tok0 = self.tok1.take(); self.tok1 = nxt.take(); t } fn take_documentation(&mut self, until: u32) -> Option<(u32, EcoString)> { let mut content = String::new(); let mut doc_start = u32::MAX; while let Some((start, line)) = self.doc_comments.front() { if *start < doc_start { doc_start = *start; } if *start >= until { break; } if self.extra.has_comment_between(*start, until) { // We ignore doc comments that come before a regular comment. let location = SrcSpan::new(*start, start + line.len() as u32); _ = self.doc_comments.pop_front(); self.detached_doc_comments.push(location); continue; } content.push_str(line); content.push('\n'); _ = self.doc_comments.pop_front(); } if content.is_empty() { None } else { Some((doc_start, content.into())) } } fn parse_attributes( &mut self, attributes: &mut Attributes, ) -> Result, ParseError> { let mut attributes_span = None; while let Some((start, end)) = self.maybe_one(&Token::At) { if attributes_span.is_none() { attributes_span = Some(SrcSpan { start, end }); } let end = self.parse_attribute(start, attributes)?; attributes_span = attributes_span.map(|span| SrcSpan { start: span.start, end, }); } Ok(attributes_span) } fn parse_attribute( &mut self, start: u32, attributes: &mut Attributes, ) -> Result { // Parse the name of the attribute. let (_, name, end) = self.expect_name()?; let end = match name.as_str() { "external" => { let _ = self.expect_one(&Token::LeftParen)?; self.parse_external_attribute(start, end, attributes) } "target" => self.parse_target_attribute(start, end, attributes), "deprecated" => { let _ = self.expect_one(&Token::LeftParen)?; self.parse_deprecated_attribute(start, end, attributes) } "internal" => self.parse_internal_attribute(start, end, attributes), _ => parse_error(ParseErrorType::UnknownAttribute, SrcSpan { start, end }), }?; Ok(end) } fn parse_target_attribute( &mut self, start: u32, end: u32, attributes: &mut Attributes, ) -> Result { let (paren_start, paren_end) = self.expect_one(&Token::LeftParen)?; let target = self.expect_target(SrcSpan::new(paren_start, paren_end))?; if attributes.target.is_some() { return parse_error(ParseErrorType::DuplicateAttribute, SrcSpan { start, end }); } let (_, end) = self.expect_one(&Token::RightParen)?; if attributes.target.is_some() { return parse_error(ParseErrorType::DuplicateAttribute, SrcSpan { start, end }); } attributes.target = Some(target); Ok(end) } fn parse_external_attribute( &mut self, start: u32, end: u32, attributes: &mut Attributes, ) -> Result { let (_, name, _) = self.expect_name()?; let target = match name.as_str() { "erlang" => Target::Erlang, "javascript" => Target::JavaScript, _ => return parse_error(ParseErrorType::UnknownTarget, SrcSpan::new(start, end)), }; let _ = self.expect_one(&Token::Comma)?; let (_, module, _) = self.expect_string()?; let _ = self.expect_one(&Token::Comma)?; let (_, function, _) = self.expect_string()?; let _ = self.maybe_one(&Token::Comma); let (_, end) = self.expect_one(&Token::RightParen)?; if attributes.has_external_for(target) { return parse_error(ParseErrorType::DuplicateAttribute, SrcSpan { start, end }); } attributes.set_external_for(target, Some((module, function, SrcSpan { start, end }))); Ok(end) } fn parse_deprecated_attribute( &mut self, start: u32, end: u32, attributes: &mut Attributes, ) -> Result { if attributes.deprecated.is_deprecated() { return parse_error(ParseErrorType::DuplicateAttribute, SrcSpan::new(start, end)); } let (_, message, _) = self.expect_string().map_err(|_| ParseError { error: ParseErrorType::ExpectedDeprecationMessage, location: SrcSpan { start, end }, })?; let (_, end) = self.expect_one(&Token::RightParen)?; attributes.deprecated = Deprecation::Deprecated { message }; Ok(end) } fn parse_internal_attribute( &mut self, start: u32, end: u32, attributes: &mut Attributes, ) -> Result { match attributes.internal { // If `internal` is present that means that we have already run into // another `@internal` annotation, so it results in a `DuplicateAttribute` // error. InternalAttribute::Present(_) => { parse_error(ParseErrorType::DuplicateAttribute, SrcSpan::new(start, end)) } InternalAttribute::Missing => { attributes.internal = InternalAttribute::Present(SrcSpan::new(start, end)); Ok(end) } } } } fn concat_pattern_variable_left_hand_side_error(start: u32, end: u32) -> Result { Err(ParseError { error: ParseErrorType::ConcatPatternVariableLeftHandSide, location: SrcSpan::new(start, end), }) } // Operator Precedence Parsing // // Higher number means higher precedence. // All operators are left associative. /// Simple-Precedence-Parser, handle seeing an operator or end fn handle_op( next_op: Option<(Spanned, u8)>, opstack: &mut Vec<(Spanned, u8)>, estack: &mut Vec, do_reduce: &impl Fn(Spanned, &mut Vec), ) -> Option { let mut next_op = next_op; loop { match (opstack.pop(), next_op.take()) { (None, None) => match estack.pop() { Some(fin) => { if estack.is_empty() { return Some(fin); } else { panic!("Expression not fully reduced.") } } _ => { return None; } }, (None, Some(op)) => { opstack.push(op); break; } (Some((op, _)), None) => do_reduce(op, estack), (Some((opl, pl)), Some((opr, pr))) => { match pl.cmp(&pr) { // all ops are left associative Ordering::Greater | Ordering::Equal => { do_reduce(opl, estack); next_op = Some((opr, pr)); } Ordering::Less => { opstack.push((opl, pl)); opstack.push((opr, pr)); break; } } } } } None } fn precedence(t: &Token) -> Option { if t == &Token::Pipe { return Some(6); }; tok_to_binop(t).map(|op| op.precedence()) } fn tok_to_binop(t: &Token) -> Option { match t { Token::VbarVbar => Some(BinOp::Or), Token::AmperAmper => Some(BinOp::And), Token::EqualEqual => Some(BinOp::Eq), Token::NotEqual => Some(BinOp::NotEq), Token::Less => Some(BinOp::LtInt), Token::LessEqual => Some(BinOp::LtEqInt), Token::Greater => Some(BinOp::GtInt), Token::GreaterEqual => Some(BinOp::GtEqInt), Token::LessDot => Some(BinOp::LtFloat), Token::LessEqualDot => Some(BinOp::LtEqFloat), Token::GreaterDot => Some(BinOp::GtFloat), Token::GreaterEqualDot => Some(BinOp::GtEqFloat), Token::Plus => Some(BinOp::AddInt), Token::Minus => Some(BinOp::SubInt), Token::PlusDot => Some(BinOp::AddFloat), Token::MinusDot => Some(BinOp::SubFloat), Token::Percent => Some(BinOp::RemainderInt), Token::Star => Some(BinOp::MultInt), Token::StarDot => Some(BinOp::MultFloat), Token::Slash => Some(BinOp::DivInt), Token::SlashDot => Some(BinOp::DivFloat), Token::Concatenate => Some(BinOp::Concatenate), Token::Name { .. } | Token::UpName { .. } | Token::DiscardName { .. } | Token::Int { .. } | Token::Float { .. } | Token::String { .. } | Token::CommentDoc { .. } | Token::LeftParen | Token::RightParen | Token::LeftSquare | Token::RightSquare | Token::LeftBrace | Token::RightBrace | Token::Colon | Token::Comma | Token::Hash | Token::Bang | Token::Equal | Token::Vbar | Token::LtLt | Token::GtGt | Token::Pipe | Token::Dot | Token::RArrow | Token::LArrow | Token::DotDot | Token::At | Token::EndOfFile | Token::CommentNormal | Token::CommentModule | Token::NewLine | Token::As | Token::Assert | Token::Auto | Token::Case | Token::Const | Token::Delegate | Token::Derive | Token::Echo | Token::Else | Token::Fn | Token::If | Token::Implement | Token::Import | Token::Let | Token::Macro | Token::Opaque | Token::Panic | Token::Pub | Token::Test | Token::Todo | Token::Type | Token::Use => None, } } /// Simple-Precedence-Parser, perform reduction for expression fn do_reduce_expression(op: Spanned, estack: &mut Vec) { match (estack.pop(), estack.pop()) { (Some(er), Some(el)) => { let new_e = expr_op_reduction(op, el, er); estack.push(new_e); } _ => panic!("Tried to reduce without 2 expressions"), } } /// Simple-Precedence-Parser, perform reduction for clause guard fn do_reduce_clause_guard(op: Spanned, estack: &mut Vec) { match (estack.pop(), estack.pop()) { (Some(er), Some(el)) => { let new_e = clause_guard_reduction(op, el, er); estack.push(new_e); } _ => panic!("Tried to reduce without 2 guards"), } } fn expr_op_reduction( (token_start, token, token_end): Spanned, l: UntypedExpr, r: UntypedExpr, ) -> UntypedExpr { if token == Token::Pipe { let expressions = if let UntypedExpr::PipeLine { mut expressions } = l { expressions.push(r); expressions } else { vec1![l, r] }; UntypedExpr::PipeLine { expressions } } else { match tok_to_binop(&token) { Some(bin_op) => UntypedExpr::BinOp { location: SrcSpan { start: l.location().start, end: r.location().end, }, name: bin_op, name_location: SrcSpan { start: token_start, end: token_end, }, left: Box::new(l), right: Box::new(r), }, _ => { panic!("Token could not be converted to binop.") } } } } fn clause_guard_reduction( (_, token, _): Spanned, l: UntypedClauseGuard, r: UntypedClauseGuard, ) -> UntypedClauseGuard { let location = SrcSpan { start: l.location().start, end: r.location().end, }; let left = Box::new(l); let right = Box::new(r); let operator = tok_to_binop(&token).expect("Token could not be converted to binop."); UntypedClauseGuard::BinaryOperator { location, operator, left, right, } } // BitArray Parse Helpers // // BitArrays in patterns, guards, and expressions have a very similar structure // but need specific types. These are helpers for that. There is probably a // rustier way to do this :) fn bit_array_size_int(value: EcoString, int_value: BigInt, start: u32, end: u32) -> UntypedPattern { Pattern::BitArraySize(BitArraySize::Int { location: SrcSpan { start, end }, value, int_value, }) } fn bit_array_expr_int(value: EcoString, int_value: BigInt, start: u32, end: u32) -> UntypedExpr { UntypedExpr::Int { location: SrcSpan { start, end }, value, int_value, } } fn bit_array_const_int( value: EcoString, int_value: BigInt, start: u32, end: u32, ) -> UntypedConstant { Constant::Int { location: SrcSpan { start, end }, value, int_value, } } fn str_to_bit_array_option(lit: &str, location: SrcSpan) -> Option> { match lit { "bytes" => Some(BitArrayOption::Bytes { location }), "int" => Some(BitArrayOption::Int { location }), "float" => Some(BitArrayOption::Float { location }), "bits" => Some(BitArrayOption::Bits { location }), "utf8" => Some(BitArrayOption::Utf8 { location }), "utf16" => Some(BitArrayOption::Utf16 { location }), "utf32" => Some(BitArrayOption::Utf32 { location }), "utf8_codepoint" => Some(BitArrayOption::Utf8Codepoint { location }), "utf16_codepoint" => Some(BitArrayOption::Utf16Codepoint { location }), "utf32_codepoint" => Some(BitArrayOption::Utf32Codepoint { location }), "signed" => Some(BitArrayOption::Signed { location }), "unsigned" => Some(BitArrayOption::Unsigned { location }), "big" => Some(BitArrayOption::Big { location }), "little" => Some(BitArrayOption::Little { location }), "native" => Some(BitArrayOption::Native { location }), _ => None, } } // // Error Helpers // fn parse_error(error: ParseErrorType, location: SrcSpan) -> Result { Err(ParseError { error, location }) } // // Misc Helpers // // Parsing a function call into the appropriate structure #[derive(Debug)] pub enum ParserArg { Arg(Box>), Hole { name: EcoString, /// The whole span of the argument. arg_location: SrcSpan, /// Just the span of the ignore name. discard_location: SrcSpan, label: Option, }, } pub fn make_call( fun: UntypedExpr, arguments: Vec, start: u32, end: u32, ) -> Result { let mut hole_location = None; let arguments = arguments .into_iter() .map(|argument| match argument { ParserArg::Arg(arg) => Ok(*arg), ParserArg::Hole { arg_location, discard_location, name, label, } => { if hole_location.is_some() { return parse_error(ParseErrorType::TooManyArgHoles, SrcSpan { start, end }); } hole_location = Some(discard_location); if name != "_" { return parse_error( ParseErrorType::UnexpectedToken { token: Token::Name { name }, expected: vec!["An expression".into(), "An underscore".into()], hint: None, }, arg_location, ); } Ok(CallArg { implicit: None, label, location: arg_location, value: UntypedExpr::Var { location: discard_location, name: CAPTURE_VARIABLE.into(), }, }) } }) .collect::>()?; let call = UntypedExpr::Call { location: SrcSpan { start, end }, fun: Box::new(fun), arguments, }; match hole_location { // A normal call None => Ok(call), // An anon function using the capture syntax run(_, 1, 2) Some(hole_location) => Ok(UntypedExpr::Fn { location: call.location(), end_of_head_byte_index: call.location().end, kind: FunctionLiteralKind::Capture { hole: hole_location, }, arguments: vec![Arg { location: hole_location, annotation: None, names: ArgNames::Named { name: CAPTURE_VARIABLE.into(), location: hole_location, }, type_: (), }], body: vec1![Statement::Expression(call)], return_annotation: None, }), } } #[derive(Debug, Default)] struct ParsedUnqualifiedImports { types: Vec, values: Vec, } /// Parses an Int value to a bigint. /// pub fn parse_int_value(value: &str) -> Option { let (radix, value) = if let Some(value) = value.strip_prefix("0x") { (16, value) } else if let Some(value) = value.strip_prefix("0o") { (8, value) } else if let Some(value) = value.strip_prefix("0b") { (2, value) } else { (10, value) }; let value = value.trim_start_matches('_'); BigInt::parse_bytes(value.as_bytes(), radix) } #[derive(Debug, PartialEq, Clone, Copy)] enum ExpressionUnitContext { FollowingPipe, Other, } #[derive(Debug, Clone, Copy)] pub enum PatternPosition { LetAssignment, CaseClause, UsePattern, } impl PatternPosition { pub fn to_declaration(&self) -> VariableDeclaration { match self { PatternPosition::LetAssignment => VariableDeclaration::LetPattern, PatternPosition::CaseClause => VariableDeclaration::ClausePattern, PatternPosition::UsePattern => VariableDeclaration::UsePattern, } } } /// A thin f64 wrapper that does not permit NaN. /// This allows us to implement `Eq`, which require reflexivity. /// /// Used for gleam float literals, which cannot be NaN. /// /// While there is no syntax for "infinity", float literals might be too big and /// overflow into infinity. This is still allowed so we can parse big literal /// numbers and the error will be raised during the analysis phase. #[derive(Clone, Copy, Debug, PartialEq)] pub struct LiteralFloatValue(f64); impl LiteralFloatValue { pub const ONE: Self = LiteralFloatValue(1.0); pub const ZERO: Self = LiteralFloatValue(0.0); /// Parse from a string, returning `None` if the string /// is not a valid f64 or the float is `NaN`` pub fn parse(value: &str) -> Option { value .replace("_", "") .parse::() .ok() .filter(|float| !float.is_nan()) .map(LiteralFloatValue) } pub fn value(&self) -> f64 { self.0 } } impl Eq for LiteralFloatValue {} impl Ord for LiteralFloatValue { fn cmp(&self, other: &Self) -> Ordering { self.0 .partial_cmp(&other.0) .expect("Only NaN comparisons should fail") } } impl PartialOrd for LiteralFloatValue { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Hash for LiteralFloatValue { fn hash(&self, state: &mut H) { self.0.to_bits().hash(state) } } impl Serialize for LiteralFloatValue { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.serialize_f64(self.0) } } impl<'de> Deserialize<'de> for LiteralFloatValue { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let value = f64::deserialize(deserializer)?; if value.is_nan() { Err(serde::de::Error::custom("NaN is not allowed")) } else { Ok(LiteralFloatValue(value)) } } } ================================================ FILE: compiler-core/src/paths.rs ================================================ use crate::{ build::{Mode, Target}, manifest::Base16Checksum, }; use camino::{Utf8Path, Utf8PathBuf}; pub const ARTEFACT_DIRECTORY_NAME: &str = "_gleam_artefacts"; #[derive(Debug, Clone)] pub struct ProjectPaths { root: Utf8PathBuf, } impl ProjectPaths { pub fn new(root: Utf8PathBuf) -> Self { Self { root } } pub fn at_filesystem_root() -> Self { let path = if cfg!(target_family = "windows") { r"C:\" } else { "/" }; Self::new(Utf8PathBuf::from(path)) } pub fn root(&self) -> &Utf8Path { &self.root } pub fn root_config(&self) -> Utf8PathBuf { self.root.join("gleam.toml") } pub fn readme(&self) -> Utf8PathBuf { self.root.join("README.md") } pub fn manifest(&self) -> Utf8PathBuf { self.root.join("manifest.toml") } pub fn src_directory(&self) -> Utf8PathBuf { self.root.join("src") } pub fn test_directory(&self) -> Utf8PathBuf { self.root.join("test") } pub fn dev_directory(&self) -> Utf8PathBuf { self.root.join("dev") } pub fn build_directory(&self) -> Utf8PathBuf { self.root.join("build") } pub fn build_packages_directory(&self) -> Utf8PathBuf { self.build_directory().join("packages") } pub fn build_packages_toml(&self) -> Utf8PathBuf { self.build_packages_directory().join("packages.toml") } pub fn build_packages_package(&self, package_name: &str) -> Utf8PathBuf { self.build_packages_directory().join(package_name) } // build_deps_package_config pub fn build_packages_package_config(&self, package_name: &str) -> Utf8PathBuf { self.build_packages_package(package_name).join("gleam.toml") } pub fn build_export_hex_tarball(&self, package_name: &str, version: &str) -> Utf8PathBuf { self.build_directory() .join(format!("{package_name}-{version}.tar")) } pub fn build_directory_for_mode(&self, mode: Mode) -> Utf8PathBuf { self.build_directory().join(mode.to_string()) } pub fn erlang_shipment_directory(&self) -> Utf8PathBuf { self.build_directory().join("erlang-shipment") } pub fn build_documentation_directory(&self, package: &str) -> Utf8PathBuf { self.build_directory_for_mode(Mode::Dev) .join("docs") .join(package) } pub fn build_directory_for_target(&self, mode: Mode, target: Target) -> Utf8PathBuf { let target = match target { Target::Erlang => "erlang", Target::JavaScript => "javascript", }; self.build_directory_for_mode(mode).join(target) } /// Note this uses the "application name", not the name of this package. /// This is because in BEAM applications one can specify an application /// name that is not the same as the Hex package name. Ideally we would /// always use the package name, but the BEAM runtime knows nothing /// about packages, only applications, so it will look on the filesystem /// for the application name when loading it. pub fn build_directory_for_package( &self, mode: Mode, target: Target, application_name: &str, ) -> Utf8PathBuf { self.build_directory_for_target(mode, target) .join(application_name) } pub fn build_packages_ebins_glob(&self, mode: Mode, target: Target) -> Utf8PathBuf { self.build_directory_for_package(mode, target, "*") .join("ebin") } /// A path to a special file that contains the version of gleam that last built /// the artifacts. If this file does not match the current version of gleam we /// will rebuild from scratch pub fn build_gleam_version(&self, mode: Mode, target: Target) -> Utf8PathBuf { self.build_directory_for_target(mode, target) .join("gleam_version") } /// The path to the source gleam.toml file for a path dependency. pub fn path_dependency_gleam_toml_path(&self, dependency_path: &Utf8Path) -> Utf8PathBuf { self.root().join(dependency_path).join("gleam.toml") } pub fn dependency_gleam_toml_fingerprint_path(&self, dependency_name: &str) -> Utf8PathBuf { self.build_packages_directory() .join(format!("{}.config_fingerprint", dependency_name)) } } pub fn global_package_cache_package_tarball(checksum: &Base16Checksum) -> Utf8PathBuf { global_packages_cache().join(format!("{}.tar", checksum.to_string())) } pub fn global_hexpm_oauth_credentials_path() -> Utf8PathBuf { global_hexpm_cache().join("credentials.toml") } pub fn global_hexpm_legacy_credentials_path() -> Utf8PathBuf { global_hexpm_cache().join("credentials") } fn global_hexpm_cache() -> Utf8PathBuf { default_global_gleam_cache().join("hex").join("hexpm") } fn global_packages_cache() -> Utf8PathBuf { global_hexpm_cache().join("packages") } pub fn default_global_gleam_cache() -> Utf8PathBuf { Utf8PathBuf::from_path_buf( dirs_next::cache_dir() .expect("Failed to determine user cache directory") .join("gleam"), ) .expect("Non Utf8 Path") } pub fn unnest(within: &Utf8Path) -> Utf8PathBuf { let mut path = Utf8PathBuf::new(); for _ in within { path = path.join("..") } path } #[test] fn paths() { assert!(default_global_gleam_cache().ends_with("gleam")); assert!(global_packages_cache().ends_with("hex/hexpm/packages")); assert!( global_package_cache_package_tarball(&Base16Checksum(vec![0, 0, 0, 0])) .ends_with("hex/hexpm/packages/00000000.tar") ); assert!( global_package_cache_package_tarball(&Base16Checksum(vec![0x3A, 0x21, 0xF4])) .ends_with("hex/hexpm/packages/3A21F4.tar") ); } ================================================ FILE: compiler-core/src/pretty/tests.rs ================================================ use super::Document::*; use super::Mode::*; use super::*; use im::vector; use pretty_assertions::assert_eq; #[test] fn fits_test() { // Negative limits never fit assert!(!fits(-1, 0, vector![])); // If no more documents it always fits assert!(fits(0, 0, vector![])); // ForceBreak never fits let doc = ForceBroken(Box::new(nil())); assert!(!fits(100, 0, vector![(0, Unbroken, &doc)])); let doc = ForceBroken(Box::new(nil())); assert!(!fits(100, 0, vector![(0, Broken, &doc)])); // Break in Broken fits always assert!(fits( 1, 0, vector![( 0, Broken, &Break { broken: "12", unbroken: "", kind: BreakKind::Strict, } )] )); // Break in Unbroken mode fits if `unbroken` fits assert!(fits( 3, 0, vector![( 0, Unbroken, &Break { broken: "", unbroken: "123", kind: BreakKind::Strict, } )] )); assert!(!fits( 2, 0, vector![( 0, Unbroken, &Break { broken: "", unbroken: "123", kind: BreakKind::Strict, } )] )); // Line always fits assert!(fits(0, 0, vector![(0, Broken, &Line(100))])); assert!(fits(0, 0, vector![(0, Unbroken, &Line(100))])); // String fits if smaller than limit let doc = Document::str("Hello"); assert!(fits(5, 0, vector![(0, Broken, &doc)])); let doc = Document::str("Hello"); assert!(fits(5, 0, vector![(0, Unbroken, &doc)])); let doc = Document::str("Hello"); assert!(!fits(4, 0, vector![(0, Broken, &doc)])); let doc = Document::str("Hello"); assert!(!fits(4, 0, vector![(0, Unbroken, &doc)])); // Cons fits if combined smaller than limit let doc = Document::str("1").append(Document::str("2")); assert!(fits(2, 0, vector![(0, Broken, &doc)])); let doc = Document::str("1").append(Document::str("2")); assert!(fits(2, 0, vector![(0, Unbroken, &doc,)])); let doc = Document::str("1").append(Document::str("2")); assert!(!fits(1, 0, vector![(0, Broken, &doc)])); let doc = Document::str("1").append(Document::str("2")); assert!(!fits(1, 0, vector![(0, Unbroken, &doc)])); // Nest fits if combined smaller than limit let doc = Nest( 1, NestMode::Increase, NestCondition::Always, Box::new(Document::str("12")), ); assert!(fits(2, 0, vector![(0, Broken, &doc)])); assert!(fits(2, 0, vector![(0, Unbroken, &doc)])); assert!(!fits(1, 0, vector![(0, Broken, &doc)])); assert!(!fits(1, 0, vector![(0, Unbroken, &doc)])); // Nest fits if combined smaller than limit let doc = Nest( 0, NestMode::Increase, NestCondition::Always, Box::new(Document::str("12")), ); assert!(fits(2, 0, vector![(0, Broken, &doc)])); assert!(fits(2, 0, vector![(0, Unbroken, &doc)])); assert!(!fits(1, 0, vector![(0, Broken, &doc)])); assert!(!fits(1, 0, vector![(0, Unbroken, &doc)])); let doc = ZeroWidthString { string: "this is a very long string that doesn't count towards line width".into(), }; assert!(fits(10, 0, vector![(0, Unbroken, &doc)])); assert!(fits(10, 9, vector![(0, Unbroken, &doc)])); let string_doc = "hello!".to_doc(); assert!(fits( 10, 0, vector![(0, Unbroken, &string_doc), (0, Unbroken, &doc)] )); } #[test] fn format_test() { let doc = Document::str("Hi"); assert_eq!("Hi", doc.to_pretty_string(10)); let doc = Document::str("Hi").append(Document::str(", world!")); assert_eq!("Hi, world!", doc.clone().to_pretty_string(10)); let doc = &Break { broken: "broken", unbroken: "unbroken", kind: BreakKind::Strict, } .group(); assert_eq!("unbroken", doc.clone().to_pretty_string(10)); let doc = &Break { broken: "broken", unbroken: "unbroken", kind: BreakKind::Strict, } .group(); assert_eq!("broken\n", doc.clone().to_pretty_string(5)); let doc = Nest( 2, NestMode::Increase, NestCondition::Always, Box::new(Document::str("1").append(Line(1).append(Document::str("2")))), ); assert_eq!("1\n 2", doc.to_pretty_string(1)); let doc = Group(Box::new(ForceBroken(Box::new(Break { broken: "broken", unbroken: "unbroken", kind: BreakKind::Strict, })))); assert_eq!("broken\n".to_string(), doc.to_pretty_string(100)); let doc = ForceBroken(Box::new(Break { broken: "broken", unbroken: "unbroken", kind: BreakKind::Flex, })); assert_eq!("unbroken".to_string(), doc.to_pretty_string(100)); let doc = Vec(vec![ Break { broken: "broken", unbroken: "unbroken", kind: BreakKind::Strict, }, zero_width_string("".into()), Break { broken: "broken", unbroken: "unbroken", kind: BreakKind::Strict, }, ]); assert_eq!( "unbrokenunbroken", doc.to_pretty_string(20) ); } #[test] fn forcing_test() { let docs = join( [ "hello".to_doc(), "a".to_doc(), "b".to_doc(), "c".to_doc(), "d".to_doc(), ], break_("", " "), ); assert_eq!( "hello\na\nb\nc\nd", docs.clone().force_break().group().to_pretty_string(80) ); assert_eq!( "hello a b c d", docs.clone() .force_break() .next_break_fits(NextBreakFitsMode::Enabled) .group() .to_pretty_string(80) ); assert_eq!( "hello\na\nb\nc\nd", docs.clone() .force_break() .next_break_fits(NextBreakFitsMode::Enabled) .next_break_fits(NextBreakFitsMode::Disabled) .group() .to_pretty_string(80) ); } #[test] fn nest_if_broken_test() { assert_eq!( "hello\n world", concat(["hello".to_doc(), break_("", " "), "world".to_doc()]) .nest_if_broken(2) .group() .to_pretty_string(10) ); let list_doc = concat([ concat([ break_("[", "["), "a,".to_doc(), break_("", " "), "b".to_doc(), ]) .nest(2), break_(",", ""), "]".to_doc(), ]) .group(); let arguments_doc = concat([ break_("", ""), "one".to_doc(), ",".to_doc(), break_("", " "), list_doc.group().next_break_fits(NextBreakFitsMode::Enabled), ]) .nest_if_broken(2) .group(); let function_call_doc = concat([ "some_function_call(".to_doc(), arguments_doc, break_("", ""), ")".to_doc(), ]) .group(); assert_eq!( "some_function_call(\n one,\n [\n a,\n b,\n ]\n)", function_call_doc.clone().to_pretty_string(2) ); assert_eq!( "some_function_call(\n one,\n [a, b]\n)", function_call_doc.clone().to_pretty_string(20) ); assert_eq!( "some_function_call(one, [\n a,\n b,\n])", function_call_doc.clone().to_pretty_string(25) ); assert_eq!( "some_function_call(one, [a, b])", function_call_doc.clone().to_pretty_string(80) ); } #[test] fn let_left_side_fits_test() { let elements = break_("", "").append("1").nest(2).append(break_("", "")); let list = "[".to_doc().append(elements).append("]").group(); let doc = list.clone().append(" = ").append(list); assert_eq!( "[1] = [ 1 ]", doc.clone().to_pretty_string(7) ); assert_eq!( "[ 1 ] = [ 1 ]", doc.clone().to_pretty_string(2) ); assert_eq!("[1] = [1]", doc.clone().to_pretty_string(16)); } #[test] fn empty_documents() { // nil assert!(nil().is_empty()); // lines assert!(lines(0).is_empty()); assert!(!line().is_empty()); // force break assert!(nil().force_break().is_empty()); assert!(!"ok".to_doc().force_break().is_empty()); // strings assert!("".to_doc().is_empty()); assert!(!"wibble".to_doc().is_empty()); assert!(!" ".to_doc().is_empty()); assert!(!"\n".to_doc().is_empty()); // containers assert!("".to_doc().nest(2).is_empty()); assert!(!"wibble".to_doc().nest(2).is_empty()); assert!("".to_doc().group().is_empty()); assert!(!"wibble".to_doc().group().is_empty()); assert!(break_("", "").is_empty()); assert!(!break_("wibble", "wibble").is_empty()); assert!(!break_("wibble\nwobble", "wibble wobble").is_empty()); assert!("".to_doc().append("".to_doc()).is_empty()); assert!(!"wibble".to_doc().append("".to_doc()).is_empty()); assert!(!"".to_doc().append("wibble".to_doc()).is_empty()); assert!(!zero_width_string("wibble".into()).is_empty()); } #[test] fn set_nesting() { let doc = Vec(vec!["wibble".to_doc(), break_("", " "), "wobble".to_doc()]).group(); assert_eq!( "wibble\nwobble", doc.set_nesting(0).nest(2).to_pretty_string(1) ); } ================================================ FILE: compiler-core/src/pretty.rs ================================================ //! This module implements the functionality described in //! ["Strictly Pretty" (2000) by Christian Lindig][0], with a few //! extensions. //! //! This module is heavily influenced by Elixir's Inspect.Algebra and //! JavaScript's Prettier. //! //! [0]: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.34.2200 //! //! ## Extensions //! //! - `ForcedBreak` from Elixir. //! - `FlexBreak` from Elixir. //! //! The way this module works is fairly simple conceptually, however the actual //! behaviour in practice can be hard to wrap one's head around. //! //! The basic premise is the `Document` type, which is a tree structure, //! containing some text as well as information on how it can be formatted. //! Once the document is constructed, it can be printed using the //! `to_pretty_string` function. //! //! It will then traverse the tree, and construct //! a string, attempting to wrap lines to that they do not exceed the line length //! limit specified. Where and when it wraps lines is determined by the structure //! of the `Document` itself. //! #![allow(clippy::wrong_self_convention)] #[cfg(test)] mod tests; use ecow::{EcoString, eco_format}; use itertools::Itertools; use num_bigint::BigInt; use unicode_segmentation::UnicodeSegmentation; use crate::{Result, io::Utf8Writer}; /// Join multiple documents together in a vector. This macro calls the `to_doc` /// method on each element, providing a concise way to write a document sequence. /// For example: /// /// ```rust:norun /// docvec!["Hello", line(), "world!"] /// ``` /// /// Note: each document in a docvec is not separated in any way: the formatter /// will never break a line unless a `Document::Break` or `Document::Line` /// is used. Therefore, `docvec!["a", "b", "c"]` is equivalent to /// `"abc".to_doc()`. /// #[macro_export] macro_rules! docvec { () => { Document::Vec(Vec::new()) }; // A docvec![] with a single element ($first:expr $(,)?) => { Document::Vec(vec![$first.to_doc()]) }; // A docvec![] with multiple elements. ($first:expr, $($rest:expr),+ $(,)?) => { // A document that looks like this: `Vec[Vec[..rest], ..other_rest]` // is exactly the same as a flat: `Vec[..rest, ..other_rest]`. // So in case a `docvec!` starts with a `Vec` we flatten it out to avoid // having deeply nested documents. match $first.to_doc() { Document::Vec(mut vec) => { $( vec.push($rest.to_doc()); )* Document::Vec(vec) }, first => Document::Vec(vec![first, $($rest.to_doc()),+]) } }; } /// Coerce a value into a Document. /// Note we do not implement this for String as a slight pressure to favour str /// over String. pub trait Documentable<'a> { fn to_doc(self) -> Document<'a>; } impl<'a> Documentable<'a> for char { fn to_doc(self) -> Document<'a> { Document::eco_string(eco_format!("{self}")) } } impl<'a> Documentable<'a> for &'a str { fn to_doc(self) -> Document<'a> { Document::str(self) } } impl<'a> Documentable<'a> for EcoString { fn to_doc(self) -> Document<'a> { Document::eco_string(self) } } impl<'a> Documentable<'a> for &EcoString { fn to_doc(self) -> Document<'a> { Document::eco_string(self.clone()) } } impl<'a> Documentable<'a> for isize { fn to_doc(self) -> Document<'a> { Document::eco_string(eco_format!("{self}")) } } impl<'a> Documentable<'a> for i64 { fn to_doc(self) -> Document<'a> { Document::eco_string(eco_format!("{self}")) } } impl<'a> Documentable<'a> for usize { fn to_doc(self) -> Document<'a> { Document::eco_string(eco_format!("{self}")) } } impl<'a> Documentable<'a> for f64 { fn to_doc(self) -> Document<'a> { Document::eco_string(eco_format!("{self:?}")) } } impl<'a> Documentable<'a> for u64 { fn to_doc(self) -> Document<'a> { Document::eco_string(eco_format!("{self:?}")) } } impl<'a> Documentable<'a> for u32 { fn to_doc(self) -> Document<'a> { Document::eco_string(eco_format!("{self}")) } } impl<'a> Documentable<'a> for u16 { fn to_doc(self) -> Document<'a> { Document::eco_string(eco_format!("{self}")) } } impl<'a> Documentable<'a> for u8 { fn to_doc(self) -> Document<'a> { Document::eco_string(eco_format!("{self}")) } } impl<'a> Documentable<'a> for BigInt { fn to_doc(self) -> Document<'a> { Document::eco_string(eco_format!("{self}")) } } impl<'a> Documentable<'a> for Document<'a> { fn to_doc(self) -> Document<'a> { self } } impl<'a> Documentable<'a> for Vec> { fn to_doc(self) -> Document<'a> { Document::Vec(self) } } impl<'a, D: Documentable<'a>> Documentable<'a> for Option { fn to_doc(self) -> Document<'a> { self.map(Documentable::to_doc).unwrap_or_else(nil) } } /// Joins an iterator into a single document, in the same way as `docvec!`. pub fn concat<'a>(docs: impl IntoIterator>) -> Document<'a> { Document::Vec(docs.into_iter().collect()) } /// Joins an iterator into a single document, interspersing each element with /// another document. This is useful for example in argument lists, where a /// list of arguments must all be separated with a comma. pub fn join<'a>( docs: impl IntoIterator>, separator: Document<'a>, ) -> Document<'a> { concat(Itertools::intersperse(docs.into_iter(), separator)) } /// A pretty printable document. A tree structure, made up of text and other /// elements which determine how it can be formatted. /// /// The variants of this enum should probably not be constructed directly, /// rather use the helper functions of the same names to construct them. /// For example, use `line()` instead of `Document::Line(1)`. /// #[derive(Debug, Clone, PartialEq, Eq)] pub enum Document<'a> { /// A mandatory linebreak. This is always printed as a string of newlines, /// equal in length to the number specified. Line(usize), /// Forces the breaks of the wrapped document to be considered as not /// fitting on a single line. Used in combination with a `Group` it can be /// used to force its `Break`s to always break. ForceBroken(Box), /// Ignore the next break, forcing it to render as unbroken. NextBreakFits(Box, NextBreakFitsMode), /// A document after which the formatter can insert a newline. This determines /// where line breaks can occur, outside of hardcoded `Line`s. /// See `break_` and `flex_break` for usage. Break { broken: &'a str, unbroken: &'a str, kind: BreakKind, }, /// Join multiple documents together. The documents are not separated in any /// way: the formatter will only print newlines if `Document::Break` or /// `Document::Line` is used. Vec(Vec), /// Nests the given document by the given indent, depending on the specified /// condition. See `Document::nest`, `Document::set_nesting` and /// `Document::nest_if_broken` for usages. Nest(isize, NestMode, NestCondition, Box), /// Groups a document. When pretty printing a group, the formatter will /// first attempt to fit the entire group on one line. If it fails, all /// `break_` documents in the group will render broken. /// /// Nested groups are handled separately to their parents, so if the /// outermost group is broken, any sub-groups might be rendered broken /// or unbroken, depending on whether they fit on a single line. Group(Box), /// Renders a string slice. This will always render the string verbatim, /// without any line breaks or other modifications to it. Str { string: &'a str, /// The number of extended grapheme clusters in the string. /// This is what the pretty printer uses as the width of the string as it /// is closes to what a human would consider the "length" of a string. /// /// Since computing the number of grapheme clusters requires walking over /// the string we precompute it to avoid iterating through a string over /// and over again in the pretty printing algorithm. /// graphemes: isize, }, /// Renders an `EcoString`. This will always render the string verbatim, /// without any line breaks or other modifications to it. EcoString { string: EcoString, /// The number of extended grapheme clusters in the string. /// This is what the pretty printer uses as the width of the string as it /// is closes to what a human would consider the "length" of a string. /// /// Since computing the number of grapheme clusters requires walking over /// the string we precompute it to avoid iterating through a string over /// and over again in the pretty printing algorithm. /// graphemes: isize, }, /// A string that is not taken into account when determining line length. /// This is useful for additional formatting text which won't be rendered /// in the final output, such as ANSI codes or HTML elements. ZeroWidthString { string: EcoString }, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Mode { /// The mode used when a group doesn't fit on a single line: when `Broken` /// the `Break`s inside it will be rendered as newlines, splitting the /// group. Broken, /// The default mode used when a group can fit on a single line: all its /// `Break`s will be rendered as their unbroken string and kept on a single /// line. Unbroken, /// This mode is used by the `NextBreakFit` document to force a break to be /// considered as broken. ForcedBroken, /// This mode is used to disable a `NextBreakFit` document. ForcedUnbroken, } /// A flag that can be used to enable or disable a `NextBreakFit` document. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum NextBreakFitsMode { Enabled, Disabled, } /// A flag that can be used to conditionally disable a `Nest` document. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum NestCondition { /// This always applies the nesting. This is a sensible default that will /// work for most of the cases. Always, /// Only applies the nesting if the wrapping `Group` couldn't fit on a /// single line and has been broken. IfBroken, } /// Used to change the way nesting of documents work. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum NestMode { /// If the nesting mode is `Increase`, the current indentation will be /// increased by the specified value. Increase, /// If the nesting mode is `Set`, the current indentation is going to be set /// to exactly the specified value. /// /// `doc.nest(2).set_nesting(0)` /// "wibble /// wobble <- no indentation is added! /// wubble" Set, } fn fits( limit: isize, mut current_width: isize, mut docs: im::Vector<(isize, Mode, &Document<'_>)>, ) -> bool { // The `fits` function is going to take each document from the `docs` queue // and check if those can fit on a single line. In order to do so documents // are going to be pushed in front of this queue and have to be accompanied // by additional information: // - the document indentation, that can be increased by the `Nest` block. // - the current mode; this is needed to know if a group is being broken or // not and treat `Break`s differently as a consequence. You can see how // the behaviour changes in [ref:break-fit]. // // The loop might be broken earlier without checking all documents under one // of two conditions: // - the documents exceed the line `limit` and surely won't fit // [ref:document-unfit]. // - the documents are sure to fit the line - for example, if we meet a // broken `Break` [ref:break-fit] or a newline [ref:newline-fit]. loop { // [tag:document-unfit] If we've exceeded the maximum width allowed for // a line, it means that the document won't fit on a single line, we can // break the loop. if current_width > limit { return false; }; // We start by checking the first document of the queue. If there's no // documents then we can safely say that it fits (if reached this point // it means that the limit wasn't exceeded). let (indent, mode, document) = match docs.pop_front() { Some(x) => x, None => return true, }; match document { // If a document is marked as `ForceBroken` we can immediately say // that it doesn't fit, so that every break is going to be // forcefully broken. Document::ForceBroken(doc) => match mode { // If the mode is `ForcedBroken` it means that we have to ignore // this break [ref:forced-broken], so we go check the inner // document ignoring the effects of this one. Mode::ForcedBroken => docs.push_front((indent, mode, doc)), Mode::Broken | Mode::Unbroken | Mode::ForcedUnbroken => return false, }, // [tag:newline-fit] When we run into a line we know that the // document has a bit that fits in the current line; if it didn't // fit (that is, it exceeded the maximum allowed width) the loop // would have been broken by one of the earlier checks. Document::Line(_) => return true, // If the nesting level is increased we go on checking the wrapped // document and increase its indentation level based on the nesting // condition. Document::Nest(i, nest_mode, condition, doc) => match condition { NestCondition::IfBroken => docs.push_front((indent, mode, doc)), NestCondition::Always => { let new_indent = match nest_mode { NestMode::Increase => indent + i, NestMode::Set => *i, }; docs.push_front((new_indent, mode, doc)) } }, // As a general rule, a group fits if it can stay on a single line // without its breaks being broken down. Document::Group(doc) => match mode { // If an outer group was broken, we still try to fit the inner // group on a single line, that's why for the inner document // we change the mode back to `Unbroken`. Mode::Broken => docs.push_front((indent, Mode::Unbroken, doc)), // Any other mode is preserved as-is: if the mode is forced it // has to be left unchanged, and if the mode is already unbroken // there's no need to change it. Mode::Unbroken | Mode::ForcedBroken | Mode::ForcedUnbroken => { docs.push_front((indent, mode, doc)) } }, // When we run into a string we increase the current_width; looping // back we will check if we've exceeded the maximum allowed width. Document::Str { graphemes, .. } | Document::EcoString { graphemes, .. } => { current_width += graphemes } // Zero width strings do nothing: they do not contribute to line length Document::ZeroWidthString { .. } => {} // If we get to a break we need to first see if it has to be // rendered as its unbroken or broken string, depending on the mode. Document::Break { unbroken, .. } => match mode { // [tag:break-fit] If the break has to be broken we're done! // We haven't exceeded the maximum length (otherwise the loop // iteration would have stopped with one of the earlier checks), // and - since it needs to be broken - we'll have to go on a new // line anyway. // This means that the document inspected so far will fit on a // single line, thus we return true. Mode::Broken | Mode::ForcedBroken => return true, // If the break is not broken then it will be rendered inline as // its unbroken string, so we treat it exactly as if it were a // normal string. Mode::Unbroken | Mode::ForcedUnbroken => current_width += unbroken.len() as isize, }, // The `NextBreakFits` can alter the current mode to `ForcedBroken` // or `ForcedUnbroken` based on its enabled flag. Document::NextBreakFits(doc, enabled) => match enabled { // [tag:disable-next-break] If it is disabled then we check the // wrapped document changing the mode to `ForcedUnbroken`. NextBreakFitsMode::Disabled => docs.push_front((indent, Mode::ForcedUnbroken, doc)), NextBreakFitsMode::Enabled => match mode { // If we're in `ForcedUnbroken` mode it means that the check // was disabled by a document wrapping this one // [ref:disable-next-break]; that's why we do nothing and // check the wrapped document as if it were a normal one. Mode::ForcedUnbroken => docs.push_front((indent, mode, doc)), // [tag:forced-broken] Any other mode is turned into // `ForcedBroken` so that when we run into a break, the // response to the question "Does the document fit?" will be // yes [ref:break-fit]. // This is why this is called `NextBreakFit` I think. Mode::Broken | Mode::Unbroken | Mode::ForcedBroken => { docs.push_front((indent, Mode::ForcedBroken, doc)) } }, }, // If there's a sequence of documents we will check each one, one // after the other to see if - as a whole - they can fit on a single // line. Document::Vec(vec) => { // The array needs to be reversed to preserve the order of the // documents since each one is pushed _to the front_ of the // queue of documents to check. for doc in vec.iter().rev() { docs.push_front((indent, mode, doc)); } } } } } /// The kind of line break this `Document::Break` is. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BreakKind { /// A `flex_break`. Flex, /// A `break_`. Strict, } fn format( writer: &mut impl Utf8Writer, limit: isize, mut width: isize, mut docs: im::Vector<(isize, Mode, &Document<'_>)>, ) -> Result<()> { // As long as there are documents to print we'll take each one by one and // output the corresponding string to the given writer. // // Each document in the `docs` queue also has an accompanying indentation // and mode: // - the indentation is used to keep track of the current indentation, // you might notice in [ref:format-nest] that it adds documents to the // queue increasing their current indentation. // - the mode is used to keep track of the state of the documents inside a // group. For example, if a group doesn't fit on a single line its // documents will be split into multiple lines and the mode set to // `Broken` to keep track of this. while let Some((indent, mode, document)) = docs.pop_front() { match document { // When we run into a line we print the given number of newlines and // add the indentation required by the given document. Document::Line(i) => { for _ in 0..*i { writer.str_write("\n")?; } for _ in 0..indent { writer.str_write(" ")?; } width = indent; } // Flex breaks are NOT conditional to the mode: if the mode is // already `Unbroken`, then the break is left unbroken (like strict // breaks); any other mode is ignored. // A flexible break will only be split if the following documents // can't fit on the same line; otherwise, it is just displayed as an // unbroken `Break`. Document::Break { broken, unbroken, kind: BreakKind::Flex, } => { let unbroken_width = width + unbroken.len() as isize; // Every time we need to check again if the remaining piece can // fit. If it does, the flexible break is not broken. if mode == Mode::Unbroken || fits(limit, unbroken_width, docs.clone()) { writer.str_write(unbroken)?; width = unbroken_width; } else { writer.str_write(broken)?; writer.str_write("\n")?; for _ in 0..indent { writer.str_write(" ")?; } width = indent; } } // Strict breaks are conditional to the mode. They differ from // flexible break because, if a group gets split - that is the mode // is `Broken` or `ForceBroken` - ALL of the breaks in that group // will be split. You can notice the difference with flexible breaks // because here we only check the mode and then take action; before // we would try and see if the remaining documents fit on a single // line before deciding if the (flexible) break can be split or not. Document::Break { broken, unbroken, kind: BreakKind::Strict, } => match mode { // If the mode requires the break to be broken, then its broken // string is printed, then we start a newline and indent it // according to the current indentation level. Mode::Broken | Mode::ForcedBroken => { writer.str_write(broken)?; writer.str_write("\n")?; for _ in 0..indent { writer.str_write(" ")?; } width = indent; } // If the mode doesn't require the break to be broken, then its // unbroken string is printed as if it were a normal string; // also updating the width of the current line. Mode::Unbroken | Mode::ForcedUnbroken => { writer.str_write(unbroken)?; width += unbroken.len() as isize } }, // Strings are printed as they are and the current width is // increased accordingly. Document::EcoString { string, graphemes } => { width += graphemes; writer.str_write(string)?; } Document::Str { string, graphemes } => { width += graphemes; writer.str_write(string)?; } Document::ZeroWidthString { string } => { // We write the string, but do not increment the length writer.str_write(string)?; } // If multiple documents need to be printed, then they are all // pushed to the front of the queue and will be printed one by one. Document::Vec(vec) => { // Just like `fits`, the elements will be pushed _on the front_ // of the queue. In order to keep their original order they need // to be pushed in reverse order. for doc in vec.iter().rev() { docs.push_front((indent, mode, doc)); } } // A `Nest` document doesn't result in anything being printed, its // only effect is to increase the current nesting level for the // wrapped document [tag:format-nest]. Document::Nest(i, nest_mode, condition, doc) => match (condition, mode) { // The nesting is only applied under two conditions: // - either the nesting condition is `Always`. // - or the condition is `IfBroken` and the group was actually // broken (that is, the current mode is `Broken`). (NestCondition::Always, _) | (NestCondition::IfBroken, Mode::Broken) => { let new_indent = match nest_mode { NestMode::Increase => indent + i, NestMode::Set => *i, }; docs.push_front((new_indent, mode, doc)) } // If none of the above conditions is met, then the nesting is // not applied. _ => docs.push_front((indent, mode, doc)), }, Document::Group(doc) => { // When we see a group we first try and see if it can fit on a // single line without breaking any break; that is why we use // the `Unbroken` mode here: we want to try to fit everything on // a single line. let group_docs = im::vector![(indent, Mode::Unbroken, doc.as_ref())]; if fits(limit, width, group_docs) { // If everything can stay on a single line we print the // wrapped document with the `Unbroken` mode, leaving all // the group's break as unbroken. docs.push_front((indent, Mode::Unbroken, doc)); } else { // Otherwise, we need to break the group. We print the // wrapped document changing its mode to `Broken` so that // all its breaks will be split on newlines. docs.push_front((indent, Mode::Broken, doc)); } } // `ForceBroken` and `NextBreakFits` only change the way the `fit` // function works but do not actually change the formatting of a // document by themselves. That's why when we run into those we // just go on printing the wrapped document without altering the // current mode. Document::ForceBroken(document) | Document::NextBreakFits(document, _) => { docs.push_front((indent, mode, document)); } } } Ok(()) } /// Renders an empty document. pub fn nil<'a>() -> Document<'a> { Document::Vec(vec![]) } /// Renders a single newline. pub fn line<'a>() -> Document<'a> { Document::Line(1) } /// Renders a string of newlines, equal in length to the number provided. pub fn lines<'a>(i: usize) -> Document<'a> { Document::Line(i) } /// A document after which the formatter can insert a newline. This determines /// where line breaks can occur, outside of hardcoded `Line`s. /// /// If the formatter determines that a group cannot fit on a single line, /// all breaks in the group will be rendered as broken. Otherwise, they /// will be rendered as unbroken. /// /// A broken `Break` renders the `broken` string, followed by a newline. /// An unbroken `Break` renders the `unbroken` string by itself. /// /// For example: /// ```rust:norun /// let document = docvec!["Hello", break_("", ", "), "world!"]; /// assert_eq!(document.to_pretty_string(20), "Hello, world!"); /// assert_eq!(document.to_pretty_string(10), "Hello\nworld!"); /// ``` /// pub fn break_<'a>(broken: &'a str, unbroken: &'a str) -> Document<'a> { Document::Break { broken, unbroken, kind: BreakKind::Strict, } } /// A document after which the formatter can insert a newline, similar to /// `break_()`. The difference is that when a group is rendered broken, all /// breaks are rendered broken. However, `flex_break` decides whether to /// break or not for every individual `flex_break`. /// /// For example: /// ```rust:norun /// let with_breaks = docvec!["Hello", break_("", ", "), "pretty", break_("", ", "), "printed!"]; /// assert_eq!(with_breaks.to_pretty_string(20), "Hello\npretty\nprinted!"); /// /// let with_flex_breaks = docvec!["Hello", flex_break("", ", "), "pretty", flex_break("", ", "), "printed!"]; /// assert_eq!(with_flex_breaks.to_pretty_string(20), "Hello, pretty\nprinted!"); /// ``` /// pub fn flex_break<'a>(broken: &'a str, unbroken: &'a str) -> Document<'a> { Document::Break { broken, unbroken, kind: BreakKind::Flex, } } /// A string that is not taken into account when determining line length. /// This is useful for additional formatting text which won't be rendered /// in the final output, such as ANSI codes or HTML elements. /// /// For example: /// ```rust:norun /// let document = docvec!["Hello", zero_width_string("This is a very long string"), break_("", ""), "world"]; /// assert_eq!(document.to_pretty_string(20), "HelloThis is a very long stringworld"); /// ``` /// pub fn zero_width_string<'a>(string: EcoString) -> Document<'a> { Document::ZeroWidthString { string } } impl<'a> Document<'a> { /// Creates a document from a string slice. pub fn str(string: &'a str) -> Self { Document::Str { graphemes: string.graphemes(true).count() as isize, string, } } /// Creates a document from an owned `EcoString`. pub fn eco_string(string: EcoString) -> Self { Document::EcoString { graphemes: string.graphemes(true).count() as isize, string, } } /// Groups a document. When pretty printing a group, the formatter will /// first attempt to fit the entire group on one line. If it fails, all /// `break_` documents in the group will render broken. /// /// Nested groups are handled separately to their parents, so if the /// outermost group is broken, any sub-groups might be rendered broken /// or unbroken, depending on whether they fit on a single line. pub fn group(self) -> Self { Self::Group(Box::new(self)) } /// Sets the indentation level of a document. pub fn set_nesting(self, indent: isize) -> Self { Self::Nest(indent, NestMode::Set, NestCondition::Always, Box::new(self)) } /// Nests a document by a certain indentation. When rending linebreaks, the /// formatter will print a new line followed by the current indentation. pub fn nest(self, indent: isize) -> Self { Self::Nest( indent, NestMode::Increase, NestCondition::Always, Box::new(self), ) } /// Nests a document by a certain indentation, but only if the current /// group is broken. pub fn nest_if_broken(self, indent: isize) -> Self { Self::Nest( indent, NestMode::Increase, NestCondition::IfBroken, Box::new(self), ) } /// Forces all `break_` and `flex_break` documents in the current group /// to render broken. pub fn force_break(self) -> Self { Self::ForceBroken(Box::new(self)) } /// Force the next `Break` to render unbroken, regardless of whether it /// fits on the line or not. pub fn next_break_fits(self, mode: NextBreakFitsMode) -> Self { Self::NextBreakFits(Box::new(self), mode) } /// Appends one document to another. Equivalent to `docvec![self, second]`, /// except that it `self` is already a `Document::Vec`, it will append /// directly to it instead of allocating a new vector. /// /// Useful when chaining multiple documents together in a fashion where /// they cannot be put all into one `docvec!` macro. pub fn append(self, second: impl Documentable<'a>) -> Self { match self { Self::Vec(mut vec) => { vec.push(second.to_doc()); Self::Vec(vec) } Self::Line(..) | Self::ForceBroken(..) | Self::NextBreakFits(..) | Self::Break { .. } | Self::Nest(..) | Self::Group(..) | Self::Str { .. } | Self::EcoString { .. } | Self::ZeroWidthString { .. } => Self::Vec(vec![self, second.to_doc()]), } } /// Prints a document into a `String`, attempting to limit lines to `limit` /// characters in length. pub fn to_pretty_string(self, limit: isize) -> String { let mut buffer = String::new(); self.pretty_print(limit, &mut buffer) .expect("Writing to string buffer failed"); buffer } /// Surrounds a document in two delimiters. Equivalent to /// `docvec![option, self, closed]`. pub fn surround(self, open: impl Documentable<'a>, closed: impl Documentable<'a>) -> Self { open.to_doc().append(self).append(closed) } /// Prints a document into `writer`, attempting to limit lines to `limit` /// characters in length. pub fn pretty_print(&self, limit: isize, writer: &mut impl Utf8Writer) -> Result<()> { let docs = im::vector![(0, Mode::Unbroken, self)]; format(writer, limit, 0, docs)?; Ok(()) } /// Returns true when the document contains no printable characters /// (whitespace and newlines are considered printable characters). pub fn is_empty(&self) -> bool { use Document::*; match self { Line(n) => *n == 0, EcoString { string, .. } => string.is_empty(), Str { string, .. } => string.is_empty(), // assuming `broken` and `unbroken` are equivalent Break { broken, .. } => broken.is_empty(), ForceBroken(d) | Nest(_, _, _, d) | Group(d) | NextBreakFits(d, _) => d.is_empty(), Vec(docs) => docs.iter().all(|d| d.is_empty()), // Zero-width strings don't count towards line length, but they are // still printed and so are not empty. (Unless their string contents // is also empty) ZeroWidthString { string } => string.is_empty(), } } } ================================================ FILE: compiler-core/src/reference.rs ================================================ use std::collections::{HashMap, HashSet}; use crate::ast::{Publicity, SrcSpan}; use bimap::{BiMap, Overwritten}; use ecow::EcoString; use petgraph::{ Directed, Direction, stable_graph::{NodeIndex, StableGraph}, }; #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum ReferenceKind { Qualified, Unqualified, Import, Definition, Alias, } #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct Reference { pub location: SrcSpan, pub kind: ReferenceKind, } pub type ReferenceMap = HashMap<(EcoString, EcoString), Vec>; #[derive(Debug, Clone)] pub struct EntityInformation { pub origin: SrcSpan, pub kind: EntityKind, } /// Information about an "Entity". This determines how we warn about an entity /// being unused. #[derive(Debug, Clone, Eq, PartialEq)] pub enum EntityKind { Function, Constant, Constructor, Type, ImportedModule { module_name: EcoString }, ModuleAlias { module: EcoString }, ImportedConstructor { module: EcoString }, ImportedType { module: EcoString }, ImportedValue { module: EcoString }, } /// Like `ast::Layer`, this type differentiates between different scopes. For example, /// there can be a `wibble` value, a `wibble` module and a `wibble` type in the same /// scope all at once! /// #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] enum EntityLayer { /// An entity which exists in the type layer: a custom type, type variable /// or type alias. Type, /// An entity which exists in the value layer: a constant, function or /// custom type variant constructor. Value, /// An entity which has been shadowed. This allows us to keep track of unused /// imports even if they have been shadowed by another value in the current /// module. /// This extra variant is needed because we used `Entity` as a key in a hashmap, /// and so a duplicate key would not be able to exist. /// We also would not want to get this shadowed entity when performing a lookup /// of a named entity; we only want it to register it as an entity in the /// `unused` function. Shadowed, /// The name of an imported module. Modules are separate to values! Module, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Entity { pub name: EcoString, layer: EntityLayer, } #[derive(Debug, Default)] pub struct ReferenceTracker { /// A call-graph which tracks which values are referenced by which other value, /// used for dead code detection. graph: StableGraph<(), (), Directed>, entities: BiMap, current_node: NodeIndex, public_entities: HashSet, entity_information: HashMap, /// The locations of the references to each value in this module, used for /// renaming and go-to reference. pub value_references: ReferenceMap, /// The locations of the references to each type in this module, used for /// renaming and go-to reference. pub type_references: ReferenceMap, /// This map is used to access the nodes of modules that were not /// aliased, given their name. /// We need this to keep track of references made to imports by unqualified /// values/types: when an unqualified item is used we want to add an edge /// pointing to the import it comes from, so that if the item is used the /// import won't be marked as unused: /// /// ```gleam /// import wibble/wobble.{used} /// /// pub fn main() { /// used /// } /// ``` /// /// And each imported entity carries around the _name of the module_ and not /// just the alias (here it would be `wibble/wobble` and not just `wobble`). /// module_name_to_node: HashMap, } impl ReferenceTracker { pub fn new() -> Self { Self::default() } fn get_or_create_node(&mut self, name: EcoString, layer: EntityLayer) -> NodeIndex { let entity = Entity { name, layer }; match self.entities.get_by_left(&entity) { Some(index) => *index, None => { let index = self.graph.add_node(()); _ = self.entities.insert(entity, index); index } } } fn create_node(&mut self, name: EcoString, layer: EntityLayer) -> NodeIndex { let entity = Entity { name, layer }; let index = self.graph.add_node(()); self.create_node_and_maybe_shadow(entity, index); index } fn create_node_and_maybe_shadow(&mut self, entity: Entity, index: NodeIndex) { match self.entities.insert(entity, index) { Overwritten::Neither => {} Overwritten::Left(mut entity, index) | Overwritten::Right(mut entity, index) | Overwritten::Pair(mut entity, index) | Overwritten::Both((mut entity, index), _) => { // If an entity with the same name as this already exists, // we still need to keep track of its usage! Thought it cannot // be referenced anymore, it still might have been used before this // point, or need to be marked as unused. // To do this, we keep track of a "Shadowed" entity in `entity_information`. if let Some(information) = self.entity_information.get(&entity) { entity.layer = EntityLayer::Shadowed; _ = self .entity_information .insert(entity.clone(), information.clone()); _ = self.entities.insert(entity, index); } } } } /// This function exists because of a specific edge-case where constants /// can shadow imported values. For example: /// ```gleam /// import math.{pi} /// /// pub const pi = pi /// ``` /// Here, the new `pi` constant shadows the imported `pi` value, but it still /// references it, so it should not be marked as unused. /// In order for this to work, we must first set the `current_function` field /// so that the `pi` value is referenced by the public `pi` constant. /// However, we can't insert the `pi` constant into the name scope yet, since /// then it would count as referencing itself. We first need to set `current_function`, /// then once we have analysed the right-hand-side of the constant, we can /// register it in the scope using `register_constant`. /// pub fn begin_constant(&mut self) { self.current_node = self.graph.add_node(()); } pub fn register_constant(&mut self, name: EcoString, location: SrcSpan, publicity: Publicity) { let entity = Entity { name, layer: EntityLayer::Value, }; self.create_node_and_maybe_shadow(entity.clone(), self.current_node); match publicity { Publicity::Public | Publicity::Internal { .. } => { let _ = self.public_entities.insert(entity.clone()); } Publicity::Private => {} } _ = self.entity_information.insert( entity, EntityInformation { kind: EntityKind::Constant, origin: location, }, ); } pub fn register_value( &mut self, name: EcoString, kind: EntityKind, location: SrcSpan, publicity: Publicity, ) { self.current_node = self.create_node(name.clone(), EntityLayer::Value); self.register_module_reference_from_imported_entity(&kind); let entity = Entity { name, layer: EntityLayer::Value, }; match publicity { Publicity::Public | Publicity::Internal { .. } => { let _ = self.public_entities.insert(entity.clone()); } Publicity::Private => {} } _ = self.entity_information.insert( entity, EntityInformation { kind, origin: location, }, ); } pub fn set_current_node(&mut self, name: EcoString) { self.current_node = self.get_or_create_node(name, EntityLayer::Value); } pub fn register_type( &mut self, name: EcoString, kind: EntityKind, location: SrcSpan, publicity: Publicity, ) { self.current_node = self.create_node(name.clone(), EntityLayer::Type); self.register_module_reference_from_imported_entity(&kind); let entity = Entity { name, layer: EntityLayer::Type, }; match publicity { Publicity::Public | Publicity::Internal { .. } => { let _ = self.public_entities.insert(entity.clone()); } Publicity::Private => {} } _ = self.entity_information.insert( entity, EntityInformation { kind, origin: location, }, ); } pub fn register_aliased_module( &mut self, used_name: EcoString, module_name: EcoString, alias_location: SrcSpan, import_location: SrcSpan, ) { // We first record a node for the module being aliased. We use its entire // name to identify it in this case and keep track of the node it's // associated with. self.register_module(module_name.clone(), module_name.clone(), import_location); // Then we create a node for the alias, as the alias itself might be // unused! self.current_node = self.create_node(used_name.clone(), EntityLayer::Module); // Also we want to register the fact that if this alias is used then the // import is used: so we add a reference from the alias to the import // we've just added. self.register_module_reference(module_name.clone()); // Finally we can add information for this alias: let entity = Entity { name: used_name, layer: EntityLayer::Module, }; _ = self.entity_information.insert( entity, EntityInformation { kind: EntityKind::ModuleAlias { module: module_name, }, origin: alias_location, }, ); } pub fn register_module( &mut self, used_name: EcoString, module_name: EcoString, location: SrcSpan, ) { self.current_node = self.create_node(used_name.clone(), EntityLayer::Module); let _ = self .module_name_to_node .insert(module_name.clone(), self.current_node); let entity = Entity { name: used_name, layer: EntityLayer::Module, }; _ = self.entity_information.insert( entity, EntityInformation { kind: EntityKind::ImportedModule { module_name }, origin: location, }, ); } fn register_module_reference_from_imported_entity(&mut self, entity_kind: &EntityKind) { match entity_kind { EntityKind::Function | EntityKind::Constant | EntityKind::Constructor | EntityKind::Type | EntityKind::ImportedModule { .. } | EntityKind::ModuleAlias { .. } => (), EntityKind::ImportedConstructor { module } | EntityKind::ImportedType { module } | EntityKind::ImportedValue { module } => { self.register_module_reference(module.clone()) } } } pub fn register_value_reference( &mut self, module: EcoString, name: EcoString, referenced_name: &EcoString, location: SrcSpan, kind: ReferenceKind, ) { match kind { ReferenceKind::Qualified | ReferenceKind::Import | ReferenceKind::Definition => {} ReferenceKind::Alias | ReferenceKind::Unqualified => { let target = self.get_or_create_node(referenced_name.clone(), EntityLayer::Value); _ = self.graph.add_edge(self.current_node, target, ()); } } self.value_references .entry((module, name)) .or_default() .push(Reference { location, kind }); } pub fn register_type_reference( &mut self, module: EcoString, name: EcoString, referenced_name: &EcoString, location: SrcSpan, kind: ReferenceKind, ) { match kind { ReferenceKind::Qualified | ReferenceKind::Import | ReferenceKind::Definition => {} ReferenceKind::Alias | ReferenceKind::Unqualified => { self.register_type_reference_in_call_graph(referenced_name.clone()) } } self.type_references .entry((module, name)) .or_default() .push(Reference { location, kind }); } /// Like `register_type_reference`, but doesn't modify `self.type_references`. /// This is used when we define a constructor for a custom type. The constructor /// doesn't actually "reference" its type, but if the constructor is used, the /// type should also be considered used. The best way to represent this relationship /// is to make a connection between them in the call graph. /// pub fn register_type_reference_in_call_graph(&mut self, name: EcoString) { let target = self.get_or_create_node(name, EntityLayer::Type); _ = self.graph.add_edge(self.current_node, target, ()); } pub fn register_module_reference(&mut self, name: EcoString) { let target = match self.module_name_to_node.get(&name) { Some(target) => *target, None => self.get_or_create_node(name, EntityLayer::Module), }; _ = self.graph.add_edge(self.current_node, target, ()); } pub fn unused(&self) -> HashMap { let mut unused_values = HashMap::with_capacity(self.entities.len()); for (entity, information) in self.entity_information.iter() { _ = unused_values.insert(entity.clone(), information.clone()); } for entity in self.public_entities.iter() { if let Some(index) = self.entities.get_by_left(entity) { self.mark_entity_as_used(&mut unused_values, entity, *index); } } for (entity, _) in self.entities.iter() { let Some(index) = self.entities.get_by_left(entity) else { continue; }; if self.public_entities.contains(entity) { self.mark_entity_as_used(&mut unused_values, entity, *index); } else { // If the entity is not public, we still want to mark referenced // imports as used. self.mark_referenced_imports_as_used(&mut unused_values, entity, *index); } } unused_values } fn mark_entity_as_used( &self, unused: &mut HashMap, entity: &Entity, index: NodeIndex, ) { if unused.remove(entity).is_some() { for node in self.graph.neighbors_directed(index, Direction::Outgoing) { if let Some(entity) = self.entities.get_by_right(&node) { self.mark_entity_as_used(unused, entity, node); } } } } fn mark_referenced_imports_as_used( &self, unused: &mut HashMap, entity: &Entity, index: NodeIndex, ) { // If the entity is a module there's no way it can reference other // modules so we just ignore it. // This also means that module aliases do not count as using a module! if entity.layer == EntityLayer::Module { return; } for node in self.graph.neighbors_directed(index, Direction::Outgoing) { // We only want to mark referenced modules as used, so if the node // is not a module we just skip it. let Some( module @ Entity { layer: EntityLayer::Module, .. }, ) = self.entities.get_by_right(&node) else { continue; }; // If the value appears in the module import list, it doesn't count // as using it! let is_imported_type = self .type_references .contains_key(&(module.name.clone(), entity.name.clone())); let is_imported_value = self .value_references .contains_key(&(module.name.clone(), entity.name.clone())); let appears_in_module_import_list = is_imported_type || is_imported_value; if !(appears_in_module_import_list) { self.mark_entity_as_used(unused, module, node); } } } } ================================================ FILE: compiler-core/src/requirement.rs ================================================ use std::fmt; use std::str::FromStr; use crate::Error; use crate::error::Result; use crate::io::make_relative; use camino::{Utf8Path, Utf8PathBuf}; use ecow::EcoString; use hexpm::version::Range; use serde::Deserialize; use serde::de::{self, Deserializer, MapAccess, Visitor}; use serde::ser::{Serialize, SerializeMap, Serializer}; #[derive(Deserialize, Debug, PartialEq, Eq, Clone)] #[serde(untagged, remote = "Self", deny_unknown_fields)] pub enum Requirement { Hex { #[serde(deserialize_with = "deserialise_range")] version: Range, }, Path { path: Utf8PathBuf, }, Git { git: EcoString, #[serde(rename = "ref")] ref_: EcoString, }, } impl Requirement { pub fn hex(range: &str) -> Result { Ok(Requirement::Hex { version: Range::new(range.to_string()).map_err(|e| Error::InvalidVersionFormat { input: range.to_string(), error: e.to_string(), })?, }) } pub fn path(path: &str) -> Requirement { Requirement::Path { path: path.into() } } pub fn git(url: &str, ref_: &str) -> Requirement { Requirement::Git { git: url.into(), ref_: ref_.into(), } } pub fn to_toml(&self, root_path: &Utf8Path) -> String { match self { Requirement::Hex { version: range } => { format!(r#"{{ version = "{range}" }}"#) } Requirement::Path { path } => { format!( r#"{{ path = "{}" }}"#, make_relative(root_path, path).as_str().replace('\\', "/") ) } Requirement::Git { git: url, ref_ } => { format!(r#"{{ git = "{url}", ref = "{ref_}" }}"#) } } } } // Serialization impl Serialize for Requirement { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut map = serializer.serialize_map(Some(1))?; match self { Requirement::Hex { version: range } => map.serialize_entry("version", range)?, Requirement::Path { path } => map.serialize_entry("path", path)?, Requirement::Git { git: url, ref_ } => { map.serialize_entry("git", url)?; map.serialize_entry("ref", ref_)?; } } map.end() } } // Deserialization fn deserialise_range<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { let version = String::deserialize(deserializer)?; Range::new(version).map_err(de::Error::custom) } #[derive(Debug, Copy, Clone)] pub struct Void; impl FromStr for Requirement { type Err = Error; fn from_str(s: &str) -> Result { Requirement::hex(s) } } struct RequirementVisitor; impl<'de> Visitor<'de> for RequirementVisitor { type Value = Requirement; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("string or map") } fn visit_str(self, value: &str) -> Result where E: de::Error, { match value.parse::() { Ok(value) => Ok(value), Err(error) => Err(de::Error::custom(error)), } } fn visit_map(self, visitor: M) -> Result where M: MapAccess<'de>, { Requirement::deserialize(de::value::MapAccessDeserializer::new(visitor)) } } impl<'de> Deserialize<'de> for Requirement { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { deserializer.deserialize_any(RequirementVisitor) } } #[cfg(test)] mod tests { use super::*; use std::collections::HashMap; #[test] fn read_requirement() { let toml = r#" short = "~> 0.5" hex = { version = "~> 1.0.0" } local = { path = "/path/to/package" } github = { git = "https://github.com/gleam-lang/otp.git", ref = "4d34935" } "#; let deps: HashMap = toml::from_str(toml).unwrap(); assert_eq!(deps["short"], Requirement::hex("~> 0.5").unwrap()); assert_eq!(deps["hex"], Requirement::hex("~> 1.0.0").unwrap()); assert_eq!(deps["local"], Requirement::path("/path/to/package")); assert_eq!( deps["github"], Requirement::git("https://github.com/gleam-lang/otp.git", "4d34935") ); } #[test] fn read_wrong_version() { let toml = r#" short = ">= 2.0 and < 3.0.0" "#; let error = toml::from_str::>(toml).expect_err("invalid version"); insta::assert_snapshot!(error.to_string()); } } ================================================ FILE: compiler-core/src/snapshots/gleam_core__config__barebones_package_config_to_json.snap ================================================ --- source: compiler-core/src/config.rs assertion_line: 1193 expression: output snapshot_kind: text --- --- GLEAM.TOML name = "my_project" version = "1.0.0" --- EXPORTED JSON { "name": "my_project", "version": "1.0.0", "gleam": null, "licences": [], "description": "", "documentation": { "pages": [] }, "dependencies": {}, "dev_dependencies": {}, "repository": null, "links": [], "erlang": { "application_start_module": null, "application_start_argument": null, "extra_applications": [] }, "javascript": { "typescript_declarations": false, "runtime": "nodejs", "deno": { "allow_env": [], "allow_sys": false, "allow_hrtime": false, "allow_net": [], "allow_ffi": false, "allow_read": [], "allow_run": [], "allow_write": [], "allow_all": false, "unstable": false, "location": null } }, "target": "erlang", "internal_modules": null } ================================================ FILE: compiler-core/src/snapshots/gleam_core__config__deny_extra_deps_properties.snap ================================================ --- source: compiler-core/src/config.rs expression: error.pretty_string() --- error: File IO failure An error occurred while trying to parse this file: gleam.toml The error message from the file IO library was: TOML parse error at line 6, column 18 | 6 | aide_generator = { git = "git@github.com:crowdhailer/aide.git", ref = "f559c5bc", extra = "idk what this is" } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ data did not match any variant of untagged enum Requirement ================================================ FILE: compiler-core/src/snapshots/gleam_core__config__name_with_dash.snap ================================================ --- source: compiler-core/src/config.rs expression: "toml::from_str::(input).unwrap_err().to_string()" --- TOML parse error at line 2, column 8 | 2 | name = "one-two" | ^^^^^^^^^ Package names may only contain lowercase letters, numbers, and underscores ================================================ FILE: compiler-core/src/snapshots/gleam_core__config__name_with_number_start.snap ================================================ --- source: compiler-core/src/config.rs expression: "toml::from_str::(input).unwrap_err().to_string()" --- TOML parse error at line 2, column 8 | 2 | name = "1" | ^^^ Package names may only contain lowercase letters, numbers, and underscores ================================================ FILE: compiler-core/src/snapshots/gleam_core__config__package_config_to_json.snap ================================================ --- source: compiler-core/src/config.rs assertion_line: 1177 expression: output snapshot_kind: text --- --- GLEAM.TOML name = "my_project" version = "1.0.0" licences = ["Apache-2.0", "MIT"] description = "Pretty complex config" target = "erlang" repository = { type = "github", user = "example", repo = "my_dep" } links = [{ title = "Home page", href = "https://example.com" }] internal_modules = ["my_app/internal"] gleam = ">= 0.30.0" [dependencies] gleam_stdlib = ">= 0.18.0 and < 2.0.0" my_other_project = { path = "../my_other_project" } [dev_dependencies] gleeunit = ">= 1.0.0 and < 2.0.0" [documentation] pages = [{ title = "My Page", path = "my-page.html", source = "./path/to/my-page.md" }] [erlang] application_start_module = "my_app/application" extra_applications = ["inets", "ssl"] [javascript] typescript_declarations = true runtime = "node" [javascript.deno] allow_all = false allow_ffi = true allow_env = ["DATABASE_URL"] allow_net = ["example.com:443"] allow_read = ["./database.sqlite"] --- EXPORTED JSON { "name": "my_project", "version": "1.0.0", "gleam": ">= 0.30.0", "licences": [ "Apache-2.0", "MIT" ], "description": "Pretty complex config", "documentation": { "pages": [ { "title": "My Page", "path": "my-page.html", "source": "./path/to/my-page.md" } ] }, "dependencies": { "gleam_stdlib": { "version": ">= 0.18.0 and < 2.0.0" }, "my_other_project": { "path": "../my_other_project" } }, "dev_dependencies": { "gleeunit": { "version": ">= 1.0.0 and < 2.0.0" } }, "repository": { "type": "github", "user": "example", "repo": "my_dep", "path": null, "tag_prefix": null }, "links": [ { "title": "Home page", "href": "https://example.com/" } ], "erlang": { "application_start_module": "my_app/application", "application_start_argument": null, "extra_applications": [ "inets", "ssl" ] }, "javascript": { "typescript_declarations": true, "runtime": "nodejs", "deno": { "allow_env": [ "DATABASE_URL" ], "allow_sys": false, "allow_hrtime": false, "allow_net": [ "example.com:443" ], "allow_ffi": true, "allow_read": [ "./database.sqlite" ], "allow_run": [], "allow_write": [], "allow_all": false, "unstable": false, "location": null } }, "target": "erlang", "internal_modules": [ "my_app/internal" ] } ================================================ FILE: compiler-core/src/snapshots/gleam_core__dependency__tests__resolution_error_message.snap ================================================ --- source: compiler-core/src/dependency.rs assertion_line: 1091 expression: message snapshot_kind: text --- There's no compatible version of `woo`: - You require woo >= 2.0.0 and < 3.0.0 - You require wibble >= 1.0.0 and < 2.0.0 - wibble requires wobble >= 1.0.0 and < 3.0.0 - wobble requires woo >= 1.0.0 and < 2.0.0 There's no compatible version of `waa`: - You require wibble >= 1.0.0 and < 2.0.0 - wibble requires wobble >= 1.0.0 and < 3.0.0 - wobble requires waa >= 1.0.0 and < 2.0.0 - You require waa >= 2.0.0 and < 3.0.0 ================================================ FILE: compiler-core/src/snapshots/gleam_core__docs__barebones_package_config_to_json.snap ================================================ --- source: compiler-core/src/docs.rs assertion_line: 786 expression: output snapshot_kind: text --- --- GLEAM.TOML name = "my_project" version = "1.0.0" --- EXPORTED JSON { "gleam.toml": { "name": "my_project", "version": "1.0.0", "gleam": null, "licences": [], "description": "", "documentation": { "pages": [] }, "dependencies": {}, "dev_dependencies": {}, "repository": null, "links": [], "erlang": { "application_start_module": null, "application_start_argument": null, "extra_applications": [] }, "javascript": { "typescript_declarations": false, "runtime": "nodejs", "deno": { "allow_env": [], "allow_sys": false, "allow_hrtime": false, "allow_net": [], "allow_ffi": false, "allow_read": [], "allow_run": [], "allow_write": [], "allow_all": false, "unstable": false, "location": null } }, "target": "erlang", "internal_modules": null } } ================================================ FILE: compiler-core/src/snapshots/gleam_core__docs__package_config_to_json.snap ================================================ --- source: compiler-core/src/docs.rs assertion_line: 770 expression: output snapshot_kind: text --- --- GLEAM.TOML name = "my_project" version = "1.0.0" licences = ["Apache-2.0", "MIT"] description = "Pretty complex config" target = "erlang" repository = { type = "github", user = "example", repo = "my_dep" } links = [{ title = "Home page", href = "https://example.com" }] internal_modules = ["my_app/internal"] gleam = ">= 0.30.0" [dependencies] gleam_stdlib = ">= 0.18.0 and < 2.0.0" my_other_project = { path = "../my_other_project" } [dev_dependencies] gleeunit = ">= 1.0.0 and < 2.0.0" [documentation] pages = [{ title = "My Page", path = "my-page.html", source = "./path/to/my-page.md" }] [erlang] application_start_module = "my_app/application" extra_applications = ["inets", "ssl"] [javascript] typescript_declarations = true runtime = "node" [javascript.deno] allow_all = false allow_ffi = true allow_env = ["DATABASE_URL"] allow_net = ["example.com:443"] allow_read = ["./database.sqlite"] --- EXPORTED JSON { "gleam.toml": { "name": "my_project", "version": "1.0.0", "gleam": ">= 0.30.0", "licences": [ "Apache-2.0", "MIT" ], "description": "Pretty complex config", "documentation": { "pages": [ { "title": "My Page", "path": "my-page.html", "source": "./path/to/my-page.md" } ] }, "dependencies": { "gleam_stdlib": { "version": ">= 0.18.0 and < 2.0.0" }, "my_other_project": { "path": "../my_other_project" } }, "dev_dependencies": { "gleeunit": { "version": ">= 1.0.0 and < 2.0.0" } }, "repository": { "type": "github", "user": "example", "repo": "my_dep", "path": null, "tag_prefix": null }, "links": [ { "title": "Home page", "href": "https://example.com/" } ], "erlang": { "application_start_module": "my_app/application", "application_start_argument": null, "extra_applications": [ "inets", "ssl" ] }, "javascript": { "typescript_declarations": true, "runtime": "nodejs", "deno": { "allow_env": [ "DATABASE_URL" ], "allow_sys": false, "allow_hrtime": false, "allow_net": [ "example.com:443" ], "allow_ffi": true, "allow_read": [ "./database.sqlite" ], "allow_run": [], "allow_write": [], "allow_all": false, "unstable": false, "location": null } }, "target": "erlang", "internal_modules": [ "my_app/internal" ] } } ================================================ FILE: compiler-core/src/snapshots/gleam_core__requirement__tests__read_wrong_version.snap ================================================ --- source: compiler-core/src/requirement.rs expression: error.to_string() --- TOML parse error at line 2, column 21 | 2 | short = ">= 2.0 and < 3.0.0" | ^^^^^^^^^^^^^^^^^^^^ >= 2.0 and < 3.0.0 is not a valid version. missing patch version: 2.0 ================================================ FILE: compiler-core/src/strings.rs ================================================ use ecow::EcoString; use itertools::Itertools; use crate::ast::Endianness; /// Converts any escape sequences from the given string to their correct /// bytewise UTF-8 representation and returns the resulting string. pub fn convert_string_escape_chars(str: &EcoString) -> EcoString { let mut filtered_str = EcoString::new(); let mut str_iter = str.chars().peekable(); loop { match str_iter.next() { Some('\\') => match str_iter.next() { // Check for Unicode escape sequence, e.g. \u{00012FF} Some('u') => { if str_iter.peek() != Some(&'{') { // Invalid Unicode escape sequence filtered_str.push('u'); continue; } // Consume the left brace after peeking let _ = str_iter.next(); let codepoint_str = str_iter .peeking_take_while(char::is_ascii_hexdigit) .collect::(); if codepoint_str.is_empty() || str_iter.peek() != Some(&'}') { // Invalid Unicode escape sequence filtered_str.push_str("u{"); filtered_str.push_str(&codepoint_str); continue; } let codepoint = u32::from_str_radix(&codepoint_str, 16) .ok() .and_then(char::from_u32); if let Some(codepoint) = codepoint { // Consume the right brace after peeking let _ = str_iter.next(); // Consider this codepoint's length instead of // that of the Unicode escape sequence itself filtered_str.push(codepoint); } else { // Invalid Unicode escape sequence // (codepoint value not in base 16 or too large) filtered_str.push_str("u{"); filtered_str.push_str(&codepoint_str); } } Some('n') => filtered_str.push('\n'), Some('r') => filtered_str.push('\r'), Some('f') => filtered_str.push('\u{C}'), Some('t') => filtered_str.push('\t'), Some('"') => filtered_str.push('\"'), Some('\\') => filtered_str.push('\\'), Some(c) => filtered_str.push(c), None => break, }, Some(c) => filtered_str.push(c), None => break, } } filtered_str } pub fn to_snake_case(string: &str) -> EcoString { let mut snake_case = EcoString::with_capacity(string.len()); let mut is_word_boundary = true; for char in string.chars() { match char { '_' | ' ' => { is_word_boundary = true; continue; } _ if char.is_uppercase() => { is_word_boundary = true; } _ => {} } if is_word_boundary { // We don't want to push an underscore at the start of the string, // even if it starts with a capital letter or other delimiter. if !snake_case.is_empty() { snake_case.push('_'); } is_word_boundary = false; } snake_case.push(char.to_ascii_lowercase()); } snake_case } pub fn to_upper_camel_case(string: &str) -> EcoString { let mut pascal_case = EcoString::with_capacity(string.len()); let mut chars = string.chars(); while let Some(char) = chars.next() { if char == '_' { let Some(next) = chars.next() else { break }; pascal_case.push(next.to_ascii_uppercase()); } else { pascal_case.push(char); } } pascal_case } /// Converts a string into its UTF-16 representation in bytes pub fn string_to_utf16_bytes(string: &str, endianness: Endianness) -> Vec { let mut bytes = Vec::with_capacity(string.len() * 2); let mut character_buffer = [0, 0]; for character in string.chars() { let segments = character.encode_utf16(&mut character_buffer); for segment in segments { let segment_bytes = match endianness { Endianness::Big => segment.to_be_bytes(), Endianness::Little => segment.to_le_bytes(), }; bytes.push(segment_bytes[0]); bytes.push(segment_bytes[1]); } } bytes } /// Converts a string into its UTF-32 representation in bytes pub fn string_to_utf32_bytes(string: &str, endianness: Endianness) -> Vec { let mut bytes = Vec::with_capacity(string.len() * 4); for character in string.chars() { let character_bytes = match endianness { Endianness::Big => (character as u32).to_be_bytes(), Endianness::Little => (character as u32).to_le_bytes(), }; bytes.extend(character_bytes); } bytes } /// Gets the number of UTF-16 codepoints it would take to encode a given string. pub fn length_utf16(string: &str) -> usize { let mut length = 0; for char in string.chars() { length += char.len_utf16() } length } /// Gets the number of UTF-32 codepoints in a string pub fn length_utf32(string: &str) -> usize { string.chars().count() } ================================================ FILE: compiler-core/src/type_/environment.rs ================================================ use pubgrub::Range; use crate::{ analyse::TargetSupport, ast::{PIPE_VARIABLE, Publicity}, build::Target, error::edit_distance, reference::{EntityKind, ReferenceTracker}, uid::UniqueIdGenerator, }; use super::*; use std::collections::HashMap; #[derive(Debug)] pub struct EnvironmentArguments<'a> { pub ids: UniqueIdGenerator, pub current_package: EcoString, pub gleam_version: Option>, pub current_module: EcoString, pub target: Target, pub importable_modules: &'a im::HashMap, pub target_support: TargetSupport, pub current_origin: Origin, pub dev_dependencies: &'a HashSet, } impl<'a> EnvironmentArguments<'a> { pub fn build(self) -> Environment<'a> { Environment::new(self) } } #[derive(Debug)] pub struct Environment<'a> { pub current_package: EcoString, pub origin: Origin, /// The gleam version range required by the current package as stated in its /// gleam.toml pub gleam_version: Option>, pub current_module: EcoString, pub target: Target, pub ids: UniqueIdGenerator, previous_id: u64, /// Names of types or values that have been imported an unqualified fashion /// from other modules. Used to prevent multiple imports using the same name. pub unqualified_imported_names: HashMap, pub unqualified_imported_types: HashMap, pub importable_modules: &'a im::HashMap, /// Modules that have been imported by the current module, along with the /// location of the import statement where they were imported. pub imported_modules: HashMap, /// Values defined in the current function (or the prelude) pub scope: im::HashMap, // The names of all the ignored variables and arguments in scope: // `let _var = 10` `pub fn main(_var) { todo }`. pub discarded_names: im::HashMap, /// Types defined in the current module (or the prelude) pub module_types: HashMap, /// Mapping from types to constructor names in the current module (or the prelude) pub module_types_constructors: HashMap, pub module_type_aliases: HashMap, /// Values defined in the current module (or the prelude) pub module_values: HashMap, /// Accessors defined in the current module pub accessors: HashMap, /// local_variable_usages is a stack of scopes. When a local variable is created it is /// added to the top scope. When a local variable is used we crawl down the scope /// stack for a variable with that name and mark it as used. pub local_variable_usages: Vec>, /// Used to determine if all functions/constants need to support the current /// compilation target. pub target_support: TargetSupport, pub names: Names, /// Deferred type variable aliases: `(source_generic_id, instantiated_type)`. /// During `instantiate`, when a generic type variable is replaced with a new /// unbound var, we record the pair here. After type checking, we resolve /// these by following link chains in the types to find the final unbound var /// IDs and register the original names for them. pub deferred_type_variable_aliases: Vec<(u64, Arc)>, /// Wether we ran into an `echo` or not while analysing the current module. pub echo_found: bool, pub references: ReferenceTracker, pub dev_dependencies: &'a HashSet, } #[derive(Debug)] pub struct VariableUsage { origin: VariableOrigin, location: SrcSpan, usages: usize, recursive_usages: usize, } impl<'a> Environment<'a> { pub fn new( EnvironmentArguments { ids, current_package, gleam_version, current_module, target, importable_modules, target_support, current_origin: origin, dev_dependencies, }: EnvironmentArguments<'a>, ) -> Self { let prelude = importable_modules .get(PRELUDE_MODULE_NAME) .expect("Unable to find prelude in importable modules"); let names = Self::build_names(prelude, importable_modules); Self { current_package, gleam_version, previous_id: ids.next(), ids, origin, target, module_types: prelude.types.clone(), module_types_constructors: prelude.types_value_constructors.clone(), module_values: HashMap::new(), imported_modules: HashMap::new(), unqualified_imported_names: HashMap::new(), unqualified_imported_types: HashMap::new(), accessors: prelude.accessors.clone(), scope: prelude.values.clone().into(), discarded_names: im::HashMap::new(), importable_modules, current_module, local_variable_usages: vec![HashMap::new()], target_support, names, deferred_type_variable_aliases: Vec::new(), module_type_aliases: HashMap::new(), echo_found: false, references: ReferenceTracker::new(), dev_dependencies, } } fn build_names( prelude: &ModuleInterface, importable_modules: &im::HashMap, ) -> Names { let mut names = Names::new(); // Insert prelude types and values into scope for name in prelude.values.keys() { names.named_constructor_in_scope( PRELUDE_MODULE_NAME.into(), name.clone(), name.clone(), ); } for name in prelude.types.keys() { names.named_type_in_scope(PRELUDE_MODULE_NAME.into(), name.clone(), name.clone()); } // Find potential type aliases which reexport internal types for module in importable_modules.values() { // Internal modules are not part of the public API so they are also // not considered. if module.is_internal { continue; } for (alias_name, alias) in module.type_aliases.iter() { // An alias can only be a public reexport if it is public. if alias.publicity.is_public() { names.maybe_register_reexport_alias(&module.package, alias_name, alias); } } } names } } #[derive(Debug)] pub struct ScopeResetData { local_values: im::HashMap, discarded_names: im::HashMap, } impl Environment<'_> { pub fn in_new_scope( &mut self, problems: &mut Problems, process_scope: impl FnOnce(&mut Self, &mut Problems) -> Result, ) -> Result { // Record initial scope state let initial = self.open_new_scope(); // Process scope let result = process_scope(self, problems); self.close_scope(initial, result.is_ok(), problems); // Return result of typing the scope result } pub fn open_new_scope(&mut self) -> ScopeResetData { let local_values = self.scope.clone(); let discarded_names = self.discarded_names.clone(); self.local_variable_usages.push(HashMap::new()); ScopeResetData { local_values, discarded_names, } } pub fn close_scope( &mut self, data: ScopeResetData, was_successful: bool, problems: &mut Problems, ) { let ScopeResetData { local_values, discarded_names, } = data; let unused = self .local_variable_usages .pop() .expect("There was no top entity scope."); // We only check for unused entities if the scope was successfully // processed. If it was not then any seemingly unused entities may have // been used beyond the point where the error occurred, so we don't want // to incorrectly warn about them. if was_successful { self.handle_unused_variables(unused, problems); } self.scope = local_values; self.discarded_names = discarded_names; } pub fn next_uid(&mut self) -> u64 { let id = self.ids.next(); self.previous_id = id; id } pub fn previous_uid(&self) -> u64 { self.previous_id } /// Resolve deferred type variable aliases. During `instantiate`, generic /// type variables are replaced with new unbound vars, but those vars may /// later get linked to other vars during unification. This method follows /// the link chains to find the final unbound var IDs and registers the /// original names for them. pub fn resolve_deferred_type_variable_aliases(&mut self) { for (source_id, type_) in self.deferred_type_variable_aliases.drain(..) { if let Some(alias) = self.names.get_type_variable(source_id) { let final_id = Self::resolve_type_var_id(&type_); if let Some(id) = final_id { // Only register if the target doesn't already have a name. // This avoids overwriting user-provided names (e.g. from // annotations) with names from instantiated type parameters. if self.names.get_type_variable(id).is_none() { self.names.type_variable_in_scope(id, alias.clone()); } } } } } /// Follow link chains in a type to find the final unbound or generic var ID. fn resolve_type_var_id(type_: &Type) -> Option { match type_ { Type::Var { type_ } => match type_.borrow().deref() { TypeVar::Unbound { id } | TypeVar::Generic { id } => Some(*id), TypeVar::Link { type_ } => Self::resolve_type_var_id(type_), }, Type::Named { .. } | Type::Fn { .. } | Type::Tuple { .. } => None, } } /// Create a new unbound type that is a specific type, we just don't /// know which one yet. /// pub fn new_unbound_var(&mut self) -> Arc { unbound_var(self.next_uid()) } /// Create a new generic type that can stand in for any type. /// pub fn new_generic_var(&mut self) -> Arc { generic_var(self.next_uid()) } /// Insert a variable in the current scope. /// pub fn insert_local_variable( &mut self, name: EcoString, location: SrcSpan, origin: VariableOrigin, type_: Arc, ) { let _ = self.scope.insert( name, ValueConstructor::local_variable(location, origin, type_), ); } /// Insert a variable in the current scope. /// pub fn insert_variable( &mut self, name: EcoString, variant: ValueConstructorVariant, type_: Arc, publicity: Publicity, deprecation: Deprecation, ) { let _ = self.scope.insert( name, ValueConstructor { publicity, deprecation, variant, type_, }, ); } /// Insert (or overwrites) a value into the current module. /// pub fn insert_module_value(&mut self, name: EcoString, value: ValueConstructor) { let _ = self.module_values.insert(name, value); } /// Lookup a variable in the current scope. /// pub fn get_variable(&self, name: &EcoString) -> Option<&ValueConstructor> { self.scope.get(name) } /// Lookup a module constant in the current scope. /// pub fn get_module_const(&mut self, name: &EcoString) -> Option<&ValueConstructor> { self.increment_usage(name); self.module_values .get(name) .filter(|ValueConstructor { variant, .. }| { matches!(variant, ValueConstructorVariant::ModuleConstant { .. }) }) } /// Map a type in the current scope. /// Errors if the module already has a type with that name, unless the type is from the /// prelude. /// pub fn insert_type_constructor( &mut self, type_name: EcoString, info: TypeConstructor, ) -> Result<(), Error> { let name = type_name.clone(); let location = info.origin; match self.module_types.insert(type_name, info) { None => Ok(()), Some(prelude_type) if is_prelude_module(&prelude_type.module) => Ok(()), Some(previous) => Err(Error::DuplicateTypeName { name, location, previous_location: previous.origin, }), } } /// Map a type alias in the current scope. /// Errors if the module already has a type with that name, unless the type is from the /// prelude. /// pub fn insert_type_alias( &mut self, type_name: EcoString, info: TypeAliasConstructor, ) -> Result<(), Error> { let name = type_name.clone(); let location = info.origin; match self.module_type_aliases.insert(type_name, info) { None => Ok(()), Some(prelude_type) if is_prelude_module(&prelude_type.module) => Ok(()), Some(previous) => Err(Error::DuplicateTypeName { name, location, previous_location: previous.origin, }), } } pub fn assert_unique_type_name( &mut self, name: &EcoString, location: SrcSpan, ) -> Result<(), Error> { match self.module_types.get(name) { None => Ok(()), Some(prelude_type) if is_prelude_module(&prelude_type.module) => Ok(()), Some(previous) => Err(Error::DuplicateTypeName { name: name.clone(), location, previous_location: previous.origin, }), } } /// Map a type to constructors in the current scope. /// pub fn insert_type_to_constructors( &mut self, type_name: EcoString, constructors: TypeVariantConstructors, ) { let _ = self .module_types_constructors .insert(type_name, constructors); } /// Lookup a type in the current scope. /// pub fn get_type_constructor( &mut self, module: &Option<(EcoString, SrcSpan)>, name: &EcoString, ) -> Result<&TypeConstructor, UnknownTypeConstructorError> { match module { None => self .module_types .get(name) .ok_or_else(|| UnknownTypeConstructorError::Type { name: name.clone(), hint: self.unknown_type_hint(name), }), Some((module_name, _)) => { let (_, module) = self.imported_modules.get(module_name).ok_or_else(|| { UnknownTypeConstructorError::Module { name: module_name.clone(), suggestions: self .suggest_modules(module_name, Imported::Type(name.clone())), } })?; self.references .register_module_reference(module_name.clone()); module.get_public_type(name).ok_or_else(|| { UnknownTypeConstructorError::ModuleType { name: name.clone(), module_name: module.name.clone(), type_constructors: module.public_type_names(), imported_type_as_value: false, } }) } } } fn unknown_type_hint(&self, type_name: &EcoString) -> UnknownTypeHint { match self.scope.contains_key(type_name) { true => UnknownTypeHint::ValueInScopeWithSameName, false => UnknownTypeHint::AlternativeTypes(self.module_types.keys().cloned().collect()), } } /// Lookup constructors for type in the current scope. /// pub fn get_constructors_for_type( &self, module: &EcoString, name: &EcoString, ) -> Result<&TypeVariantConstructors, UnknownTypeConstructorError> { let module = if module.is_empty() || *module == self.current_module { None } else { Some(module) }; match module { None => self.module_types_constructors.get(name).ok_or_else(|| { UnknownTypeConstructorError::Type { name: name.clone(), hint: self.unknown_type_hint(name), } }), Some(m) => { let module = self.importable_modules.get(m).ok_or_else(|| { UnknownTypeConstructorError::Module { name: name.clone(), suggestions: self.suggest_modules(m, Imported::Type(name.clone())), } })?; module.types_value_constructors.get(name).ok_or_else(|| { UnknownTypeConstructorError::ModuleType { name: name.clone(), module_name: module.name.clone(), type_constructors: module.public_type_names(), imported_type_as_value: false, } }) } } } /// Lookup a value constructor in the current scope. /// pub fn get_value_constructor( &mut self, module: Option<&EcoString>, name: &EcoString, ) -> Result<&ValueConstructor, UnknownValueConstructorError> { match module { None => self.scope.get(name).ok_or_else(|| { let type_with_name_in_scope = self.module_types.keys().any(|type_| type_ == name); UnknownValueConstructorError::Variable { name: name.clone(), variables: self.local_value_names(), type_with_name_in_scope, } }), Some(module_name) => { let (_, module) = self.imported_modules.get(module_name).ok_or_else(|| { UnknownValueConstructorError::Module { name: module_name.clone(), suggestions: self .suggest_modules(module_name, Imported::Value(name.clone())), } })?; self.references .register_module_reference(module_name.clone()); module.get_public_value(name).ok_or_else(|| { UnknownValueConstructorError::ModuleValue { name: name.clone(), module_name: module.name.clone(), value_constructors: module.public_value_names(), imported_value_as_type: false, } }) } } } pub fn get_type_variants_fields( &self, module: &EcoString, name: &EcoString, ) -> Vec<&EcoString> { self.get_constructors_for_type(module, name) .iter() .flat_map(|c| &c.variants) .filter_map(|variant| { self.type_value_constructor_to_constructor(module, variant)? .variant .record_field_map() }) .flat_map(|field_map| field_map.fields.keys()) .collect_vec() } fn type_value_constructor_to_constructor( &self, module: &EcoString, variant: &TypeValueConstructor, ) -> Option<&ValueConstructor> { if *module == self.current_module { self.scope.get(&variant.name) } else { let (_, module) = self.imported_modules.get(module)?; module.get_public_value(&variant.name) } } pub fn insert_accessors(&mut self, type_name: EcoString, accessors: AccessorsMap) { let _ = self.accessors.insert(type_name, accessors); } /// Instantiate converts generic variables into unbound ones. /// pub fn instantiate( &mut self, t: Arc, ids: &mut im::HashMap>, hydrator: &Hydrator, ) -> Arc { match t.deref() { Type::Named { publicity, name, package, module, arguments, inferred_variant, } => { let arguments = arguments .iter() .map(|type_| self.instantiate(type_.clone(), ids, hydrator)) .collect(); Arc::new(Type::Named { publicity: *publicity, name: name.clone(), package: package.clone(), module: module.clone(), arguments, inferred_variant: *inferred_variant, }) } Type::Var { type_ } => { match type_.borrow().deref() { TypeVar::Link { type_ } => { return self.instantiate(type_.clone(), ids, hydrator); } TypeVar::Unbound { .. } => { return Arc::new(Type::Var { type_: type_.clone(), }); } TypeVar::Generic { id } => match ids.get(id) { Some(t) => return t.clone(), None => { if !hydrator.is_rigid(id) { // Check this in the hydrator, i.e. is it a created type let v = self.new_unbound_var(); let _ = ids.insert(*id, v.clone()); // Record this pairing so that after type checking // we can resolve any link chains and register the // original name for the final unbound variable. self.deferred_type_variable_aliases.push((*id, v.clone())); return v; } } }, } Arc::new(Type::Var { type_: type_.clone(), }) } Type::Fn { arguments, return_, .. } => fn_( arguments .iter() .map(|type_| self.instantiate(type_.clone(), ids, hydrator)) .collect(), self.instantiate(return_.clone(), ids, hydrator), ), Type::Tuple { elements } => tuple( elements .iter() .map(|type_| self.instantiate(type_.clone(), ids, hydrator)) .collect(), ), } } /// Inserts a local variable at the current scope for usage tracking. pub fn init_usage( &mut self, name: EcoString, origin: VariableOrigin, location: SrcSpan, problems: &mut Problems, ) { if let Some(VariableUsage { origin, location, usages: 0, recursive_usages, }) = self .local_variable_usages .last_mut() .expect("Attempted to access non-existent entity usages scope") .insert( name.clone(), VariableUsage { origin, location, usages: 0, recursive_usages: 0, }, ) { // an entity was overwritten in the top most scope without being used let mut unused = HashMap::with_capacity(1); let _ = unused.insert( name, VariableUsage { origin, location, usages: 0, recursive_usages, }, ); self.handle_unused_variables(unused, problems); } } /// Increments an entity's usage in the current or nearest enclosing scope pub fn increment_usage(&mut self, name: &EcoString) { if let Some(VariableUsage { usages, .. }) = self .local_variable_usages .iter_mut() .rev() .find_map(|scope| scope.get_mut(name)) { *usages += 1; } } /// Marks an argument as being passed recursively to a function call. pub fn increment_recursive_usage(&mut self, name: &EcoString) { if let Some(VariableUsage { recursive_usages, .. }) = self .local_variable_usages .iter_mut() .rev() .find_map(|scope| scope.get_mut(name)) { *recursive_usages += 1; } } /// Emit warnings for unused definitions, imports, expressions, etc. /// /// Returns the source byte start positions of all unused definitions. /// pub fn handle_unused(&mut self, problems: &mut Problems) -> HashSet { let mut unused_positions = HashSet::new(); let unused = self .local_variable_usages .pop() .expect("Expected a bottom level of entity usages."); self.handle_unused_variables(unused, problems); // We have to handle unused imported entites a bit differently when // emitting warning: when an import list is unused all its items and // the import itself are unused: // // ``` // import wibble.{unused, also_unused} // ^^^^^^ ^^^^^^ ^^^^^^^^^^^ Everything is unused here // ``` // // But instead of emitting three warnings, what we really want is to // emit just a single warning encompassing the entire line! So we have // to hold on all unused imported entities and emit a warning for those // only if the module they come from is not also unused. let mut unused_modules = HashSet::new(); let mut unused_imported_items = vec![]; for (entity, info) in self.references.unused() { let name = entity.name; let location = info.origin; let warning = match info.kind { EntityKind::Function => { let _ = unused_positions.insert(location.start); Warning::UnusedPrivateFunction { location, name } } EntityKind::Constant => { let _ = unused_positions.insert(location.start); Warning::UnusedPrivateModuleConstant { location, name } } EntityKind::Constructor => Warning::UnusedConstructor { location, name, imported: false, }, EntityKind::Type => { let _ = unused_positions.insert(location.start); Warning::UnusedType { name, imported: false, location, } } EntityKind::ImportedModule { module_name } => { let _ = unused_modules.insert(module_name.clone()); Warning::UnusedImportedModule { name, location } } EntityKind::ImportedType { module } => { unused_imported_items.push(( module, Warning::UnusedType { name, imported: true, location, }, )); continue; } EntityKind::ImportedConstructor { module } => { unused_imported_items.push(( module, Warning::UnusedConstructor { name, imported: true, location, }, )); continue; } EntityKind::ImportedValue { module } => { unused_imported_items .push((module, Warning::UnusedImportedValue { name, location })); continue; } EntityKind::ModuleAlias { module } => { unused_imported_items.push(( module.clone(), Warning::UnusedImportedModuleAlias { module_name: module.clone(), alias: name, location, }, )); continue; } }; problems.warning(warning); } unused_imported_items .into_iter() .filter(|(module, _)| !unused_modules.contains(module)) .for_each(|(_, warning)| problems.warning(warning)); unused_positions } fn handle_unused_variables( &mut self, unused: HashMap, problems: &mut Problems, ) { for VariableUsage { origin, location, usages, recursive_usages, } in unused.into_values() { if usages == 0 { problems.warning(Warning::UnusedVariable { location, origin }); } // If the function parameter is actually used somewhere, but all the // usages are just passing it along in a recursive call, then it // counts as being unused too. else if origin.is_function_parameter() && recursive_usages == usages { problems.warning(Warning::UnusedRecursiveArgument { location }); } } } pub fn local_value_names(&self) -> Vec { self.scope .keys() .filter(|&t| PIPE_VARIABLE != t) .cloned() .collect() } /// Suggest modules to import or use, for an unknown module pub fn suggest_modules(&self, module: &str, imported: Imported) -> Vec { let mut suggestions = self .importable_modules .iter() .filter_map(|(importable, module_info)| { if module_info.is_internal && module_info.package != self.current_package { return None; } match &imported { // Don't suggest importing modules if they are already imported _ if self .imported_modules .contains_key(importable.split('/').next_back().unwrap_or(importable)) => { None } Imported::Type(name) if module_info.get_public_type(name).is_some() => { Some(ModuleSuggestion::Importable(importable.clone())) } Imported::Value(name) if module_info.get_public_value(name).is_some() => { Some(ModuleSuggestion::Importable(importable.clone())) } Imported::Module | Imported::Type(_) | Imported::Value(_) => None, } }) .collect_vec(); suggestions.extend( self.imported_modules .keys() .map(|module| ModuleSuggestion::Imported(module.clone())), ); let threshold = std::cmp::max(module.chars().count() / 3, 1); // Filter and sort options based on edit distance. suggestions .into_iter() .sorted() .filter_map(|suggestion| { edit_distance(module, suggestion.last_name_component(), threshold) .map(|distance| (suggestion, distance)) }) .sorted_by_key(|&(_, distance)| distance) .map(|(suggestion, _)| suggestion) .collect() } pub fn type_variant_name( &self, type_module: &EcoString, type_name: &EcoString, variant_index: u16, ) -> Option<&EcoString> { let type_constructors = if type_module == &self.current_module { &self.module_types_constructors } else { &self .importable_modules .get(type_module)? .types_value_constructors }; type_constructors .get(type_name) .and_then(|type_constructors| type_constructors.variants.get(variant_index as usize)) .map(|variant| &variant.name) } } #[derive(Debug)] /// An imported name, for looking up a module which exports it pub enum Imported { /// An imported module, with no extra information Module, /// An imported type Type(EcoString), /// An imported value Value(EcoString), } /// Unify two types that should be the same. /// Any unbound type variables will be linked to the other type as they are the same. /// /// It two types are found to not be the same an error is returned. /// pub fn unify(t1: Arc, t2: Arc) -> Result<(), UnifyError> { if t1 == t2 { return Ok(()); } // Collapse right hand side type links. Left hand side will be collapsed in the next block. if let Type::Var { type_ } = t2.deref() && let TypeVar::Link { type_ } = type_.borrow().deref() { return unify(t1, type_.clone()); } if let Type::Var { type_ } = t1.deref() { enum Action { Unify(Arc), CouldNotUnify, Link, } let action = match type_.borrow().deref() { TypeVar::Link { type_ } => Action::Unify(type_.clone()), TypeVar::Unbound { id } => { unify_unbound_type(&t2, *id)?; Action::Link } TypeVar::Generic { id } => { if let Type::Var { type_ } = t2.deref() && type_.borrow().is_unbound() { *type_.borrow_mut() = TypeVar::Generic { id: *id }; return Ok(()); } Action::CouldNotUnify } }; return match action { Action::Link => { let mut t2 = t2.deref().clone(); t2.generalise_custom_type_variant(); *type_.borrow_mut() = TypeVar::Link { type_: Arc::new(t2), }; Ok(()) } Action::Unify(t) => { unify(t.clone(), t2)?; // Note that type_ is always a Link in this branch. // unify may replace t's inner value with another link // (See the Action::Link branch just above) // This can cause the compiler to build up an ever-growing chain of links. // Therefore, we try to collapse the links. However, the RefCell in type_ // may already be borrowed by collapsing the links in t2 at the start // of the function, in which case accept the extra link. if let Ok(mut type_) = type_.try_borrow_mut() { *type_ = TypeVar::Link { type_: collapse_links(t.clone()), } } Ok(()) } Action::CouldNotUnify => Err(UnifyError::CouldNotUnify { expected: t1.clone(), given: t2, situation: None, }), }; } if let Type::Var { .. } = t2.deref() { return unify(t2, t1).map_err(flip_unify_error); } match (t1.deref(), t2.deref()) { ( Type::Named { module: m1, name: n1, arguments: arguments1, .. }, Type::Named { module: m2, name: n2, arguments: arguments2, .. }, ) if m1 == m2 && n1 == n2 && arguments1.len() == arguments2.len() => { for (a, b) in arguments1.iter().zip(arguments2) { unify_enclosed_type(t1.clone(), t2.clone(), unify(a.clone(), b.clone()))?; } Ok(()) } ( Type::Tuple { elements: elements1, .. }, Type::Tuple { elements: elements2, .. }, ) if elements1.len() == elements2.len() => { for (a, b) in elements1.iter().zip(elements2) { unify_enclosed_type(t1.clone(), t2.clone(), unify(a.clone(), b.clone()))?; } Ok(()) } ( Type::Fn { arguments: arguments1, return_: return1, .. }, Type::Fn { arguments: arguments2, return_: return2, .. }, ) => { if arguments1.len() != arguments2.len() { Err(unify_wrong_arity( &t1, arguments1.len(), &t2, arguments2.len(), ))? } for (i, (a, b)) in arguments1.iter().zip(arguments2).enumerate() { unify(a.clone(), b.clone()) .map_err(|_| unify_wrong_arguments(&t1, a, &t2, b, i))?; } unify(return1.clone(), return2.clone()) .map_err(|_| unify_wrong_returns(&t1, return1, &t2, return2)) } _ => Err(UnifyError::CouldNotUnify { expected: t1.clone(), given: t2.clone(), situation: None, }), } } #[cfg(test)] mod unify_tests { use std::{cell::RefCell, ops::Deref, sync::Arc}; use crate::type_::{Type, TypeVar, unify}; // Repeated unification used to add a link to t1 for each branch // See https://github.com/gleam-lang/gleam/issues/4805 #[test] fn repeated_unify_does_not_add_extra_links() { // The case's return type starts unbound let t1 = unbound(0); // In practice, this would usually be something like Result(String, _), // but unify recurses into the type parameters and only the unbound one matters let t2 = unbound(1); // After unifying with the first clause, we have a direct link to the clause's return type assert!(unify(t1.clone(), t2).is_ok()); assert_direct_link_to_unbound(t1.deref(), 1); // Before the fix, this used to _add a new link_ into the nested Var let t2 = unbound(2); assert!(unify(t1.clone(), t2).is_ok()); assert_direct_link_to_unbound(t1.deref(), 2); // And this would add a link to the var of the new link // As unify is recursive, this eventually cause a stack overflow let t2 = unbound(3); assert!(unify(t1.clone(), t2).is_ok()); assert_direct_link_to_unbound(t1.deref(), 3); } fn assert_direct_link_to_unbound(t1: &Type, expect_id: u64) { if let Type::Var { type_: var } = t1 && let TypeVar::Link { type_: link } = var.borrow().deref() && let Type::Var { type_: var } = link.deref() && let TypeVar::Unbound { id } = var.borrow().deref() { assert_eq!(*id, expect_id, "Expected unbound id to be unified") } else { panic!("Expected t1 to be a direct link but found: {t1:?}") } } fn unbound(id: u64) -> Arc { Arc::new(Type::Var { type_: Arc::new(RefCell::new(TypeVar::Unbound { id })), }) } } ================================================ FILE: compiler-core/src/type_/error.rs ================================================ use super::{ FieldAccessUsage, expression::{ArgumentKind, CallKind}, }; use crate::{ ast::{BinOp, BitArraySegmentTruncation, Layer, SrcSpan, TodoKind}, build::Target, exhaustiveness::ImpossibleBitArraySegmentPattern, parse::LiteralFloatValue, type_::{Type, expression::ComparisonOutcome}, }; use camino::Utf8PathBuf; use ecow::EcoString; use hexpm::version::Version; use num_bigint::BigInt; #[cfg(test)] use pretty_assertions::assert_eq; use std::sync::Arc; /// Errors and warnings discovered when compiling a module. /// #[derive(Debug, Eq, PartialEq, Clone, Default)] pub struct Problems { errors: Vec, warnings: Vec, } impl Problems { pub fn new() -> Self { Default::default() } /// Sort the warnings and errors by their location. /// pub fn sort(&mut self) { self.errors.sort_by_key(|e| e.start_location()); self.warnings.sort_by_key(|w| w.location().start); } /// Register an error. /// pub fn error(&mut self, error: Error) { self.errors.push(error) } /// Register a warning. /// pub fn warning(&mut self, warning: Warning) { self.warnings.push(warning) } /// Take all the errors, leaving an empty vector in its place. /// pub fn take_errors(&mut self) -> Vec { std::mem::take(&mut self.errors) } /// Take all the warnings, leaving an empty vector in its place. /// pub fn take_warnings(&mut self) -> Vec { std::mem::take(&mut self.warnings) } } /// This is used by the unknown record field error to tell if an unknown field /// is a field appearing in another variant of the same type to provide a better /// error message explaining why it can't be accessed: /// /// ```gleam /// pub type Wibble { /// Wibble(field: Int) /// Wobble(thing: String) /// } /// /// Wobble("hello").field /// // ^^^^^^ /// ``` /// /// Here the error can be extra useful and explain that to access `field` all /// variants should have that field at the same position and with the same type. /// #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum UnknownField { /// The field we're trying to access appears in at least a variant, so it /// can be useful to explain why it cannot be accessed and how to fix it /// (adding it to all variants/making sure it has the same type/making sure /// it's in the same position). /// AppearsInAVariant, /// The field we are trying to access appears in a variant, but we can /// infer that the value we are accessing on is never the one that this /// value is, so we can give information accordingly. AppearsInAnImpossibleVariant, /// The field is not in any of the variants, this might truly be a typo and /// there's no need to add further explanations. /// TrulyUnknown, /// The type that the user is trying to access has no fields whatsoever, /// such as Int or fn(..) -> _ NoFields, } /// A suggestion for an unknown module #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum ModuleSuggestion { /// A module which which has a similar name, and an /// exported value matching the one being accessed Importable(EcoString), /// A module already imported in the current scope Imported(EcoString), } impl ModuleSuggestion { pub fn suggestion(&self, module: &str) -> String { match self { ModuleSuggestion::Importable(name) => { // Add a little extra information if the names don't match let imported_name = self.last_name_component(); if module == imported_name { format!("Did you mean to import `{name}`?") } else { format!("Did you mean to import `{name}` and reference `{imported_name}`?") } } ModuleSuggestion::Imported(name) => format!("Did you mean `{name}`?"), } } pub fn last_name_component(&self) -> &str { match self { ModuleSuggestion::Imported(name) | ModuleSuggestion::Importable(name) => { name.split('/').next_back().unwrap_or(name) } } } } #[derive(Debug, Eq, PartialEq, Clone)] pub enum Error { InvalidImport { location: SrcSpan, importing_module: EcoString, imported_module: EcoString, kind: InvalidImportKind, }, BitArraySegmentError { error: crate::bit_array::ErrorType, location: SrcSpan, }, UnknownLabels { unknown: Vec<(EcoString, SrcSpan)>, valid: Vec, supplied: Vec, }, UnknownVariable { location: SrcSpan, name: EcoString, variables: Vec, /// If there's a discarded variable with the same name in the same scope /// this will contain its location. discarded_location: Option, type_with_name_in_scope: bool, }, UnknownType { location: SrcSpan, name: EcoString, hint: UnknownTypeHint, }, UnknownModule { location: SrcSpan, name: EcoString, suggestions: Vec, }, UnknownModuleType { location: SrcSpan, name: EcoString, module_name: EcoString, type_constructors: Vec, value_with_same_name: bool, }, UnknownModuleValue { location: SrcSpan, name: EcoString, module_name: EcoString, value_constructors: Vec, type_with_same_name: bool, context: ModuleValueUsageContext, }, ModuleAliasUsedAsName { location: SrcSpan, name: EcoString, }, NotFn { location: SrcSpan, type_: Arc, }, UnknownRecordField { location: SrcSpan, type_: Arc, label: EcoString, fields: Vec, usage: FieldAccessUsage, unknown_field: UnknownField, }, IncorrectArity { location: SrcSpan, expected: usize, context: IncorrectArityContext, given: usize, labels: Vec, }, UnsafeRecordUpdate { location: SrcSpan, reason: UnsafeRecordUpdateReason, }, UnnecessarySpreadOperator { location: SrcSpan, arity: usize, }, IncorrectTypeArity { location: SrcSpan, name: EcoString, expected: usize, given: usize, }, CouldNotUnify { location: SrcSpan, situation: Option, expected: Arc, given: Arc, }, RecursiveType { location: SrcSpan, }, DuplicateName { location_a: SrcSpan, location_b: SrcSpan, name: EcoString, }, DuplicateImport { location: SrcSpan, previous_location: SrcSpan, name: EcoString, }, DuplicateTypeName { location: SrcSpan, previous_location: SrcSpan, name: EcoString, }, DuplicateArgument { location: SrcSpan, label: EcoString, }, DuplicateField { location: SrcSpan, label: EcoString, }, PrivateTypeLeak { location: SrcSpan, leaked: Type, }, UnexpectedLabelledArg { location: SrcSpan, label: EcoString, kind: UnexpectedLabelledArgKind, }, PositionalArgumentAfterLabelled { location: SrcSpan, }, IncorrectNumClausePatterns { location: SrcSpan, expected: usize, given: usize, }, NonLocalClauseGuardVariable { location: SrcSpan, name: EcoString, }, ExtraVarInAlternativePattern { location: SrcSpan, name: EcoString, }, MissingVarInAlternativePattern { location: SrcSpan, name: EcoString, }, DuplicateVarInPattern { location: SrcSpan, name: EcoString, }, OutOfBoundsTupleIndex { location: SrcSpan, index: u64, size: usize, }, NotATuple { location: SrcSpan, given: Arc, }, NotATupleUnbound { location: SrcSpan, }, RecordAccessUnknownType { location: SrcSpan, }, RecordUpdateInvalidConstructor { location: SrcSpan, }, UnexpectedTypeHole { location: SrcSpan, }, ReservedModuleName { name: EcoString, }, KeywordInModuleName { name: EcoString, keyword: EcoString, }, NotExhaustivePatternMatch { location: SrcSpan, unmatched: Vec, kind: PatternMatchKind, }, /// A function was defined with multiple arguments with the same name /// /// # Examples /// /// ```gleam /// fn main(x, x) { Nil } /// ``` /// ```gleam /// fn main() { /// fn(x, x) { Nil } /// } /// ``` ArgumentNameAlreadyUsed { location: SrcSpan, name: EcoString, }, /// A function was defined with an unlabelled argument after a labelled one. UnlabelledAfterlabelled { location: SrcSpan, }, /// A type alias was defined directly or indirectly in terms of itself, which would /// cause it to expand to infinite size. /// e.g. /// type ForkBomb = #(ForkBomb, ForkBomb) RecursiveTypeAlias { location: SrcSpan, cycle: Vec, }, /// A function has been given an external implementation but not all the /// type annotations have been given. The annotations are required as we /// cannot infer the types of external implementations. ExternalMissingAnnotation { location: SrcSpan, kind: MissingAnnotation, }, /// A function has been given without either a Gleam implementation or an /// external one. NoImplementation { location: SrcSpan, }, /// A function/constant that is used doesn't have an implementation for the /// current compilation target. UnsupportedExpressionTarget { location: SrcSpan, target: Target, }, /// A function's JavaScript implementation has been given but it does not /// have a valid module name. InvalidExternalJavascriptModule { location: SrcSpan, module: EcoString, name: EcoString, }, /// A function's JavaScript implementation has been given but it does not /// have a valid function name. InvalidExternalJavascriptFunction { location: SrcSpan, function: EcoString, name: EcoString, }, /// A case expression is missing one or more patterns to match all possible /// values of the type. InexhaustiveCaseExpression { location: SrcSpan, missing: Vec, }, /// A case expression is missing its body. MissingCaseBody { location: SrcSpan, }, /// Let assignment's pattern does not match all possible values of the type. InexhaustiveLetAssignment { location: SrcSpan, missing: Vec, }, /// A type alias has a type variable but it is not used in the definition. /// /// For example, here `unused` is not used /// /// ```gleam /// pub type Wibble(unused) = /// Int /// ``` UnusedTypeAliasParameter { location: SrcSpan, name: EcoString, }, /// A definition has two type parameters with the same name. /// /// ```gleam /// pub type Wibble(a, a) = /// Int /// ``` /// ```gleam /// pub type Wibble(a, a) { /// Wibble /// } /// ``` DuplicateTypeParameter { location: SrcSpan, name: EcoString, }, /// A public function doesn't have an implementation for the current target. /// This is only raised when compiling a package with `TargetSupport::Enforced`, which is /// typically the root package, deps not being enforced. /// /// For example, if compiling to Erlang: /// /// ```gleam /// @external(javascript, "one", "two") /// pub fn wobble() -> Int /// ``` UnsupportedPublicFunctionTarget { target: Target, name: EcoString, location: SrcSpan, }, /// When there's something that is not a function to the left of the `<-` /// operator in a use expression: /// /// For example: /// /// ```gleam /// use <- "wibble" /// todo /// ``` NotFnInUse { location: SrcSpan, type_: Arc, }, /// When the function to the right hand side of `<-` in a `use` expression /// is called with the wrong number of arguments (given already takes into /// account the use callback passed to the function). /// UseFnIncorrectArity { location: SrcSpan, expected: usize, given: usize, }, /// When on the left hand side of `<-` in a `use` expression there is the /// wrong number of patterns. /// /// For example: /// /// ```gleam /// use _, _ <- result.try(res) /// todo /// ``` /// UseCallbackIncorrectArity { call_location: SrcSpan, pattern_location: SrcSpan, expected: usize, given: usize, }, /// When on the right hand side of use there is a function that doesn't take /// a callback function as its last argument. /// /// For example: /// /// ```gleam /// use <- io.println /// ``` /// UseFnDoesntTakeCallback { location: SrcSpan, actual_type: Option, }, /// When the name assigned to a variable or function doesn't follow the gleam /// naming conventions. /// /// For example: /// /// ```gleam /// let myBadName = 42 /// ``` BadName { location: SrcSpan, kind: Named, name: EcoString, }, /// Occurs when all the variant types of a custom type are deprecated /// /// ```gleam /// type Wibble { /// @deprecated("1") /// Wobble1 /// @deprecated("1") /// Wobble1 /// } /// ``` AllVariantsDeprecated { location: SrcSpan, }, /// Occurs when any varient of a custom type is deprecated while /// the custom type itself is deprecated DeprecatedVariantOnDeprecatedType { location: SrcSpan, }, /// Occurs when a literal floating point has a value that is outside of the /// range representable by floats: -1.7976931348623157e308 to /// 1.7976931348623157e308. LiteralFloatOutOfRange { location: SrcSpan, }, /// When the echo keyword is not followed by an expression to be printed. /// The only place where echo is allowed to appear on its own is as a step /// of a pipeline, otherwise omitting the expression will result in this /// error. For example: /// /// ```gleam /// call(echo, 1, 2) /// // ^^^^ Error! /// ``` /// EchoWithNoFollowingExpression { location: SrcSpan, }, /// When someone tries concatenating two string values using the `+` operator. /// /// ```gleam /// "aaa" + "bbb" /// // ^ We wont to suggest using `<>` instead! /// ``` StringConcatenationWithAddInt { location: SrcSpan, }, /// When someone tries using an int operator on two floats. /// /// ```gleam /// 1 +. 3 /// //^ We wont to suggest using `+` instead! /// ``` FloatOperatorOnInts { operator: BinOp, location: SrcSpan, }, /// When someone tries using an int operator on two floats. /// /// ```gleam /// 1.2 + 1.0 /// // ^ We wont to suggest using `+.` instead! /// ``` IntOperatorOnFloats { operator: BinOp, location: SrcSpan, }, DoubleVariableAssignmentInBitArray { location: SrcSpan, }, NonUtf8StringAssignmentInBitArray { location: SrcSpan, }, /// This happens when a private type is marked as opaque. Only public types /// can be opaque. /// /// ```gleam /// opaque type Wibble { /// Wobble /// } /// ``` /// PrivateOpaqueType { location: SrcSpan, }, SrcImportingDevDependency { importing_module: EcoString, imported_module: EcoString, package: EcoString, location: SrcSpan, }, /// This happens when a type has no type parameters (for example `Int`) but /// it is being used as a constructor: `Int()`, `Bool(a, b)`. /// TypeUsedAsAConstructor { location: SrcSpan, name: EcoString, }, /// The `@external` annotation on custom types can only be used for external /// types, types with no constructors. /// ExternalTypeWithConstructors { location: SrcSpan, }, LowercaseBoolPattern { location: SrcSpan, }, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum UnexpectedLabelledArgKind { FunctionParameter, RecordConstructorArgument, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ModuleValueUsageContext { UnqualifiedImport, ModuleAccess, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum MissingAnnotation { Parameter, Return, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PatternMatchKind { Case, Assignment, } #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum EmptyListCheckKind { Empty, NonEmpty, } #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum LiteralCollectionKind { List, Tuple, Record, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum IncorrectArityContext { Pattern, Function, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum InvalidImportKind { SrcImportingTest, SrcImportingDev, DevImportingTest, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Named { Type, TypeAlias, TypeVariable, CustomTypeVariant, Variable, Argument, Label, Constant, Function, Discard, } impl Named { pub fn as_str(self) -> &'static str { match self { Named::Type => "Type", Named::TypeAlias => "Type alias", Named::TypeVariable => "Type variable", Named::CustomTypeVariant => "Type variant", Named::Variable => "Variable", Named::Argument => "Argument", Named::Label => "Label", Named::Constant => "Constant", Named::Function => "Function", Named::Discard => "Discard", } } } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct VariableOrigin { pub syntax: VariableSyntax, pub declaration: VariableDeclaration, } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] /// The syntax used to define a variable. Used to determine how it can be ignored /// when unused. pub enum VariableSyntax { /// A variable that can be ignored by prefixing with an underscore, `_name` Variable(EcoString), /// A variable from label shorthand syntax, which can be ignored with an underscore: `label: _` LabelShorthand(EcoString), /// A variable from an assignment pattern, which can be ignored by removing `as name`, AssignmentPattern, /// A variable generated by the compiler. This should never need to be ignored Generated, } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] /// The source of a variable, such as a `let` assignment, or function parameter. pub enum VariableDeclaration { LetPattern, UsePattern, ClausePattern, FunctionParameter { /// The name of the function defining the parameter. This will be None /// for parameters introduced by anoynmous functions: `fn(a) { a }` /// function_name: Option, /// The index of the parameter in the function's parameter list. /// index: usize, }, Generated, } impl VariableOrigin { pub fn how_to_ignore(&self) -> Option { match &self.syntax { VariableSyntax::Variable(name) => { Some(format!("You can ignore it with an underscore: `_{name}`.")) } VariableSyntax::LabelShorthand(label) => Some(format!( "You can ignore it with an underscore: `{label}: _`." )), VariableSyntax::AssignmentPattern => Some("You can safely remove it.".to_string()), VariableSyntax::Generated => None, } } pub fn generated() -> Self { Self { syntax: VariableSyntax::Generated, declaration: VariableDeclaration::Generated, } } pub fn is_function_parameter(&self) -> bool { match self.declaration { VariableDeclaration::FunctionParameter { .. } => true, VariableDeclaration::LetPattern | VariableDeclaration::UsePattern | VariableDeclaration::ClausePattern | VariableDeclaration::Generated => false, } } } #[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)] pub enum Warning { Todo { kind: TodoKind, location: SrcSpan, type_: Arc, }, ImplicitlyDiscardedResult { location: SrcSpan, }, UnusedLiteral { location: SrcSpan, }, UnusedValue { location: SrcSpan, }, NoFieldsRecordUpdate { location: SrcSpan, }, AllFieldsRecordUpdate { location: SrcSpan, }, UnusedType { location: SrcSpan, imported: bool, name: EcoString, }, UnusedConstructor { location: SrcSpan, imported: bool, name: EcoString, }, UnusedImportedValue { location: SrcSpan, name: EcoString, }, UnusedImportedModule { location: SrcSpan, name: EcoString, }, UnusedImportedModuleAlias { location: SrcSpan, alias: EcoString, module_name: EcoString, }, UnusedPrivateModuleConstant { location: SrcSpan, name: EcoString, }, UnusedPrivateFunction { location: SrcSpan, name: EcoString, }, UnusedVariable { location: SrcSpan, origin: VariableOrigin, }, UnnecessaryDoubleIntNegation { location: SrcSpan, }, UnnecessaryDoubleBoolNegation { location: SrcSpan, }, InefficientEmptyListCheck { location: SrcSpan, kind: EmptyListCheckKind, }, TransitiveDependencyImported { location: SrcSpan, module: EcoString, package: EcoString, }, DeprecatedItem { location: SrcSpan, message: EcoString, layer: Layer, }, UnreachableCasePattern { location: SrcSpan, reason: UnreachablePatternReason, }, UnusedDiscardPattern { location: SrcSpan, name: EcoString, }, /// This happens when someone tries to write a case expression where one of /// the subjects is a literal tuple, list or bit array for example: /// /// ```gleam /// case #(wibble, wobble) { ... } /// ``` /// /// Matching on a literal collection of elements is redundant since we can /// always pass the single items it's made of separated by a comma: /// /// ```gleam /// case wibble, wobble { ... } /// ``` /// CaseMatchOnLiteralCollection { kind: LiteralCollectionKind, location: SrcSpan, }, /// This happens if someone tries to match on some kind of literal value /// like an Int, a String, an empty List etc. /// /// ```gleam /// case #() { ... } /// ``` /// /// The whole match becomes redundant since one can already tell beforehand /// the structure of the value being matched. /// /// Note: for non-empty literal collection of values we want to provide a /// better error message that suggests to drop the wrapper, for that /// we have the `CaseMatchOnLiteralCollection` variant. /// CaseMatchOnLiteralValue { location: SrcSpan, }, /// This happens when someone defines an external type (with no /// constructors) and marks it as opqaue: /// /// ```gleam /// opaque type External /// ``` /// /// Since an external type already has no constructors, marking it as /// opaque is redundant. /// OpaqueExternalType { location: SrcSpan, }, /// This happens when an internal type is accidentally exposed in the public /// API. Since internal types are excluded from documentation, completions /// and the package interface, this would lead to poor developer experience. /// /// ```gleam /// @internal type Wibble /// /// pub fn wibble(thing: Wibble) { todo } /// // ^^^^^^^^^^^^^ There would be no documentation /// // explaining what `Wibble` is in the /// // package's doc site. /// ``` InternalTypeLeak { location: SrcSpan, leaked: Type, }, RedundantAssertAssignment { location: SrcSpan, }, AssertAssignmentOnImpossiblePattern { location: SrcSpan, reason: AssertImpossiblePattern, }, /// When a `todo` or `panic` is used as a function instead of providing the /// error message with the `as` syntax. /// /// ```gleam /// todo("this won't appear in the error message") /// ``` /// TodoOrPanicUsedAsFunction { kind: TodoOrPanic, location: SrcSpan, arguments_location: Option, arguments: usize, }, UnreachableCodeAfterPanic { location: SrcSpan, panic_position: PanicPosition, }, /// When a function capture is used in a pipe to pipe into the first /// argument of a function: /// /// ```gleam /// wibble |> wobble(_, 1) /// ^ Redundant and can be removed /// ``` /// RedundantPipeFunctionCapture { location: SrcSpan, }, /// When the `gleam` range specified in the package's `gleam.toml` is too /// low and would include a version that's too low to support this feature. /// /// For example, let's say that a package is saying `gleam = ">=1.1.0"` /// but it is using label shorthand syntax: `wibble(label:)`. /// That requires a version that is `>=1.4.0`, so the constraint expressed /// in the `gleam.toml` is too permissive and if someone were to run this /// code with v1.1.0 they would run into compilation errors since the /// compiler cannot know of label shorthands! /// FeatureRequiresHigherGleamVersion { location: SrcSpan, minimum_required_version: Version, wrongfully_allowed_version: Version, feature_kind: FeatureKind, }, /// When targeting JavaScript and an `Int` value is specified that lies /// outside the range `Number.MIN_SAFE_INTEGER` - `Number.MAX_SAFE_INTEGER`. /// JavaScriptIntUnsafe { location: SrcSpan, }, /// When we are trying to use bool assert on a literal boolean. For example: /// ```gleam /// assert True /// ^ The programmer knows this will never panic, so it's useless /// ``` AssertLiteralBool { location: SrcSpan, }, /// When a segment has a constant value that is bigger than its size and we /// know for certain is going to be truncated. /// BitArraySegmentTruncatedValue { truncation: BitArraySegmentTruncation, location: SrcSpan, }, /// In Gleam v1 it is possible to import one module twice using different aliases. /// This is deprecated, and likely would be removed in a Gleam v2. ModuleImportedTwice { name: EcoString, first: SrcSpan, second: SrcSpan, }, /// Top-level definition should not shadow an imported one. /// This includes constant or function imports. TopLevelDefinitionShadowsImport { location: SrcSpan, name: EcoString, }, /// This warning is raised when we perform a comparison that the compiler /// can tell is always going to succeed or fail. For example: /// /// ```gleam /// 1 == 1 // This always succeeds /// 2 != 2 // This always fails /// 1 > 10 // This always fails /// a == a // This always succeeds /// ``` RedundantComparison { location: SrcSpan, outcome: ComparisonOutcome, }, /// When a function's argument is only ever used unchanged in recursive /// calls. For example: /// /// ```gleam /// pub fn wibble(x, n) { /// // ^ This argument is not needed, /// case n { /// 0 -> Nil /// _ -> wibble(x, n - 1) /// // ^ It's only used in recursive calls! /// } /// } /// ``` /// UnusedRecursiveArgument { location: SrcSpan, }, } #[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)] pub enum AssertImpossiblePattern { /// When `let assert`-ing on a variant that's different from the inferred /// one. /// /// ```gleam /// let assert Error(_) = Ok(_) /// ``` /// InferredVariant, /// When `let assert`-ing on a pattern that will never match because it's /// matching on impossible segment(s). /// /// ```gleam /// let assert <<-2:unsigned>> = bit_array /// ``` /// ImpossibleSegments { segments: Vec, }, } #[derive(Debug, Eq, Copy, PartialEq, Clone, serde::Serialize, serde::Deserialize)] pub enum FeatureKind { LabelShorthandSyntax, ConstantStringConcatenation, ArithmeticInGuards, ConcatenateInGuards, UnannotatedUtf8StringSegment, UnannotatedFloatSegment, NestedTupleAccess, InternalAnnotation, AtInJavascriptModules, RecordUpdateVariantInference, RecordAccessVariantInference, LetAssertWithMessage, VariantWithDeprecatedAnnotation, JavaScriptUnalignedBitArray, BoolAssert, ExternalCustomType, ConstantRecordUpdate, ExpressionInSegmentSize, } impl FeatureKind { pub fn required_version(&self) -> Version { match self { FeatureKind::InternalAnnotation | FeatureKind::NestedTupleAccess => { Version::new(1, 1, 0) } FeatureKind::AtInJavascriptModules => Version::new(1, 2, 0), FeatureKind::ArithmeticInGuards => Version::new(1, 3, 0), FeatureKind::ConcatenateInGuards => Version::new(1, 15, 0), FeatureKind::LabelShorthandSyntax | FeatureKind::ConstantStringConcatenation => { Version::new(1, 4, 0) } FeatureKind::UnannotatedUtf8StringSegment => Version::new(1, 5, 0), FeatureKind::RecordUpdateVariantInference | FeatureKind::RecordAccessVariantInference => Version::new(1, 6, 0), FeatureKind::VariantWithDeprecatedAnnotation | FeatureKind::LetAssertWithMessage => { Version::new(1, 7, 0) } FeatureKind::JavaScriptUnalignedBitArray => Version::new(1, 9, 0), FeatureKind::UnannotatedFloatSegment => Version::new(1, 10, 0), FeatureKind::BoolAssert => Version::new(1, 11, 0), FeatureKind::ExpressionInSegmentSize => Version::new(1, 12, 0), FeatureKind::ExternalCustomType | FeatureKind::ConstantRecordUpdate => { Version::new(1, 14, 0) } } } } #[derive(Debug, Eq, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)] pub enum PanicPosition { /// When the unreachable part is a function argument, this means that one /// of the previous arguments must be a panic. PreviousFunctionArgument, /// When the unreachable part is a function call, this means that its last /// argument must be a panic. LastFunctionArgument, /// When the expression to be printed by echo panics. EchoExpression, /// Any expression that doesn't fall in the previous two categories PreviousExpression, } #[derive(Debug, Eq, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)] pub enum TodoOrPanic { Todo, Panic, } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum UnreachablePatternReason { /// The clause is unreachable because a previous pattern /// matches the same case. DuplicatePattern, /// The clause is unreachable because we have inferred the variant /// of the custom type that we are matching on, and this matches /// against one of the variants we know it isn't. ImpossibleVariant, /// The clause is unreachable because it is matching on a pattern segment /// that we could tell is never going to match ImpossibleSegments(Vec), } impl Error { // Location where the error started pub fn start_location(&self) -> u32 { match self { Error::InvalidImport { location, .. } | Error::BitArraySegmentError { location, .. } | Error::UnknownVariable { location, .. } | Error::UnknownType { location, .. } | Error::UnknownModule { location, .. } | Error::UnknownModuleType { location, .. } | Error::UnknownModuleValue { location, .. } | Error::ModuleAliasUsedAsName { location, .. } | Error::NotFn { location, .. } | Error::UnknownRecordField { location, .. } | Error::IncorrectArity { location, .. } | Error::UnsafeRecordUpdate { location, .. } | Error::UnnecessarySpreadOperator { location, .. } | Error::IncorrectTypeArity { location, .. } | Error::TypeUsedAsAConstructor { location, .. } | Error::CouldNotUnify { location, .. } | Error::RecursiveType { location, .. } | Error::DuplicateName { location_a: location, .. } | Error::DuplicateImport { location, .. } | Error::DuplicateTypeName { location, .. } | Error::DuplicateArgument { location, .. } | Error::DuplicateField { location, .. } | Error::PrivateTypeLeak { location, .. } | Error::UnexpectedLabelledArg { location, .. } | Error::PositionalArgumentAfterLabelled { location, .. } | Error::IncorrectNumClausePatterns { location, .. } | Error::NonLocalClauseGuardVariable { location, .. } | Error::ExtraVarInAlternativePattern { location, .. } | Error::MissingVarInAlternativePattern { location, .. } | Error::DuplicateVarInPattern { location, .. } | Error::OutOfBoundsTupleIndex { location, .. } | Error::NotATuple { location, .. } | Error::NotATupleUnbound { location, .. } | Error::RecordAccessUnknownType { location, .. } | Error::RecordUpdateInvalidConstructor { location, .. } | Error::UnexpectedTypeHole { location, .. } | Error::NotExhaustivePatternMatch { location, .. } | Error::ArgumentNameAlreadyUsed { location, .. } | Error::UnlabelledAfterlabelled { location, .. } | Error::RecursiveTypeAlias { location, .. } | Error::ExternalMissingAnnotation { location, .. } | Error::NoImplementation { location, .. } | Error::UnsupportedExpressionTarget { location, .. } | Error::InvalidExternalJavascriptModule { location, .. } | Error::InvalidExternalJavascriptFunction { location, .. } | Error::InexhaustiveCaseExpression { location, .. } | Error::MissingCaseBody { location } | Error::InexhaustiveLetAssignment { location, .. } | Error::UnusedTypeAliasParameter { location, .. } | Error::DuplicateTypeParameter { location, .. } | Error::UnsupportedPublicFunctionTarget { location, .. } | Error::NotFnInUse { location, .. } | Error::UseCallbackIncorrectArity { pattern_location: location, .. } | Error::UseFnDoesntTakeCallback { location, .. } | Error::UseFnIncorrectArity { location, .. } | Error::BadName { location, .. } | Error::AllVariantsDeprecated { location } | Error::EchoWithNoFollowingExpression { location } | Error::DeprecatedVariantOnDeprecatedType { location } | Error::LiteralFloatOutOfRange { location } | Error::FloatOperatorOnInts { location, .. } | Error::IntOperatorOnFloats { location, .. } | Error::StringConcatenationWithAddInt { location } | Error::DoubleVariableAssignmentInBitArray { location } | Error::NonUtf8StringAssignmentInBitArray { location } | Error::PrivateOpaqueType { location } | Error::SrcImportingDevDependency { location, .. } | Error::ExternalTypeWithConstructors { location, .. } | Error::LowercaseBoolPattern { location } => location.start, Error::UnknownLabels { unknown, .. } => { unknown.iter().map(|(_, s)| s.start).min().unwrap_or(0) } Error::ReservedModuleName { .. } => 0, Error::KeywordInModuleName { .. } => 0, } } pub fn with_unify_error_situation(mut self, new_situation: UnifyErrorSituation) -> Self { if let Error::CouldNotUnify { ref mut situation, .. } = self { *situation = Some(new_situation); self } else { self } } } impl Warning { pub fn into_warning(self, path: Utf8PathBuf, src: EcoString) -> crate::Warning { crate::Warning::Type { path, src, warning: self, } } pub(crate) fn location(&self) -> SrcSpan { match self { Warning::Todo { location, .. } | Warning::ImplicitlyDiscardedResult { location, .. } | Warning::UnusedLiteral { location, .. } | Warning::UnusedValue { location, .. } | Warning::NoFieldsRecordUpdate { location, .. } | Warning::AllFieldsRecordUpdate { location, .. } | Warning::UnusedType { location, .. } | Warning::UnusedConstructor { location, .. } | Warning::UnusedImportedValue { location, .. } | Warning::UnusedImportedModule { location, .. } | Warning::UnusedImportedModuleAlias { location, .. } | Warning::UnusedPrivateModuleConstant { location, .. } | Warning::UnusedPrivateFunction { location, .. } | Warning::UnusedVariable { location, .. } | Warning::UnnecessaryDoubleIntNegation { location, .. } | Warning::UnnecessaryDoubleBoolNegation { location, .. } | Warning::InefficientEmptyListCheck { location, .. } | Warning::TransitiveDependencyImported { location, .. } | Warning::DeprecatedItem { location, .. } | Warning::UnreachableCasePattern { location, .. } | Warning::CaseMatchOnLiteralCollection { location, .. } | Warning::CaseMatchOnLiteralValue { location, .. } | Warning::OpaqueExternalType { location, .. } | Warning::InternalTypeLeak { location, .. } | Warning::RedundantAssertAssignment { location, .. } | Warning::AssertAssignmentOnImpossiblePattern { location, .. } | Warning::TodoOrPanicUsedAsFunction { location, .. } | Warning::UnreachableCodeAfterPanic { location, .. } | Warning::RedundantPipeFunctionCapture { location, .. } | Warning::FeatureRequiresHigherGleamVersion { location, .. } | Warning::JavaScriptIntUnsafe { location, .. } | Warning::AssertLiteralBool { location, .. } | Warning::BitArraySegmentTruncatedValue { location, .. } | Warning::TopLevelDefinitionShadowsImport { location, .. } | Warning::UnusedDiscardPattern { location, .. } | Warning::ModuleImportedTwice { second: location, .. } | Warning::RedundantComparison { location, .. } | Warning::UnusedRecursiveArgument { location, .. } => *location, } } pub(crate) fn is_todo(&self) -> bool { matches!(self, Self::Todo { .. }) } } #[derive(Debug, PartialEq, Eq)] pub enum UnknownValueConstructorError { Variable { name: EcoString, variables: Vec, type_with_name_in_scope: bool, }, Module { name: EcoString, suggestions: Vec, }, ModuleValue { name: EcoString, module_name: EcoString, value_constructors: Vec, imported_value_as_type: bool, }, } pub fn convert_get_value_constructor_error( e: UnknownValueConstructorError, location: SrcSpan, module_location: Option, ) -> Error { match e { UnknownValueConstructorError::Variable { name, variables, type_with_name_in_scope, } => Error::UnknownVariable { location, name, variables, discarded_location: None, type_with_name_in_scope, }, UnknownValueConstructorError::Module { name, suggestions } => Error::UnknownModule { location: module_location.unwrap_or(location), name, suggestions, }, UnknownValueConstructorError::ModuleValue { name, module_name, value_constructors, imported_value_as_type, } => Error::UnknownModuleValue { location, name, module_name, value_constructors, type_with_same_name: imported_value_as_type, context: ModuleValueUsageContext::ModuleAccess, }, } } #[derive(Debug, Eq, PartialEq, Clone)] pub enum UnsafeRecordUpdateReason { UnknownVariant { constructed_variant: EcoString, }, WrongVariant { constructed_variant: EcoString, spread_variant: EcoString, }, IncompatibleFieldTypes { constructed_variant: Arc, record_variant: Arc, expected_field_type: Arc, record_field_type: Arc, field: RecordField, }, } #[derive(Debug, Eq, PartialEq, Clone)] pub enum RecordField { Labelled(EcoString), Unlabelled(u32), } #[derive(Clone, Debug, PartialEq, Eq)] pub enum UnknownTypeHint { AlternativeTypes(Vec), ValueInScopeWithSameName, } #[derive(Debug, PartialEq, Eq)] pub enum UnknownTypeConstructorError { Type { name: EcoString, hint: UnknownTypeHint, }, Module { name: EcoString, suggestions: Vec, }, ModuleType { name: EcoString, module_name: EcoString, type_constructors: Vec, imported_type_as_value: bool, }, } pub fn convert_get_type_constructor_error( e: UnknownTypeConstructorError, location: &SrcSpan, module_location: Option, ) -> Error { match e { UnknownTypeConstructorError::Type { name, hint } => Error::UnknownType { location: *location, name, hint, }, UnknownTypeConstructorError::Module { name, suggestions } => Error::UnknownModule { location: module_location.unwrap_or(*location), name, suggestions, }, UnknownTypeConstructorError::ModuleType { name, module_name, type_constructors, imported_type_as_value, } => Error::UnknownModuleType { location: *location, name, module_name, type_constructors, value_with_same_name: imported_type_as_value, }, } } #[derive(Debug, Clone)] pub enum MatchFunTypeError { IncorrectArity { expected: usize, given: usize, arguments: Vec>, return_type: Arc, }, NotFn { type_: Arc, }, } pub fn convert_not_fun_error( e: MatchFunTypeError, fn_location: SrcSpan, call_location: SrcSpan, call_kind: CallKind, ) -> Error { match (call_kind, e) { ( CallKind::Function, MatchFunTypeError::IncorrectArity { expected, given, .. }, ) => Error::IncorrectArity { labels: vec![], location: call_location, context: IncorrectArityContext::Function, expected, given, }, (CallKind::Function, MatchFunTypeError::NotFn { type_ }) => Error::NotFn { location: fn_location, type_, }, ( CallKind::Use { call_location, .. }, MatchFunTypeError::IncorrectArity { expected, given, .. }, ) => Error::UseFnIncorrectArity { location: call_location, expected, given, }, (CallKind::Use { call_location, .. }, MatchFunTypeError::NotFn { type_ }) => { Error::NotFnInUse { location: call_location, type_, } } } } pub fn flip_unify_error(e: UnifyError) -> UnifyError { match e { UnifyError::CouldNotUnify { expected, given, situation: note, } => UnifyError::CouldNotUnify { expected: given, given: expected, situation: note, }, UnifyError::ExtraVarInAlternativePattern { .. } | UnifyError::MissingVarInAlternativePattern { .. } | UnifyError::DuplicateVarInPattern { .. } | UnifyError::RecursiveType => e, } } #[test] fn flip_unify_error_test() { assert_eq!( UnifyError::CouldNotUnify { expected: crate::type_::int(), given: crate::type_::float(), situation: Some(UnifyErrorSituation::CaseClauseMismatch { clause_location: SrcSpan::default() }), }, flip_unify_error(UnifyError::CouldNotUnify { expected: crate::type_::float(), given: crate::type_::int(), situation: Some(UnifyErrorSituation::CaseClauseMismatch { clause_location: SrcSpan::default() }), }) ); } pub fn unify_enclosed_type( e1: Arc, e2: Arc, result: Result<(), UnifyError>, ) -> Result<(), UnifyError> { // If types cannot unify, show the type error with the enclosing types, e1 and e2. match result { Err(UnifyError::CouldNotUnify { situation: note, .. }) => Err(UnifyError::CouldNotUnify { expected: e1, given: e2, situation: note, }), _ => result, } } #[test] fn unify_enclosed_type_test() { assert_eq!( Err(UnifyError::CouldNotUnify { expected: crate::type_::int(), given: crate::type_::float(), situation: Some(UnifyErrorSituation::CaseClauseMismatch { clause_location: SrcSpan::default() }) }), unify_enclosed_type( crate::type_::int(), crate::type_::float(), Err(UnifyError::CouldNotUnify { expected: crate::type_::string(), given: crate::type_::bit_array(), situation: Some(UnifyErrorSituation::CaseClauseMismatch { clause_location: SrcSpan::default() }) }) ) ); } pub fn unify_wrong_arity( t1: &Arc, arity1: usize, t2: &Arc, arity2: usize, ) -> UnifyError { UnifyError::CouldNotUnify { expected: t1.clone(), given: t2.clone(), situation: Some(UnifyErrorSituation::FunctionsMismatch { reason: FunctionsMismatchReason::Arity { expected: arity1, given: arity2, }, }), } } pub fn unify_wrong_arguments( expected: &Arc, expected_arg: &Arc, given: &Arc, given_arg: &Arc, position: usize, ) -> UnifyError { UnifyError::CouldNotUnify { expected: expected.clone(), given: given.clone(), situation: Some(UnifyErrorSituation::FunctionsMismatch { reason: FunctionsMismatchReason::Argument { expected: expected_arg.clone(), given: given_arg.clone(), position, }, }), } } pub fn unify_wrong_returns( expected: &Arc, expected_return: &Arc, given: &Arc, given_return: &Arc, ) -> UnifyError { UnifyError::CouldNotUnify { expected: expected.clone(), given: given.clone(), situation: Some(UnifyErrorSituation::FunctionsMismatch { reason: FunctionsMismatchReason::Results { expected: expected_return.clone(), given: given_return.clone(), }, }), } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum UnifyErrorSituation { /// Clauses in a case expression were found to return different types. CaseClauseMismatch { clause_location: SrcSpan, }, /// A function was found to return a value that did not match its return /// annotation. ReturnAnnotationMismatch, PipeTypeMismatch, /// The operands of a binary operator were incorrect. Operator(BinOp), /// One of the elements of a list was not the same type as the others. ListElementMismatch, /// The tail of the list is not the same type as the other elements. ListTailMismatch, /// When two functions cannot be unified. FunctionsMismatch { reason: FunctionsMismatchReason, }, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum FunctionsMismatchReason { Results { expected: Arc, given: Arc, }, Arity { expected: usize, given: usize, }, Argument { expected: Arc, given: Arc, position: usize, }, } impl UnifyErrorSituation { pub fn description(&self) -> Option<&'static str> { match self { Self::CaseClauseMismatch { clause_location: _ } => Some( "This case clause was found to return a different type than the previous one, but all case clauses must return the same type.", ), Self::ReturnAnnotationMismatch => Some( "The type of this returned value doesn't match the return type annotation of this function.", ), Self::PipeTypeMismatch => { Some("This function cannot handle the argument sent through the (|>) pipe:") } Self::Operator(_op) => None, Self::ListElementMismatch => Some( "All elements of a list must be the same type, but this one doesn't match the one before it.", ), Self::ListTailMismatch => Some( "All elements in a list must have the same type, but the elements of this list don't match the type of the elements being prepended to it.", ), Self::FunctionsMismatch { .. } => None, } } } #[derive(Debug, PartialEq)] pub enum UnifyError { CouldNotUnify { expected: Arc, given: Arc, situation: Option, }, ExtraVarInAlternativePattern { name: EcoString, }, MissingVarInAlternativePattern { name: EcoString, }, DuplicateVarInPattern { name: EcoString, }, RecursiveType, } impl UnifyError { pub fn with_unify_error_situation(self, situation: UnifyErrorSituation) -> Self { match self { Self::CouldNotUnify { expected, given, .. } => Self::CouldNotUnify { expected, given, situation: Some(situation), }, Self::ExtraVarInAlternativePattern { .. } | Self::MissingVarInAlternativePattern { .. } | Self::DuplicateVarInPattern { .. } | Self::RecursiveType => self, } } pub fn case_clause_mismatch(self, clause_location: SrcSpan) -> Self { self.with_unify_error_situation(UnifyErrorSituation::CaseClauseMismatch { clause_location }) } pub fn list_element_mismatch(self) -> Self { self.with_unify_error_situation(UnifyErrorSituation::ListElementMismatch) } pub fn list_tail_mismatch(self) -> Self { self.with_unify_error_situation(UnifyErrorSituation::ListTailMismatch) } pub fn return_annotation_mismatch(self) -> Self { self.with_unify_error_situation(UnifyErrorSituation::ReturnAnnotationMismatch) } pub fn operator_situation(self, binop: BinOp) -> Self { self.with_unify_error_situation(UnifyErrorSituation::Operator(binop)) } pub fn into_error(self, location: SrcSpan) -> Error { match self { Self::CouldNotUnify { expected, given, situation: note, } => Error::CouldNotUnify { location, expected, given, situation: note, }, Self::ExtraVarInAlternativePattern { name } => { Error::ExtraVarInAlternativePattern { location, name } } Self::MissingVarInAlternativePattern { name } => { Error::MissingVarInAlternativePattern { location, name } } Self::DuplicateVarInPattern { name } => Error::DuplicateVarInPattern { location, name }, Self::RecursiveType => Error::RecursiveType { location }, } } pub fn into_use_unify_error( self, function_location: SrcSpan, pattern_location: SrcSpan, last_statement_location: SrcSpan, body_location: SrcSpan, ) -> Error { match &self { // If the expected value is not a function, that means we're trying // to pass something that doesn't take a function as callback to the // right hand side of use. Self::CouldNotUnify { expected, given: _, situation: _, } if !expected.as_ref().is_fun() => Error::UseFnDoesntTakeCallback { location: function_location, actual_type: Some(expected.as_ref().clone()), }, Self::CouldNotUnify { situation: Some(UnifyErrorSituation::FunctionsMismatch { reason }), .. } => match reason { // If both the expected and given values are functions we can be a // bit more specific with the error and highlight the reason of the // problem instead of having a generic type mismatch error. FunctionsMismatchReason::Results { expected: one, given: other, } => Error::CouldNotUnify { location: last_statement_location, expected: one.clone(), given: other.clone(), situation: None, }, FunctionsMismatchReason::Arity { expected: one, given: other, } => Error::UseCallbackIncorrectArity { call_location: function_location, pattern_location, expected: *one, given: *other, }, // For this one we just fallback to the generic cannot unify error // as it is already plenty clear in a `use` expression. FunctionsMismatchReason::Argument { .. } => self.into_error(body_location), }, // In all other cases we fallback to the generic cannot unify error. Self::CouldNotUnify { .. } | Self::ExtraVarInAlternativePattern { .. } | Self::MissingVarInAlternativePattern { .. } | Self::DuplicateVarInPattern { .. } | Self::RecursiveType => self.into_error(body_location), } } } pub fn convert_unify_error(e: UnifyError, location: SrcSpan) -> Error { e.into_error(location) } pub fn convert_unify_call_error(e: UnifyError, location: SrcSpan, kind: ArgumentKind) -> Error { match kind { ArgumentKind::UseCallback { function_location, assignments_location, last_statement_location, } => e.into_use_unify_error( function_location, assignments_location, last_statement_location, location, ), ArgumentKind::Regular => convert_unify_error(e, location), } } /// When targeting JavaScript, adds a warning if the given Int value is outside the range of /// safe integers as defined by Number.MIN_SAFE_INTEGER and Number.MAX_SAFE_INTEGER. /// pub fn check_javascript_int_safety(int_value: &BigInt, location: SrcSpan, problems: &mut Problems) { let js_min_safe_integer = -9007199254740991i64; let js_max_safe_integer = 9007199254740991i64; if *int_value < js_min_safe_integer.into() || *int_value > js_max_safe_integer.into() { problems.warning(Warning::JavaScriptIntUnsafe { location }); } } /// When targeting Erlang, adds an error if the given Float value is outside the range /// -1.7976931348623157e308 to 1.7976931348623157e308 which is the allowed range for /// Erlang's floating point numbers /// pub fn check_float_safety(value: LiteralFloatValue, location: SrcSpan, problems: &mut Problems) { let min_float = -1.7976931348623157e308f64; let max_float = 1.7976931348623157e308f64; let float_value = value.value(); if float_value < min_float || float_value > max_float { problems.error(Error::LiteralFloatOutOfRange { location }); } } ================================================ FILE: compiler-core/src/type_/expression.rs ================================================ use super::{pipe::PipeTyper, *}; use crate::{ STDLIB_PACKAGE_NAME, analyse::{Inferred, infer_bit_array_option, name::check_argument_names}, ast::{ Arg, Assert, Assignment, AssignmentKind, BinOp, BitArrayOption, BitArraySegment, CAPTURE_VARIABLE, CallArg, Clause, ClauseGuard, Constant, FunctionLiteralKind, HasLocation, ImplicitCallArgOrigin, InvalidExpression, Layer, RECORD_UPDATE_VARIABLE, RecordBeingUpdated, SrcSpan, Statement, TodoKind, TypeAst, TypedArg, TypedAssert, TypedAssignment, TypedClause, TypedClauseGuard, TypedConstant, TypedExpr, TypedMultiPattern, TypedStatement, USE_ASSIGNMENT_VARIABLE, UntypedArg, UntypedAssert, UntypedAssignment, UntypedClause, UntypedClauseGuard, UntypedConstant, UntypedConstantBitArraySegment, UntypedExpr, UntypedExprBitArraySegment, UntypedMultiPattern, UntypedStatement, UntypedUse, UntypedUseAssignment, Use, UseAssignment, }, build::Target, exhaustiveness::{self, CompileCaseResult, CompiledCase, Reachability}, parse::{LiteralFloatValue, PatternPosition}, reference::ReferenceKind, }; use ecow::eco_format; use hexpm::version::{LowestVersion, Version}; use im::hashmap; use itertools::Itertools; use num_bigint::BigInt; use vec1::Vec1; #[derive( Clone, Copy, Debug, Eq, PartialOrd, Ord, PartialEq, serde::Serialize, serde::Deserialize, )] pub struct Implementations { /// Whether the function has a pure-gleam implementation. /// /// It's important to notice that, even if all individual targets are /// supported, it would not be the same as being pure Gleam. /// Imagine this scenario: /// /// ```gleam /// @external(javascript, "wibble", "wobble") /// @external(erlang, "wibble", "wobble") /// pub fn func() -> Int /// ``` /// /// `func` supports all _current_ Gleam targets; however, if a new target /// is added - say a WASM target - `func` wouldn't support it! On the other /// hand, a pure Gleam function will support all future targets. pub gleam: bool, pub can_run_on_erlang: bool, pub can_run_on_javascript: bool, /// Whether the function has an implementation that uses external erlang /// code. pub uses_erlang_externals: bool, /// Whether the function has an implementation that uses external javascript /// code. pub uses_javascript_externals: bool, } impl Implementations { pub fn supporting_all() -> Self { Self { gleam: true, can_run_on_erlang: true, can_run_on_javascript: true, uses_javascript_externals: false, uses_erlang_externals: false, } } } /// The purity of a function. /// /// This is not actually proper purity tracking, rather an approximation, which /// is good enough for the purpose it is currently used for: warning for unused /// pure functions. The current system contains some false negatives, i.e. some /// cases where it will fail to emit a warning when it probably should. /// /// If we wanted to properly track function side effects - say to perform /// optimisations on pure Gleam code - we would probably need to lift that /// tracking into the type system, the same way that variant inference currently /// works. This would require quite a lot of work and doesn't seem a worthwhile /// amount of effort for a single warning message, where a much simpler solution /// is generally going to be good enough. /// /// In the future we may want to implement a full side effect tracking system; /// this current implementation will not be sufficient for anything beyond a /// warning message to help people out in certain cases. /// #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum Purity { /// The function is in pure Gleam, and does not reference any language /// feature that can cause side effects, such as `panic`, `assert` or `echo`. /// It also does not call any impure functions. Pure, /// This function is part of the standard library, or an otherwise trusted /// source, and though it might use FFI, we can trust that the FFI function /// will not cause any side effects. TrustedPure, /// This function is impure because it either uses FFI, panics, uses `echo`, /// or calls another impure function. Impure, /// We don't know the purity of this function. This highlights the main issue /// with the current purity tracking system. In the following code for example: /// /// ```gleam /// let f = function.identity /// /// f(10) /// ``` /// /// Since purity is not currently part of the type system, when analysing the /// call of the local `f` function, we now have no information about the /// purity of it, and therefore cannot infer the consequences of calling it. /// /// If there was a `purity` or `side_effects` field in the `Type::Fn` variant, /// we would be able to properly infer it. /// Unknown, } impl Purity { pub fn is_pure(&self) -> bool { match self { Purity::Pure | Purity::TrustedPure => true, Purity::Impure | Purity::Unknown => false, } } #[must_use] pub fn merge(self, other: Purity) -> Purity { match (self, other) { // If we call a trusted pure function, the current function remains pure (Purity::Pure, Purity::TrustedPure) => Purity::Pure, (Purity::Pure, other) => other, // If we call a pure function, the current function remains trusted pure (Purity::TrustedPure, Purity::Pure) => Purity::TrustedPure, (Purity::TrustedPure, other) => other, // Nothing can make an already impure function pure again (Purity::Impure, _) => Purity::Impure, // If we call an impure function from a function we don't know the // purity of, we are now certain that it is impure. (Purity::Unknown, Purity::Impure) => Purity::Impure, (Purity::Unknown, _) => Purity::Impure, } } } /// Tracking whether the function being currently type checked has externals /// implementations or not. /// This is used to determine whether an error should be raised in the case when /// a value is used that does not have an implementation for the current target. #[derive(Clone, Copy, Debug)] pub struct FunctionDefinition { /// The function has { ... } after the function head pub has_body: bool, /// The function has @external(erlang, "...", "...") pub has_erlang_external: bool, /// The function has @external(JavaScript, "...", "...") pub has_javascript_external: bool, } impl FunctionDefinition { pub fn has_external_for_target(&self, target: Target) -> bool { match target { Target::Erlang => self.has_erlang_external, Target::JavaScript => self.has_javascript_external, } } } impl Implementations { /// Given the implementations of a function update those with taking into /// account the `implementations` of another function (or constant) used /// inside its body. pub fn update_from_use( &mut self, implementations: &Implementations, current_function_definition: &FunctionDefinition, ) { // With this pattern matching we won't forget to deal with new targets // when those are added :) let Implementations { gleam, uses_erlang_externals: other_uses_erlang_externals, uses_javascript_externals: other_uses_javascript_externals, can_run_on_erlang: other_can_run_on_erlang, can_run_on_javascript: other_can_run_on_javascript, } = implementations; let FunctionDefinition { has_body: _, has_erlang_external, has_javascript_external, } = current_function_definition; // If a pure-Gleam function uses a function that doesn't have a pure // Gleam implementation, then it's no longer pure-Gleam. self.gleam = self.gleam && *gleam; // A function can run on a target if the code that it uses can run on on // the same target, self.can_run_on_erlang = *has_erlang_external || (self.can_run_on_erlang && (*gleam || *other_can_run_on_erlang)); self.can_run_on_javascript = *has_javascript_external || (self.can_run_on_javascript && (*gleam || *other_can_run_on_javascript)); // If a function uses a function that relies on external code (be it // javascript or erlang) then it's considered as using external code as // well. // // For example: // ```gleam // @external(erlang, "wibble", "wobble") // pub fn erlang_only_with_pure_gleam_default() -> Int { // 1 + 1 // } // // pub fn main() { erlang_only_with_pure_gleam_default() } // ``` // Both functions will end up using external erlang code and have the // following implementations: // `Implementations { gleam: true, uses_erlang_externals: true, uses_javascript_externals: false}`. // They have a pure gleam implementation and an erlang specific external // implementation. self.uses_erlang_externals = self.uses_erlang_externals || *other_uses_erlang_externals; self.uses_javascript_externals = self.uses_javascript_externals || *other_uses_javascript_externals; } /// Returns true if the current target is supported by the given /// implementations. /// If something has a pure gleam implementation then it supports all /// targets automatically. pub fn supports(&self, target: Target) -> bool { self.gleam || match target { Target::Erlang => self.can_run_on_erlang, Target::JavaScript => self.can_run_on_javascript, } } } /// This is used to tell apart regular function calls and `use` expressions: /// a `use` is still typed as if it were a normal function call but we want to /// be able to tell the difference in order to provide better error message. /// #[derive(Eq, PartialEq, Debug, Copy, Clone)] pub enum CallKind { Function, Use { call_location: SrcSpan, assignments_location: SrcSpan, last_statement_location: SrcSpan, }, } /// This is used to tell apart regular call arguments and the callback that is /// implicitly passed to a `use` function call. /// Both are going to be typed as usual but we want to tell them apart in order /// to report better error messages for `use` expressions. /// #[derive(Eq, PartialEq, Debug, Copy, Clone)] pub enum ArgumentKind { Regular, UseCallback { function_location: SrcSpan, assignments_location: SrcSpan, last_statement_location: SrcSpan, }, } #[derive(Debug)] pub(crate) struct ExprTyper<'a, 'b> { pub(crate) environment: &'a mut Environment<'b>, /// The minimum Gleam version required to compile the typed expression. pub minimum_required_version: Version, // This is set to true if the previous expression that has been typed is // determined to always panic. // For example when typing a literal `panic`, this flag will be set to true. // The same goes, for example, if the branches of a case expression all // panic. pub(crate) previous_panics: bool, // This is used to track if we've already warned for unreachable code. // After emitting the first unreachable code warning we never emit another // one to avoid flooding with repetitive warnings. pub(crate) already_warned_for_unreachable_code: bool, pub(crate) implementations: Implementations, pub(crate) purity: Purity, pub(crate) current_function_definition: FunctionDefinition, // Type hydrator for creating types from annotations pub(crate) hydrator: Hydrator, // Accumulated errors and warnings found while typing the expression pub(crate) problems: &'a mut Problems, } impl<'a, 'b> ExprTyper<'a, 'b> { pub fn new( environment: &'a mut Environment<'b>, definition: FunctionDefinition, problems: &'a mut Problems, ) -> Self { let mut hydrator = Hydrator::new(); let implementations = Implementations { // We start assuming the function is pure Gleam and narrow it down // if we run into functions/constants that have only external // implementations for some of the targets. gleam: definition.has_body, can_run_on_erlang: definition.has_body || definition.has_erlang_external, can_run_on_javascript: definition.has_body || definition.has_javascript_external, uses_erlang_externals: definition.has_erlang_external, uses_javascript_externals: definition.has_javascript_external, }; let uses_externals = match environment.target { Target::Erlang => implementations.uses_erlang_externals, Target::JavaScript => implementations.uses_javascript_externals, }; let purity = if is_trusted_pure_module(environment) { // The standard library uses a lot of FFI, but as we are the // maintainers we know that it can be trusted to be pure. Purity::TrustedPure } else if uses_externals { Purity::Impure } else { Purity::Pure }; hydrator.permit_holes(true); Self { hydrator, previous_panics: false, already_warned_for_unreachable_code: false, environment, implementations, purity, current_function_definition: definition, minimum_required_version: Version::new(0, 1, 0), problems, } } fn in_new_scope( &mut self, process_scope: impl FnOnce(&mut Self) -> Result, ) -> Result { self.scoped(|this| { let result = process_scope(this); let was_successful = result.is_ok(); (result, was_successful) }) } fn value_in_new_scope(&mut self, process_scope: impl FnOnce(&mut Self) -> A) -> A { self.scoped(|this| (process_scope(this), true)) } fn expr_in_new_scope( &mut self, process_scope: impl FnOnce(&mut Self) -> TypedExpr, ) -> TypedExpr { self.scoped(|this| { let expr = process_scope(this); let was_successful = !expr.is_invalid(); (expr, was_successful) }) } fn scoped(&mut self, process_scope: impl FnOnce(&mut Self) -> (A, bool)) -> A { // Create new scope let environment_reset_data = self.environment.open_new_scope(); let hydrator_reset_data = self.hydrator.open_new_scope(); // Process the scope let (result, was_successful) = process_scope(self); // Close scope, discarding any scope local state self.environment .close_scope(environment_reset_data, was_successful, self.problems); self.hydrator.close_scope(hydrator_reset_data); result } pub fn type_from_ast(&mut self, ast: &TypeAst) -> Result, Error> { self.hydrator .type_from_ast(ast, self.environment, self.problems) } fn instantiate(&mut self, t: Arc, ids: &mut im::HashMap>) -> Arc { self.environment.instantiate(t, ids, &self.hydrator) } pub fn new_unbound_var(&mut self) -> Arc { self.environment.new_unbound_var() } pub fn infer_or_error(&mut self, expr: UntypedExpr) -> Result { if self.previous_panics { self.warn_for_unreachable_code(expr.location(), PanicPosition::PreviousExpression); } match expr { UntypedExpr::Todo { location, message: label, kind, .. } => Ok(self.infer_todo(location, kind, label)), UntypedExpr::Panic { location, message, .. } => Ok(self.infer_panic(location, message)), UntypedExpr::Echo { location, keyword_end, expression, message, } => Ok(self.infer_echo(location, keyword_end, expression, message)), UntypedExpr::Var { location, name, .. } => { self.infer_var(name, location, ReferenceRegistration::Register) } UntypedExpr::Int { location, value, int_value, .. } => { if self.environment.target == Target::JavaScript && !self.current_function_definition.has_javascript_external { check_javascript_int_safety(&int_value, location, self.problems); } Ok(self.infer_int(value, int_value, location)) } UntypedExpr::Block { statements, location, } => Ok(self.infer_block(statements, location)), UntypedExpr::Tuple { location, elements, .. } => Ok(self.infer_tuple(elements, location)), UntypedExpr::Float { location, value, float_value, } => { check_float_safety(float_value, location, self.problems); Ok(self.infer_float(value, float_value, location)) } UntypedExpr::String { location, value, .. } => Ok(self.infer_string(value, location)), UntypedExpr::PipeLine { expressions } => Ok(self.infer_pipeline(expressions)), UntypedExpr::Fn { location, kind, arguments, body, return_annotation, .. } => Ok(self.infer_fn(arguments, &[], body, kind, return_annotation, location)), UntypedExpr::Case { location, subjects, clauses, .. } => Ok(self.infer_case(subjects, clauses, location)), UntypedExpr::List { location, elements, tail, } => Ok(self.infer_list(elements, tail, location)), UntypedExpr::Call { location, fun, arguments, .. } => Ok(self.infer_call(*fun, arguments, location, CallKind::Function)), UntypedExpr::BinOp { location, name, name_location, left, right, } => Ok(self.infer_binop(name, name_location, *left, *right, location)), UntypedExpr::FieldAccess { label_location, label, container, location, } => Ok(self.infer_field_access( *container, location, label, label_location, FieldAccessUsage::Other, )), UntypedExpr::TupleIndex { location, index, tuple, .. } => self.infer_tuple_index(*tuple, index, location), UntypedExpr::BitArray { location, segments } => { self.infer_bit_array(segments, location) } UntypedExpr::RecordUpdate { location, constructor, record, arguments, } => self.infer_record_update(*constructor, record, arguments, location), UntypedExpr::NegateBool { location, value } => { Ok(self.infer_negate_bool(location, *value)) } UntypedExpr::NegateInt { location, value } => { Ok(self.infer_negate_int(location, *value)) } } } fn infer_pipeline(&mut self, expressions: Vec1) -> TypedExpr { PipeTyper::infer(self, expressions) } fn infer_todo( &mut self, location: SrcSpan, kind: TodoKind, message: Option>, ) -> TypedExpr { // Type the todo as whatever it would need to be to type check. let type_ = self.new_unbound_var(); // Emit a warning that there is a todo in the code. let warning_location = match kind { TodoKind::Keyword | TodoKind::IncompleteUse | TodoKind::EmptyBlock => location, TodoKind::EmptyFunction { function_location } => function_location, }; self.problems.warning(Warning::Todo { kind, location: warning_location, type_: type_.clone(), }); self.purity = Purity::Impure; let message = message.map(|message| Box::new(self.infer_and_unify(*message, string()))); TypedExpr::Todo { location, type_, message, kind, } } fn infer_panic(&mut self, location: SrcSpan, message: Option>) -> TypedExpr { let type_ = self.new_unbound_var(); self.purity = Purity::Impure; let message = message.map(|message| Box::new(self.infer_and_unify(*message, string()))); self.previous_panics = true; TypedExpr::Panic { location, type_, message, } } fn infer_echo( &mut self, location: SrcSpan, keyword_end: u32, expression: Option>, message: Option>, ) -> TypedExpr { self.environment.echo_found = true; self.purity = Purity::Impure; let expression = if let Some(expression) = expression { let expression = self.infer(*expression); if self.previous_panics { self.warn_for_unreachable_code(location, PanicPosition::EchoExpression); } expression } else { let location = SrcSpan { start: location.start, end: keyword_end, }; self.problems .error(Error::EchoWithNoFollowingExpression { location }); self.error_expr(location) }; TypedExpr::Echo { location, type_: expression.type_(), expression: Some(Box::new(expression)), message: message.map(|message| Box::new(self.infer_and_unify(*message, string()))), } } pub(crate) fn warn_for_unreachable_code( &mut self, location: SrcSpan, panic_position: PanicPosition, ) { // We don't want to warn twice for unreachable code inside the same // block, so we have to keep track if we've already emitted a warning of // this kind. if !self.already_warned_for_unreachable_code { self.already_warned_for_unreachable_code = true; self.problems.warning(Warning::UnreachableCodeAfterPanic { location, panic_position, }) } } fn infer_string(&mut self, value: EcoString, location: SrcSpan) -> TypedExpr { TypedExpr::String { location, value, type_: string(), } } fn infer_int(&mut self, value: EcoString, int_value: BigInt, location: SrcSpan) -> TypedExpr { TypedExpr::Int { location, value, int_value, type_: int(), } } fn infer_float( &mut self, value: EcoString, float_value: LiteralFloatValue, location: SrcSpan, ) -> TypedExpr { TypedExpr::Float { location, value, float_value, type_: float(), } } /// Emit a warning if the given expressions should not be discarded. /// e.g. because it's a literal (why was it made in the first place?) /// e.g. because it's of the `Result` type (errors should be handled) fn expression_discarded(&mut self, discarded: &TypedExpr) { if discarded.is_literal() { self.problems.warning(Warning::UnusedLiteral { location: discarded.location(), }); } else if discarded.type_().is_result() { self.problems.warning(Warning::ImplicitlyDiscardedResult { location: discarded.location(), }); } else if discarded.is_pure_value_constructor() { self.problems.warning(Warning::UnusedValue { location: discarded.location(), }) } } pub(crate) fn infer_statements( &mut self, untyped: Vec1, ) -> Vec1 { let count = untyped.len(); let location = SrcSpan::new( untyped.first().location().start, untyped.last().location().end, ); self.infer_iter_statements(location, count, untyped.into_iter()) } // Helper to create a new error expr. fn error_expr(&mut self, location: SrcSpan) -> TypedExpr { TypedExpr::Invalid { location, type_: self.new_unbound_var(), extra_information: None, } } fn error_expr_with_information( &mut self, location: SrcSpan, extra_information: Option, ) -> TypedExpr { TypedExpr::Invalid { location, type_: self.new_unbound_var(), extra_information, } } fn infer_iter_statements>( &mut self, location: SrcSpan, count: usize, mut untyped: StatementsIter, ) -> Vec1 { let mut i = 0; let mut statements: Vec = Vec::with_capacity(count); while let Some(statement) = untyped.next() { i += 1; match statement { Statement::Use(use_) => { let statement = self.infer_use(use_, location, untyped.collect()); statements.push(statement); break; // Inferring the use has consumed the rest of the exprs } Statement::Expression(expression) => { let location = expression.location(); let expression = match self.infer_or_error(expression) { Ok(expression) => expression, Err(error) => { self.problems.error(error); self.error_expr(location) } }; // This isn't the final expression in the sequence, so call the // `expression_discarded` function to see if anything is being // discarded that we think shouldn't be. if i < count { self.expression_discarded(&expression); } statements.push(Statement::Expression(expression)); } Statement::Assignment(assignment) => { let assignment = self.infer_assignment(*assignment); statements.push(Statement::Assignment(Box::new(assignment))); } Statement::Assert(assert) => { let assert = self.infer_assert(assert); statements.push(Statement::Assert(assert)); } } } Vec1::try_from_vec(statements).expect("empty sequence") } fn infer_use( &mut self, use_: UntypedUse, sequence_location: SrcSpan, mut following_expressions: Vec, ) -> TypedStatement { let use_call_location = use_.call.location(); let mut call = get_use_expression_call(*use_.call); let assignments = UseAssignments::from_use_expression(use_.assignments); let assignments_count = assignments.body_assignments.len(); let mut statements = assignments.body_assignments; if following_expressions.is_empty() { let todo = Statement::Expression(UntypedExpr::Todo { location: use_.location, message: None, kind: TodoKind::IncompleteUse, }); statements.push(todo); } else { statements.append(&mut following_expressions); } let statements = Vec1::try_from_vec(statements).expect("safe: todo added above"); // We need this to report good error messages in case there's a type error // in use. We consider `use` to be the last statement of a block since // it consumes everything that comes below it and returns a single value. let last_statement_location = statements .iter() .find_or_last(|statement| statement.is_use()) .expect("safe: iter from non empty vec") .location(); let first = statements.first().location(); // Collect the following expressions into a function to be passed as a // callback to the use's call function. let callback = UntypedExpr::Fn { arguments: assignments.function_arguments, location: SrcSpan::new(first.start, sequence_location.end), end_of_head_byte_index: sequence_location.end, return_annotation: None, kind: FunctionLiteralKind::Use { location: use_.location, }, body: statements, }; // Add this new callback function to the arguments to function call call.arguments.push(CallArg { label: None, location: SrcSpan::new(first.start, sequence_location.end), value: callback, // This argument is implicitly given by Gleam's use syntax so we // mark it as such. implicit: Some(ImplicitCallArgOrigin::Use), }); let call_location = SrcSpan { start: use_.location.start, end: sequence_location.end, }; // We use `stacker` to prevent overflowing the stack when many `use` // expressions are chained. See https://github.com/gleam-lang/gleam/issues/4287 let infer_call = || { self.infer_call( *call.function, call.arguments, call_location, CallKind::Use { call_location: use_call_location, assignments_location: use_.assignments_location, last_statement_location, }, ) }; let call = stacker::maybe_grow(128 * 1024, 2 * 1024 * 1024, infer_call); // After typing the call we know that the last argument must be an // anonymous function and the first assignments in its body are the // typed assignments on the left hand side of a `use`. let assignments = extract_typed_use_call_assignments(&call, assignments_count); Statement::Use(Use { call: Box::new(call), location: use_.location, right_hand_side_location: use_.right_hand_side_location, assignments_location: use_.assignments_location, assignments, }) } fn infer_negate_bool(&mut self, location: SrcSpan, value: UntypedExpr) -> TypedExpr { self.infer_multiple_negate_bool(location, 1, location, value) } fn infer_multiple_negate_bool( &mut self, starting_location: SrcSpan, negations: usize, location: SrcSpan, value: UntypedExpr, ) -> TypedExpr { // If we're typing a double negation we just keep going increasing the // number of consecutive negations, inferring the wrapped value. if let UntypedExpr::NegateBool { location: inner_location, value, } = value { return TypedExpr::NegateBool { location, value: Box::new(self.infer_multiple_negate_bool( starting_location, negations + 1, inner_location, *value, )), }; } // We know the last value can't be a bool negation if we're here, so // we're ready to produce a typed value! let value = self.infer(value); if let Err(error) = unify(bool(), value.type_()) { self.problems .error(convert_unify_error(error, value.location())); } // If there's more than a single negation we can raise a warning // highlighting the unneded ones. How many negations are highlighted // depends if they're an even or odd number: // // ```gleam // !!True // all negations are superfluous. // !!!True // we can remove all but one negation. // ``` if negations > 1 { let location = if negations.is_multiple_of(2) { SrcSpan { start: starting_location.start, end: location.start + 1, } } else { SrcSpan { start: starting_location.start, end: location.start, } }; self.problems .warning(Warning::UnnecessaryDoubleBoolNegation { location }); } TypedExpr::NegateBool { location, value: Box::new(value), } } fn infer_negate_int(&mut self, location: SrcSpan, value: UntypedExpr) -> TypedExpr { self.infer_multiple_negate_int(location, 1, location, value) } fn infer_multiple_negate_int( &mut self, starting_location: SrcSpan, mut negations: usize, location: SrcSpan, value: UntypedExpr, ) -> TypedExpr { // If we're typing a double negation we just keep going increasing the // number of consecutive negations, inferring the wrapped value. if let UntypedExpr::NegateInt { location: inner_location, value, } = value { return TypedExpr::NegateInt { location, value: Box::new(self.infer_multiple_negate_int( starting_location, negations + 1, inner_location, *value, )), }; } // We know the last value can't be an int negation, so we're ready to // produce a typed value! let value = self.infer(value); if let Err(error) = unify(int(), value.type_()) { self.problems .error(convert_unify_error(error, value.location())); } // This is used to emit a warning in case there's multiple negations. let mut end = location.start; // There's one special case where the final integer being typed might be // negated as well, in that case we need to update the number of // consecutive negations. if let TypedExpr::Int { value: ref v, ref location, .. } = value && v.starts_with('-') { negations += 1; end = location.start; } // If there's more than a single negation we can raise a warning // highlighting the unneded ones. How many negations are highlighted // depends if they're an even or odd number: // // ```gleam // --1 // all negations are superfluous. // ---1 // we can remove all but one negation. // ``` if negations > 1 { let location = if negations.is_multiple_of(2) { SrcSpan { start: starting_location.start, end: end + 1, } } else { SrcSpan { start: starting_location.start, end, } }; self.problems .warning(Warning::UnnecessaryDoubleIntNegation { location }); } TypedExpr::NegateInt { location, value: Box::new(value), } } fn infer_fn( &mut self, arguments: Vec, expected_arguments: &[Arc], body: Vec1, kind: FunctionLiteralKind, return_annotation: Option, location: SrcSpan, ) -> TypedExpr { for Arg { names, .. } in arguments.iter() { check_argument_names(names, self.problems); } let already_warned_for_unreachable_code = self.already_warned_for_unreachable_code; self.already_warned_for_unreachable_code = false; self.previous_panics = false; let outer_purity = self.purity; // If an anonymous function can panic, that doesn't mean that the outer // function can too, so we track the purity separately. For example, in // this code: // // ```gleam // pub fn divide_partial(dividend: Int) { // fn(divisor) { // case divisor { // 0 -> panic as "Cannot divide by 0" // _ -> dividend / divisor // } // } // } // ``` // // Although the `divide_partial` function uses the `panic` keyword, it is // actually pure. Only the anonymous function that it constructs is impure; // constructing and returning it does not have any side effects, so there is // no way for a call to `divide_partial` to produce any side effects. self.purity = Purity::Pure; let (arguments, body) = match self.do_infer_fn( None, arguments, expected_arguments, body, &return_annotation, ) { Ok(result) => result, Err(error) => { self.problems.error(error); return self.error_expr(location); } }; let arguments_types = arguments.iter().map(|a| a.type_.clone()).collect(); let type_ = fn_(arguments_types, body.last().type_()); // Defining an anonymous function never panics. self.already_warned_for_unreachable_code = already_warned_for_unreachable_code; self.previous_panics = false; let function_purity = self.purity; self.purity = outer_purity; TypedExpr::Fn { location, type_, kind, arguments, body, return_annotation, purity: function_purity, } } fn infer_arg( &mut self, arg: UntypedArg, expected: Option>, ) -> Result { let Arg { names, annotation, location, .. } = arg; let type_ = annotation .clone() .map(|type_| self.type_from_ast(&type_)) .unwrap_or_else(|| Ok(self.new_unbound_var()))?; match &names { ArgNames::Named { .. } | ArgNames::NamedLabelled { .. } => (), ArgNames::Discard { name, .. } | ArgNames::LabelledDiscard { name, .. } => { let _ = self .environment .discarded_names .insert(name.clone(), location); } } // If we know the expected type of the argument from its contextual // usage then unify the newly constructed type with the expected type. // We do this here because then there is more type information for the // function being type checked, resulting in better type errors and the // record field access syntax working. if let Some(expected) = expected { unify(expected, type_.clone()).map_err(|e| convert_unify_error(e, location))?; } Ok(Arg { names, location, annotation, type_, }) } fn infer_call( &mut self, fun: UntypedExpr, arguments: Vec>, location: SrcSpan, kind: CallKind, ) -> TypedExpr { let (fun, arguments, type_) = self.do_infer_call(fun, arguments, location, kind); // One common mistake is to think that the syntax for adding a message // to a `todo` or a `panic` exception is to `todo("...")`, but really // this does nothing as the `todo` or `panic` throws the exception // before it gets to the function call `("...")`. // If we find code doing this then emit a warning. let todopanic = match fun { TypedExpr::Todo { .. } => Some((location, TodoOrPanic::Todo)), TypedExpr::Panic { .. } => Some((location, TodoOrPanic::Panic)), TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => None, }; if let Some((location, kind)) = todopanic { let arguments_location = match (arguments.first(), arguments.last()) { (Some(first), Some(last)) => Some(SrcSpan { start: first.location().start, end: last.location().end, }), _ => None, }; self.problems.warning(Warning::TodoOrPanicUsedAsFunction { kind, location, arguments_location, arguments: arguments.len(), }); } self.purity = self.purity.merge(fun.called_function_purity()); TypedExpr::Call { location, type_, arguments, fun: Box::new(fun), } } fn infer_list( &mut self, elements: Vec, tail: Option>, location: SrcSpan, ) -> TypedExpr { let type_ = self.new_unbound_var(); // Type check each elements let mut inferred_elements = Vec::with_capacity(elements.len()); for element in elements { let element = self.infer(element); if let Err(error) = unify(type_.clone(), element.type_()) { self.problems.error(convert_unify_error( error.list_element_mismatch(), element.location(), )) }; inferred_elements.push(element); } // Type check the ..tail, if there is one let type_ = list(type_); let tail = match tail { Some(tail) => { let tail = self.infer(*tail); // Ensure the tail has the same type as the preceding elements if let Err(error) = unify(type_.clone(), tail.type_()) { self.problems.error(convert_unify_error( error.list_tail_mismatch(), tail.location(), )) } Some(Box::new(tail)) } None => None, }; TypedExpr::List { location, type_, elements: inferred_elements, tail, } } fn infer_tuple(&mut self, elements: Vec, location: SrcSpan) -> TypedExpr { let elements = elements .into_iter() .map(|element| self.infer(element)) .collect_vec(); let type_ = tuple(elements.iter().map(HasType::type_).collect_vec()); TypedExpr::Tuple { location, elements, type_, } } fn infer_var( &mut self, name: EcoString, location: SrcSpan, register_reference: ReferenceRegistration, ) -> Result { let constructor = self.do_infer_value_constructor(&None, &name, &location, register_reference)?; self.narrow_implementations(location, &constructor.variant)?; Ok(TypedExpr::Var { constructor, location, name, }) } fn narrow_implementations( &mut self, location: SrcSpan, variant: &ValueConstructorVariant, ) -> Result<(), Error> { let variant_implementations = match variant { ValueConstructorVariant::ModuleConstant { implementations, .. } => implementations, ValueConstructorVariant::ModuleFn { implementations, .. } => implementations, ValueConstructorVariant::Record { .. } | ValueConstructorVariant::LocalVariable { .. } => return Ok(()), }; self.implementations .update_from_use(variant_implementations, &self.current_function_definition); if self.environment.target_support.is_enforced() // If the value used doesn't have an implementation that can be used // for the current target... && !variant_implementations.supports(self.environment.target) // ... and there is not an external implementation for it && !self .current_function_definition .has_external_for_target(self.environment.target) { Err(Error::UnsupportedExpressionTarget { target: self.environment.target, location, }) } else { Ok(()) } } /// Attempts to infer a record access. If the attempt fails, then will fallback to attempting to infer a module access. /// If both fail, then the error from the record access will be used. fn infer_field_access( &mut self, container: UntypedExpr, // The SrcSpan of the entire field access: // ```gleam // wibble.wobble // // ^^^^^^^^^^^^^ This // ``` // location: SrcSpan, label: EcoString, // The SrcSpan of the selection label: // ```gleam // wibble.wobble // // ^^^^^^ This // ``` // label_location: SrcSpan, usage: FieldAccessUsage, ) -> TypedExpr { let container_location = container.location(); // Computes a potential module access. This will be used if a record access can't be used. // Computes both the inferred access and if it shadows a variable. let module_access = if let UntypedExpr::Var { name, .. } = &container { let module_access = self.infer_module_access(name, label.clone(), &container_location, label_location); // Returns the result and if it shadows an existing variable in scope Some((module_access, self.environment.scope.contains_key(name))) } else { None }; let record = if let UntypedExpr::Var { location, name } = container { // If the left-hand-side of the record access is a variable, this might actually be // module access. In that case, we only want to register a reference to the variable // if we actually referencing it in the record access. self.infer_var(name, location, ReferenceRegistration::DoNotRegister) } else { self.infer_or_error(container) }; // TODO: is this clone avoidable? we need to box the record for inference in both // the success case and in the valid record but invalid label case let record_access = match record.clone() { Ok(record) => self.infer_known_record_expression_access( record, label.clone(), location, label_location, container_location.start, usage, ), Err(e) => Err(e), }; match (record_access, module_access) { // Record access is valid (Ok(record_access), _) => { // If this is actually record access and not module access, and we didn't register // the reference earlier, we register it now. if let TypedExpr::RecordAccess { record, .. } = &record_access && let TypedExpr::Var { location, constructor, name, } = record.as_ref() { self.register_value_constructor_reference( name, &constructor.variant, *location, ReferenceKind::Unqualified, ) } record_access } // Record access is invalid but module access is valid ( _, Some(( Ok(TypedExpr::ModuleSelect { location, field_start, type_, label, module_name, module_alias, constructor, }), _, )), ) => { // We only register the reference here, if we know that this is a module access. // Otherwise we would register module access even if we are actually accessing // the field on a record self.environment.references.register_value_reference( module_name.clone(), label.clone(), &label, SrcSpan::new(field_start, location.end), ReferenceKind::Qualified, ); TypedExpr::ModuleSelect { location, field_start, type_, label, module_name, module_alias, constructor, } } // If module access failed because the module exists but that module does not export // the referenced value, we return extra information about the invalid module select, // so that we have information about the attempted module select and can use it, for // example, in the "Generate function" code action to support other modules. ( _, Some(( Err(Error::UnknownModuleValue { name, module_name, location, value_constructors, type_with_same_name, context, }), false, )), ) => { self.problems.error(Error::UnknownModuleValue { name: name.clone(), module_name: module_name.clone(), location, value_constructors, type_with_same_name, context, }); TypedExpr::Invalid { location, type_: self.new_unbound_var(), extra_information: Some(InvalidExpression::ModuleSelect { module_name, label }), } } // If module access failed for some other reason, and no local variable shadows the // module, we just return an invalid expression. (_, Some((Err(module_access_err), false))) => { self.problems.error(module_access_err); TypedExpr::Invalid { location, type_: self.new_unbound_var(), extra_information: None, } } // In any other case use the record access for the error (Err(record_access_err), _) => { self.problems.error(record_access_err); match record { // If the record is valid then use a placeholder access // This allows autocomplete to know a record access is being attempted // Even if the access is not valid Ok(record) => TypedExpr::RecordAccess { location, field_start: container_location.start, type_: self.new_unbound_var(), label: "".into(), index: u64::MAX, record: Box::new(record), documentation: None, }, Err(_) => TypedExpr::Invalid { location, type_: self.new_unbound_var(), extra_information: None, }, } } } } fn infer_tuple_index( &mut self, tuple: UntypedExpr, index: u64, location: SrcSpan, ) -> Result { if let UntypedExpr::TupleIndex { .. } = tuple { self.track_feature_usage(FeatureKind::NestedTupleAccess, location); } let tuple = self.infer_or_error(tuple)?; match collapse_links(tuple.type_()).as_ref() { Type::Tuple { elements } => { let type_ = elements .get(index as usize) .ok_or_else(|| Error::OutOfBoundsTupleIndex { location: SrcSpan { start: tuple.location().end, end: location.end, }, index, size: elements.len(), })? .clone(); Ok(TypedExpr::TupleIndex { location, index, tuple: Box::new(tuple), type_, }) } type_ if type_.is_unbound() => Err(Error::NotATupleUnbound { location: tuple.location(), }), Type::Named { .. } | Type::Fn { .. } | Type::Var { .. } => Err(Error::NotATuple { location: tuple.location(), given: tuple.type_(), }), } } fn infer_bit_array( &mut self, segments: Vec, location: SrcSpan, ) -> Result { let segments = segments .into_iter() .map(|mut segment| { // If the segment doesn't have an explicit type option we add a default // one ourselves if the pattern is unambiguous: literal strings are // implicitly considered utf-8 encoded strings, while floats are // implicitly given the float type option. if !segment.has_type_option() { match segment.value.as_ref() { UntypedExpr::String { location, .. } => { self.track_feature_usage( FeatureKind::UnannotatedUtf8StringSegment, *location, ); segment.options.push(BitArrayOption::Utf8 { location: SrcSpan::default(), }); } UntypedExpr::Float { location, .. } => { self.track_feature_usage( FeatureKind::UnannotatedFloatSegment, *location, ); segment.options.push(BitArrayOption::Float { location: SrcSpan::default(), }) } UntypedExpr::Int { .. } | UntypedExpr::Block { .. } | UntypedExpr::Var { .. } | UntypedExpr::Fn { .. } | UntypedExpr::List { .. } | UntypedExpr::Call { .. } | UntypedExpr::BinOp { .. } | UntypedExpr::PipeLine { .. } | UntypedExpr::Case { .. } | UntypedExpr::FieldAccess { .. } | UntypedExpr::Tuple { .. } | UntypedExpr::TupleIndex { .. } | UntypedExpr::Todo { .. } | UntypedExpr::Panic { .. } | UntypedExpr::Echo { .. } | UntypedExpr::BitArray { .. } | UntypedExpr::RecordUpdate { .. } | UntypedExpr::NegateBool { .. } | UntypedExpr::NegateInt { .. } => (), } } let segment = self.infer_bit_segment( *segment.value, segment.options, segment.location, |env, expr| env.infer_or_error(expr), ); if let Ok(segment) = &segment { // If we could successfully infer the segment we need to // check if it's `size` option uses any feature that has to // be tracked! self.check_segment_size_expression(&segment.options); }; segment }) .try_collect()?; Ok(TypedExpr::BitArray { location, segments, type_: bit_array(), }) } fn infer_constant_bit_array( &mut self, segments: Vec, location: SrcSpan, ) -> Result { let segments = segments .into_iter() .map(|mut segment| { // If the segment doesn't have an explicit type option we add a default // one ourselves if the pattern is unambiguous: literal strings are // implicitly considered utf-8 encoded strings, while floats are // implicitly given the float type option. if !segment.has_type_option() { match segment.value.as_ref() { Constant::String { location, .. } => { self.track_feature_usage( FeatureKind::UnannotatedUtf8StringSegment, *location, ); segment.options.push(BitArrayOption::Utf8 { location: SrcSpan::default(), }); } Constant::Float { location, .. } => { self.track_feature_usage( FeatureKind::UnannotatedFloatSegment, *location, ); segment.options.push(BitArrayOption::Float { location: SrcSpan::default(), }) } Constant::Int { .. } | Constant::Tuple { .. } | Constant::List { .. } | Constant::Record { .. } | Constant::RecordUpdate { .. } | Constant::BitArray { .. } | Constant::Var { .. } | Constant::StringConcatenation { .. } | Constant::Invalid { .. } => (), } } let segment = self.infer_bit_segment( *segment.value, segment.options, segment.location, |env, expr| Ok(env.infer_const(&None, expr)), ); if let Ok(segment) = &segment { // If we could successfully infer the segment we need to // check if it's `size` option uses any feature that has to // be tracked! self.check_constant_segment_size_expression(&segment.options); } segment }) .try_collect()?; Ok(Constant::BitArray { location, segments }) } fn infer_bit_segment( &mut self, value: UntypedValue, options: Vec>, location: SrcSpan, mut infer: InferFn, ) -> Result>, Error> where InferFn: FnMut(&mut Self, UntypedValue) -> Result, TypedValue: HasType + HasLocation + Clone + bit_array::GetLiteralValue, { let value = infer(self, value)?; let infer_option = |segment_option: BitArrayOption| { infer_bit_array_option(segment_option, |value, type_| { let typed_value = infer(self, value)?; unify(type_, typed_value.type_()) .map_err(|e| convert_unify_error(e, typed_value.location()))?; Ok(typed_value) }) }; let options: Vec<_> = options.into_iter().map(infer_option).try_collect()?; let type_ = bit_array::type_options_for_value(&options, self.environment.target).map_err( |error| Error::BitArraySegmentError { error: error.error, location: error.location, }, )?; // Track usage of the unaligned bit arrays feature on JavaScript so that // warnings can be emitted if the Gleam version constraint is too low if self.environment.target == Target::JavaScript && !self.current_function_definition.has_javascript_external { for option in options.iter() { if let BitArrayOption::::Size { value, location, .. } = option { let mut using_unaligned_bit_array = false; if type_.is_int() { match &(**value).as_int_literal() { Some(size) if size % 8 != BigInt::ZERO => { using_unaligned_bit_array = true; } _ => (), } } else if type_.is_bit_array() { using_unaligned_bit_array = true; } if using_unaligned_bit_array { self.track_feature_usage( FeatureKind::JavaScriptUnalignedBitArray, *location, ); break; } } } } unify(type_.clone(), value.type_()) .map_err(|e| convert_unify_error(e, value.location()))?; let segment = BitArraySegment { location, type_, value: Box::new(value), options, }; if let Some(truncation) = segment.check_for_truncated_value() { self.problems .warning(Warning::BitArraySegmentTruncatedValue { location, truncation, }); } Ok(segment) } /// Same as `self.infer_or_error` but instead of returning a `Result` with an error, /// records the error and returns an invalid expression. /// pub fn infer(&mut self, expression: UntypedExpr) -> TypedExpr { let location = expression.location(); match self.infer_or_error(expression) { Ok(result) => result, Err(error) => { let information = if let Error::UnknownVariable { name, .. } = &error { Some(InvalidExpression::UnknownVariable { name: name.clone() }) } else { None }; self.problems.error(error); self.error_expr_with_information(location, information) } } } /// Infers the type of the given function and tries to unify it with the /// given type, recording any unification error that might take place. /// The typed expression is returned in any case. /// pub fn infer_and_unify(&mut self, expression: UntypedExpr, type_: Arc) -> TypedExpr { let expression = self.infer(expression); if let Err(error) = unify(type_, expression.type_()) { self.problems .error(convert_unify_error(error, expression.location())) } expression } fn infer_binop( &mut self, name: BinOp, name_location: SrcSpan, left: UntypedExpr, right: UntypedExpr, location: SrcSpan, ) -> TypedExpr { let (input_type, output_type) = match &name { BinOp::Eq | BinOp::NotEq => { let left = self.infer(left); let right = self.infer(right); if let Err(error) = unify(left.type_(), right.type_()) { self.problems .error(convert_unify_error(error, right.location())); } else { // We only want to warn for redundant comparisons if it // makes sense to compare the two values. // That is, their types should match! self.check_for_redundant_comparison(name, &left, &right, location); } self.check_for_inefficient_empty_list_check(name, &left, &right, location); return TypedExpr::BinOp { location, name, name_location, type_: bool(), left: Box::new(left), right: Box::new(right), }; } BinOp::And => (bool(), bool()), BinOp::Or => (bool(), bool()), BinOp::LtInt => (int(), bool()), BinOp::LtEqInt => (int(), bool()), BinOp::LtFloat => (float(), bool()), BinOp::LtEqFloat => (float(), bool()), BinOp::GtEqInt => (int(), bool()), BinOp::GtInt => (int(), bool()), BinOp::GtEqFloat => (float(), bool()), BinOp::GtFloat => (float(), bool()), BinOp::AddInt => (int(), int()), BinOp::AddFloat => (float(), float()), BinOp::SubInt => (int(), int()), BinOp::SubFloat => (float(), float()), BinOp::MultInt => (int(), int()), BinOp::MultFloat => (float(), float()), BinOp::DivInt => (int(), int()), BinOp::DivFloat => (float(), float()), BinOp::RemainderInt => (int(), int()), BinOp::Concatenate => (string(), string()), }; let left = self.infer(left); let right = self.infer(right); let unify_left = unify(input_type.clone(), left.type_()); let unify_right = unify(input_type.clone(), right.type_()); if unify_left.is_ok() && unify_right.is_ok() { // We only want to warn for redundant comparisons if it makes sense // to compare the two values. That is, their types should match! self.check_for_redundant_comparison(name, &left, &right, location); } // There's some common cases in which we can provide nicer error messages: // - if we're using a float operator on int values // - if we're using an int operator on float values // - if we're using `+` on strings if name.is_float_operator() && left.type_().is_int() && right.type_().is_int() { self.problems.error(Error::FloatOperatorOnInts { operator: name, location: name_location, }) } else if name.is_int_operator() && left.type_().is_float() && right.type_().is_float() { self.problems.error(Error::IntOperatorOnFloats { operator: name, location: name_location, }) } else if name == BinOp::AddInt && left.type_().is_string() && right.type_().is_string() { self.problems.error(Error::StringConcatenationWithAddInt { location: name_location, }) } else { // In all other cases we just report an error for each of the operands. if let Err(error) = unify_left { self.problems.error( error .operator_situation(name) .into_error(left.type_defining_location()), ); } if let Err(error) = unify_right { self.problems.error( error .operator_situation(name) .into_error(right.type_defining_location()), ); } } self.check_for_inefficient_empty_list_check(name, &left, &right, location); TypedExpr::BinOp { location, name, name_location, type_: output_type, left: Box::new(left), right: Box::new(right), } } /// Checks for inefficient usage of `list.length` for checking for the empty list. /// /// If we find one of these usages, emit a warning to use comparison with empty list instead. fn check_for_inefficient_empty_list_check( &mut self, binop: BinOp, left: &TypedExpr, right: &TypedExpr, location: SrcSpan, ) { // Look for a call expression as either of the binary operands. let fun = match (&left, &right) { (TypedExpr::Call { fun, .. }, _) | (_, TypedExpr::Call { fun, .. }) => fun, _ => return, }; // Extract the module information from the call expression. let (module_name, module_alias, label) = if let TypedExpr::ModuleSelect { module_name, module_alias, label, .. } = fun.as_ref() { (module_name, module_alias, label) } else { return; }; // Check if we have a `list.length` call from `gleam/list`. if module_name != "gleam/list" || label != "length" { return; } // Resolve the module against the imported modules we have available. let list_module = match self.environment.imported_modules.get(module_alias) { Some((_, list_module)) => list_module, None => return, }; // Check that we're actually using `list.length` from the standard library. if list_module.package != STDLIB_PACKAGE_NAME { return; } // Check the kind of the empty list check so we know whether to recommend // `== []` or `!= []` syntax as a replacement. let kind = match get_empty_list_check_kind(binop, left, right) { Some(kind) => kind, None => return, }; // If we've gotten this far, go ahead and emit the warning. self.problems .warning(Warning::InefficientEmptyListCheck { location, kind }); } fn check_for_redundant_comparison( &mut self, binop: BinOp, left: &TypedExpr, right: &TypedExpr, location: SrcSpan, ) { let outcome = match (left, binop, right) { (left, BinOp::Eq, right) => match static_compare(left, right) { StaticComparison::CertainlyEqual => ComparisonOutcome::AlwaysSucceeds, StaticComparison::CertainlyDifferent => ComparisonOutcome::AlwaysFails, StaticComparison::CantTell => return, }, (left, BinOp::NotEq, right) => match static_compare(left, right) { StaticComparison::CertainlyEqual => ComparisonOutcome::AlwaysFails, StaticComparison::CertainlyDifferent => ComparisonOutcome::AlwaysSucceeds, StaticComparison::CantTell => return, }, // We special handle int literals as there's other comparisons we // might want to perform (TypedExpr::Int { int_value: n, .. }, op, TypedExpr::Int { int_value: m, .. }) => { match op { BinOp::LtInt if n < m => ComparisonOutcome::AlwaysSucceeds, BinOp::LtInt => ComparisonOutcome::AlwaysFails, BinOp::LtEqInt if n <= m => ComparisonOutcome::AlwaysSucceeds, BinOp::LtEqInt => ComparisonOutcome::AlwaysFails, BinOp::GtInt if n > m => ComparisonOutcome::AlwaysSucceeds, BinOp::GtInt => ComparisonOutcome::AlwaysFails, BinOp::GtEqInt if n >= m => ComparisonOutcome::AlwaysSucceeds, BinOp::GtEqInt => ComparisonOutcome::AlwaysFails, BinOp::And | BinOp::Or | BinOp::Eq | BinOp::NotEq | BinOp::LtFloat | BinOp::LtEqFloat | BinOp::GtEqFloat | BinOp::GtFloat | BinOp::AddInt | BinOp::AddFloat | BinOp::SubInt | BinOp::SubFloat | BinOp::MultInt | BinOp::MultFloat | BinOp::DivInt | BinOp::DivFloat | BinOp::RemainderInt | BinOp::Concatenate => return, } } ( TypedExpr::Float { float_value: n, .. }, op, TypedExpr::Float { float_value: m, .. }, ) => match op { BinOp::LtFloat if n < m => ComparisonOutcome::AlwaysSucceeds, BinOp::LtFloat => ComparisonOutcome::AlwaysFails, BinOp::LtEqFloat if n <= m => ComparisonOutcome::AlwaysSucceeds, BinOp::LtEqFloat => ComparisonOutcome::AlwaysFails, BinOp::GtFloat if n > m => ComparisonOutcome::AlwaysSucceeds, BinOp::GtFloat => ComparisonOutcome::AlwaysFails, BinOp::GtEqFloat if n >= m => ComparisonOutcome::AlwaysSucceeds, BinOp::GtEqFloat => ComparisonOutcome::AlwaysFails, BinOp::And | BinOp::Or | BinOp::Eq | BinOp::NotEq | BinOp::LtInt | BinOp::LtEqInt | BinOp::GtEqInt | BinOp::GtInt | BinOp::AddInt | BinOp::AddFloat | BinOp::SubInt | BinOp::SubFloat | BinOp::MultInt | BinOp::MultFloat | BinOp::DivInt | BinOp::DivFloat | BinOp::RemainderInt | BinOp::Concatenate => return, }, _ => return, }; self.problems .warning(Warning::RedundantComparison { location, outcome }); } fn infer_assignment(&mut self, assignment: UntypedAssignment) -> TypedAssignment { let Assignment { pattern, value, kind, annotation, location, compiled_case: _, } = assignment; let value = self.expr_in_new_scope(|this| this.infer(value)); let type_ = value.type_(); let kind = self.infer_assignment_kind(kind.clone()); // Ensure the pattern matches the type of the value let mut pattern_typer = pattern::PatternTyper::new( self.environment, &self.current_function_definition, &self.hydrator, self.problems, PatternPosition::LetAssignment, ); let pattern = pattern_typer.infer_single_pattern(pattern, &value); let minimum_required_version = pattern_typer.minimum_required_version; if minimum_required_version > self.minimum_required_version { self.minimum_required_version = minimum_required_version; } let pattern_typechecked_successfully = !pattern_typer.error_encountered; // Check that any type annotation is accurate. if let Some(annotation) = &annotation { match self .type_from_ast(annotation) .map(|type_| self.instantiate(type_, &mut hashmap![])) { Ok(annotated_type) => { if let Err(error) = unify(annotated_type, type_.clone()) .map_err(|e| convert_unify_error(e, value.type_defining_location())) { self.problems.error(error); } } Err(error) => { self.problems.error(error); } } } // The exhaustiveness checker expects patterns to be valid and to type check; // if they are invalid, it will crash. Therefore, if any errors were found // when type checking the pattern, we don't perform the exhaustiveness check. if !pattern_typechecked_successfully { return Assignment { location, annotation, kind, compiled_case: CompiledCase::failure(), pattern, value, }; } let (output, not_exhaustive_error) = self.check_let_exhaustiveness(location, value.type_(), &pattern); match (&kind, not_exhaustive_error) { // The pattern is exhaustive in a let assignment, there's no problem here. (AssignmentKind::Let | AssignmentKind::Generated, Ok(_)) => (), // If the pattern is not exhaustive and we're not asserting we want to // report the error! (AssignmentKind::Let | AssignmentKind::Generated, Err(e)) => { self.problems.error(e); } // If we're asserting but the pattern already covers all cases then the // `assert` is redundant and can be safely removed. ( AssignmentKind::Assert { location, assert_keyword_start, .. }, Ok(_), ) => self.problems.warning(Warning::RedundantAssertAssignment { location: SrcSpan::new(*assert_keyword_start, location.end), }), // Otherwise, if the pattern is never reachable (through variant inference), // we can warn the user about this. (AssignmentKind::Assert { .. }, Err(_)) => { // There is only one pattern to match, so it is index 0 match output.is_reachable(0, 0) { Reachability::Unreachable(UnreachablePatternReason::ImpossibleVariant) => self .problems .warning(Warning::AssertAssignmentOnImpossiblePattern { location: pattern.location(), reason: AssertImpossiblePattern::InferredVariant, }), Reachability::Unreachable(UnreachablePatternReason::ImpossibleSegments( segments, )) => self .problems .warning(Warning::AssertAssignmentOnImpossiblePattern { location: pattern.location(), reason: AssertImpossiblePattern::ImpossibleSegments { segments }, }), // A duplicate pattern warning should not happen, since there is only one pattern. Reachability::Reachable | Reachability::Unreachable(UnreachablePatternReason::DuplicatePattern) => {} } } }; Assignment { location, annotation, kind, compiled_case: output.compiled_case, pattern, value, } } fn infer_assignment_kind( &mut self, kind: AssignmentKind, ) -> AssignmentKind { match kind { AssignmentKind::Let => AssignmentKind::Let, AssignmentKind::Generated => AssignmentKind::Generated, AssignmentKind::Assert { location, message, assert_keyword_start, } => { self.purity = Purity::Impure; let message = match message { Some(message) => { self.track_feature_usage( FeatureKind::LetAssertWithMessage, message.location(), ); Some(self.infer_and_unify(message, string())) } None => None, }; AssignmentKind::Assert { location, message, assert_keyword_start, } } } } fn infer_assert(&mut self, assert: UntypedAssert) -> TypedAssert { let Assert { value, location, message, } = assert; let value_location = value.location(); let value = self.infer(value); self.purity = Purity::Impure; if value.is_known_bool() { self.problems.warning(Warning::AssertLiteralBool { location: value_location, }); } match unify(bool(), value.type_()) { Ok(()) => {} Err(error) => self .problems .error(convert_unify_error(error, value_location)), } let message = message.map(|message| self.infer_and_unify(message, string())); self.track_feature_usage(FeatureKind::BoolAssert, location); Assert { location, value, message, } } fn infer_case( &mut self, subjects: Vec, clauses: Option>, location: SrcSpan, ) -> TypedExpr { let subjects_count = subjects.len(); let mut typed_subjects = Vec::with_capacity(subjects_count); let mut subject_types = Vec::with_capacity(subjects_count); let return_type = self.new_unbound_var(); self.previous_panics = false; let mut any_subject_panics = false; for subject in subjects { let subject = self.expr_in_new_scope(|this| this.infer(subject)); any_subject_panics = any_subject_panics || self.previous_panics; subject_types.push(subject.type_()); typed_subjects.push(subject); } let clauses = match clauses { Some(clauses) => clauses, None => { self.problems.error(Error::MissingCaseBody { location }); return TypedExpr::Case { location, type_: return_type, compiled_case: CompiledCase::failure(), subjects: typed_subjects, clauses: Vec::new(), }; } }; let mut typed_clauses = Vec::with_capacity(clauses.len()); let mut has_a_guard = false; let mut all_patterns_are_discards = true; // NOTE: if there are 0 clauses then there are 0 panics let mut all_clauses_panic = !clauses.is_empty(); let mut patterns_typechecked_successfully = true; for clause in clauses { has_a_guard = has_a_guard || clause.guard.is_some(); all_patterns_are_discards = all_patterns_are_discards && clause.pattern.iter().all(|p| p.is_discard()); self.previous_panics = false; let (typed_clause, error_typing_patterns) = self.infer_clause(clause, &typed_subjects); if error_typing_patterns { patterns_typechecked_successfully = false } all_clauses_panic = all_clauses_panic && self.previous_panics; if let Err(error) = unify(return_type.clone(), typed_clause.then.type_()) { self.problems.error( error .case_clause_mismatch(typed_clause.location) .into_error(typed_clause.then.type_defining_location()), ); } typed_clauses.push(typed_clause); } self.previous_panics = all_clauses_panic || any_subject_panics; // The exhaustiveness checker expects patterns to be valid and to type check; // if they are invalid, it will crash. Therefore, if any errors were found // when type checking the pattern, we don't perform the exhaustiveness check. let compiled_case = if patterns_typechecked_successfully { self.check_case_exhaustiveness(location, &subject_types, &typed_clauses) } else { CompiledCase::failure() }; // We track if the case expression is used like an if: that is all its // patterns are discarded and there's at least a guard. For example: // ```gleam // case True { // _ if condition -> todo // _ if other_condition -> todo // _ -> todo // } // ``` // This is the only case were it actually makes sense to match on a // constant value so when checking, we won't emit warnings for matching // on a literal value in this case. let case_used_like_if = all_patterns_are_discards && has_a_guard; typed_subjects .iter() .filter_map(|subject| check_subject_for_redundant_match(subject, case_used_like_if)) .for_each(|warning| self.problems.warning(warning)); TypedExpr::Case { location, compiled_case, type_: return_type, subjects: typed_subjects, clauses: typed_clauses, } } /// Returns a tuple with the typed clause and a bool that is true if an error /// was encountered while typing the clause patterns. /// fn infer_clause( &mut self, clause: UntypedClause, subjects: &[TypedExpr], ) -> (TypedClause, bool) { let Clause { pattern, alternative_patterns, guard, then, location, } = clause; self.value_in_new_scope(|this| { let (typed_pattern, typed_alternatives, error_encountered) = this.infer_clause_pattern(pattern, alternative_patterns, subjects, &location); let guard = match this.infer_optional_clause_guard(guard) { Ok(guard) => guard, // If an error occurs inferring guard then assume no guard Err(error) => { this.problems.error(error); None } }; let then = this.infer(then); let clause = Clause { location, pattern: typed_pattern, alternative_patterns: typed_alternatives, guard, then, }; (clause, error_encountered) }) } fn infer_clause_pattern( &mut self, pattern: UntypedMultiPattern, alternatives: Vec, subjects: &[TypedExpr], location: &SrcSpan, ) -> (TypedMultiPattern, Vec, bool) { let mut pattern_typer = pattern::PatternTyper::new( self.environment, &self.current_function_definition, &self.hydrator, self.problems, PatternPosition::CaseClause, ); let typed_pattern = pattern_typer.infer_multi_pattern(pattern, subjects); // Each case clause has one or more patterns that may match the // subject in order for the clause to be selected, so we must type // check every pattern. let mut typed_alternatives = Vec::with_capacity(alternatives.len()); for m in alternatives { typed_alternatives .push(pattern_typer.infer_alternative_multi_pattern(m, subjects, location)); } let minimum_required_version = pattern_typer.minimum_required_version; if minimum_required_version > self.minimum_required_version { self.minimum_required_version = minimum_required_version; } ( typed_pattern, typed_alternatives, pattern_typer.error_encountered, ) } fn infer_optional_clause_guard( &mut self, guard: Option, ) -> Result, Error> { match guard { // If there is no guard we do nothing None => Ok(None), // If there is a guard we assert that it is of type Bool Some(guard) => { let guard = self.infer_clause_guard(guard)?; unify(bool(), guard.type_()) .map_err(|e| convert_unify_error(e, guard.location()))?; Ok(Some(guard)) } } } fn infer_clause_guard(&mut self, guard: UntypedClauseGuard) -> Result { match guard { ClauseGuard::Var { location, name, .. } => { let constructor = self.infer_value_constructor(&None, &name, &location)?; // We cannot support all values in guard expressions as the BEAM does not let (definition_location, origin) = match &constructor.variant { ValueConstructorVariant::LocalVariable { location, origin, .. } => (*location, origin.clone()), ValueConstructorVariant::ModuleFn { .. } | ValueConstructorVariant::Record { .. } => { return Err(Error::NonLocalClauseGuardVariable { location, name }); } ValueConstructorVariant::ModuleConstant { literal, .. } => { return Ok(ClauseGuard::Constant(literal.clone())); } }; Ok(ClauseGuard::Var { location, name, origin, type_: constructor.type_, definition_location, }) } ClauseGuard::TupleIndex { location, tuple, index, .. } => { let tuple = self.infer_clause_guard(*tuple)?; match tuple.type_().as_ref() { Type::Tuple { elements } => { let type_ = elements .get(index as usize) .ok_or(Error::OutOfBoundsTupleIndex { location, index, size: elements.len(), })? .clone(); Ok(ClauseGuard::TupleIndex { location, index, type_, tuple: Box::new(tuple), }) } type_ if type_.is_unbound() => Err(Error::NotATupleUnbound { location: tuple.location(), }), Type::Named { .. } | Type::Fn { .. } | Type::Var { .. } => { Err(Error::NotATuple { location: tuple.location(), given: tuple.type_(), }) } } } ClauseGuard::FieldAccess { label_location, label, container, index: _, type_: (), } => match self.infer_clause_guard(*container.clone()) { Ok(container) => self.infer_guard_record_access(container, label, label_location), Err(err) => { if let ClauseGuard::Var { name, location, .. } = *container { self.infer_guard_module_access(name, label, location, label_location, err) } else { Err(Error::RecordAccessUnknownType { location: label_location, }) } } }, ClauseGuard::ModuleSelect { location, .. } => { Err(Error::RecordAccessUnknownType { location }) } ClauseGuard::Not { location, expression, } => { let expression = self.infer_clause_guard(*expression)?; unify(bool(), expression.type_()) .map_err(|e| convert_unify_error(e, expression.location()))?; Ok(ClauseGuard::Not { location, expression: Box::new(expression), }) } ClauseGuard::Constant(constant) => { Ok(ClauseGuard::Constant(self.infer_const(&None, constant))) } ClauseGuard::Block { value, location } => { let value = self.infer_clause_guard(*value)?; Ok(ClauseGuard::Block { location, value: Box::new(value), }) } ClauseGuard::BinaryOperator { location, operator, left, right, } => { let left = self.infer_clause_guard(*left)?; let right = self.infer_clause_guard(*right)?; match operator { BinOp::And | BinOp::Or => { unify(bool(), left.type_()) .map_err(|e| convert_unify_error(e, left.location()))?; unify(bool(), right.type_()) .map_err(|e| convert_unify_error(e, right.location()))?; } BinOp::Eq | BinOp::NotEq => { unify(left.type_(), right.type_()) .map_err(|e| convert_unify_error(e, location))?; } BinOp::GtInt | BinOp::GtEqInt | BinOp::LtInt | BinOp::LtEqInt | BinOp::AddInt | BinOp::SubInt | BinOp::DivInt | BinOp::MultInt | BinOp::RemainderInt => { self.track_feature_usage(FeatureKind::ArithmeticInGuards, location); if left.type_().is_float() && right.type_().is_float() { return Err(Error::IntOperatorOnFloats { operator, location }); } unify(int(), left.type_()) .map_err(|e| convert_unify_error(e, left.location()))?; unify(int(), right.type_()) .map_err(|e| convert_unify_error(e, right.location()))?; } BinOp::GtFloat | BinOp::GtEqFloat | BinOp::LtFloat | BinOp::LtEqFloat | BinOp::AddFloat | BinOp::SubFloat | BinOp::DivFloat | BinOp::MultFloat => { self.track_feature_usage(FeatureKind::ArithmeticInGuards, location); if left.type_().is_int() && right.type_().is_int() { return Err(Error::FloatOperatorOnInts { operator, location }); } unify(float(), left.type_()) .map_err(|e| convert_unify_error(e, left.location()))?; unify(float(), right.type_()) .map_err(|e| convert_unify_error(e, right.location()))?; } BinOp::Concatenate => { self.track_feature_usage(FeatureKind::ConcatenateInGuards, location); unify(string(), left.type_()) .map_err(|e| convert_unify_error(e, left.location()))?; unify(string(), right.type_()) .map_err(|e| convert_unify_error(e, right.location()))?; } } Ok(ClauseGuard::BinaryOperator { location, operator, left: Box::new(left), right: Box::new(right), }) } } } fn infer_guard_record_access( &mut self, container: TypedClauseGuard, label: EcoString, location: SrcSpan, ) -> Result { let container = Box::new(container); let container_type = container.type_(); let RecordAccessor { index, label, type_, documentation: _, } = self.infer_known_record_access( container_type, container.location(), FieldAccessUsage::Other, location, label, )?; Ok(ClauseGuard::FieldAccess { container, label, index: Some(index), label_location: location, type_, }) } fn infer_guard_module_access( &mut self, name: EcoString, label: EcoString, module_location: SrcSpan, label_location: SrcSpan, record_access_error: Error, ) -> Result { let module_access = self .infer_module_access(&name, label, &module_location, label_location) .and_then(|ma| { if let TypedExpr::ModuleSelect { location, field_start: _, type_, label, module_name, module_alias, constructor, } = ma { match constructor { ModuleValueConstructor::Constant { literal, location: definition_location, .. } => { self.environment.references.register_value_reference( module_name.clone(), label.clone(), &label, label_location, ReferenceKind::Qualified, ); Ok(ClauseGuard::ModuleSelect { location, field_start: label_location.start, definition_location, type_, label, module_name, module_alias, literal, }) } ModuleValueConstructor::Record { .. } | ModuleValueConstructor::Fn { .. } => { Err(Error::RecordAccessUnknownType { location }) } } } else { Err(Error::RecordAccessUnknownType { location: module_location, }) } }); // If the name is in the environment, use the original error from // inferring the record access, so that we can suggest possible // misspellings of field names if self.environment.scope.contains_key(&name) { module_access.map_err(|_| record_access_error) } else { module_access } } fn infer_module_access( &mut self, // This is the name of the module coming before the `.`: for example // in `result.try` it's `result`. module_alias: &EcoString, label: EcoString, module_location: &SrcSpan, select_location: SrcSpan, ) -> Result { let location = module_location.merge(&select_location); let (module_name, constructor) = { let (_, module) = self .environment .imported_modules .get(module_alias) .ok_or_else(|| Error::UnknownModule { name: module_alias.clone(), location: *module_location, suggestions: self .environment .suggest_modules(module_alias, Imported::Value(label.clone())), })?; let constructor = module .get_public_value(&label) .ok_or_else(|| Error::UnknownModuleValue { name: label.clone(), location: select_location, module_name: module.name.clone(), value_constructors: module.public_value_names(), type_with_same_name: module.get_public_type(&label).is_some(), context: ModuleValueUsageContext::ModuleAccess, })?; // Emit a warning if the value being used is deprecated. if let Deprecation::Deprecated { message } = &constructor.deprecation { self.problems.warning(Warning::DeprecatedItem { location: select_location, message: message.clone(), layer: Layer::Value, }) } self.environment .references .register_module_reference(module_alias.clone()); (module.name.clone(), constructor.clone()) }; let type_ = self.instantiate(constructor.type_, &mut hashmap![]); self.narrow_implementations(select_location, &constructor.variant)?; let constructor = match &constructor.variant { variant @ ValueConstructorVariant::ModuleFn { name, module, .. } => { variant.to_module_value_constructor(Arc::clone(&type_), module, name) } variant @ (ValueConstructorVariant::LocalVariable { .. } | ValueConstructorVariant::ModuleConstant { .. } | ValueConstructorVariant::Record { .. }) => { variant.to_module_value_constructor(Arc::clone(&type_), &module_name, &label) } }; Ok(TypedExpr::ModuleSelect { location, field_start: select_location.start, label, type_: Arc::clone(&type_), module_name, module_alias: module_alias.clone(), constructor, }) } fn infer_known_record_expression_access( &mut self, record: TypedExpr, label: EcoString, location: SrcSpan, label_location: SrcSpan, field_start: u32, usage: FieldAccessUsage, ) -> Result { let record = Box::new(record); let record_type = record.type_(); let RecordAccessor { index, label, type_, documentation, } = self.infer_known_record_access( record_type, record.location(), usage, label_location, label, )?; Ok(TypedExpr::RecordAccess { record, label, field_start, index, location, type_, documentation, }) } fn infer_known_record_access( &mut self, record_type: Arc, record_location: SrcSpan, usage: FieldAccessUsage, location: SrcSpan, label: EcoString, ) -> Result { if record_type.is_unbound() { return Err(Error::RecordAccessUnknownType { location: record_location, }); } let unknown_field = |fields| { self.unknown_field_error(fields, record_type.clone(), location, label.clone(), usage) }; let (accessors_map, variant_accessors) = match collapse_links(record_type.clone()).as_ref() { // A type in the current module which may have fields Type::Named { module, name, inferred_variant, .. } if module == &self.environment.current_module => { self.environment.accessors.get(name).map(|accessors_map| { ( accessors_map, accessors_map.accessors_for_variant(*inferred_variant), ) }) } // A type in another module which may have fields Type::Named { module, name, inferred_variant, .. } => self .environment .importable_modules .get(module) .and_then(|module| module.accessors.get(name)) .filter(|a| { a.publicity.is_importable() || module == &self.environment.current_module }) .map(|accessors_map| { ( accessors_map, accessors_map.accessors_for_variant(*inferred_variant), ) }), // Non-named types do not have fields Type::Fn { .. } | Type::Var { .. } | Type::Tuple { .. } => { return Err(unknown_field(vec![])); } } .ok_or_else(|| unknown_field(vec![]))?; let RecordAccessor { index, label, type_, documentation, } = variant_accessors .get(&label) .ok_or_else(|| unknown_field(variant_accessors.keys().cloned().collect()))? .clone(); let accessor_record_type = accessors_map.type_.clone(); // If the accessor isn't shared across variants, this requires variant inference if !accessors_map.shared_accessors.contains_key(&label) { match usage { FieldAccessUsage::MethodCall | FieldAccessUsage::Other => { self.track_feature_usage(FeatureKind::RecordAccessVariantInference, location); } // This feature for record updates should be tracked in // `infer_record_update`, so we don't track it here as it would lead // to a duplicate warning with a confusing message. FieldAccessUsage::RecordUpdate => {} } } let mut type_vars = hashmap![]; let accessor_record_type = self.instantiate(accessor_record_type, &mut type_vars); let type_ = self.instantiate(type_, &mut type_vars); unify(accessor_record_type, record_type) .map_err(|e| convert_unify_error(e, record_location))?; Ok(RecordAccessor { index, label, type_, documentation, }) } fn infer_record_update( &mut self, constructor: UntypedExpr, record: RecordBeingUpdated, arguments: Vec, location: SrcSpan, ) -> Result { // infer the constructor being used let typed_constructor = self.infer_or_error(constructor.clone())?; let (module, name) = match &typed_constructor { TypedExpr::ModuleSelect { module_alias, label, location, .. } => (Some((module_alias, location)), label), TypedExpr::Var { name, .. } => (None, name), TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => { return Err(Error::RecordUpdateInvalidConstructor { location: typed_constructor.location(), }); } }; let value_constructor = self .environment .get_value_constructor(module.map(|(module, _)| module), name) .map_err(|error| { convert_get_value_constructor_error( error, location, module.map(|(_, location)| *location), ) })? .clone(); // infer the record being updated let record = self.infer_or_error(*record.base)?; let record_location = record.location(); let record_type = record.type_(); let (record_var, record_assignment) = if record.is_var() { (record, None) } else { // We create an Assignment for the old record expression and will // use a Var expression to refer back to it while constructing the // arguments. let record_assignment = Assignment { location: record_location, pattern: Pattern::Variable { location: record_location, name: RECORD_UPDATE_VARIABLE.into(), type_: record_type.clone(), origin: VariableOrigin::generated(), }, annotation: None, compiled_case: CompiledCase::failure(), kind: AssignmentKind::Generated, value: record, }; let record_var = TypedExpr::Var { location: record_location, constructor: ValueConstructor { publicity: Publicity::Private, deprecation: Deprecation::NotDeprecated, type_: record_type, variant: ValueConstructorVariant::LocalVariable { location: record_location, origin: VariableOrigin::generated(), }, }, name: RECORD_UPDATE_VARIABLE.into(), }; (record_var, Some(Box::new(record_assignment))) }; // infer the fields of the variant we want to update let variant = self.infer_record_update_variant(&typed_constructor, &value_constructor, &record_var)?; let arguments = self.infer_record_update_arguments(&variant, &record_var, arguments, location)?; Ok(TypedExpr::RecordUpdate { location, type_: variant.return_type, record_assignment, constructor: Box::new(typed_constructor), arguments, }) } fn infer_record_update_arguments( &mut self, variant: &RecordUpdateVariant<'_>, record: &TypedExpr, arguments: Vec, location: SrcSpan, ) -> Result, Error> { let record_location = record.location(); let record_type = record.type_(); let return_type = variant.return_type.clone(); // We clone the fields to remove all explicitly mentioned fields in the // record update. let mut fields = variant.field_map.fields.clone(); // We go over all arguments used in the record update and infer those. let mut explicit_arguments = vec![]; for argument in arguments.iter() { let UntypedRecordUpdateArg { label, value, location, } = argument; let value = self.infer(value.clone()); if argument.uses_label_shorthand() { self.track_feature_usage(FeatureKind::LabelShorthandSyntax, *location); } if let Some(index) = fields.remove(label) { // If the variant has the given field, we need to check it's // inferred type is correct. // If an error happens we record it, but don't early return. // We still want to accumulate errors for all field to come! if let Err(error) = unify(variant.arg_type(index), value.type_()) { self.problems.error(convert_unify_error(error, *location)); }; explicit_arguments.push(( index, CallArg { label: Some(label.clone()), location: *location, value, implicit: None, }, )) } else if variant.has_field(label) { // The variant has this field but it was already removed in a // previous iteration. This means we've found a duplicate field! self.problems.error(Error::DuplicateArgument { location: *location, label: label.clone(), }) } else { // Otherwise, it's just an unknown field! self.problems.error(self.unknown_field_error( variant.field_names(), record_type.clone(), *location, label.clone(), FieldAccessUsage::RecordUpdate, )) }; } // Generate the remaining copied arguments, making sure they unify with // our return type. let convert_incompatible_fields_error = |error: UnifyError, field: RecordField| match error { UnifyError::CouldNotUnify { expected, given, .. } => Error::UnsafeRecordUpdate { location: record_location, reason: UnsafeRecordUpdateReason::IncompatibleFieldTypes { constructed_variant: return_type.clone(), record_variant: record_type.clone(), expected_field_type: expected, record_field_type: given, field, }, }, UnifyError::ExtraVarInAlternativePattern { .. } | UnifyError::MissingVarInAlternativePattern { .. } | UnifyError::DuplicateVarInPattern { .. } | UnifyError::RecursiveType => convert_unify_error(error, record_location), }; let indices_to_labels = variant.field_map.indices_to_labels(); let mut implicit_arguments = Vec::new(); for index in 0..variant.field_map.arity { if let Some(&label) = indices_to_labels.get(&index) { if !fields.contains_key(label) { continue; } let record_access = self.infer_known_record_expression_access( record.clone(), label.clone(), record_location, record_location, record_location.start, FieldAccessUsage::RecordUpdate, )?; unify(variant.arg_type(index), record_access.type_()).map_err(|e| { convert_incompatible_fields_error(e, RecordField::Labelled(label.clone())) })?; implicit_arguments.push(( index, CallArg { location: record_location, label: Some(label.clone()), value: record_access, implicit: Some(ImplicitCallArgOrigin::RecordUpdate), }, )) } else { let (accessor_type, positional_fields) = match collapse_links(record.type_()).as_ref() { // A type in the current module Type::Named { module, name, inferred_variant, .. } if module == &self.environment.current_module => { self.environment .accessors .get(name) .and_then(|accessors_map| { Some(( accessors_map.type_.clone(), // For record updates we must know the variant of the record, so if the // variant has not been inferred, that means there must only be one. accessors_map .positional_accessors(inferred_variant.unwrap_or(0))?, )) }) } // A type in another module Type::Named { module, name, inferred_variant, .. } => self .environment .importable_modules .get(module) .and_then(|module| module.accessors.get(name)) .filter(|a| { a.publicity.is_importable() || module == &self.environment.current_module }) .and_then(|accessors_map| { Some(( accessors_map.type_.clone(), accessors_map .positional_accessors(inferred_variant.unwrap_or(0))?, )) }), Type::Fn { .. } | Type::Var { .. } | Type::Tuple { .. } => { panic!("Type has already checked to be valid") } } .expect("Variant has already checked to be valid"); // Positional fields must always be defined before any labelled fields. // As such, we expect that field indices 0..(positional_fields.length) // all correspond to positional fields. If this is not the case, then the // user currently has a mistake in the definition of their custom type. // // However, we do not want to show an error here, because it would be // confusing for the programmer. They can already see the error at the type // definition site. let type_ = if let Some(type_) = positional_fields.get(index as usize) { type_.clone() } else { continue; }; let mut type_vars = im::HashMap::new(); let accessor_type = self.instantiate(accessor_type, &mut type_vars); let type_ = self.instantiate(type_, &mut type_vars); unify(accessor_type, record_type.clone()).map_err(|e| { convert_incompatible_fields_error(e, RecordField::Unlabelled(index)) })?; let record_access = TypedExpr::PositionalAccess { record: Box::new(record.clone()), index: index as u64, location: record_location, type_: type_.clone(), }; unify(variant.arg_type(index), type_.clone()).map_err(|e| { convert_incompatible_fields_error(e, RecordField::Unlabelled(index)) })?; implicit_arguments.push(( index, CallArg { location: record_location, label: None, value: record_access, implicit: Some(ImplicitCallArgOrigin::RecordUpdate), }, )) } } if arguments.is_empty() { self.problems .warning(Warning::NoFieldsRecordUpdate { location }); } if implicit_arguments.is_empty() { self.problems .warning(Warning::AllFieldsRecordUpdate { location }); } let arguments = explicit_arguments .into_iter() .chain(implicit_arguments) .sorted_by_key(|(index, _)| *index) .map(|(_, value)| value) .collect(); Ok(arguments) } fn infer_record_update_variant<'c>( &mut self, constructor: &TypedExpr, value_constructor: &'c ValueConstructor, record: &TypedExpr, ) -> Result, Error> { let record_type = record.type_(); // The record constructor needs to be a function. let (arguments_types, return_type) = match constructor.type_().as_ref() { Type::Fn { arguments, return_ } => (arguments.clone(), return_.clone()), Type::Named { .. } | Type::Var { .. } | Type::Tuple { .. } => { return Err(Error::RecordUpdateInvalidConstructor { location: constructor.location(), }); } }; // It must be a record with a field map for us to be able to update it let (field_map, variants_count, variant_index, name) = match &value_constructor.variant { ValueConstructorVariant::Record { field_map: Some(field_map), variants_count, variant_index, name, .. } => (field_map, *variants_count, *variant_index, name.clone()), ValueConstructorVariant::Record { field_map: None, .. } => { return Err(Error::RecordUpdateInvalidConstructor { location: constructor.location(), }); } ValueConstructorVariant::LocalVariable { .. } | ValueConstructorVariant::ModuleConstant { .. } | ValueConstructorVariant::ModuleFn { .. } => { return Err(Error::RecordUpdateInvalidConstructor { location: constructor.location(), }); } }; // Check that the record type unifies with the return type of the constructor, and is // not some unrelated other type. This should not affect our returned type, so we // instantiate a new copy of the generic return type for our value constructor. let return_type_copy = match value_constructor.type_.as_ref() { Type::Fn { return_, .. } => self.instantiate(return_.clone(), &mut hashmap![]), Type::Named { .. } | Type::Var { .. } | Type::Tuple { .. } => { return Err(Error::RecordUpdateInvalidConstructor { location: constructor.location(), }); } }; unify(return_type_copy, record_type.clone()) .map_err(|e| convert_unify_error(e, record.location()))?; let record_index = record_type.custom_type_inferred_variant(); // Updating a record with only one variant is always safe if variants_count == 1 { return Ok(RecordUpdateVariant { arguments: arguments_types, return_type, field_map, }); } // if we know the record that is being spread, and it does match the one being constructed, // we can safely perform this record update due to variant inference. if record_index.is_some_and(|index| index == variant_index) { self.track_feature_usage(FeatureKind::RecordUpdateVariantInference, record.location()); return Ok(RecordUpdateVariant { arguments: arguments_types, return_type, field_map, }); } // We definitely know that we can't do this record update safely. // // If we know the variant of the value being spread, and it doesn't match the // one being constructed, we can tell the user that it's always wrong if record_index.is_some() { let Type::Named { module: record_module, name: record_name, inferred_variant: Some(record_index), .. } = record_type.deref() else { panic!("Spread type must be named and with an index") }; return Err(Error::UnsafeRecordUpdate { location: record.location(), reason: UnsafeRecordUpdateReason::WrongVariant { constructed_variant: name, spread_variant: self .environment .type_variant_name(record_module, record_name, *record_index) .expect("Spread type must exist and variant must be valid") .clone(), }, }); } // If we don't have information about the variant being spread, we tell the user // that it's not safe to update it as it could be any variant Err(Error::UnsafeRecordUpdate { location: record.location(), reason: UnsafeRecordUpdateReason::UnknownVariant { constructed_variant: name, }, }) } fn unknown_field_error( &self, fields: Vec, record_type: Arc, location: SrcSpan, label: EcoString, usage: FieldAccessUsage, ) -> Error { let error = |unknown_field| Error::UnknownRecordField { usage, type_: record_type.clone(), location, label: label.clone(), fields, unknown_field, }; let Type::Named { module, name, inferred_variant, .. } = record_type.deref() else { return error(UnknownField::NoFields); }; let all_fields = self.environment.get_type_variants_fields(module, name); if all_fields.is_empty() { return error(UnknownField::NoFields); } if !all_fields.iter().contains(&&label) { return error(UnknownField::TrulyUnknown); } // If we know the variant, the field must exist on a different // variant from the one we have inferred. if inferred_variant.is_some() { error(UnknownField::AppearsInAnImpossibleVariant) } else { error(UnknownField::AppearsInAVariant) } } fn infer_value_constructor( &mut self, module: &Option<(EcoString, SrcSpan)>, name: &EcoString, location: &SrcSpan, ) -> Result { self.do_infer_value_constructor(module, name, location, ReferenceRegistration::Register) } fn do_infer_value_constructor( &mut self, module: &Option<(EcoString, SrcSpan)>, name: &EcoString, location: &SrcSpan, register_reference: ReferenceRegistration, ) -> Result { let constructor = match module { // Look in the current scope for a binding with this name None => self .environment .get_variable(name) .cloned() .ok_or_else(|| self.report_name_error(name, location))?, // Look in an imported module for a binding with this name Some((module_name, module_location)) => { let (_, module) = &self .environment .imported_modules .get(module_name) .ok_or_else(|| Error::UnknownModule { location: *module_location, name: module_name.clone(), suggestions: self .environment .suggest_modules(module_name, Imported::Value(name.clone())), })?; self.environment .references .register_module_reference(module_name.clone()); module .values .get(name) .cloned() .ok_or_else(|| Error::UnknownModuleValue { location: *location, module_name: module_name.clone(), name: name.clone(), value_constructors: module.public_value_names(), type_with_same_name: module.get_public_type(name).is_some(), context: ModuleValueUsageContext::ModuleAccess, })? } }; let ValueConstructor { publicity, variant, type_, deprecation, } = constructor; self.check_recursive_argument_usage(name, &variant, ®ister_reference); // Emit a warning if the value being used is deprecated. if let Deprecation::Deprecated { message } = &deprecation { self.problems.warning(Warning::DeprecatedItem { location: *location, message: message.clone(), layer: Layer::Value, }) } self.narrow_implementations(*location, &variant)?; match register_reference { ReferenceRegistration::DoNotRegister => (), ReferenceRegistration::Register | ReferenceRegistration::VariableArgument { .. } => { self.register_value_constructor_reference( name, &variant, *location, if module.is_some() { ReferenceKind::Qualified } else { ReferenceKind::Unqualified }, ); } } // Instantiate generic variables into unbound variables for this usage let type_ = self.instantiate(type_, &mut hashmap![]); Ok(ValueConstructor { publicity, deprecation, variant, type_, }) } fn check_recursive_argument_usage( &mut self, name: &EcoString, variant: &ValueConstructorVariant, register_reference: &ReferenceRegistration, ) { // If we are registering references for a call argument let ReferenceRegistration::VariableArgument { called_function, argument_index, } = register_reference else { return; }; // If the passed argument is a function's parameter. let ValueConstructorVariant::LocalVariable { origin, .. } = variant else { return; }; let VariableDeclaration::FunctionParameter { function_name: declaration_function, index: declaration_index, } = &origin.declaration else { return; }; // If the called function is the same where the argument is defined, // and the argument is passed unchanged. if declaration_function.as_ref() == Some(called_function) && declaration_index == argument_index { self.environment.increment_recursive_usage(name); } } fn register_value_constructor_reference( &mut self, referenced_name: &EcoString, variant: &ValueConstructorVariant, location: SrcSpan, kind: ReferenceKind, ) { match variant { ValueConstructorVariant::ModuleFn { module, name: value_name, .. } | ValueConstructorVariant::Record { module, name: value_name, .. } | ValueConstructorVariant::ModuleConstant { module, name: value_name, .. } if value_name != referenced_name => { self.environment.references.register_value_reference( module.clone(), value_name.clone(), referenced_name, location, ReferenceKind::Alias, ) } ValueConstructorVariant::ModuleFn { name, module, .. } | ValueConstructorVariant::Record { name, module, .. } | ValueConstructorVariant::ModuleConstant { name, module, .. } => { self.environment.references.register_value_reference( module.clone(), name.clone(), referenced_name, location, kind, ) } ValueConstructorVariant::LocalVariable { .. } => { self.environment.increment_usage(referenced_name) } } } fn report_name_error(&mut self, name: &EcoString, location: &SrcSpan) -> Error { // First try to see if this is a module alias: // `import gleam/io` // `io.debug(io)` // Show nice error message for this case. let module = self.environment.imported_modules.get(name); match module { Some(_) => Error::ModuleAliasUsedAsName { location: *location, name: name.clone(), }, None => Error::UnknownVariable { location: *location, name: name.clone(), variables: self.environment.local_value_names(), discarded_location: self .environment .discarded_names .get(&eco_format!("_{name}")) .cloned(), type_with_name_in_scope: self .environment .module_types .keys() .any(|typ| typ == name), }, } } // helper for infer_const to get the value of a constant ignoring annotations fn infer_const_value(&mut self, value: UntypedConstant) -> TypedConstant { match value { Constant::Int { location, value, int_value, } => { if self.environment.target == Target::JavaScript { check_javascript_int_safety(&int_value, location, self.problems); } Constant::Int { location, value, int_value, } } Constant::Float { location, value, float_value, } => { check_float_safety(float_value, location, self.problems); Constant::Float { location, value, float_value, } } Constant::String { location, value, .. } => Constant::String { location, value }, Constant::Tuple { elements, location, .. } => self.infer_const_tuple(elements, location), Constant::List { elements, location, tail, .. } => self.infer_const_list(elements, location, tail), Constant::BitArray { location, segments } => { match self.infer_constant_bit_array(segments, location) { Ok(inferred) => inferred, Err(error) => { self.problems.error(error); Constant::Invalid { location, type_: bit_array(), extra_information: None, } } } } Constant::RecordUpdate { constructor_location, module, location, name, record, arguments, .. } => { self.track_feature_usage(FeatureKind::ConstantRecordUpdate, location); let constructor = match self.infer_value_constructor(&module, &name, &location) { Ok(constructor) => constructor, Err(error) => { self.problems.error(error); return self.new_invalid_constant(location); } }; let (tag, field_map) = match &constructor.variant { ValueConstructorVariant::Record { name, field_map: Some(field_map), .. } => (name.clone(), field_map.clone()), ValueConstructorVariant::Record { field_map: None, .. } => { self.problems.error(Error::RecordUpdateInvalidConstructor { location: constructor_location, }); return self.new_invalid_constant(location); } ValueConstructorVariant::ModuleFn { .. } | ValueConstructorVariant::LocalVariable { .. } => { self.problems .error(Error::NonLocalClauseGuardVariable { location, name }); return self.new_invalid_constant(location); } ValueConstructorVariant::ModuleConstant { literal, .. } => { return literal.clone(); } }; // Type-check the record being updated let typed_record = self.infer_const(&None, *record.base.clone()); let typed_record_type = typed_record.type_(); // Instantiate the constructor type to enable generic re-specialization. let instantiated_constructor_type = self.instantiate(constructor.type_.clone(), &mut hashmap![]); // Extract field types and return type from the instantiated constructor let (field_types, expected_type) = match instantiated_constructor_type.as_ref() { Type::Fn { arguments, return_ } => (arguments.clone(), return_.clone()), Type::Named { .. } | Type::Var { .. } | Type::Tuple { .. } => { self.problems.error(Error::RecordUpdateInvalidConstructor { location: constructor_location, }); return self.new_invalid_constant(location); } }; // If the record being updated is a reference to a constant variable, resolve // it to get the actual record value let resolved_record = match &typed_record { Constant::Var { constructor: Some(value_constructor), .. } => match &value_constructor.variant { ValueConstructorVariant::ModuleConstant { literal, .. } => literal.clone(), ValueConstructorVariant::LocalVariable { .. } | ValueConstructorVariant::ModuleFn { .. } | ValueConstructorVariant::Record { .. } => typed_record, }, Constant::Int { .. } | Constant::Float { .. } | Constant::String { .. } | Constant::Tuple { .. } | Constant::List { .. } | Constant::Record { .. } | Constant::RecordUpdate { .. } | Constant::BitArray { .. } | Constant::Var { .. } | Constant::StringConcatenation { .. } | Constant::Invalid { .. } => typed_record, }; // Get the field arguments from the record that we'll use as the base. let (base_arguments, base_tag) = if let Constant::Record { arguments, tag, .. } = resolved_record { (arguments, tag) } else { self.problems.error(convert_unify_error( UnifyError::CouldNotUnify { expected: expected_type.clone(), given: typed_record_type, situation: None, }, record.location, )); return self.new_invalid_constant(location); }; // Check that the variant being spread matches the constructor variant // For multi-variant custom types, you can't spread Dog to create Cat if tag != base_tag { self.problems.error(Error::UnsafeRecordUpdate { location: record.location, reason: UnsafeRecordUpdateReason::WrongVariant { constructed_variant: tag, spread_variant: base_tag, }, }); return self.new_invalid_constant(location); } // Emit warning if no fields are being overridden if arguments.is_empty() { self.problems .warning(Warning::NoFieldsRecordUpdate { location }); } let mut implicit_labelled_arguments = field_map.fields.clone(); let mut update_argument_indices = HashSet::new(); let mut final_arguments = base_arguments; for argument in arguments { if argument.uses_label_shorthand() { self.track_feature_usage( FeatureKind::LabelShorthandSyntax, argument.location, ); } let label = &argument.label; let typed_value = self.infer_const(&None, argument.value); let Some(index) = implicit_labelled_arguments.remove(label) else { if field_map.fields.contains_key(label) { self.problems.error(Error::DuplicateArgument { location: argument.location, label: label.clone(), }); } else { self.problems.error(self.unknown_field_error( field_map.fields.keys().cloned().collect(), expected_type.clone(), argument.location, label.clone(), FieldAccessUsage::Other, )); } return self.new_invalid_constant(location); }; // Record update argument value must match the field type if let Some(expected_type) = field_types.get(index as usize) && let Err(error) = unify(expected_type.clone(), typed_value.type_()) { self.problems .error(convert_unify_error(error, typed_value.location())); return self.new_invalid_constant(location); } let _ = update_argument_indices.insert(index as usize); *final_arguments .get_mut(index as usize) .expect("Index out of bounds") = CallArg { label: Some(label.clone()), value: typed_value, location: argument.location, implicit: None, }; } // Emit warning if all fields are being overriden if implicit_labelled_arguments.is_empty() { self.problems .warning(Warning::AllFieldsRecordUpdate { location }); } // Check that fields implicitly overridden (including unlabelled ones) have compatible types. for (index, field_arg) in final_arguments.iter().enumerate() { // Skip fields that were record update arguments, as they've already been type-checked above if update_argument_indices.contains(&index) { continue; } if let Some(expected_field_type) = field_types.get(index) && let Err(unify_error) = unify(expected_field_type.clone(), field_arg.value.type_()) { let field = field_map .fields .iter() .find(|(_, i)| **i == index as u32) .map(|(name, _)| RecordField::Labelled(name.clone())) .unwrap_or_else(|| RecordField::Unlabelled(index as u32)); self.problems.error( if let UnifyError::CouldNotUnify { expected, given, .. } = unify_error { Error::UnsafeRecordUpdate { location: record.location, reason: UnsafeRecordUpdateReason::IncompatibleFieldTypes { constructed_variant: expected_type.clone(), record_variant: typed_record_type.clone(), expected_field_type: expected, record_field_type: given, field, }, } } else { convert_unify_error(unify_error, location) }, ); return self.new_invalid_constant(location); } } Constant::Record { module, location, name, arguments: final_arguments, type_: expected_type, tag, field_map: Inferred::Known(field_map), record_constructor: Some(Box::new(constructor)), } } Constant::Record { module, location, name, arguments, .. } if arguments.is_empty() => { // Type check the record constructor let constructor = match self.infer_value_constructor(&module, &name, &location) { Ok(constructor) => constructor, Err(error) => { self.problems.error(error); return self.new_invalid_constant(location); } }; let (tag, field_map) = match &constructor.variant { ValueConstructorVariant::Record { name, field_map, .. } => ( name.clone(), match field_map { Some(fm) => Inferred::Known(fm.clone()), None => Inferred::Unknown, }, ), ValueConstructorVariant::ModuleFn { .. } | ValueConstructorVariant::LocalVariable { .. } => { self.problems .error(Error::NonLocalClauseGuardVariable { location, name }); return self.new_invalid_constant(location); } // TODO: remove this clone. Could use an rc instead ValueConstructorVariant::ModuleConstant { literal, .. } => { return literal.clone(); } }; Constant::Record { module, location, name, arguments: vec![], type_: constructor.type_.clone(), tag, field_map, record_constructor: Some(Box::new(constructor)), } } Constant::Record { module, location, name, mut arguments, .. } => { let constructor = match self.infer_value_constructor(&module, &name, &location) { Ok(constructor) => constructor, Err(error) => { self.problems.error(error); return self.new_invalid_constant(location); } }; let (tag, field_map, variant_index) = match &constructor.variant { ValueConstructorVariant::Record { name, field_map, variant_index, .. } => ( name.clone(), match field_map { Some(fm) => Inferred::Known(fm.clone()), None => Inferred::Unknown, }, *variant_index, ), ValueConstructorVariant::ModuleFn { .. } | ValueConstructorVariant::LocalVariable { .. } => { self.problems .error(Error::NonLocalClauseGuardVariable { location, name }); return self.new_invalid_constant(location); } // TODO: remove this clone. Could be an rc instead ValueConstructorVariant::ModuleConstant { literal, .. } => { return literal.clone(); } }; // Pretty much all the other infer functions operate on UntypedExpr // or TypedExpr rather than ClauseGuard. To make things easier we // build the TypedExpr equivalent of the constructor and use that // TODO: resvisit this. It is rather awkward at present how we // have to convert to this other data structure. let fun = match &module { Some((module_alias, module_location)) => { let type_ = Arc::clone(&constructor.type_); let module_name = self .environment .imported_modules // TODO: remove .get(module_alias) .expect("Failed to find previously located module import") .1 .name .clone(); let module_value_constructor = ModuleValueConstructor::Record { name: name.clone(), variant_index, field_map: match &field_map { Inferred::Known(fm) => Some(fm.clone()), Inferred::Unknown => None, }, arity: arguments.len() as u16, type_: Arc::clone(&type_), location: constructor.variant.definition_location(), documentation: None, }; TypedExpr::ModuleSelect { location: module_location.merge(&location), field_start: location.start, label: name.clone(), module_alias: module_alias.clone(), module_name, type_, constructor: module_value_constructor, } } None => TypedExpr::Var { constructor: constructor.clone(), location, name: name.clone(), }, }; // This is basically the same code as do_infer_call_with_known_fun() // except the args are typed with infer_clause_guard() here. // This duplication is a bit awkward but it works! // Potentially this could be improved later let result = match self.get_field_map(&fun) { // There's an error retrieving the field map, in that case we // return an invalid constant. Err(error) => { self.problems .error(convert_get_value_constructor_error(error, location, None)); return self.new_invalid_constant(location); } // The fun has a field map so labelled arguments may be present // and need to be reordered. Ok(Some(field_map)) => { field_map.reorder(&mut arguments, location, IncorrectArityContext::Function) } // The fun or constructor has no field map and so we error // if arguments have been labelled. Ok(None) if fun.is_record_constructor_function() => { assert_no_labelled_arguments( &arguments, UnexpectedLabelledArgKind::RecordConstructorArgument, ) } Ok(None) => assert_no_labelled_arguments( &arguments, UnexpectedLabelledArgKind::FunctionParameter, ), }; // If there's an error reordering the fields, or there's labelled // arguments with no field map, then we return an invalid expression. if let Err(error) = result { self.problems.error(error); return self.new_invalid_constant(location); } let (mut arguments_types, return_type) = match match_fun_type(fun.type_(), arguments.len(), self.environment) { Ok((arguments_types, return_type)) => (arguments_types, return_type), Err(error) => { self.problems.error(convert_not_fun_error( error, fun.location(), location, CallKind::Function, )); return self.new_invalid_constant(location); } }; let arguments = arguments_types .iter_mut() .zip(arguments) .map(|(type_, argument): (&mut Arc, _)| { if argument.uses_label_shorthand() { self.track_feature_usage( FeatureKind::LabelShorthandSyntax, argument.location, ); } let CallArg { label, value, location, implicit, } = argument; let value = self.infer_const(&None, value); if let Err(error) = unify(type_.clone(), value.type_()) { self.problems .error(convert_unify_error(error, value.location())) } CallArg { label, value, implicit, location, } }) .collect_vec(); Constant::Record { module, location, name, arguments, type_: return_type, tag, field_map, record_constructor: Some(Box::new(constructor)), } } Constant::Var { location, module, name, .. } => { // Infer the type of this constant let constructor = match self.infer_value_constructor(&module, &name, &location) { Ok(constructor) => constructor, Err(error) => { self.problems.error(error); return Constant::Invalid { location, type_: self.new_unbound_var(), extra_information: Some(match module { Some((module_name, _)) => InvalidExpression::ModuleSelect { module_name, label: name, }, None => InvalidExpression::UnknownVariable { name }, }), }; } }; match constructor.variant { ValueConstructorVariant::ModuleConstant { .. } | ValueConstructorVariant::ModuleFn { .. } | ValueConstructorVariant::LocalVariable { .. } => Constant::Var { location, module, name, type_: Arc::clone(&constructor.type_), constructor: Some(Box::from(constructor)), }, // It cannot be a Record because then this constant would have been // parsed as a Constant::Record. Therefore this code is unreachable. ValueConstructorVariant::Record { .. } => unreachable!(), } } Constant::StringConcatenation { location, left, right, } => { self.track_feature_usage(FeatureKind::ConstantStringConcatenation, location); let left = self.infer_const(&None, *left); if let Err(error) = unify(string(), left.type_()) { self.problems.error( error .operator_situation(BinOp::Concatenate) .into_error(left.location()), ) }; let right = self.infer_const(&None, *right); if let Err(error) = unify(string(), right.type_()) { self.problems.error( error .operator_situation(BinOp::Concatenate) .into_error(right.location()), ) }; Constant::StringConcatenation { location, left: Box::new(left), right: Box::new(right), } } Constant::Invalid { .. } => panic!("invalid constants can not be in an untyped ast"), } } /// Returns an invalid constant with an unbound type and no extra information /// attached. fn new_invalid_constant(&mut self, location: SrcSpan) -> TypedConstant { Constant::Invalid { location, type_: self.new_unbound_var(), extra_information: None, } } pub fn infer_const( &mut self, annotation: &Option, value: UntypedConstant, ) -> TypedConstant { let inferred = self.infer_const_value(value); match annotation .as_ref() .map(|annotation| self.type_from_ast(annotation)) { Some(Err(error)) => { self.problems.error(error); inferred } // If there's an annotation we try and unify it with the inferred // type. Some(Ok(annotated_type)) => { if let Err(error) = unify(annotated_type.clone(), inferred.type_()) { self.problems .error(convert_unify_error(error, inferred.location())); invalid_with_annotated_type(inferred, annotated_type) } else { inferred } } None => inferred, } } fn infer_const_tuple( &mut self, untyped_elements: Vec, location: SrcSpan, ) -> TypedConstant { let mut elements = Vec::with_capacity(untyped_elements.len()); for element in untyped_elements { let element = self.infer_const(&None, element); elements.push(element); } let type_ = tuple(elements.iter().map(HasType::type_).collect_vec()); Constant::Tuple { elements, location, type_, } } fn infer_const_list( &mut self, untyped_elements: Vec, location: SrcSpan, tail: Option>, ) -> TypedConstant { let element_type = self.new_unbound_var(); let mut elements = Vec::with_capacity(untyped_elements.len()); for element in untyped_elements { let element = self.infer_const(&None, element); if let Err(error) = unify(element_type.clone(), element.type_()) { self.problems .error(convert_unify_error(error, element.location())); } elements.push(element); } let type_ = list(element_type); let tail = if let Some(tail) = tail { let tail = self.infer_const(&None, *tail); if let Err(error) = unify(type_.clone(), tail.type_()) { self.problems .error(convert_unify_error(error, tail.location())) } Some(Box::new(tail)) } else { None }; Constant::List { elements, location, type_, tail, } } fn get_field_map( &mut self, constructor: &TypedExpr, ) -> Result, UnknownValueConstructorError> { let (module, name) = match constructor { TypedExpr::ModuleSelect { module_alias, label, .. } => (Some(module_alias), label), TypedExpr::Var { name, .. } => (None, name), TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => return Ok(None), }; Ok(self .environment .get_value_constructor(module, name)? .field_map()) } pub fn do_infer_call( &mut self, fun: UntypedExpr, arguments: Vec>, location: SrcSpan, kind: CallKind, ) -> (TypedExpr, Vec, Arc) { let fun = match fun { UntypedExpr::FieldAccess { label, container, label_location, location, } => self.infer_field_access( *container, location, label, label_location, FieldAccessUsage::MethodCall, ), UntypedExpr::Fn { location, kind, arguments: fn_arguments, body, return_annotation, .. } if fn_arguments.len() == arguments.len() => self.infer_fn_with_call_context( fn_arguments, &arguments, body, kind, return_annotation, location, ), UntypedExpr::Int { .. } | UntypedExpr::Float { .. } | UntypedExpr::String { .. } | UntypedExpr::Block { .. } | UntypedExpr::Var { .. } | UntypedExpr::Fn { .. } | UntypedExpr::List { .. } | UntypedExpr::Call { .. } | UntypedExpr::BinOp { .. } | UntypedExpr::PipeLine { .. } | UntypedExpr::Case { .. } | UntypedExpr::Tuple { .. } | UntypedExpr::TupleIndex { .. } | UntypedExpr::Todo { .. } | UntypedExpr::Panic { .. } | UntypedExpr::Echo { .. } | UntypedExpr::BitArray { .. } | UntypedExpr::RecordUpdate { .. } | UntypedExpr::NegateBool { .. } | UntypedExpr::NegateInt { .. } => self.infer(fun), }; let (fun, arguments, type_) = self.do_infer_call_with_known_fun(fun, arguments, location, kind); (fun, arguments, type_) } fn infer_fn_with_call_context( &mut self, arguments: Vec, call_arguments: &[CallArg], body: Vec1, kind: FunctionLiteralKind, return_annotation: Option, location: SrcSpan, ) -> TypedExpr { let typed_call_arguments: Vec> = call_arguments .iter() .map(|argument| { match self.infer_or_error(argument.value.clone()) { Ok(argument) => argument, Err(_e) => self.error_expr(location), } .type_() }) .collect_vec(); self.infer_fn( arguments, &typed_call_arguments, body, kind, return_annotation, location, ) } pub fn do_infer_call_with_known_fun( &mut self, fun: TypedExpr, mut arguments: Vec>, location: SrcSpan, kind: CallKind, ) -> (TypedExpr, Vec, Arc) { let mut labelled_arity_error = false; // Check to see if the function accepts labelled arguments let field_map = self .get_field_map(&fun) .map_err(|e| convert_get_value_constructor_error(e, location, None)) .and_then(|field_map| { match field_map { // The fun has a field map so labelled arguments may be // present and need to be reordered. Some(field_map) => { field_map.reorder(&mut arguments, location, IncorrectArityContext::Function) } // The fun or constructor has no field map and so we error // if arguments have been labelled. // There's an exception to this rule: if the function itself // doesn't exist (that is it's an `Invalid` expression), then // we don't want to error on any labels that might have been // used as it would be quite noisy. Once the function is // known to be a valid function we can make sure that there's // no labelled arguments if it doesn't actually have a field map. None if fun.is_invalid() => Ok(()), None if fun.is_record_constructor_function() => assert_no_labelled_arguments( &arguments, UnexpectedLabelledArgKind::RecordConstructorArgument, ), None => assert_no_labelled_arguments( &arguments, UnexpectedLabelledArgKind::FunctionParameter, ), } }); if let Err(e) = field_map { if let Error::IncorrectArity { expected, given, context, labels, location, } = e { labelled_arity_error = true; self.problems.error(Error::IncorrectArity { expected, given, context, labels, location, }); } else { self.problems.error(e); } } let mut missing_arguments = 0; let mut ignored_labelled_arguments = vec![]; // Extract the type of the fun, ensuring it actually is a function let (mut arguments_types, return_type) = match match_fun_type(fun.type_(), arguments.len(), self.environment) { Ok(function) => function, Err(error) => { let converted_error = convert_not_fun_error(error.clone(), fun.location(), location, kind); match error { // If the function was valid but had the wrong number of arguments passed. // Then we keep the error but still want to continue analysing the arguments that were passed. MatchFunTypeError::IncorrectArity { arguments: arg_types, return_type, expected, given, .. } => { missing_arguments = expected.saturating_sub(given); // If the function has labels then arity issues will already // be handled by the field map so we can ignore them here. if !labelled_arity_error { self.problems.error(converted_error); (arg_types, return_type) } else { // Since arity errors with labels cause incorrect // ordering, we can't type check the labelled arguments here. let first_labelled_arg = arguments.iter().position(|arg| arg.label.is_some()); ignored_labelled_arguments = arguments .iter() .skip_while(|argument| argument.label.is_none()) .map(|argument| { ( argument.label.clone(), argument.location, argument.implicit, ) }) .collect_vec(); let arguments_to_keep = first_labelled_arg.unwrap_or(arguments.len()); ( arg_types.iter().take(arguments_to_keep).cloned().collect(), return_type, ) } } MatchFunTypeError::NotFn { .. } => { self.problems.error(converted_error); (vec![], self.new_unbound_var()) } } } }; // When typing the function's arguments we don't care if the previous // expression panics or not because we want to provide a specialised // error message for this particular case. // So we set `previous_panics` to false to avoid raising any // unnecessarily generic warning. self.previous_panics = false; // Now if we had a mismatched arity error and we're typing a use call, // we want to insert all the missing arguments before the callback // argument that is implicitly passed by the compiler. // This way we can provide better argument hints for incomplete use // expressions. if let CallKind::Use { .. } = kind && let Some(last) = arguments.pop() { for _ in 0..missing_arguments { arguments.push(CallArg { label: None, location, value: UntypedExpr::Panic { // We intentionally give this an empty span since it // is an implicit argument being passed by the compiler // that doesn't appear in the source code. location: SrcSpan { start: last.location().start, end: last.location().start, }, message: None, }, implicit: Some(ImplicitCallArgOrigin::IncorrectArityUse), }); } arguments.push(last); }; // Ensure that the given args have the correct types let arguments_count = arguments_types.len(); let mut typed_arguments: Vec<_> = arguments_types .iter_mut() .zip(arguments) .enumerate() .map(|(argument_index, (type_, arg))| { if arg.uses_label_shorthand() { self.track_feature_usage(FeatureKind::LabelShorthandSyntax, arg.location); } let CallArg { label, value, location, implicit, } = arg; // If we're typing a `use` call then the last argument is the // use callback and we want to treat it differently to report // better errors. let argument_kind = match kind { CallKind::Use { call_location, last_statement_location, assignments_location, } if argument_index == arguments_count - 1 => ArgumentKind::UseCallback { function_location: call_location, assignments_location, last_statement_location, }, CallKind::Use { .. } | CallKind::Function => ArgumentKind::Regular, }; // We don't want to emit a warning for unreachable function call if the // function being called is itself `panic`, for that we emit a more // specialised warning. if self.previous_panics && !fun.is_panic() { self.warn_for_unreachable_code( value.location(), PanicPosition::PreviousFunctionArgument, ) } let value = self.infer_call_argument( &fun, value, argument_index, type_.clone(), argument_kind, ); CallArg { label, value, implicit, location, } }) .collect(); // Now if we had supplied less arguments than required and some of those // were labelled, in the previous step we would have got rid of those // _before_ typing. // That is because we can only reliably type positional arguments in // case of mismatched arity, as labelled arguments cannot be reordered. // // So now what we want to do is add back those labelled arguments to // make sure the LS can still see that those were explicitly supplied. for (label, location, implicit) in ignored_labelled_arguments { typed_arguments.push(CallArg { label, value: TypedExpr::Invalid { location, type_: self.new_unbound_var(), extra_information: None, }, implicit, location, }) } // We don't want to emit a warning for unreachable function call if the // function being called is itself `panic`, for that we emit a more // specialised warning. if self.previous_panics && !fun.is_panic() { self.warn_for_unreachable_code(fun.location(), PanicPosition::LastFunctionArgument); } (fun, typed_arguments, return_type) } fn infer_call_argument( &mut self, called_function: &TypedExpr, argument: UntypedExpr, argument_index: usize, type_: Arc, kind: ArgumentKind, ) -> TypedExpr { let type_ = collapse_links(type_); let value = match (&*type_, argument) { // If the argument is expected to be a function and we are passed a // function literal with the correct number of arguments then we // have special handling of this argument, passing in information // about what the expected arguments are. This extra information // when type checking the function body means that the // `record.field` access syntax can be used, and improves error // messages. ( Type::Fn { arguments: expected_arguments, .. }, UntypedExpr::Fn { arguments, body, return_annotation, location, kind, .. }, ) if expected_arguments.len() == arguments.len() => self.infer_fn( arguments, expected_arguments, body, kind, return_annotation, location, ), // If the argument is a regular var then we want to add some extra // checks. The value will be inferred regularly, but we also want to // see if this is an argument that is being passed recursively to // the same function that defined it! (_, UntypedExpr::Var { location, name }) => { self.infer_variable_call_arg(called_function, name, location, argument_index) } // Otherwise just perform normal type inference. (_, argument) => self.infer(argument), }; if let Err(error) = unify(type_.clone(), value.type_()) { self.problems .error(convert_unify_call_error(error, value.location(), kind)); } value } fn infer_variable_call_arg( &mut self, called_function: &TypedExpr, argument_name: EcoString, argument_location: SrcSpan, argument_index: usize, ) -> TypedExpr { // If the called function is a function defined in this same module we // pass it along to the `infer_var` function so that we can check if the // argument is being passed recursively to the function that is defining // it. let references = if let TypedExpr::Var { constructor: ValueConstructor { variant: ValueConstructorVariant::ModuleFn { name, module, .. }, .. }, .. } = called_function && *module == self.environment.current_module { ReferenceRegistration::VariableArgument { called_function: name.clone(), argument_index, } } else { ReferenceRegistration::Register }; match self.infer_var(argument_name.clone(), argument_location, references) { Ok(result) => result, Err(error) => { self.problems.error(error); self.error_expr_with_information( argument_location, Some(InvalidExpression::UnknownVariable { name: argument_name, }), ) } } } pub fn do_infer_fn( &mut self, function_name: Option, arguments: Vec, expected_arguments: &[Arc], body: Vec1, return_annotation: &Option, ) -> Result<(Vec, Vec1), Error> { // Construct an initial type for each argument of the function- either an unbound // type variable or a type provided by an annotation. let arguments: Vec<_> = arguments .into_iter() .enumerate() .map(|(i, argument)| self.infer_arg(argument, expected_arguments.get(i).cloned())) .try_collect()?; let return_type = match return_annotation { Some(ann) => Some(self.type_from_ast(ann)?), None => None, }; let (arguments, body) = self.infer_fn_with_known_types(function_name, arguments, body.to_vec(), return_type)?; let body = Vec1::try_from_vec(body).expect("body guaranteed to have at least one statement"); Ok((arguments, body)) } pub fn infer_fn_with_known_types( &mut self, function_name: Option, arguments: Vec, body: Vec, return_type: Option>, ) -> Result<(Vec, Vec), Error> { // If a function has an empty body then it doesn't have a pure gleam // implementation. if body.is_empty() { self.implementations.gleam = false; } self.in_new_scope(|body_typer| { // Used to track if any argument names are used more than once let mut argument_names = HashSet::with_capacity(arguments.len()); for (argument_index, argument) in arguments.iter().enumerate() { match &argument.names { ArgNames::Named { name, location } | ArgNames::NamedLabelled { name, name_location: location, .. } => { // Check that this name has not already been used for // another argument if !argument_names.insert(name) { return Err(Error::ArgumentNameAlreadyUsed { location: argument.location, name: name.clone(), }); } let syntax = if name == CAPTURE_VARIABLE { VariableSyntax::Generated } else { VariableSyntax::Variable(name.clone()) }; let origin = VariableOrigin { syntax, declaration: VariableDeclaration::FunctionParameter { function_name: function_name.clone(), index: argument_index, }, }; // Insert a variable for the argument into the environment body_typer.environment.insert_local_variable( name.clone(), *location, origin.clone(), argument.type_.clone(), ); if !body.is_empty() { // Register the variable in the usage tracker so that we // can identify if it is unused body_typer.environment.init_usage( name.clone(), origin, *location, body_typer.problems, ); } } ArgNames::Discard { .. } | ArgNames::LabelledDiscard { .. } => (), }; } if let Ok(body) = Vec1::try_from_vec(body) { let mut body = body_typer.infer_statements(body); // Check that any return type is accurate. if let Some(return_type) = return_type && let Err(error) = unify(return_type, body.last().type_()) { let error = error .return_annotation_mismatch() .into_error(body.last().type_defining_location()); body_typer.problems.error(error); // If the return type doesn't match with the annotation we // add a new expression to the end of the function to match // the annotated type and allow type inference to keep // going. body.push(Statement::Expression(TypedExpr::Invalid { // This is deliberately an empty span since this // placeholder expression is implicitly inserted by the // compiler and doesn't actually appear in the source // code. location: SrcSpan { start: body.last().location().end, end: body.last().location().end, }, type_: body_typer.new_unbound_var(), extra_information: None, })) }; Ok((arguments, body.to_vec())) } else { Ok((arguments, vec![])) } }) } fn infer_block(&mut self, statements: Vec1, location: SrcSpan) -> TypedExpr { self.expr_in_new_scope(|typer| { let statements = typer.infer_statements(statements); TypedExpr::Block { statements, location, } }) } /// Returns `Ok(())` if the let is exhaustive, returns an `InexhaustiveLetAssignment` /// error if the given pattern doesn't cover all possible cases. /// fn check_let_exhaustiveness( &self, location: SrcSpan, subject: Arc, pattern: &TypedPattern, ) -> (CompileCaseResult, Result<(), Error>) { let mut case = exhaustiveness::CaseToCompile::new(&[subject]); case.add_pattern(pattern); let output = case.compile(self.environment); // Error for missing clauses that would cause a crash let result = if output.diagnostics.missing { Err(Error::InexhaustiveLetAssignment { location, missing: output.missing_patterns(self.environment), }) } else { Ok(()) }; (output, result) } fn check_case_exhaustiveness( &mut self, location: SrcSpan, subject_types: &[Arc], clauses: &[TypedClause], ) -> CompiledCase { let mut case = exhaustiveness::CaseToCompile::new(subject_types); clauses.iter().for_each(|clause| case.add_clause(clause)); let result = case.compile(self.environment); // Error for missing clauses that would cause a crash if result.diagnostics.missing { self.problems.error(Error::InexhaustiveCaseExpression { location, missing: result.missing_patterns(self.environment), }); } // Emit warnings for unreachable patterns for (clause_index, clause) in clauses.iter().enumerate() { let patterns_iterator = std::iter::once(&clause.pattern).chain(clause.alternative_patterns.iter()); for (pattern_index, multi_pattern) in patterns_iterator.enumerate() { match result.is_reachable(clause_index, pattern_index) { Reachability::Reachable => {} Reachability::Unreachable(reason) => { let first = multi_pattern .first() .expect("All case expressions match at least one subject"); let last = multi_pattern .last() .expect("All case expressions match at least one subject"); let location = SrcSpan::new(first.location().start, last.location().end); self.problems .warning(Warning::UnreachableCasePattern { location, reason }) } } } } result.compiled_case } fn track_feature_usage(&mut self, feature_kind: FeatureKind, location: SrcSpan) { let minimum_required_version = feature_kind.required_version(); // Then if the required version is not in the specified version for the // range we emit a warning highlighting the usage of the feature. if let Some(gleam_version) = &self.environment.gleam_version && let Some(lowest_allowed_version) = gleam_version.lowest_version() { // There is a version in the specified range that is lower than // the one required by this feature! This means that the // specified range is wrong and would allow someone to run a // compiler that is too old to know of this feature. if minimum_required_version > lowest_allowed_version { self.problems .warning(Warning::FeatureRequiresHigherGleamVersion { location, feature_kind, minimum_required_version: minimum_required_version.clone(), wrongfully_allowed_version: lowest_allowed_version, }) } } if minimum_required_version > self.minimum_required_version { self.minimum_required_version = minimum_required_version; } } /// Checks if one of the options is a size option using an expression. /// This needs to be tracked as it was introduced in Gleam 1.12.0. fn check_segment_size_expression(&mut self, options: &[BitArrayOption]) { let Some(size_value) = options.iter().find_map(|option| match option { BitArrayOption::Size { value, .. } => Some(value), BitArrayOption::Bytes { .. } | BitArrayOption::Int { .. } | BitArrayOption::Float { .. } | BitArrayOption::Bits { .. } | BitArrayOption::Utf8 { .. } | BitArrayOption::Utf16 { .. } | BitArrayOption::Utf32 { .. } | BitArrayOption::Utf8Codepoint { .. } | BitArrayOption::Utf16Codepoint { .. } | BitArrayOption::Utf32Codepoint { .. } | BitArrayOption::Signed { .. } | BitArrayOption::Unsigned { .. } | BitArrayOption::Big { .. } | BitArrayOption::Little { .. } | BitArrayOption::Native { .. } | BitArrayOption::Unit { .. } => None, }) else { return; }; match size_value.as_ref() { // Ints and vars were always allowed from the start TypedExpr::Int { .. } | TypedExpr::Var { .. } => (), // Blocks and binops were added in Gleam 1.12.0! TypedExpr::Block { location, .. } | TypedExpr::BinOp { location, .. } => { self.track_feature_usage(FeatureKind::ExpressionInSegmentSize, *location) } // None of these are currently supported... for now! TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => (), } } /// Checks if one of the options is a size option using an expression. /// This needs to be tracked as it was introduced in Gleam 1.12.0. /// /// This is basically the same as the function above working on expressions! fn check_constant_segment_size_expression(&self, options: &[BitArrayOption]) { let Some(size_value) = options.iter().find_map(|option| match option { BitArrayOption::Size { value, .. } => Some(value), BitArrayOption::Bytes { .. } | BitArrayOption::Int { .. } | BitArrayOption::Float { .. } | BitArrayOption::Bits { .. } | BitArrayOption::Utf8 { .. } | BitArrayOption::Utf16 { .. } | BitArrayOption::Utf32 { .. } | BitArrayOption::Utf8Codepoint { .. } | BitArrayOption::Utf16Codepoint { .. } | BitArrayOption::Utf32Codepoint { .. } | BitArrayOption::Signed { .. } | BitArrayOption::Unsigned { .. } | BitArrayOption::Big { .. } | BitArrayOption::Little { .. } | BitArrayOption::Native { .. } | BitArrayOption::Unit { .. } => None, }) else { return; }; // Expressions are not allowed in constants so for now nothing needs // tracking. Though this is handy to have already in place if we were to // lift this restriction for constant bit arrays as well! match size_value.as_ref() { // Ints and vars were always allowed from the start TypedConstant::Int { .. } | TypedConstant::Var { .. } => (), // None of these are currently supported... for now! Constant::Float { .. } | Constant::String { .. } | Constant::Tuple { .. } | Constant::List { .. } | Constant::Record { .. } | Constant::RecordUpdate { .. } | Constant::BitArray { .. } | Constant::StringConcatenation { .. } | Constant::Invalid { .. } => (), } } } /// Given a constants, this will change its type into the given one, turning /// the constant into an `Invalid` one if necessary. /// fn invalid_with_annotated_type(constant: TypedConstant, new_type: Arc) -> TypedConstant { // In case the types cannot be unified we change the inferred we // return a constant where the type matches the annotated one. // This can help minimise fals positive later on! match constant { // For simple variants that don't carry their own type we // replace them with an invalid constant with the same // location and the new annotated type. Constant::Int { location, .. } | Constant::Float { location, .. } | Constant::String { location, .. } | Constant::BitArray { location, .. } | Constant::StringConcatenation { location, .. } => TypedConstant::Invalid { location, type_: new_type, extra_information: None, }, // In all other cases we don't want to lose information on // the actual structure of the invalid expression. So we just // replace the type with the annotated one. Constant::Invalid { location, type_: _, extra_information, } => Constant::Invalid { location, type_: new_type, extra_information, }, Constant::Tuple { location, elements, type_: _, } => Constant::Tuple { location, elements, type_: new_type, }, Constant::List { location, elements, type_: _, tail, } => Constant::List { location, elements, type_: new_type, tail, }, Constant::Record { location, module, name, arguments, tag, type_: _, field_map, record_constructor, } => Constant::Record { location, module, name, arguments, tag, type_: new_type, field_map, record_constructor, }, Constant::RecordUpdate { location, constructor_location, module, name, record, arguments, tag, type_: _, field_map, } => Constant::RecordUpdate { location, constructor_location, module, name, record, arguments, tag, type_: new_type, field_map, }, Constant::Var { location, module, name, constructor, type_: _, } => Constant::Var { location, module, name, constructor, type_: new_type, }, } } /// Returns `true` if the current module is one that the Gleam core team /// maintains and we know it to be pure. /// Used in purity tracking. fn is_trusted_pure_module(environment: &Environment<'_>) -> bool { if environment.current_package != STDLIB_PACKAGE_NAME { return false; } // The gleam/io module has side effects if environment.current_module == "gleam/io" { return false; } // Test and dev modules may have side effects environment.origin == Origin::Src } #[derive(Debug, Clone)] enum ReferenceRegistration { Register, DoNotRegister, /// A special case that happens if we're registering references for /// a variable call argument being passed to a function defined in the /// current module. VariableArgument { /// The name of the function being called, the function is defined in /// the current module. called_function: EcoString, /// The position where the variable is being passed as an argument. argument_index: usize, }, } fn extract_typed_use_call_assignments( call: &TypedExpr, assignments_count: usize, ) -> Vec>> { // A use call function has the use callback as its last argument, the // assignments will be the first statements in its body. let Some(use_callback_body) = call .call_arguments() .and_then(|call_arguments| call_arguments.last()) .and_then(|last_call_argument| last_call_argument.value.fn_expression_body()) else { return vec![]; }; // Once we get a hold of the callback function body we take out the first // `assignments_count` statements and turn those into typed // `UseAssignments`. // // Note how we can't just `.expect` them to be a `Statement::Assignment` // because in case of type errors those might be invalid expressions and we // don't want to crash the compiler in that case! use_callback_body .iter() .take(assignments_count) .map(|statement| match statement { Statement::Expression(_) | Statement::Use(_) | Statement::Assert(_) => None, Statement::Assignment(assignment) => Some(UseAssignment { location: assignment.location, pattern: assignment.pattern.clone(), annotation: assignment.annotation.clone(), }), }) .collect::>>() .unwrap_or(vec![]) } fn check_subject_for_redundant_match( subject: &TypedExpr, case_used_like_if: bool, ) -> Option { match subject { TypedExpr::Tuple { elements, .. } if !elements.is_empty() => { Some(Warning::CaseMatchOnLiteralCollection { kind: LiteralCollectionKind::Tuple, location: subject.location(), }) } TypedExpr::List { elements, tail, .. } if !elements.is_empty() || tail.is_some() => { Some(Warning::CaseMatchOnLiteralCollection { kind: LiteralCollectionKind::List, location: subject.location(), }) } TypedExpr::BitArray { segments, .. } if !segments.is_empty() => { // We don't want a warning when matching on literal bit arrays // because it can make sense to do it; for example if one is // matching on segments that do not align with the segments used // for construction. None } TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => match subject.record_constructor_arity() { // We make sure to not emit warnings if the case is being used like an // if expression: // ```gleam // case True { // _ if condition -> todo // _ if other_condition -> todo // _ -> todo // } // ``` Some(0) if !case_used_like_if => Some(Warning::CaseMatchOnLiteralValue { location: subject.location(), }), Some(0) => None, Some(_) => Some(Warning::CaseMatchOnLiteralCollection { kind: LiteralCollectionKind::Record, location: subject.location(), }), None if subject.is_literal() && !case_used_like_if => { Some(Warning::CaseMatchOnLiteralValue { location: subject.location(), }) } None => None, }, } } /// Returns the kind of an empty list check. /// /// Based on the binary operator being used and the position of the operands we /// can categorize an empty list check in one of two ways: /// - Checking for the empty list /// - Checking for a non-empty list fn get_empty_list_check_kind<'a>( binop: BinOp, left: &'a TypedExpr, right: &'a TypedExpr, ) -> Option { match (&left, &right) { // For `==` and `!=` we don't care which side each of the operands are on. (_, TypedExpr::Int { value, .. }) | (TypedExpr::Int { value, .. }, _) if binop == BinOp::Eq || binop == BinOp::NotEq => { match (binop, value.as_str()) { (BinOp::Eq, "0" | "-0") => Some(EmptyListCheckKind::Empty), (BinOp::NotEq, "0" | "-0") => Some(EmptyListCheckKind::NonEmpty), _ => None, } } (_, TypedExpr::Int { value, .. }) => match (binop, value.as_str()) { (BinOp::LtEqInt, "0" | "-0") | (BinOp::LtInt, "1") => Some(EmptyListCheckKind::Empty), (BinOp::GtInt, "0" | "-0") => Some(EmptyListCheckKind::NonEmpty), _ => None, }, (TypedExpr::Int { value, .. }, _) => match (binop, value.as_str()) { (BinOp::GtEqInt | BinOp::LtInt, "0" | "-0") | (BinOp::GtInt, "1") => { Some(EmptyListCheckKind::NonEmpty) } _ => None, }, _ => None, } } struct UseCall { function: Box, arguments: Vec>, } fn get_use_expression_call(call: UntypedExpr) -> UseCall { // Ensure that the use's call is of the right structure. i.e. it is a // call to a function. if let UntypedExpr::Call { fun: function, arguments, .. } = call { UseCall { arguments, function, } } else { UseCall { function: Box::new(call), arguments: vec![], } } } #[derive(Debug, Default)] struct UseAssignments { /// With sugar /// ```gleam /// use Box(x) = ... /// ``` /// Without sugar /// ```gleam /// fn(_use1) { let Box(x) = _use1 } /// // ^^^^^ The function arguments /// ``` function_arguments: Vec, /// With sugar /// ```gleam /// use Box(x) = ... /// ``` /// Without sugar /// ```gleam /// fn(_use1) { let Box(x) = _use1 } /// // ^^^^^^^^^^^^^^^^^^ The body assignments /// ``` body_assignments: Vec, } impl UseAssignments { fn from_use_expression(sugar_assignments: Vec) -> UseAssignments { let mut assignments = UseAssignments::default(); for (index, assignment) in sugar_assignments.into_iter().enumerate() { let UseAssignment { location, pattern, annotation, } = assignment; match pattern { // For discards we add a discard function arguments. Pattern::Discard { name, .. } => assignments.function_arguments.push(Arg { location, names: ArgNames::Discard { name, location }, annotation: None, type_: (), }), // For simple patterns of a single variable we add a regular // function argument. Pattern::Variable { name, .. } => assignments.function_arguments.push(Arg { location, annotation, names: ArgNames::Named { name, location }, type_: (), }), // For more complex patterns we add a function argument and also // an assignment in the function body to handle the pattern. pattern @ (Pattern::Int { .. } | Pattern::Float { .. } | Pattern::String { .. } | Pattern::BitArraySize { .. } | Pattern::Assign { .. } | Pattern::List { .. } | Pattern::Constructor { .. } | Pattern::Tuple { .. } | Pattern::BitArray { .. } | Pattern::StringPrefix { .. } | Pattern::Invalid { .. }) => { let name: EcoString = format!("{USE_ASSIGNMENT_VARIABLE}{index}").into(); assignments.function_arguments.push(Arg { location, names: ArgNames::Named { name: name.clone(), location, }, annotation: None, type_: (), }); let assignment = Assignment { location, pattern, annotation, compiled_case: CompiledCase::failure(), kind: AssignmentKind::Generated, value: UntypedExpr::Var { location, name }, }; assignments .body_assignments .push(Statement::Assignment(Box::new(assignment))) } } } assignments } } #[derive(Debug)] struct RecordUpdateVariant<'a> { arguments: Vec>, return_type: Arc, field_map: &'a FieldMap, } impl RecordUpdateVariant<'_> { fn arg_type(&self, index: u32) -> Arc { self.arguments .get(index as usize) .expect("Failed to get record argument type after successfully inferring that field") .clone() } fn has_field(&self, str: &EcoString) -> bool { self.field_map.fields.contains_key(str) } fn field_names(&self) -> Vec { self.field_map.fields.keys().cloned().collect() } } #[derive(Debug, Eq, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)] pub enum ComparisonOutcome { AlwaysFails, AlwaysSucceeds, } enum StaticComparison { /// When we can statically tell that two values are going to be exactly the /// same. CertainlyEqual, /// When we can statically tell that two values are not going to be the /// same. CertainlyDifferent, /// When it's impossible to statically tell if two values are the same. CantTell, } fn static_compare(one: &TypedExpr, other: &TypedExpr) -> StaticComparison { if one.is_record_constructor_function() && other.is_record_constructor_function() { return StaticComparison::CantTell; } match (one, other) { ( TypedExpr::Var { name: one, constructor: constructor_one, .. }, TypedExpr::Var { name: other, constructor: constructor_other, .. }, ) => match (&constructor_one.variant, &constructor_other.variant) { ( ValueConstructorVariant::LocalVariable { .. }, ValueConstructorVariant::LocalVariable { .. }, ) | ( ValueConstructorVariant::ModuleConstant { .. }, ValueConstructorVariant::ModuleConstant { .. }, ) | (ValueConstructorVariant::Record { .. }, ValueConstructorVariant::Record { .. }) if one == other => { StaticComparison::CertainlyEqual } ( ValueConstructorVariant::Record { variant_index: index_one, .. }, ValueConstructorVariant::Record { variant_index: index_other, .. }, ) if index_one != index_other => StaticComparison::CertainlyDifferent, ( ValueConstructorVariant::LocalVariable { .. } | ValueConstructorVariant::ModuleConstant { .. } | ValueConstructorVariant::ModuleFn { .. } | ValueConstructorVariant::Record { .. }, _, ) => StaticComparison::CantTell, }, (TypedExpr::Int { int_value: n, .. }, TypedExpr::Int { int_value: m, .. }) => { if n == m { StaticComparison::CertainlyEqual } else { StaticComparison::CertainlyDifferent } } (TypedExpr::Float { float_value: n, .. }, TypedExpr::Float { float_value: m, .. }) => { if n == m { StaticComparison::CertainlyEqual } else { StaticComparison::CertainlyDifferent } } (TypedExpr::String { value: one, .. }, TypedExpr::String { value: other, .. }) => { if one == other { StaticComparison::CertainlyEqual } else { StaticComparison::CertainlyDifferent } } (TypedExpr::NegateInt { value: one, .. }, TypedExpr::NegateInt { value: other, .. }) | (TypedExpr::NegateBool { value: one, .. }, TypedExpr::NegateBool { value: other, .. }) => { static_compare(one, other) } ( TypedExpr::List { elements: elements_one, tail: tail_one, .. }, TypedExpr::List { elements: elements_other, tail: tail_other, .. }, ) => { match (tail_one, tail_other) { (Some(one_tail), Some(other_tail)) => match static_compare(one_tail, other_tail) { StaticComparison::CertainlyDifferent => { return StaticComparison::CertainlyDifferent; } StaticComparison::CantTell => return StaticComparison::CantTell, StaticComparison::CertainlyEqual => (), }, (None, Some(_)) | (Some(_), None) => return StaticComparison::CantTell, (None, None) => (), }; // If we can tell the two lists have a different number of items // then we know it's never going to match. if elements_one.len() != elements_other.len() { return StaticComparison::CertainlyDifferent; } let mut comparison = StaticComparison::CertainlyEqual; for (one, other) in elements_one.iter().zip(elements_other.iter()) { match static_compare(one, other) { StaticComparison::CertainlyEqual => (), StaticComparison::CertainlyDifferent => { return StaticComparison::CertainlyDifferent; } StaticComparison::CantTell => comparison = StaticComparison::CantTell, } } comparison } ( TypedExpr::Tuple { elements: elements_one, .. }, TypedExpr::Tuple { elements: elements_other, .. }, ) => { let mut comparison = StaticComparison::CertainlyEqual; for (one, other) in elements_one.iter().zip(elements_other.iter()) { match static_compare(one, other) { StaticComparison::CertainlyEqual => (), StaticComparison::CertainlyDifferent => { return StaticComparison::CertainlyDifferent; } StaticComparison::CantTell => comparison = StaticComparison::CantTell, } } comparison } ( TypedExpr::ModuleSelect { constructor: constructor_one, module_name: module_name_one, .. }, TypedExpr::ModuleSelect { constructor: constructor_other, module_name: module_name_other, .. }, ) => { if module_name_one == module_name_other && constructor_one == constructor_other { StaticComparison::CertainlyEqual } else { StaticComparison::CantTell } } ( TypedExpr::Call { fun: fun_one, arguments: arguments_one, .. }, TypedExpr::Call { fun: fun_other, arguments: arguments_other, .. }, ) => match (fun_one.variant_index(), fun_other.variant_index()) { // Both have to be literal record builders, otherwise we can't really tell // anything at compile time! (None, _) | (_, None) => StaticComparison::CantTell, // If they're both literal record builders and are building different // variants, then we know they'll always be different. (Some(index_one), Some(index_other)) if index_one != index_other => { StaticComparison::CertainlyDifferent } // Otherwise we need to check their arguments pairwise: (Some(_), Some(_)) => { let mut comparison = StaticComparison::CertainlyEqual; for (one, other) in arguments_one.iter().zip(arguments_other.iter()) { match static_compare(&one.value, &other.value) { StaticComparison::CertainlyEqual => (), // If we can tell any of the arguments are never going to // be the same then we can short circuit and be sure // that the two variants are not the same as well! StaticComparison::CertainlyDifferent => { return StaticComparison::CertainlyDifferent; } // If we can't compare two of the arguments then there's // nothing we can tell at compile time. Notice how we // don't short circuit here: we still want to go over all // the other arguments because we might find two that are // certainly going to be different! StaticComparison::CantTell => comparison = StaticComparison::CantTell, } } comparison } }, // If we're building two variants with a different index then we can // tell for sure they're going to be different. (one, other) if one .variant_index() .is_some_and(|one| other.variant_index().is_some_and(|other| one != other)) => { StaticComparison::CertainlyDifferent } ( TypedExpr::RecordAccess { index: index_one, record: record_one, .. }, TypedExpr::RecordAccess { index: index_other, record: record_other, .. }, ) => match static_compare(record_one, record_other) { StaticComparison::CertainlyEqual if index_one == index_other => { StaticComparison::CertainlyEqual } StaticComparison::CertainlyEqual | StaticComparison::CertainlyDifferent | StaticComparison::CantTell => StaticComparison::CantTell, }, // TODO: For complex expressions we just give up, maybe in future we // could be smarter and perform further comparisons but it sounds like // there's no huge value in this. // (_, _) => StaticComparison::CantTell, } } ================================================ FILE: compiler-core/src/type_/fields.rs ================================================ use super::Error; use crate::{ ast::{CallArg, SrcSpan}, type_::error::IncorrectArityContext, }; use ecow::EcoString; use itertools::Itertools; use std::collections::{HashMap, HashSet}; #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct FieldMap { /// Number of accepted arguments, including unlabelled fields. pub arity: u32, /// Map of labels to argument indices pub fields: HashMap, } #[derive(Debug, Clone, Copy)] pub struct DuplicateField; impl FieldMap { pub fn new(arity: u32) -> Self { Self { arity, fields: HashMap::new(), } } pub fn insert(&mut self, label: EcoString, index: u32) -> Result<(), DuplicateField> { match self.fields.insert(label, index) { Some(_) => Err(DuplicateField), None => Ok(()), } } pub fn into_option(self) -> Option { if self.fields.is_empty() { None } else { Some(self) } } /// Reorder an argument list so that labelled fields supplied out-of-order are /// in the correct order. /// pub fn reorder( &self, arguments: &mut Vec>, location: SrcSpan, context: IncorrectArityContext, ) -> Result<(), Error> { let mut labelled_arguments_given = false; let mut seen_labels = HashSet::new(); let mut unknown_labels = Vec::new(); let number_of_arguments = arguments.len(); if self.arity as usize != arguments.len() { return Err(Error::IncorrectArity { labels: self.missing_labels(arguments), location, context, expected: self.arity as usize, given: arguments.len(), }); } for argument in arguments.iter() { match &argument.label { Some(_) => { labelled_arguments_given = true; } None => { if labelled_arguments_given && !argument.is_implicit() { return Err(Error::PositionalArgumentAfterLabelled { location: argument.location, }); } } } } // Keeps track of which labelled arguments need to be inserted into which indices let mut labelled_arguments = HashMap::new(); // We iterate the argument in reverse order, because we have to remove elements // from the `args` list quite a lot, and removing from the end of a list is more // efficient than removing from the beginning or the middle. let mut i = arguments.len(); while i > 0 { i -= 1; let (label, &location) = match &arguments.get(i).expect("Field indexing to get label").label { // A labelled argument, we may need to reposition it Some(l) => ( l, &arguments .get(i) .expect("Indexing in labelled field reordering") .location, ), // Not a labelled argument None => { continue; } }; let position = match self.fields.get(label) { None => { unknown_labels.push((label.clone(), location)); continue; } Some(&p) => p, }; if seen_labels.contains(label) { return Err(Error::DuplicateArgument { location, label: label.clone(), }); } let _ = seen_labels.insert(label.clone()); // Add this argument to the `labelled_arguments` map, and remove if from the // existing arguments list. It will be reinserted later in the correct index let _ = labelled_arguments.insert(position as usize, arguments.remove(i)); } // The labelled arguments must be reinserted in order for i in 0..number_of_arguments { if let Some(argument) = labelled_arguments.remove(&i) { arguments.insert(i, argument); } } if unknown_labels.is_empty() { Ok(()) } else { Err(Error::UnknownLabels { valid: self.fields.keys().cloned().collect(), unknown: unknown_labels, supplied: seen_labels.into_iter().collect(), }) } } /// This returns an array of the labels that are unused given an argument /// list. /// The unused labels are in the order they are expected to be passed in /// to a call using those. /// /// ## Examples /// /// ```gleam /// pub fn wibble(label1 a, label2 b, label3 c) { todo } /// /// wibble(1, label3: 2) // -> unused labels: [label2] /// ``` /// pub fn missing_labels(&self, arguments: &[CallArg]) -> Vec { // We need to know how many positional arguments are in the function // arguments. That's given by the position of the first labelled // argument; if the first label argument is third, then we know the // function also needs two unlabelled arguments first. let Some(positional_arguments) = self.fields.values().min().cloned() else { return vec![]; }; // We need to count how many positional arguments were actually supplied // in the call, to remove the corresponding labelled arguments that have // been taken by any positional argument. let given_positional_arguments = arguments .iter() .filter(|argument| argument.label.is_none() && !argument.is_use_implicit_callback()) .count(); let explicit_labels = arguments .iter() .filter_map(|argument| argument.label.as_ref()) .collect::>(); self.fields .iter() // As a start we remove all the labels that are already used explicitly, // for sure those are not going to be unused! .filter(|(label, _)| !explicit_labels.contains(label)) // ...then we sort all the labels in order by their original position in // the function definition .sorted_by_key(|(_, position)| *position) // ... finally we remove all the ones that are taken by a positional // argument .dropping(given_positional_arguments.saturating_sub(positional_arguments as usize)) .map(|(label, _)| label.clone()) .collect_vec() } pub fn indices_to_labels(&self) -> HashMap { self.fields .iter() .map(|(name, index)| (*index, name)) .collect() } } #[derive(Debug)] pub struct FieldMapBuilder { index: u32, any_labels: bool, field_map: FieldMap, } impl FieldMapBuilder { pub fn new(size: u32) -> Self { Self { index: 0, any_labels: false, field_map: FieldMap::new(size), } } pub fn add(&mut self, label: Option<&EcoString>, location: SrcSpan) -> Result<(), Error> { match label { Some(label) => self.labelled(label, location)?, None => self.unlabelled(location)?, } self.index += 1; Ok(()) } fn labelled(&mut self, label: &EcoString, location: SrcSpan) -> Result<(), Error> { if self.field_map.insert(label.clone(), self.index).is_err() { return Err(Error::DuplicateField { label: label.clone(), location, }); }; self.any_labels = true; Ok(()) } fn unlabelled(&mut self, location: SrcSpan) -> Result<(), Error> { if self.any_labels { return Err(Error::UnlabelledAfterlabelled { location }); } Ok(()) } pub fn finish(self) -> Option { self.field_map.into_option() } } ================================================ FILE: compiler-core/src/type_/hydrator.rs ================================================ use super::*; use crate::{ analyse::name::check_name_case, ast::{Layer, TypeAst, TypeAstConstructor, TypeAstFn, TypeAstHole, TypeAstTuple, TypeAstVar}, reference::ReferenceKind, }; use std::sync::Arc; use im::hashmap; /// The Hydrator takes an AST representing a type (i.e. a type annotation /// for a function argument) and returns a Type for that annotation. /// /// If a valid Type cannot be constructed it returns an error. /// /// It keeps track of any type variables created. This is useful for: /// /// - Determining if a generic type variable should be made into an /// unbound type variable during type instantiation. /// - Ensuring that the same type is constructed if the programmer /// uses the same name for a type variable multiple times. /// #[derive(Debug)] pub struct Hydrator { created_type_variables: im::HashMap, /// A rigid type is a generic type that was specified as being generic in /// an annotation. As such it should never be instantiated into an unbound /// variable. This type_id => name map is used for reporting the original /// annotated name on error. rigid_type_names: im::HashMap, permit_new_type_variables: bool, permit_holes: bool, } #[derive(Debug)] pub struct ScopeResetData { created_type_variables: im::HashMap, rigid_type_names: im::HashMap, } impl Default for Hydrator { fn default() -> Self { Self::new() } } impl Hydrator { pub fn new() -> Self { Self { created_type_variables: hashmap![], rigid_type_names: hashmap![], permit_new_type_variables: true, permit_holes: false, } } pub fn named_type_variables(&self) -> im::HashMap { self.created_type_variables.clone() } pub fn open_new_scope(&mut self) -> ScopeResetData { let created_type_variables = self.created_type_variables.clone(); let rigid_type_names = self.rigid_type_names.clone(); ScopeResetData { created_type_variables, rigid_type_names, } } pub fn close_scope(&mut self, data: ScopeResetData) { self.created_type_variables = data.created_type_variables; self.rigid_type_names = data.rigid_type_names; } pub fn disallow_new_type_variables(&mut self) { self.permit_new_type_variables = false } pub fn permit_holes(&mut self, flag: bool) { self.permit_holes = flag } /// A rigid type is a generic type that was specified as being generic in /// an annotation. As such it should never be instantiated into an unbound /// variable. pub fn is_rigid(&self, id: &u64) -> bool { self.rigid_type_names.contains_key(id) } pub fn rigid_names(&self) -> im::HashMap { self.rigid_type_names.clone() } pub fn type_from_option_ast( &mut self, ast: &Option, environment: &mut Environment<'_>, problems: &mut Problems, ) -> Result, Error> { match ast { Some(ast) => self.type_from_ast(ast, environment, problems), None => Ok(environment.new_unbound_var()), } } /// Construct a Type from an AST Type annotation. /// pub fn type_from_ast( &mut self, ast: &TypeAst, environment: &mut Environment<'_>, problems: &mut Problems, ) -> Result, Error> { match ast { TypeAst::Constructor(TypeAstConstructor { location, name_location, module, name, arguments, start_parentheses, }) => { // Hydrate the type argument AST into types let mut argument_types = Vec::with_capacity(arguments.len()); for argument in arguments { let type_ = self.type_from_ast(argument, environment, problems)?; argument_types.push((argument.location(), type_)); } // Look up the constructor let TypeConstructor { parameters, type_: return_type, deprecation, .. } = environment .get_type_constructor(module, name) .map_err(|e| { convert_get_type_constructor_error( e, location, module.as_ref().map(|(_, location)| *location), ) })? .clone(); if let Some((type_module, type_name)) = return_type.named_type_name() { let reference_kind = if module.is_some() { ReferenceKind::Qualified } else if name != &type_name { ReferenceKind::Alias } else { ReferenceKind::Unqualified }; environment.references.register_type_reference( type_module, type_name, name, *name_location, reference_kind, ); } else { environment .references .register_type_reference_in_call_graph(name.clone()); } match deprecation { Deprecation::NotDeprecated => {} Deprecation::Deprecated { message } => { problems.warning(Warning::DeprecatedItem { location: *location, message: message.clone(), layer: Layer::Type, }) } } // Register the type constructor as being used if it is unqualified. // We do not track use of qualified type constructors as they may be // used in another module. if module.is_none() { environment.increment_usage(name); } // Ensure that the correct number of arguments have been given // to the constructor. // // This is a special case for when a type is being called as a // type constructor. For example: `Int()` or `Bool(a, b)` if let Some(start_parentheses) = start_parentheses && parameters.is_empty() { return Err(Error::TypeUsedAsAConstructor { location: SrcSpan::new(*start_parentheses, location.end), name: name.clone(), }); } else if arguments.len() != parameters.len() { return Err(Error::IncorrectTypeArity { location: *location, name: name.clone(), expected: parameters.len(), given: arguments.len(), }); } // Instantiate the constructor type for this specific usage let mut type_vars = hashmap![]; #[allow(clippy::needless_collect)] // Not needless, used for side effects let parameter_types: Vec<_> = parameters .into_iter() .map(|type_| environment.instantiate(type_, &mut type_vars, self)) .collect(); let return_type = environment.instantiate(return_type, &mut type_vars, self); // Unify argument types with instantiated parameter types so that the correct types // are inserted into the return type for (parameter, (location, argument)) in parameter_types.into_iter().zip(argument_types) { unify(parameter, argument).map_err(|e| convert_unify_error(e, location))?; } Ok(return_type) } TypeAst::Tuple(TypeAstTuple { elements, .. }) => Ok(tuple( elements .iter() .map(|type_| self.type_from_ast(type_, environment, problems)) .try_collect()?, )), TypeAst::Fn(TypeAstFn { arguments, return_, .. }) => { let arguments = arguments .iter() .map(|type_| self.type_from_ast(type_, environment, problems)) .try_collect()?; let return_ = self.type_from_ast(return_, environment, problems)?; Ok(fn_(arguments, return_)) } TypeAst::Var(TypeAstVar { name, location }) => { match self.created_type_variables.get_mut(name) { Some(var) => { var.usage_count += 1; Ok(var.type_.clone()) } None if self.permit_new_type_variables => { if let Err(error) = check_name_case(*location, name, Named::TypeVariable) { problems.error(error); } let t = environment.new_generic_var(); let _ = self .rigid_type_names .insert(environment.previous_uid(), name.clone()); environment .names .type_variable_in_scope(environment.previous_uid(), name.clone()); let _ = self.created_type_variables.insert( name.clone(), CreatedTypeVariable { type_: t.clone(), usage_count: 1, }, ); Ok(t) } None => { let hint = match environment.scope.contains_key(name) { true => UnknownTypeHint::ValueInScopeWithSameName, false => UnknownTypeHint::AlternativeTypes( environment.module_types.keys().cloned().collect(), ), }; Err(Error::UnknownType { name: name.clone(), location: *location, hint, }) } } } TypeAst::Hole(TypeAstHole { .. }) if self.permit_holes => { Ok(environment.new_unbound_var()) } TypeAst::Hole(TypeAstHole { location, .. }) => Err(Error::UnexpectedTypeHole { location: *location, }), } } pub fn clear_ridgid_type_names(&mut self) { self.rigid_type_names.clear(); } /// All the type variables that were created but never used. pub fn unused_type_variables(&self) -> impl Iterator { self.created_type_variables .iter() .filter(|(_, var)| var.usage_count == 0) .map(|(name, _)| name) } /// Create a new type variable with the given name. pub fn add_type_variable( &mut self, name: &EcoString, environment: &mut Environment<'_>, ) -> Result, Arc> { let t = environment.new_generic_var(); let v = CreatedTypeVariable { type_: t.clone(), usage_count: 0, }; environment .names .type_variable_in_scope(environment.previous_uid(), name.clone()); match self.created_type_variables.insert(name.clone(), v) { Some(_) => Err(t), None => Ok(t), } } } #[derive(Debug, Clone)] pub struct CreatedTypeVariable { pub type_: Arc, pub usage_count: usize, } ================================================ FILE: compiler-core/src/type_/pattern.rs ================================================ use ecow::eco_format; use hexpm::version::{LowestVersion, Version}; use im::hashmap; use itertools::Itertools; use num_bigint::BigInt; /// Type inference and checking of patterns used in case expressions /// and variables bindings. /// use super::*; use crate::{ analyse::{self, Inferred, name::check_name_case}, ast::{ AssignName, BitArrayOption, BitArraySize, ImplicitCallArgOrigin, Layer, TailPattern, TypedBitArraySize, UntypedPatternBitArraySegment, }, parse::PatternPosition, reference::ReferenceKind, type_::expression::FunctionDefinition, }; use std::sync::Arc; pub struct PatternTyper<'a, 'b> { environment: &'a mut Environment<'b>, current_function: &'a FunctionDefinition, hydrator: &'a Hydrator, mode: PatternMode, initial_pattern_vars: HashSet, /// Variables which have been inferred to a specific variant of their type /// from this pattern-matching. Key is the variable name, Value is the inferred variant index. inferred_variant_variables: HashMap, problems: &'a mut Problems, /// The minimum Gleam version required to compile the typed pattern. pub minimum_required_version: Version, pub error_encountered: bool, /// Variables which have been assigned in the current pattern. We can't /// register them immediately. If we're in a bit array, variables that are /// assigned in the pattern can be used as part of the pattern, e.g. /// `<>`. However, if we are not in a bit array pattern, /// variables cannot be used within the pattern. This is invalid: /// `#(size, <>)`. This is due to a limitation of Erlang. /// /// What we do instead is store the variables in this map. Each variable /// keeps track of whether it is in scope, so that we can correctly detect /// valid/invalid uses. variables: HashMap, /// What kind of pattern we are typing position: PatternPosition, } #[derive(Debug)] struct LocalVariable { location: SrcSpan, origin: VariableOrigin, type_: Arc, usage: Usage, scope: Scope, } impl LocalVariable { fn in_scope(&self) -> bool { match self.scope { Scope::CurrentBitArrayPattern => true, Scope::OtherPattern => false, } } fn was_used(&self) -> bool { match self.usage { Usage::UsedInPattern => true, Usage::UnusedSoFar => false, } } } #[derive(Debug, Clone, Copy)] enum Usage { UsedInPattern, UnusedSoFar, } #[derive(Debug, Clone, Copy)] enum Scope { CurrentBitArrayPattern, OtherPattern, } enum PatternMode { Initial, Alternative(Vec), } impl<'a, 'b> PatternTyper<'a, 'b> { pub fn new( environment: &'a mut Environment<'b>, current_function: &'a FunctionDefinition, hydrator: &'a Hydrator, problems: &'a mut Problems, position: PatternPosition, ) -> Self { Self { environment, current_function, hydrator, mode: PatternMode::Initial, initial_pattern_vars: HashSet::new(), inferred_variant_variables: HashMap::new(), minimum_required_version: Version::new(0, 1, 0), problems, error_encountered: false, variables: HashMap::new(), position, } } fn insert_variable( &mut self, name: &EcoString, type_: Arc, location: SrcSpan, origin: VariableOrigin, ) { self.check_name_case(location, name, Named::Variable); match &mut self.mode { PatternMode::Initial => { // Ensure there are no duplicate variable names in the pattern if self.initial_pattern_vars.contains(name) { self.error(convert_unify_error( UnifyError::DuplicateVarInPattern { name: name.clone() }, location, )); return; } // We no longer have access to the variable from the subject of the pattern // so it doesn't need to be inferred any more. let _ = self.inferred_variant_variables.remove(name); // Record that this variable originated in this pattern so any // following alternative patterns can be checked to ensure they // have the same variables. let _ = self.initial_pattern_vars.insert(name.clone()); _ = self.variables.insert( name.clone(), LocalVariable { location, origin: origin.clone(), type_: type_.clone(), usage: Usage::UnusedSoFar, scope: Scope::CurrentBitArrayPattern, }, ); } PatternMode::Alternative(assigned) => { match self.environment.scope.get_mut(name) { // This variable was defined in the Initial multi-pattern Some(initial) if self.initial_pattern_vars.contains(name) => { if assigned.contains(name) { self.error(convert_unify_error( UnifyError::DuplicateVarInPattern { name: name.clone() }, location, )); return; } assigned.push(name.clone()); let initial_type = initial.type_.clone(); match unify(initial_type, type_.clone()) { Ok(()) => {} Err(error) => { self.problems.error(convert_unify_error(error, location)); self.error_encountered = true; } }; unify_constructor_variants(Arc::make_mut(&mut initial.type_), &type_); } // This variable was not defined in the Initial multi-pattern _ => self.error(convert_unify_error( UnifyError::ExtraVarInAlternativePattern { name: name.clone() }, location, )), } } } } fn set_subject_variable_variant(&mut self, name: EcoString, variant_index: u16) { match &self.mode { PatternMode::Initial => { // If this name is reassigned in the pattern itself, we don't need to infer // it, since it isn't accessible in this scope anymore. if self.initial_pattern_vars.contains(&name) { return; } let variable = self .environment .scope .get(&name) .expect("Variable already exists in the case subjects"); // The type in this scope is now separate from the parent scope, so we // remove any links to ensure that they aren't linked in any way and that // we don't accidentally set the variant of the variable outside of this scope let mut type_ = collapse_links(variable.type_.clone()); Arc::make_mut(&mut type_).set_custom_type_variant(variant_index); // Mark this variable as having been inferred let _ = self .inferred_variant_variables .insert(name.clone(), variant_index); let origin = match &variable.variant { ValueConstructorVariant::LocalVariable { origin, .. } => origin.clone(), ValueConstructorVariant::ModuleConstant { .. } | ValueConstructorVariant::ModuleFn { .. } | ValueConstructorVariant::Record { .. } => VariableOrigin::generated(), }; // This variable is only inferred in this branch of the case expression self.environment.insert_local_variable( name.clone(), variable.definition_location().span, origin, type_, ); } PatternMode::Alternative(_) => { // If we haven't inferred this variable in all alternative patterns so far, // we can't set its variant here let Some(inferred_variant) = self.inferred_variant_variables.get(&name) else { return; }; // If multiple variants are possible in this pattern, we can't infer it at all // and we have to remove the variant index if *inferred_variant != variant_index { // This variable's variant is no longer known let _ = self.inferred_variant_variables.remove(&name); let variable = self .environment .scope .get_mut(&name) .expect("Variable already exists in the case subjects"); Arc::make_mut(&mut variable.type_).generalise_custom_type_variant(); } } } } pub fn infer_alternative_multi_pattern( &mut self, multi_pattern: UntypedMultiPattern, subjects: &[TypedExpr], location: &SrcSpan, ) -> Vec { self.mode = PatternMode::Alternative(vec![]); let typed_multi = self.infer_multi_pattern(multi_pattern, subjects); if self.error_encountered { return typed_multi; } match &self.mode { PatternMode::Initial => panic!("Pattern mode switched from Alternative to Initial"), PatternMode::Alternative(assigned) if assigned.len() < self.initial_pattern_vars.len() => { for name in assigned { let _ = self.initial_pattern_vars.remove(name); } self.error(Error::MissingVarInAlternativePattern { location: *location, // It is safe to use expect here as we checked the length above name: self .initial_pattern_vars .iter() .next() .expect("Getting undefined pattern variable") .clone(), }); typed_multi } PatternMode::Alternative(_) => typed_multi, } } pub fn infer_multi_pattern( &mut self, multi_pattern: UntypedMultiPattern, subjects: &[TypedExpr], ) -> Vec { // If there are N subjects the multi-pattern is expected to be N patterns if subjects.len() != multi_pattern.len() { let first = multi_pattern .first() .expect("multi-pattern to contain at least one pattern"); let last = multi_pattern .last() .expect("multi-pattern to contain at least one pattern"); self.error(Error::IncorrectNumClausePatterns { location: first.location().merge(&last.location()), expected: subjects.len(), given: multi_pattern.len(), }); return Vec::new(); } // Unify each pattern in the multi-pattern with the corresponding subject let mut typed_multi = Vec::with_capacity(multi_pattern.len()); for (pattern, subject) in multi_pattern.into_iter().zip(subjects) { let subject_variable = Self::subject_variable(subject); let pattern = self.unify(pattern, subject.type_(), subject_variable); typed_multi.push(pattern); } self.register_variables(); typed_multi } pub fn infer_single_pattern( &mut self, pattern: UntypedPattern, subject: &TypedExpr, ) -> TypedPattern { let subject_variable = Self::subject_variable(subject); let typed_pattern = self.unify(pattern, subject.type_(), subject_variable); self.register_variables(); typed_pattern } fn subject_variable(subject: &TypedExpr) -> Option { match subject { TypedExpr::Var { constructor: ValueConstructor { // Records should not be considered local variables // See: https://github.com/gleam-lang/gleam/issues/3861 variant: ValueConstructorVariant::Record { .. }, .. }, .. } => None, TypedExpr::Var { name, .. } => Some(name.clone()), // If the subject of a `case` expression is something like // `echo some_variable`, we still want to narrow the variant for // `some_variable`. TypedExpr::Echo { expression: Some(subject), .. } => Self::subject_variable(subject), TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => None, } } /// Register the variables bound in this pattern in the environment fn register_variables(&mut self) { for (name, variable) in std::mem::take(&mut self.variables) { let was_used = variable.was_used(); let LocalVariable { location, origin, type_, usage: _, scope: _, } = variable; // If this variable has already been referenced in another part of // the pattern, we don't need to register it for usage tracking as // it has already been used. if !was_used { self.environment .init_usage(name.clone(), origin.clone(), location, self.problems); } self.environment .insert_local_variable(name, location, origin, type_); } } fn infer_pattern_bit_array( &mut self, mut segments: Vec, location: SrcSpan, ) -> TypedPattern { // Any variables from other parts of the pattern are no longer in scope. // Only variables from the bit array pattern itself can be used. for (_, variable) in self.variables.iter_mut() { variable.scope = Scope::OtherPattern; } let last_segment = segments.pop(); let mut typed_segments: Vec<_> = segments .into_iter() .map(|s| self.infer_pattern_segment(s, false)) .collect(); if let Some(s) = last_segment { let typed_last_segment = self.infer_pattern_segment(s, true); typed_segments.push(typed_last_segment) } TypedPattern::BitArray { location, segments: typed_segments, } } fn infer_pattern_segment( &mut self, mut segment: UntypedPatternBitArraySegment, is_last_segment: bool, ) -> TypedPatternBitArraySegment { // If the segment doesn't have an explicit type option we add a default // one ourselves if the pattern is unambiguous: literal strings are // implicitly considered utf-8 encoded strings, while floats are // implicitly given the float type option. if !segment.has_type_option() { match segment.value_unwrapping_assign() { Pattern::String { location, .. } => { self.track_feature_usage(FeatureKind::UnannotatedUtf8StringSegment, *location); segment.options.push(BitArrayOption::Utf8 { location: SrcSpan::default(), }); } Pattern::Float { location, .. } => { self.track_feature_usage(FeatureKind::UnannotatedFloatSegment, *location); segment.options.push(BitArrayOption::Float { location: SrcSpan::default(), }) } Pattern::Int { .. } | Pattern::Variable { .. } | Pattern::BitArraySize(_) | Pattern::Assign { .. } | Pattern::Discard { .. } | Pattern::List { .. } | Pattern::Constructor { .. } | Pattern::Tuple { .. } | Pattern::BitArray { .. } | Pattern::StringPrefix { .. } | Pattern::Invalid { .. } => (), } } let has_non_utf8_string_option = segment.has_utf16_option() || segment.has_utf32_option(); let options: Vec<_> = segment .options .into_iter() .map(|option| { analyse::infer_bit_array_option(option, |value, type_| { Ok(self.unify(value, type_, None)) }) }) .try_collect() .expect("The function always returns Ok"); self.check_pattern_segment_size_expression(&options); let segment_type = match bit_array::type_options_for_pattern( &options, !is_last_segment, self.environment.target, ) { Ok(type_) => type_, Err(error) => { self.error(Error::BitArraySegmentError { error: error.error, location: error.location, }); self.environment.new_unbound_var() } }; // Track usage of the unaligned bit arrays feature on JavaScript so that // warnings can be emitted if the Gleam version constraint is too low if self.environment.target == Target::JavaScript && !self.current_function.has_javascript_external { for option in options.iter() { match option { // Use of the `bits` segment type BitArrayOption::Bits { location } => { self.track_feature_usage( FeatureKind::JavaScriptUnalignedBitArray, *location, ); } // Int segments that aren't a whole number of bytes BitArrayOption::Size { value, .. } if segment_type.is_int() => match &**value { Pattern::BitArraySize(BitArraySize::Int { location, int_value, .. }) if int_value % 8 != BigInt::ZERO => { self.track_feature_usage( FeatureKind::JavaScriptUnalignedBitArray, *location, ); } Pattern::Int { .. } | Pattern::Float { .. } | Pattern::String { .. } | Pattern::Variable { .. } | Pattern::BitArraySize(_) | Pattern::Assign { .. } | Pattern::Discard { .. } | Pattern::List { .. } | Pattern::Constructor { .. } | Pattern::Tuple { .. } | Pattern::BitArray { .. } | Pattern::StringPrefix { .. } | Pattern::Invalid { .. } => (), }, BitArrayOption::Bytes { .. } | BitArrayOption::Int { .. } | BitArrayOption::Float { .. } | BitArrayOption::Utf8 { .. } | BitArrayOption::Utf16 { .. } | BitArrayOption::Utf32 { .. } | BitArrayOption::Utf8Codepoint { .. } | BitArrayOption::Utf16Codepoint { .. } | BitArrayOption::Utf32Codepoint { .. } | BitArrayOption::Signed { .. } | BitArrayOption::Unsigned { .. } | BitArrayOption::Big { .. } | BitArrayOption::Little { .. } | BitArrayOption::Native { .. } | BitArrayOption::Size { .. } | BitArrayOption::Unit { .. } => (), } } } let type_ = match segment.value.deref() { Pattern::Assign { pattern, .. } if pattern.is_discard() && segment_type.is_string() => { self.error(Error::BitArraySegmentError { error: bit_array::ErrorType::VariableUtfSegmentInPattern, location: segment.location, }); self.environment.new_unbound_var() } Pattern::Variable { .. } if segment_type.is_string() => { self.error(Error::BitArraySegmentError { error: bit_array::ErrorType::VariableUtfSegmentInPattern, location: segment.location, }); self.environment.new_unbound_var() } Pattern::Int { .. } | Pattern::Float { .. } | Pattern::String { .. } | Pattern::Variable { .. } | Pattern::BitArraySize(_) | Pattern::Assign { .. } | Pattern::Discard { .. } | Pattern::List { .. } | Pattern::Constructor { .. } | Pattern::Tuple { .. } | Pattern::BitArray { .. } | Pattern::StringPrefix { .. } | Pattern::Invalid { .. } => segment_type, }; let typed_value = self.unify(*segment.value, type_.clone(), None); match &typed_value { // We can't directly match on the contents of a `Box`, so we must // use a guard here. Pattern::Assign { location, pattern, .. } if pattern.is_variable() => { // It is tricky to generate code on Erlang for a pattern like // `<>`, since assignment patterns are not allowed in // bit array patterns in Erlang. Since there is basically no // reason to ever need to do this anyway, we simply emit an error // here. self.error(Error::DoubleVariableAssignmentInBitArray { location: *location, }); } Pattern::Assign { location, .. } if has_non_utf8_string_option => { self.error(Error::NonUtf8StringAssignmentInBitArray { location: *location, }); } Pattern::Int { .. } | Pattern::Float { .. } | Pattern::String { .. } | Pattern::Variable { .. } | Pattern::BitArraySize(_) | Pattern::Assign { .. } | Pattern::Discard { .. } | Pattern::List { .. } | Pattern::Constructor { .. } | Pattern::Tuple { .. } | Pattern::BitArray { .. } | Pattern::StringPrefix { .. } | Pattern::Invalid { .. } => {} }; BitArraySegment { location: segment.location, value: Box::new(typed_value), options, type_, } } /// When we have an assignment or a case expression we unify the pattern with the /// inferred type of the subject in order to determine what variables to insert /// into the environment (or to detect a type error). /// fn unify( &mut self, pattern: UntypedPattern, type_: Arc, // The name of the variable this pattern matches on, if any. Used for variant inference. // // Example: // ```gleam // case some_wibble { // Wibble(..) -> { // some_wibble.field_only_present_in_wibble // } // _ -> panic // } // ``` // // Here, the pattern `Wibble(..)` has the subject variable `some_wibble`, meaning that // in the inner scope, we can infer that the `some_wibble` variable is the `Wibble` variant // subject_variable: Option, ) -> TypedPattern { match pattern { Pattern::Discard { name, location, .. } => { self.check_name_case(location, &name, Named::Discard); let _ = self .environment .discarded_names .insert(name.clone(), location); Pattern::Discard { type_, name, location, } } Pattern::Invalid { location, .. } => Pattern::Invalid { type_, location }, Pattern::Variable { name, location, origin, .. } => match name.as_str() { "true" | "false" => { self.error(Error::LowercaseBoolPattern { location }); Pattern::Invalid { location, type_ } } _ => { self.insert_variable(&name, type_.clone(), location, origin.clone()); Pattern::Variable { type_, name, location, origin, } } }, Pattern::BitArraySize(size) => { let location = size.location(); match self.bit_array_size(size, type_.clone()) { Ok(size) => Pattern::BitArraySize(size), Err(error) => { self.error(error); Pattern::Invalid { location, type_ } } } } Pattern::StringPrefix { location, left_location, right_location, left_side_string, left_side_assignment, right_side_assignment, } => { // The entire concatenate pattern must be a string self.unify_types(type_, string(), location); // The left hand side may assign a variable, which is the prefix of the string if let Some((left, left_location)) = &left_side_assignment { self.insert_variable( left, string(), *left_location, VariableOrigin { syntax: VariableSyntax::AssignmentPattern, declaration: self.position.to_declaration(), }, ); } // The right hand side may assign a variable, which is the suffix of the string match &right_side_assignment { AssignName::Variable(right) => { self.insert_variable( right, string(), right_location, VariableOrigin { syntax: VariableSyntax::Variable(right.clone()), declaration: self.position.to_declaration(), }, ); } AssignName::Discard(right) => { let _ = self .environment .discarded_names .insert(right.clone(), right_location); self.check_name_case(right_location, right, Named::Discard); } }; Pattern::StringPrefix { location, left_location, right_location, left_side_string, left_side_assignment, right_side_assignment, } } Pattern::Assign { name, pattern, location, } => { let pattern = self.unify(*pattern, type_, subject_variable); if pattern.is_discard() { self.problems.warning(Warning::UnusedDiscardPattern { location, name: name.clone(), }); } self.insert_variable( &name, pattern.type_().clone(), location, VariableOrigin { syntax: VariableSyntax::AssignmentPattern, declaration: self.position.to_declaration(), }, ); Pattern::Assign { name, pattern: Box::new(pattern), location, } } Pattern::Int { location, value, int_value, } => { self.unify_types(type_, int(), location); if self.environment.target == Target::JavaScript && !self.current_function.has_javascript_external { check_javascript_int_safety(&int_value, location, self.problems); } Pattern::Int { location, value, int_value, } } Pattern::Float { location, value, float_value, } => { self.unify_types(type_, float(), location); check_float_safety(float_value, location, self.problems); Pattern::Float { location, value, float_value, } } Pattern::String { location, value } => { self.unify_types(type_, string(), location); Pattern::String { location, value } } Pattern::List { location, elements, tail, .. } => match type_.named_type_arguments( Publicity::Public, PRELUDE_PACKAGE_NAME, PRELUDE_MODULE_NAME, "List", 1, self.environment, ) { Some(arguments) => { let type_ = arguments .first() .expect("Failed to get type argument of List") .clone(); let elements = elements .into_iter() .map(|element| self.unify(element, type_.clone(), None)) .collect(); let type_ = list(type_); let tail = tail.map(|tail| { Box::new(TailPattern { location: tail.location, pattern: self.unify(tail.pattern, type_.clone(), None), }) }); Pattern::List { location, elements, tail, type_, } } None => { self.problems.error(Error::CouldNotUnify { given: list(self.environment.new_unbound_var()), expected: type_.clone(), situation: None, location, }); self.error_encountered = true; Pattern::Invalid { location, type_ } } }, Pattern::Tuple { elements, location } => match collapse_links(type_.clone()).deref() { Type::Tuple { elements: type_elements, } => { if elements.len() != type_elements.len() { self.error(Error::IncorrectArity { labels: vec![], location, context: IncorrectArityContext::Pattern, expected: type_elements.len(), given: elements.len(), }); return Pattern::Invalid { location, type_ }; } let elements = elements .into_iter() .zip(type_elements) .map(|(pattern, type_)| self.unify(pattern, type_.clone(), None)) .collect(); Pattern::Tuple { elements, location } } Type::Var { .. } => { let elements_types: Vec<_> = (0..(elements.len())) .map(|_| self.environment.new_unbound_var()) .collect(); self.unify_types(tuple(elements_types.clone()), type_, location); let elements = elements .into_iter() .zip(elements_types) .map(|(pattern, type_)| self.unify(pattern, type_, None)) .collect(); Pattern::Tuple { elements, location } } Type::Named { .. } | Type::Fn { .. } => { let elements_types = (0..(elements.len())) .map(|_| self.environment.new_unbound_var()) .collect(); self.error(Error::CouldNotUnify { given: tuple(elements_types), expected: type_.clone(), situation: None, location, }); Pattern::Invalid { location, type_ } } }, Pattern::BitArray { location, segments } => { self.unify_types(type_, bit_array(), location); self.infer_pattern_bit_array(segments, location) } Pattern::Constructor { location, module, name_location, name, arguments: mut pattern_arguments, spread, .. } => { // Register the value as seen for detection of unused values self.environment.increment_usage(&name); let constructor = self .environment .get_value_constructor(module.as_ref().map(|(module, _)| module), &name); let constructor = match constructor { Ok(constructor) => constructor, Err(error) => { self.error(convert_get_value_constructor_error( error, location, module.as_ref().map(|(_, location)| *location), )); // If there's no constructor we still try and infer all // the pattern arguments and produce an unknown constructor. return Pattern::Constructor { location, name_location, name, arguments: self.infer_pattern_call_arguments( pattern_arguments, &[], None, ), module, constructor: Inferred::Unknown, spread, type_, }; } }; let mut incorrect_arity_error = false; match constructor.field_map() { // The fun has a field map so labelled arguments may be present and need to be reordered. Some(field_map) => { if let Some(spread_location) = spread { // Using the spread operator when you have already provided variables for all of the // record's fields throws an error if pattern_arguments.len() == field_map.arity as usize { { self.problems.error(Error::UnnecessarySpreadOperator { location: spread_location, arity: field_map.arity as usize, }); self.error_encountered = true; }; } // Insert discard variables to match the unspecified fields // In order to support both positional and labelled arguments we have to insert // them after all positional variables and before the labelled ones. This means // we have calculate that index and then insert() the discards. It would be faster // if we could put the discards anywhere which would let us use push(). // Potential future optimisation. let index_of_first_labelled_arg = pattern_arguments .iter() .position(|argument| argument.label.is_some()) .unwrap_or(pattern_arguments.len()); // In Gleam we can pass in positional unlabelled args to a constructor // even if the field was defined as labelled // // pub type Wibble { // Wibble(Int, two: Int, three: Int, four: Int) // } // Wibble(1, 2, 3, 4) // // When using `..` to ignore some fields the compiler needs to add a // placeholder implicit discard pattern for each one of the ignored // arguments. To give those discards the proper missing label we need to // know how many of the labelled fields were provided as unlabelled. // // That's why we want to keep track of the number of unlabelled argument // that have been supplied to the pattern and all the labels that have // been explicitly supplied. // // Wibble(a, b, four: c, ..) // ┬─── ┬────── // │ ╰ We supplied 1 labelled arg // ╰ We supplied 2 unlabelled args // let supplied_unlabelled_arguments = index_of_first_labelled_arg; let supplied_labelled_arguments = pattern_arguments .iter() .filter_map(|argument| argument.label.clone()) .collect::>(); let constructor_unlabelled_arguments = field_map.arity - field_map.fields.len() as u32; let labelled_arguments_supplied_as_unlabelled = supplied_unlabelled_arguments .saturating_sub(constructor_unlabelled_arguments as usize); let mut missing_labels = field_map .fields .iter() // We take the labels in order of definition in the constructor... .sorted_by_key(|(_, position)| *position) .map(|(label, _)| label.clone()) // ...and then remove the ones that were supplied as unlabelled // positional arguments... .skip(labelled_arguments_supplied_as_unlabelled) // ... lastly we still need to remove all those labels that // were explicitly supplied in the pattern. .filter(|label| !supplied_labelled_arguments.contains(label)); while pattern_arguments.len() < field_map.arity as usize { let new_call_arg = CallArg { value: Pattern::Discard { name: "_".into(), location: spread_location, type_: (), }, location: spread_location, label: missing_labels.next(), implicit: Some(ImplicitCallArgOrigin::PatternFieldSpread), }; pattern_arguments.insert(index_of_first_labelled_arg, new_call_arg); } } if let Err(error) = field_map.reorder( &mut pattern_arguments, location, IncorrectArityContext::Pattern, ) { incorrect_arity_error = true; self.problems.error(error); self.error_encountered = true; } } None => { // The constructor has no field map and so we // error if arguments have been labelled match assert_no_labelled_arguments( &pattern_arguments, UnexpectedLabelledArgKind::RecordConstructorArgument, ) { Ok(()) => {} Err(error) => { self.problems.error(error); self.error_encountered = true; } } if let Some(spread_location) = spread && let ValueConstructorVariant::Record { arity, .. } = &constructor.variant { while pattern_arguments.len() < usize::from(*arity) { pattern_arguments.push(CallArg { value: Pattern::Discard { name: "_".into(), location: spread_location, type_: (), }, location: spread_location, label: None, implicit: Some(ImplicitCallArgOrigin::PatternFieldSpread), }); } }; } } let constructor_type = constructor.type_.clone(); let constructor_deprecation = constructor.deprecation.clone(); let constructor_field_map = constructor.field_map().cloned(); let pattern_constructor = match &constructor.variant { ValueConstructorVariant::Record { name, documentation, module, location, variant_index: constructor_index, .. } => { let constructor_index = *constructor_index; let constructor = PatternConstructor { documentation: documentation.clone(), name: name.clone(), field_map: constructor.field_map().cloned(), module: module.clone(), location: *location, constructor_index, }; if let Some(ref variable_name) = subject_variable { self.set_subject_variable_variant( variable_name.clone(), constructor_index, ); } constructor } ValueConstructorVariant::LocalVariable { .. } | ValueConstructorVariant::ModuleConstant { .. } | ValueConstructorVariant::ModuleFn { .. } => { panic!("Unexpected value constructor type for a constructor pattern.") } }; match constructor_deprecation { Deprecation::NotDeprecated => {} Deprecation::Deprecated { message } => { self.problems.warning(Warning::DeprecatedItem { location, message: message.clone(), layer: Layer::Value, }) } } self.environment.references.register_value_reference( pattern_constructor.module.clone(), pattern_constructor.name.clone(), &name, name_location, if module.is_some() { ReferenceKind::Qualified } else { ReferenceKind::Unqualified }, ); let instantiated_constructor_type = self.environment .instantiate(constructor_type, &mut hashmap![], self.hydrator); match instantiated_constructor_type.deref() { Type::Fn { arguments, return_ } => { self.unify_types(type_.clone(), return_.clone(), location); if let Some((variable_to_infer, inferred_variant)) = subject_variable.zip(return_.custom_type_inferred_variant()) { self.set_subject_variable_variant(variable_to_infer, inferred_variant); } // We're emitting the incorrect arity error only if we haven't emitted // one already. This might happen when we can't reorder the field map // of a constructor because there's not enough labels. if arguments.len() != pattern_arguments.len() && !incorrect_arity_error { self.error(Error::IncorrectArity { labels: vec![], location, context: IncorrectArityContext::Pattern, expected: arguments.len(), given: pattern_arguments.len(), }); } let pattern_arguments = self.infer_pattern_call_arguments( pattern_arguments, arguments, constructor_field_map, ); Pattern::Constructor { location, name_location, name, module, constructor: Inferred::Known(pattern_constructor), arguments: pattern_arguments, spread, type_: return_.clone(), } } Type::Named { inferred_variant, .. } => { self.unify_types(type_, instantiated_constructor_type.clone(), location); if let Some((variable_to_infer, inferred_variant)) = subject_variable.zip(*inferred_variant) { self.set_subject_variable_variant(variable_to_infer, inferred_variant); } if !pattern_arguments.is_empty() { self.error(Error::IncorrectArity { labels: vec![], location, context: IncorrectArityContext::Pattern, expected: 0, given: pattern_arguments.len(), }); } Pattern::Constructor { location, name_location, module, name, arguments: vec![], constructor: Inferred::Known(pattern_constructor), spread, type_: instantiated_constructor_type, } } Type::Var { .. } | Type::Tuple { .. } => { panic!("Unexpected constructor type for a constructor pattern.") } } } } } fn infer_pattern_call_arguments( &mut self, pattern_arguments: Vec>, expected_types: &[Arc], field_map: Option, ) -> Vec> { pattern_arguments .into_iter() .enumerate() .map(|(index, arg)| { let mut index = index; if !arg.is_implicit() && arg.uses_label_shorthand() { self.track_feature_usage(FeatureKind::LabelShorthandSyntax, arg.location); if let Some(field_map) = &field_map && let Some(label) = &arg.label && let Some(field) = field_map.fields.get(label) { index = *field as usize } } let CallArg { value, location, implicit, label, } = arg; let type_ = expected_types .get(index) .cloned() .unwrap_or_else(|| self.environment.new_unbound_var()); let value = self.unify(value, type_, None); CallArg { value, location, implicit, label, } }) .collect() } fn bit_array_size( &mut self, size: BitArraySize<()>, type_: Arc, ) -> Result { let typed_size = match size { BitArraySize::Int { location, value, int_value, } => { self.unify_types(type_, int(), location); if self.environment.target == Target::JavaScript && !self.current_function.has_javascript_external { check_javascript_int_safety(&int_value, location, self.problems); } BitArraySize::Int { location, value, int_value, } } BitArraySize::Variable { name, location, .. } => { let constructor = match self.variables.get_mut(&name) { // If we've bound a variable in the current bit array pattern, // we want to use that. Some(variable) if variable.in_scope() => { variable.usage = Usage::UsedInPattern; ValueConstructor::local_variable( variable.location, variable.origin.clone(), variable.type_.clone(), ) } // Otherwise, we check the local scope. Some(_) | None => match self.environment.get_variable(&name) { Some(constructor) => constructor.clone(), None => { return Err(Error::UnknownVariable { location, name: name.clone(), variables: self.environment.local_value_names(), discarded_location: self .environment .discarded_names .get(&eco_format!("_{name}")) .cloned(), type_with_name_in_scope: self .environment .module_types .keys() .any(|type_| type_ == &name), }); } }, }; self.environment.increment_usage(&name); let type_ = self.environment.instantiate( constructor.type_.clone(), &mut hashmap![], self.hydrator, ); self.unify_types(int(), type_.clone(), location); BitArraySize::Variable { name, location, constructor: Some(Box::new(constructor)), type_, } } BitArraySize::BinaryOperator { location, operator, left, right, } => BitArraySize::BinaryOperator { location, operator, left: Box::new(self.bit_array_size(*left, type_.clone())?), right: Box::new(self.bit_array_size(*right, type_)?), }, BitArraySize::Block { location, inner } => BitArraySize::Block { location, inner: Box::new(self.bit_array_size(*inner, type_)?), }, }; Ok(typed_size) } fn check_name_case(&mut self, location: SrcSpan, name: &EcoString, kind: Named) { if let Err(error) = check_name_case(location, name, kind) { self.problems.error(error); } } fn unify_types(&mut self, first: Arc, second: Arc, location: SrcSpan) { match unify(first, second) { Ok(()) => {} Err(error) => self.error(convert_unify_error(error, location)), } } fn error(&mut self, error: Error) { self.problems.error(error); self.error_encountered = true; } fn track_feature_usage(&mut self, feature_kind: FeatureKind, location: SrcSpan) { let minimum_required_version = feature_kind.required_version(); // Then if the required version is not in the specified version for the // range we emit a warning highlighting the usage of the feature. if let Some(gleam_version) = &self.environment.gleam_version && let Some(lowest_allowed_version) = gleam_version.lowest_version() { // There is a version in the specified range that is lower than // the one required by this feature! This means that the // specified range is wrong and would allow someone to run a // compiler that is too old to know of this feature. if minimum_required_version > lowest_allowed_version { self.problems .warning(Warning::FeatureRequiresHigherGleamVersion { location, feature_kind, minimum_required_version: minimum_required_version.clone(), wrongfully_allowed_version: lowest_allowed_version, }) } } if minimum_required_version > self.minimum_required_version { self.minimum_required_version = minimum_required_version; } } /// Checks if one of the options is a size option using an expression. /// This needs to be tracked as it was introduced in Gleam 1.12.0. fn check_pattern_segment_size_expression(&mut self, options: &[BitArrayOption]) { let Some(size_value) = options.iter().find_map(|option| match option { BitArrayOption::Size { value, .. } => Some(value), BitArrayOption::Bytes { .. } | BitArrayOption::Int { .. } | BitArrayOption::Float { .. } | BitArrayOption::Bits { .. } | BitArrayOption::Utf8 { .. } | BitArrayOption::Utf16 { .. } | BitArrayOption::Utf32 { .. } | BitArrayOption::Utf8Codepoint { .. } | BitArrayOption::Utf16Codepoint { .. } | BitArrayOption::Utf32Codepoint { .. } | BitArrayOption::Signed { .. } | BitArrayOption::Unsigned { .. } | BitArrayOption::Big { .. } | BitArrayOption::Little { .. } | BitArrayOption::Native { .. } | BitArrayOption::Unit { .. } => None, }) else { return; }; let Pattern::BitArraySize(size) = size_value.as_ref() else { return; }; match size { BitArraySize::Int { .. } | BitArraySize::Variable { .. } => (), BitArraySize::BinaryOperator { location, .. } | BitArraySize::Block { location, .. } => { self.track_feature_usage(FeatureKind::ExpressionInSegmentSize, *location) } } } } /// Unifies the variants of two variables declared in alternative patterns. /// /// This ensures that constructor variant information is only stored if /// all alternate pattern variables have the same variant. For example: /// /// ```gleam /// type Wibble { /// Wibble(wibble: Int, wobble: Float) /// Wobble(wubble: String, wooble, Bool) /// } /// /// case some_value { /// Wibble(..) as wibble | Wobble(..) as wibble -> /// Wibble(..wibble, wobble: 1.3) /// } /// ``` /// /// The `wibble` variable will not have the constructor variant stored, /// since it can be one of two possible variants. /// fn unify_constructor_variants(into: &mut Type, from: &Type) { match (into, from) { ( Type::Named { inferred_variant: into_index, .. }, Type::Named { inferred_variant: from_index, .. }, ) if from_index != into_index => *into_index = None, // If the variants are the same, or they aren't both named types, // no modifications are needed _ => {} } } ================================================ FILE: compiler-core/src/type_/pipe.rs ================================================ use self::expression::CallKind; use super::*; use crate::ast::{ FunctionLiteralKind, ImplicitCallArgOrigin, PIPE_VARIABLE, PipelineAssignmentKind, Statement, TypedPipelineAssignment, UntypedExpr, }; use vec1::Vec1; #[derive(Debug)] pub(crate) struct PipeTyper<'a, 'b, 'c> { size: usize, argument_type: Arc, argument_location: SrcSpan, location: SrcSpan, first_value: TypedPipelineAssignment, assignments: Vec<(TypedPipelineAssignment, PipelineAssignmentKind)>, expr_typer: &'a mut ExprTyper<'b, 'c>, } impl<'a, 'b, 'c> PipeTyper<'a, 'b, 'c> { fn new(expr_typer: &'a mut ExprTyper<'b, 'c>, size: usize, first: TypedExpr, end: u32) -> Self { let first_type = first.type_(); let first_location = first.location(); let first_value = new_pipeline_assignment(expr_typer, first); Self { size, expr_typer, argument_type: first_type, argument_location: first_location, location: SrcSpan { start: first_location.start, end, }, assignments: Vec::with_capacity(size), first_value, } } pub fn infer( expr_typer: &'a mut ExprTyper<'b, 'c>, expressions: Vec1, ) -> TypedExpr { // The scope is reset as pipelines are rewritten into a series of // assignments, and we don't want these variables to leak out of the // pipeline. let scope = expr_typer.environment.scope.clone(); let result = PipeTyper::run(expr_typer, expressions); expr_typer.environment.scope = scope; result } fn run(expr_typer: &'a mut ExprTyper<'b, 'c>, expressions: Vec1) -> TypedExpr { let size = expressions.len(); let end = expressions.last().location().end; let mut expressions = expressions.into_iter(); let first = expressions.next().expect("Empty pipeline in typer"); let first = expr_typer.infer(first); Self::new(expr_typer, size, first, end).infer_expressions(expressions) } fn infer_expressions( mut self, expressions: impl IntoIterator, ) -> TypedExpr { let (finally, finally_kind) = self.infer_each_expression(expressions); let assignments = std::mem::take(&mut self.assignments); TypedExpr::Pipeline { location: self.location, first_value: self.first_value, assignments, finally: Box::new(finally), finally_kind, } } fn infer_each_expression( &mut self, expressions: impl IntoIterator, ) -> (TypedExpr, PipelineAssignmentKind) { let mut finally = None; for (i, call) in expressions.into_iter().enumerate() { if self.expr_typer.previous_panics { self.expr_typer .warn_for_unreachable_code(call.location(), PanicPosition::PreviousExpression); } self.warn_if_call_first_argument_is_hole(&call); let (kind, call) = match call { func @ UntypedExpr::Fn { location, kind, .. } => { let (func, arguments, return_type) = self.expr_typer.do_infer_call( func, vec![self.untyped_left_hand_value_variable_call_argument()], location, CallKind::Function, ); self.expr_typer.purity = self.expr_typer.purity.merge(func.called_function_purity()); let kind = match kind { FunctionLiteralKind::Capture { hole } => { PipelineAssignmentKind::Hole { hole } } FunctionLiteralKind::Anonymous { .. } | FunctionLiteralKind::Use { .. } => { PipelineAssignmentKind::FunctionCall } }; ( kind, TypedExpr::Call { location, arguments, type_: return_type, fun: Box::new(func), }, ) } // left |> right(..args) // ^^^^^ This is `fun` UntypedExpr::Call { fun, arguments, location, .. } => { let fun = self.expr_typer.infer(*fun); match fun.type_().fn_types() { // Rewrite as right(..args)(left) Some((fn_arguments, _)) if fn_arguments.len() == arguments.len() => { // We are calling the return value of another function. // Without lifting purity tracking into the type system, // we have no idea whether it's pure or not! self.expr_typer.purity = self.expr_typer.purity.merge(Purity::Unknown); ( PipelineAssignmentKind::FunctionCall, self.infer_apply_to_call_pipe(fun, arguments, location), ) } // Rewrite as right(left, ..args) _ => { self.expr_typer.purity = self.expr_typer.purity.merge(fun.called_function_purity()); ( PipelineAssignmentKind::FirstArgument { second_argument: arguments.first().map(|arg| arg.location), }, self.infer_insert_pipe(fun, arguments, location), ) } } } UntypedExpr::Echo { location, keyword_end: _, expression: None, message, } => { self.expr_typer.environment.echo_found = true; self.expr_typer.purity = Purity::Impure; // An echo that is not followed by an expression that is // used as a pipeline's step is just like the identity // function. // So it gets the type of the value coming from the previous // step of the pipeline. ( PipelineAssignmentKind::Echo, TypedExpr::Echo { location, expression: None, type_: self.argument_type.clone(), message: message.map(|message| { Box::new(self.expr_typer.infer_and_unify(*message, string())) }), }, ) } // right(left) UntypedExpr::Int { .. } | UntypedExpr::Float { .. } | UntypedExpr::String { .. } | UntypedExpr::Block { .. } | UntypedExpr::Var { .. } | UntypedExpr::List { .. } | UntypedExpr::BinOp { .. } | UntypedExpr::PipeLine { .. } | UntypedExpr::Case { .. } | UntypedExpr::FieldAccess { .. } | UntypedExpr::Tuple { .. } | UntypedExpr::TupleIndex { .. } | UntypedExpr::Todo { .. } | UntypedExpr::Panic { .. } | UntypedExpr::Echo { .. } | UntypedExpr::BitArray { .. } | UntypedExpr::RecordUpdate { .. } | UntypedExpr::NegateBool { .. } | UntypedExpr::NegateInt { .. } => ( PipelineAssignmentKind::FunctionCall, self.infer_apply_pipe(call), ), }; if i + 2 == self.size { finally = Some((call, kind)); } else { self.push_assignment(call, kind); } } finally.expect("Empty pipeline in typer") } /// Create a call argument that can be used to refer to the value on the /// left hand side of the pipe fn typed_left_hand_value_variable_call_argument(&self) -> CallArg { CallArg { label: None, location: self.argument_location, value: self.typed_left_hand_value_variable(), // This argument is given implicitly by the pipe, not explicitly by // the programmer. implicit: Some(ImplicitCallArgOrigin::Pipe), } } /// Create a call argument that can be used to refer to the value on the /// left hand side of the pipe fn untyped_left_hand_value_variable_call_argument(&self) -> CallArg { CallArg { label: None, location: self.argument_location, value: self.untyped_left_hand_value_variable(), // This argument is given implicitly by the pipe, not explicitly by // the programmer. implicit: Some(ImplicitCallArgOrigin::Pipe), } } /// Create a variable that can be used to refer to the value on the left /// hand side of the pipe fn typed_left_hand_value_variable(&self) -> TypedExpr { TypedExpr::Var { location: self.argument_location, name: PIPE_VARIABLE.into(), constructor: ValueConstructor::local_variable( self.argument_location, VariableOrigin::generated(), self.argument_type.clone(), ), } } /// Create a variable that can be used to refer to the value on the left /// hand side of the pipe fn untyped_left_hand_value_variable(&self) -> UntypedExpr { UntypedExpr::Var { location: self.argument_location, name: PIPE_VARIABLE.into(), } } /// Push an assignment for the value on the left hand side of the pipe fn push_assignment(&mut self, expression: TypedExpr, kind: PipelineAssignmentKind) { self.argument_type = expression.type_(); self.argument_location = expression.location(); let assignment = new_pipeline_assignment(self.expr_typer, expression); self.assignments.push((assignment, kind)); } /// Attempt to infer a |> b(..c) as b(..c)(a) fn infer_apply_to_call_pipe( &mut self, function: TypedExpr, arguments: Vec>, location: SrcSpan, ) -> TypedExpr { let (function, arguments, type_) = self.expr_typer.do_infer_call_with_known_fun( function, arguments, location, CallKind::Function, ); let function = TypedExpr::Call { location, type_, arguments, fun: Box::new(function), }; let arguments = vec![self.untyped_left_hand_value_variable_call_argument()]; // TODO: use `.with_unify_error_situation(UnifyErrorSituation::PipeTypeMismatch)` // This will require the typing of the arguments to be lifted up out of // the function below. If it is not we don't know if the error comes // from incorrect usage of the pipe or if it originates from the // argument expressions. let (function, arguments, type_) = self.expr_typer.do_infer_call_with_known_fun( function, arguments, location, CallKind::Function, ); TypedExpr::Call { location, type_, arguments, fun: Box::new(function), } } /// Attempt to infer a |> b(c) as b(a, c) fn infer_insert_pipe( &mut self, function: TypedExpr, mut arguments: Vec>, location: SrcSpan, ) -> TypedExpr { arguments.insert(0, self.untyped_left_hand_value_variable_call_argument()); // TODO: use `.with_unify_error_situation(UnifyErrorSituation::PipeTypeMismatch)` // This will require the typing of the arguments to be lifted up out of // the function below. If it is not we don't know if the error comes // from incorrect usage of the pipe or if it originates from the // argument expressions. let (fun, arguments, type_) = self.expr_typer.do_infer_call_with_known_fun( function, arguments, location, CallKind::Function, ); TypedExpr::Call { location, type_, arguments, fun: Box::new(fun), } } /// Attempt to infer a |> b as b(a) /// b is the `function` argument. fn infer_apply_pipe(&mut self, function: UntypedExpr) -> TypedExpr { let function_location = function.location(); let function = Box::new(self.expr_typer.infer(function)); self.expr_typer.purity = self .expr_typer .purity .merge(function.called_function_purity()); let return_type = self.expr_typer.new_unbound_var(); // Ensure that the function accepts one argument of the correct type let unification_result = unify( function.type_(), fn_(vec![self.argument_type.clone()], return_type.clone()), ); match unification_result { Ok(_) => (), Err(error) => { let error = if self.check_if_pipe_type_mismatch(&error) { convert_unify_error(error, function.location()) .with_unify_error_situation(UnifyErrorSituation::PipeTypeMismatch) } else { convert_unify_error(flip_unify_error(error), function.location()) }; self.expr_typer.problems.error(error); } }; TypedExpr::Call { location: function_location, type_: return_type, fun: function, arguments: vec![self.typed_left_hand_value_variable_call_argument()], } } fn check_if_pipe_type_mismatch(&mut self, error: &UnifyError) -> bool { let types = match error { UnifyError::CouldNotUnify { expected, given, .. } => (expected.as_ref(), given.as_ref()), UnifyError::ExtraVarInAlternativePattern { .. } | UnifyError::MissingVarInAlternativePattern { .. } | UnifyError::DuplicateVarInPattern { .. } | UnifyError::RecursiveType => return false, }; match types { (Type::Fn { arguments: a, .. }, Type::Fn { arguments: b, .. }) if a.len() == b.len() => { match (a.first(), b.first()) { (Some(a), Some(b)) => unify(a.clone(), b.clone()).is_err(), _ => false, } } _ => false, } } fn warn_if_call_first_argument_is_hole(&mut self, call: &UntypedExpr) { if let UntypedExpr::Fn { kind, body, .. } = &call && kind.is_capture() && let Statement::Expression(UntypedExpr::Call { arguments, .. }) = body.first() { match arguments.as_slice() { // If the first argument is labelled, we don't warn the user // as they might be intentionally adding it to provide more // information about exactly which argument is being piped into. [first] | [first, ..] if first.is_capture_hole() && first.label.is_none() => self .expr_typer .problems .warning(Warning::RedundantPipeFunctionCapture { location: first.location, }), _ => (), } } } } fn new_pipeline_assignment( expr_typer: &mut ExprTyper<'_, '_>, expression: TypedExpr, ) -> TypedPipelineAssignment { let location = expression.location(); // Insert the variable for use in type checking the rest of the pipeline expr_typer.environment.insert_local_variable( PIPE_VARIABLE.into(), location, VariableOrigin::generated(), expression.type_(), ); TypedPipelineAssignment { location, name: PIPE_VARIABLE.into(), value: Box::new(expression), } } ================================================ FILE: compiler-core/src/type_/prelude.rs ================================================ use hexpm::version::Version; use strum::{EnumIter, IntoEnumIterator}; use crate::{ ast::{Publicity, SrcSpan}, build::Origin, line_numbers::LineNumbers, uid::UniqueIdGenerator, }; use super::{ ModuleInterface, Opaque, References, Type, TypeConstructor, TypeValueConstructor, TypeValueConstructorField, TypeVar, TypeVariantConstructors, ValueConstructor, ValueConstructorVariant, }; use crate::type_::Deprecation::NotDeprecated; use std::{cell::RefCell, collections::HashMap, sync::Arc}; const BIT_ARRAY: &str = "BitArray"; const BOOL: &str = "Bool"; const FLOAT: &str = "Float"; const INT: &str = "Int"; pub const LIST: &str = "List"; const NIL: &str = "Nil"; const RESULT: &str = "Result"; const STRING: &str = "String"; const UTF_CODEPOINT: &str = "UtfCodepoint"; pub const PRELUDE_PACKAGE_NAME: &str = ""; pub const PRELUDE_MODULE_NAME: &str = "gleam"; pub fn is_prelude_module(module: &str) -> bool { module == PRELUDE_MODULE_NAME } #[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter)] pub enum PreludeType { BitArray, Bool, Float, Int, List, Nil, Result, String, UtfCodepoint, } impl PreludeType { pub fn name(self) -> &'static str { match self { PreludeType::BitArray => BIT_ARRAY, PreludeType::Bool => BOOL, PreludeType::Float => FLOAT, PreludeType::Int => INT, PreludeType::List => LIST, PreludeType::Nil => NIL, PreludeType::Result => RESULT, PreludeType::String => STRING, PreludeType::UtfCodepoint => UTF_CODEPOINT, } } } pub fn int() -> Arc { Arc::new(Type::Named { publicity: Publicity::Public, name: INT.into(), module: PRELUDE_MODULE_NAME.into(), package: PRELUDE_PACKAGE_NAME.into(), arguments: vec![], inferred_variant: None, }) } pub fn float() -> Arc { Arc::new(Type::Named { arguments: vec![], publicity: Publicity::Public, name: FLOAT.into(), module: PRELUDE_MODULE_NAME.into(), package: PRELUDE_PACKAGE_NAME.into(), inferred_variant: None, }) } pub fn bool() -> Arc { bool_with_variant(None) } pub fn bool_with_variant(variant: Option) -> Arc { let variant = match variant { Some(true) => Some(0), Some(false) => Some(1), None => None, }; Arc::new(Type::Named { arguments: vec![], publicity: Publicity::Public, name: BOOL.into(), module: PRELUDE_MODULE_NAME.into(), package: PRELUDE_PACKAGE_NAME.into(), inferred_variant: variant, }) } pub fn string() -> Arc { Arc::new(Type::Named { arguments: vec![], publicity: Publicity::Public, name: STRING.into(), module: PRELUDE_MODULE_NAME.into(), package: PRELUDE_PACKAGE_NAME.into(), inferred_variant: None, }) } pub fn nil() -> Arc { Arc::new(Type::Named { arguments: vec![], publicity: Publicity::Public, name: NIL.into(), module: PRELUDE_MODULE_NAME.into(), package: PRELUDE_PACKAGE_NAME.into(), inferred_variant: None, }) } pub fn list(t: Arc) -> Arc { Arc::new(Type::Named { publicity: Publicity::Public, name: LIST.into(), module: PRELUDE_MODULE_NAME.into(), package: PRELUDE_PACKAGE_NAME.into(), arguments: vec![t], inferred_variant: None, }) } pub fn result(a: Arc, e: Arc) -> Arc { result_with_variant(a, e, None) } fn result_with_variant(a: Arc, e: Arc, variant_index: Option) -> Arc { Arc::new(Type::Named { publicity: Publicity::Public, name: RESULT.into(), module: PRELUDE_MODULE_NAME.into(), package: PRELUDE_PACKAGE_NAME.into(), arguments: vec![a, e], inferred_variant: variant_index, }) } pub fn tuple(elements: Vec>) -> Arc { Arc::new(Type::Tuple { elements }) } pub fn fn_(arguments: Vec>, return_: Arc) -> Arc { Arc::new(Type::Fn { return_, arguments }) } pub fn named( package: &str, module: &str, name: &str, publicity: Publicity, arguments: Vec>, ) -> Arc { Arc::new(Type::Named { publicity, package: package.into(), module: module.into(), name: name.into(), arguments, inferred_variant: None, }) } pub fn bit_array() -> Arc { Arc::new(Type::Named { arguments: vec![], publicity: Publicity::Public, name: BIT_ARRAY.into(), module: PRELUDE_MODULE_NAME.into(), package: PRELUDE_PACKAGE_NAME.into(), inferred_variant: None, }) } pub fn utf_codepoint() -> Arc { Arc::new(Type::Named { arguments: vec![], publicity: Publicity::Public, name: UTF_CODEPOINT.into(), module: PRELUDE_MODULE_NAME.into(), package: PRELUDE_PACKAGE_NAME.into(), inferred_variant: None, }) } pub fn generic_var(id: u64) -> Arc { Arc::new(Type::Var { type_: Arc::new(RefCell::new(TypeVar::Generic { id })), }) } pub fn unbound_var(id: u64) -> Arc { Arc::new(Type::Var { type_: Arc::new(RefCell::new(TypeVar::Unbound { id })), }) } #[cfg(test)] pub fn link(type_: Arc) -> Arc { Arc::new(Type::Var { type_: Arc::new(RefCell::new(TypeVar::Link { type_ })), }) } pub fn build_prelude(ids: &UniqueIdGenerator) -> ModuleInterface { let value = |variant, type_| ValueConstructor { publicity: Publicity::Public, deprecation: NotDeprecated, variant, type_, }; let mut prelude = ModuleInterface { name: PRELUDE_MODULE_NAME.into(), package: "".into(), origin: Origin::Src, types: HashMap::new(), types_value_constructors: HashMap::new(), values: HashMap::new(), accessors: HashMap::new(), is_internal: false, warnings: vec![], // prelude doesn't have real src src_path: "".into(), // prelude doesn't have real line numbers line_numbers: LineNumbers::new(""), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), }; for t in PreludeType::iter() { match t { PreludeType::BitArray => { let v = TypeConstructor { origin: Default::default(), parameters: vec![], type_: bit_array(), module: PRELUDE_MODULE_NAME.into(), publicity: Publicity::Public, deprecation: NotDeprecated, documentation: None, }; let _ = prelude.types.insert(BIT_ARRAY.into(), v.clone()); } PreludeType::Bool => { let _ = prelude.types_value_constructors.insert( BOOL.into(), TypeVariantConstructors { type_parameters_ids: vec![], variants: vec![ TypeValueConstructor { name: "True".into(), parameters: vec![], documentation: None, }, TypeValueConstructor { name: "False".into(), parameters: vec![], documentation: None, }, ], opaque: Opaque::NotOpaque, }, ); let _ = prelude.values.insert( "True".into(), value( ValueConstructorVariant::Record { documentation: None, module: PRELUDE_MODULE_NAME.into(), name: "True".into(), field_map: None, arity: 0, location: SrcSpan::default(), variants_count: 2, variant_index: 0, }, bool_with_variant(Some(true)), ), ); let _ = prelude.values.insert( "False".into(), value( ValueConstructorVariant::Record { documentation: None, module: PRELUDE_MODULE_NAME.into(), name: "False".into(), field_map: None, arity: 0, location: SrcSpan::default(), variants_count: 2, variant_index: 1, }, bool_with_variant(Some(false)), ), ); let _ = prelude.types.insert( BOOL.into(), TypeConstructor { origin: Default::default(), parameters: vec![], type_: bool(), module: PRELUDE_MODULE_NAME.into(), publicity: Publicity::Public, deprecation: NotDeprecated, documentation: None, }, ); } PreludeType::Float => { let _ = prelude.types.insert( FLOAT.into(), TypeConstructor { origin: Default::default(), parameters: vec![], type_: float(), module: PRELUDE_MODULE_NAME.into(), publicity: Publicity::Public, deprecation: NotDeprecated, documentation: None, }, ); } PreludeType::Int => { let _ = prelude.types.insert( INT.into(), TypeConstructor { parameters: vec![], type_: int(), origin: Default::default(), module: PRELUDE_MODULE_NAME.into(), publicity: Publicity::Public, deprecation: NotDeprecated, documentation: None, }, ); } PreludeType::List => { let list_parameter = generic_var(ids.next()); let _ = prelude.types.insert( LIST.into(), TypeConstructor { origin: Default::default(), parameters: vec![list_parameter.clone()], type_: list(list_parameter), module: PRELUDE_MODULE_NAME.into(), publicity: Publicity::Public, deprecation: NotDeprecated, documentation: None, }, ); } PreludeType::Nil => { let _ = prelude.values.insert( NIL.into(), value( ValueConstructorVariant::Record { documentation: None, module: PRELUDE_MODULE_NAME.into(), name: NIL.into(), arity: 0, field_map: None, location: SrcSpan::default(), variants_count: 1, variant_index: 0, }, nil(), ), ); let _ = prelude.types.insert( NIL.into(), TypeConstructor { origin: Default::default(), parameters: vec![], type_: nil(), module: PRELUDE_MODULE_NAME.into(), publicity: Publicity::Public, deprecation: NotDeprecated, documentation: None, }, ); let _ = prelude.types_value_constructors.insert( NIL.into(), TypeVariantConstructors { type_parameters_ids: vec![], variants: vec![TypeValueConstructor { name: "Nil".into(), parameters: vec![], documentation: None, }], opaque: Opaque::NotOpaque, }, ); } PreludeType::Result => { let result_value_id = ids.next(); let result_error_id = ids.next(); let result_value = generic_var(result_value_id); let result_error = generic_var(result_error_id); let _ = prelude.types.insert( RESULT.into(), TypeConstructor { origin: Default::default(), parameters: vec![result_value.clone(), result_error.clone()], type_: result(result_value.clone(), result_error.clone()), module: PRELUDE_MODULE_NAME.into(), publicity: Publicity::Public, deprecation: NotDeprecated, documentation: None, }, ); let _ = prelude.types_value_constructors.insert( RESULT.into(), TypeVariantConstructors { type_parameters_ids: vec![result_value_id, result_error_id], variants: vec![ TypeValueConstructor { name: "Ok".into(), parameters: vec![TypeValueConstructorField { type_: result_value, label: None, documentation: None, }], documentation: None, }, TypeValueConstructor { name: "Error".into(), parameters: vec![TypeValueConstructorField { type_: result_error, label: None, documentation: None, }], documentation: None, }, ], opaque: Opaque::NotOpaque, }, ); let ok = generic_var(ids.next()); let error = generic_var(ids.next()); let _ = prelude.values.insert( "Ok".into(), value( ValueConstructorVariant::Record { documentation: None, module: PRELUDE_MODULE_NAME.into(), name: "Ok".into(), field_map: None, arity: 1, location: SrcSpan::default(), variants_count: 2, variant_index: 0, }, fn_(vec![ok.clone()], result_with_variant(ok, error, Some(0))), ), ); let ok = generic_var(ids.next()); let error = generic_var(ids.next()); let _ = prelude.values.insert( "Error".into(), value( ValueConstructorVariant::Record { documentation: None, module: PRELUDE_MODULE_NAME.into(), name: "Error".into(), field_map: None, arity: 1, location: SrcSpan::default(), variants_count: 2, variant_index: 1, }, fn_(vec![error.clone()], result_with_variant(ok, error, Some(1))), ), ); } PreludeType::String => { let _ = prelude.types.insert( STRING.into(), TypeConstructor { origin: Default::default(), parameters: vec![], type_: string(), module: PRELUDE_MODULE_NAME.into(), publicity: Publicity::Public, deprecation: NotDeprecated, documentation: None, }, ); } PreludeType::UtfCodepoint => { let _ = prelude.types.insert( UTF_CODEPOINT.into(), TypeConstructor { origin: Default::default(), parameters: vec![], type_: utf_codepoint(), module: PRELUDE_MODULE_NAME.into(), publicity: Publicity::Public, deprecation: NotDeprecated, documentation: None, }, ); let _ = prelude.types_value_constructors.insert( UTF_CODEPOINT.into(), TypeVariantConstructors { type_parameters_ids: vec![], variants: vec![], opaque: Opaque::NotOpaque, }, ); } } } prelude } ================================================ FILE: compiler-core/src/type_/pretty.rs ================================================ use super::{Type, TypeVar}; use crate::{ docvec, pretty::{nil, *}, }; use ecow::EcoString; use std::sync::Arc; #[cfg(test)] use super::*; #[cfg(test)] use std::cell::RefCell; #[cfg(test)] use pretty_assertions::assert_eq; const INDENT: isize = 2; #[derive(Debug, Default)] pub struct Printer { names: im::HashMap, uid: u64, // A mapping of printd type names to the module that they are defined in. printed_types: im::HashMap, } impl Printer { pub fn new() -> Self { Default::default() } pub fn with_names(&mut self, names: im::HashMap) { self.names = names; } /// Render a Type as a well formatted string. /// pub fn pretty_print(&mut self, type_: &Type, initial_indent: usize) -> String { let mut buffer = String::with_capacity(initial_indent); for _ in 0..initial_indent { buffer.push(' '); } buffer .to_doc() .append(self.print(type_)) .nest(initial_indent as isize) .to_pretty_string(80) } // TODO: have this function return a Document that borrows from the Type. // Is this possible? The lifetime would have to go through the Arc> // for TypeVar::Link'd types. pub fn print<'a>(&mut self, type_: &Type) -> Document<'a> { match type_ { Type::Named { name, arguments, module, .. } => { let doc = if self.name_clashes_if_unqualified(name, module) { qualify_type_name(module, name) } else { let _ = self.printed_types.insert(name.clone(), module.clone()); name.to_doc() }; if arguments.is_empty() { doc } else { doc.append("(") .append(self.arguments_to_gleam_doc(arguments)) .append(")") } } Type::Fn { arguments, return_ } => "fn(" .to_doc() .append(self.arguments_to_gleam_doc(arguments)) .append(") ->") .append( break_("", " ") .append(self.print(return_)) .nest(INDENT) .group(), ), Type::Var { type_, .. } => self.type_var_doc(&type_.borrow()), Type::Tuple { elements, .. } => { self.arguments_to_gleam_doc(elements).surround("#(", ")") } } } fn name_clashes_if_unqualified(&mut self, type_: &EcoString, module: &str) -> bool { match self.printed_types.get(type_) { None => false, Some(previous_module) if module == previous_module => false, Some(_different_module) => true, } } fn type_var_doc<'a>(&mut self, type_: &TypeVar) -> Document<'a> { match type_ { TypeVar::Link { type_, .. } => self.print(type_), TypeVar::Unbound { id, .. } | TypeVar::Generic { id, .. } => self.generic_type_var(*id), } } pub fn generic_type_var<'a>(&mut self, id: u64) -> Document<'a> { match self.names.get(&id) { Some(n) => { let _ = self.printed_types.insert(n.clone(), "".into()); n.to_doc() } None => { let n = self.next_letter(); let _ = self.names.insert(id, n.clone()); let _ = self.printed_types.insert(n.clone(), "".into()); n.to_doc() } } } fn next_letter(&mut self) -> EcoString { let alphabet_length = 26; let char_offset = 97; let mut chars = vec![]; let mut n; let mut rest = self.uid; loop { n = rest % alphabet_length; rest /= alphabet_length; chars.push((n as u8 + char_offset) as char); if rest == 0 { break; } rest -= 1 } self.uid += 1; chars.into_iter().rev().collect() } fn arguments_to_gleam_doc(&mut self, arguments: &[Arc]) -> Document<'static> { if arguments.is_empty() { return nil(); } let arguments = join( arguments.iter().map(|type_| self.print(type_).group()), break_(",", ", "), ); break_("", "") .append(arguments) .nest(INDENT) .append(break_(",", "")) .group() } } fn qualify_type_name(module: &str, type_name: &str) -> Document<'static> { docvec![EcoString::from(module), ".", EcoString::from(type_name)] } #[test] fn next_letter_test() { let mut printer = Printer::new(); assert_eq!(printer.next_letter().as_str(), "a"); assert_eq!(printer.next_letter().as_str(), "b"); assert_eq!(printer.next_letter().as_str(), "c"); assert_eq!(printer.next_letter().as_str(), "d"); assert_eq!(printer.next_letter().as_str(), "e"); assert_eq!(printer.next_letter().as_str(), "f"); assert_eq!(printer.next_letter().as_str(), "g"); assert_eq!(printer.next_letter().as_str(), "h"); assert_eq!(printer.next_letter().as_str(), "i"); assert_eq!(printer.next_letter().as_str(), "j"); assert_eq!(printer.next_letter().as_str(), "k"); assert_eq!(printer.next_letter().as_str(), "l"); assert_eq!(printer.next_letter().as_str(), "m"); assert_eq!(printer.next_letter().as_str(), "n"); assert_eq!(printer.next_letter().as_str(), "o"); assert_eq!(printer.next_letter().as_str(), "p"); assert_eq!(printer.next_letter().as_str(), "q"); assert_eq!(printer.next_letter().as_str(), "r"); assert_eq!(printer.next_letter().as_str(), "s"); assert_eq!(printer.next_letter().as_str(), "t"); assert_eq!(printer.next_letter().as_str(), "u"); assert_eq!(printer.next_letter().as_str(), "v"); assert_eq!(printer.next_letter().as_str(), "w"); assert_eq!(printer.next_letter().as_str(), "x"); assert_eq!(printer.next_letter().as_str(), "y"); assert_eq!(printer.next_letter().as_str(), "z"); assert_eq!(printer.next_letter().as_str(), "aa"); assert_eq!(printer.next_letter().as_str(), "ab"); assert_eq!(printer.next_letter().as_str(), "ac"); assert_eq!(printer.next_letter().as_str(), "ad"); assert_eq!(printer.next_letter().as_str(), "ae"); assert_eq!(printer.next_letter().as_str(), "af"); assert_eq!(printer.next_letter().as_str(), "ag"); assert_eq!(printer.next_letter().as_str(), "ah"); assert_eq!(printer.next_letter().as_str(), "ai"); assert_eq!(printer.next_letter().as_str(), "aj"); assert_eq!(printer.next_letter().as_str(), "ak"); assert_eq!(printer.next_letter().as_str(), "al"); assert_eq!(printer.next_letter().as_str(), "am"); assert_eq!(printer.next_letter().as_str(), "an"); assert_eq!(printer.next_letter().as_str(), "ao"); assert_eq!(printer.next_letter().as_str(), "ap"); assert_eq!(printer.next_letter().as_str(), "aq"); assert_eq!(printer.next_letter().as_str(), "ar"); assert_eq!(printer.next_letter().as_str(), "as"); assert_eq!(printer.next_letter().as_str(), "at"); assert_eq!(printer.next_letter().as_str(), "au"); assert_eq!(printer.next_letter().as_str(), "av"); assert_eq!(printer.next_letter().as_str(), "aw"); assert_eq!(printer.next_letter().as_str(), "ax"); assert_eq!(printer.next_letter().as_str(), "ay"); assert_eq!(printer.next_letter().as_str(), "az"); assert_eq!(printer.next_letter().as_str(), "ba"); assert_eq!(printer.next_letter().as_str(), "bb"); assert_eq!(printer.next_letter().as_str(), "bc"); assert_eq!(printer.next_letter().as_str(), "bd"); assert_eq!(printer.next_letter().as_str(), "be"); assert_eq!(printer.next_letter().as_str(), "bf"); assert_eq!(printer.next_letter().as_str(), "bg"); assert_eq!(printer.next_letter().as_str(), "bh"); assert_eq!(printer.next_letter().as_str(), "bi"); assert_eq!(printer.next_letter().as_str(), "bj"); assert_eq!(printer.next_letter().as_str(), "bk"); assert_eq!(printer.next_letter().as_str(), "bl"); assert_eq!(printer.next_letter().as_str(), "bm"); assert_eq!(printer.next_letter().as_str(), "bn"); assert_eq!(printer.next_letter().as_str(), "bo"); assert_eq!(printer.next_letter().as_str(), "bp"); assert_eq!(printer.next_letter().as_str(), "bq"); assert_eq!(printer.next_letter().as_str(), "br"); assert_eq!(printer.next_letter().as_str(), "bs"); assert_eq!(printer.next_letter().as_str(), "bt"); assert_eq!(printer.next_letter().as_str(), "bu"); assert_eq!(printer.next_letter().as_str(), "bv"); assert_eq!(printer.next_letter().as_str(), "bw"); assert_eq!(printer.next_letter().as_str(), "bx"); assert_eq!(printer.next_letter().as_str(), "by"); assert_eq!(printer.next_letter().as_str(), "bz"); } #[test] fn pretty_print_test() { macro_rules! assert_string { ($src:expr, $type_:expr $(,)?) => { let mut printer = Printer::new(); assert_eq!($type_.to_string(), printer.pretty_print(&$src, 0),); }; } assert_string!( Type::Named { module: "whatever".into(), package: "whatever".into(), name: "Int".into(), publicity: Publicity::Public, arguments: vec![], inferred_variant: None, }, "Int", ); assert_string!( Type::Named { module: "themodule".into(), package: "whatever".into(), name: "Pair".into(), publicity: Publicity::Public, arguments: vec![ Arc::new(Type::Named { module: "whatever".into(), package: "whatever".into(), name: "Int".into(), publicity: Publicity::Public, arguments: vec![], inferred_variant: None, }), Arc::new(Type::Named { module: "whatever".into(), package: "whatever".into(), name: "Bool".into(), publicity: Publicity::Public, arguments: vec![], inferred_variant: None, }), ], inferred_variant: None, }, "Pair(Int, Bool)", ); assert_string!( Type::Fn { arguments: vec![ Arc::new(Type::Named { arguments: vec![], module: "whatever".into(), package: "whatever".into(), name: "Int".into(), publicity: Publicity::Public, inferred_variant: None, }), Arc::new(Type::Named { arguments: vec![], module: "whatever".into(), package: "whatever".into(), name: "Bool".into(), publicity: Publicity::Public, inferred_variant: None, }), ], return_: Arc::new(Type::Named { arguments: vec![], module: "whatever".into(), package: "whatever".into(), name: "Bool".into(), publicity: Publicity::Public, inferred_variant: None, }), }, "fn(Int, Bool) -> Bool", ); assert_string!( Type::Var { type_: Arc::new(RefCell::new(TypeVar::Link { type_: Arc::new(Type::Named { arguments: vec![], module: "whatever".into(), package: "whatever".into(), name: "Int".into(), publicity: Publicity::Public, inferred_variant: None, }), })), }, "Int", ); assert_string!( Type::Var { type_: Arc::new(RefCell::new(TypeVar::Unbound { id: 2231 })), }, "a", ); assert_string!( fn_( vec![Arc::new(Type::Var { type_: Arc::new(RefCell::new(TypeVar::Unbound { id: 78 })), })], Arc::new(Type::Var { type_: Arc::new(RefCell::new(TypeVar::Unbound { id: 2 })), }), ), "fn(a) -> b", ); assert_string!( fn_( vec![Arc::new(Type::Var { type_: Arc::new(RefCell::new(TypeVar::Generic { id: 78 })), })], Arc::new(Type::Var { type_: Arc::new(RefCell::new(TypeVar::Generic { id: 2 })), }), ), "fn(a) -> b", ); } #[test] fn function_test() { assert_eq!(pretty_print(fn_(vec![], int())), "fn() -> Int"); assert_eq!( pretty_print(fn_(vec![int(), int(), int()], int())), "fn(Int, Int, Int) -> Int" ); assert_eq!( pretty_print(fn_( vec![ float(), float(), float(), float(), float(), float(), float(), float(), float(), float(), float(), float(), float() ], float() )), "fn( Float, Float, Float, Float, Float, Float, Float, Float, Float, Float, Float, Float, Float, ) -> Float" ); assert_eq!( pretty_print(fn_( vec![ tuple(vec![float(), float(), float(), float(), float(), float()]), float(), float(), float(), float(), float(), float(), float() ], float() )), "fn( #(Float, Float, Float, Float, Float, Float), Float, Float, Float, Float, Float, Float, Float, ) -> Float" ); assert_eq!( pretty_print(fn_( vec![tuple(vec![ float(), float(), float(), float(), float(), float() ]),], tuple(vec![ tuple(vec![float(), float(), float(), float(), float(), float()]), tuple(vec![float(), float(), float(), float(), float(), float()]), ]), )), "fn(#(Float, Float, Float, Float, Float, Float)) -> #( #(Float, Float, Float, Float, Float, Float), #(Float, Float, Float, Float, Float, Float), )" ); } #[cfg(test)] fn pretty_print(type_: Arc) -> String { Printer::new().pretty_print(&type_, 0) } ================================================ FILE: compiler-core/src/type_/printer.rs ================================================ use bimap::BiMap; use ecow::{EcoString, eco_format}; use im::HashMap; use std::{collections::HashSet, sync::Arc}; use crate::{ ast::SrcSpan, type_::{Type, TypeAliasConstructor, TypeVar}, }; /// This class keeps track of what names are used for modules in the current /// scope, so they can be printed in errors, etc. /// #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct Names { /// Types that exist in the current module, either defined or imported in an /// unqualified fashion. /// /// key: (Defining module name, type name) /// value: Alias name /// /// # Example 1 /// /// ```gleam /// type Wibble = wobble.Woo /// ``` /// would result in /// - key: `("wibble", "Woo")` /// - value: `"Wibble"` /// /// # Example 2 /// /// ```gleam /// import some/module.{type Wibble} /// ``` /// would result in /// - key: `("some/module", "Wibble")` /// - value: `"Wibble"` /// /// # Example 3 /// /// ```gleam /// import some/module.{type Wibble as Wobble} /// ``` /// would result in /// - key: `("some/module", "Wibble")` /// - value: `"Wobble"` /// local_types: BiMap<(EcoString, EcoString), EcoString>, /// Mapping of imported modules to their locally used named /// /// key: The name of the module /// value: The name the module is aliased to /// /// # Example 1 /// /// ```gleam /// import mod1 as my_mod /// ``` /// would result in: /// - key: "mod1" /// - value: "my_mod" /// /// # Example 2 /// /// ```gleam /// import mod1 /// ``` /// would result in: /// - key: "mod1" /// - value: "mod1" /// imported_modules: HashMap, /// Generic type parameters that have been annotated in the current /// function. /// /// key: The id of generic type that was annotated /// value: The name that is used for the generic type in the annotation. /// /// # Example 1 /// /// ```gleam /// fn equal(x: something, y: something) -> Bool { /// arg1 == arg2 /// } /// ``` /// /// key: /// value: `"something"` /// type_variables: HashMap, /// Constructors which are imported in the current module in an /// unqualified fashion. /// /// key: (Defining module name, type name) /// value: Alias name /// /// # Example 1 /// /// ```gleam /// import wibble.{Wobble} /// ``` /// would result in /// - key: `("wibble", "Wobble")` /// - value: `"Wobble"` /// /// # Example 2 /// /// ```gleam /// import wibble.{Wobble as Woo} /// ``` /// would result in /// - key: `("wibble", "Wobble")` /// - value: `"Woo"` /// local_value_constructors: BiMap<(EcoString, EcoString), EcoString>, /// A map containing information about public alias of internal types in /// other packages. This is a common pattern in Gleam, in order to reexport /// an internal type, without exposing its implementation details. Because /// of this, we want to be able to properly handle this case, and use the /// public alias rather than the internal underlying type. Since Gleam type /// aliases are not part of the type system, we have to track them manually /// here. /// /// This is a mapping of internal types to their public aliases that we want /// to favour over the internal types. /// /// For example, if we had the following code: /// /// ```gleam /// // lustre/element.gleam /// import lustre/internal /// /// pub type Element(a) = internal.Element(a) /// ``` /// /// This map would contain a key of `("lustre/internal", "Element")` with a /// value of `("lustre/element", "Element")`. This can then be used to look /// up the alias we want to print based on the type we are printing. /// reexport_aliases: HashMap<(EcoString, EcoString), (EcoString, EcoString)>, } /// The `PartialEq` implementation for `Type` doesn't account for `TypeVar::Link`, /// so we implement an equality check that does account for it here. fn compare_arguments(arguments: &[Arc], parameters: &[Arc]) -> bool { if arguments.len() != parameters.len() { return false; } arguments .iter() .zip(parameters) .all(|(argument, parameter)| argument.same_as(parameter)) } impl Names { pub fn new() -> Self { Self { local_types: Default::default(), imported_modules: Default::default(), type_variables: Default::default(), local_value_constructors: Default::default(), reexport_aliases: Default::default(), } } /// Record a named type in this module. pub fn named_type_in_scope( &mut self, module_name: EcoString, type_name: EcoString, local_alias: EcoString, ) { _ = self.local_types.remove_by_right(&local_alias); _ = self .local_types .insert((module_name, type_name), local_alias); } pub fn type_in_scope( &mut self, local_alias: EcoString, type_: &Type, parameters: &[Arc], ) { match type_ { Type::Named { module, name, arguments, .. } if compare_arguments(arguments, parameters) => { self.named_type_in_scope(module.clone(), name.clone(), local_alias); } Type::Named { .. } | Type::Fn { .. } | Type::Var { .. } | Type::Tuple { .. } => { _ = self.local_types.remove_by_right(&local_alias); } } } /// Record a type variable in this module. pub fn type_variable_in_scope(&mut self, id: u64, local_alias: EcoString) { _ = self.type_variables.insert(id, local_alias.clone()); } /// Record an imported module in this module. /// /// Returns the location of the previous time this module was imported, if there was one. pub fn imported_module( &mut self, module_name: EcoString, module_alias: EcoString, location: SrcSpan, ) -> Option { self.imported_modules .insert(module_name, (module_alias, location)) .map(|(_, location)| location) } /// Check whether a particular type alias is reexporting an internal type, /// and if so register it so we can print it correctly. pub fn maybe_register_reexport_alias( &mut self, package: &EcoString, alias_name: &EcoString, alias: &TypeAliasConstructor, ) { match alias.type_.as_ref() { Type::Named { publicity, package: type_package, module, name, arguments, .. } => { // We only count this alias as a reexport if it is: // - aliasing a type in the same package // - the type is internal // - the alias exposes the same type parameters as the internal type if type_package == package && publicity.is_internal() && compare_arguments(arguments, &alias.parameters) { _ = self.reexport_aliases.insert( (module.clone(), name.clone()), (alias.module.clone(), alias_name.clone()), ); } } Type::Fn { .. } | Type::Var { .. } | Type::Tuple { .. } => {} } } /// Get the name and optional module qualifier for a named type. fn named_type<'a>( &'a self, module: &'a EcoString, name: &'a EcoString, print_mode: PrintMode, ) -> NameContextInformation<'a> { if print_mode == PrintMode::ExpandAliases { if let Some((module, _)) = self.imported_modules.get(module) { return NameContextInformation::Qualified(module, name.as_str()); }; return NameContextInformation::Unimported(module, name); } let key = (module.clone(), name.clone()); // Only check for local aliases if we want to print aliases // There is a local name for this type, use that. if let Some(name) = self.local_types.get_by_left(&key) { return NameContextInformation::Unqualified(name.as_str()); } if let Some((module, alias)) = self.reexport_aliases.get(&key) { if let Some((module, _)) = self.imported_modules.get(module) { return NameContextInformation::Qualified(module, alias); } else { return NameContextInformation::Unimported(module, alias); } } // This type is from a module that has been imported if let Some((module, _)) = self.imported_modules.get(module) { return NameContextInformation::Qualified(module, name.as_str()); }; NameContextInformation::Unimported(module, name) } /// Record a named value in this module. pub fn named_constructor_in_scope( &mut self, module_name: EcoString, value_name: EcoString, local_alias: EcoString, ) { _ = self.local_value_constructors.remove_by_right(&local_alias); _ = self .local_value_constructors .insert((module_name.clone(), value_name), local_alias.clone()); } /// Get the name and optional module qualifier for a named constructor. pub fn named_constructor<'a>( &'a self, module: &'a EcoString, name: &'a EcoString, ) -> NameContextInformation<'a> { let key = (module.clone(), name.clone()); // There is a local name for this value, use that. if let Some(name) = self.local_value_constructors.get_by_left(&key) { return NameContextInformation::Unqualified(name.as_str()); } // This value is from a module that has been imported if let Some((module, _)) = self.imported_modules.get(module) { return NameContextInformation::Qualified(module, name.as_str()); }; NameContextInformation::Unimported(module, name) } pub fn is_imported(&self, module: &str) -> bool { self.imported_modules.contains_key(module) } pub fn get_type_variable(&self, id: u64) -> Option<&EcoString> { self.type_variables.get(&id) } pub fn reexport_alias( &self, module: EcoString, name: EcoString, ) -> Option<&(EcoString, EcoString)> { self.reexport_aliases.get(&(module, name)) } } #[derive(Debug)] pub enum NameContextInformation<'a> { /// This type is from a module that has not been imported in this module. Unimported(&'a str, &'a str), /// This type has been imported in an unqualifid fashion in this module. Unqualified(&'a str), /// This type is from a module that has been imported. Qualified(&'a str, &'a str), } #[derive(Debug, Clone, Copy, PartialEq)] pub enum PrintMode { /// Prints the context-specific representation of a type. Normal, /// Prints full detail of the given type, always qualified. /// Useful for providing more detail to the user. /// /// For example, with this code: /// ```gleam /// type A = Int /// ``` /// If the type `gleam.Int` were printed using the `Normal` mode, /// we would print `A`, since that is the local alias for the `Int` type. /// /// However, if the user were hovering over the type `A` itself, it wouldn't be /// particularly helpful to print `A`. /// So with `ExpandAliases`, it would print `gleam.Int`, /// which tells the user exactly what type `A` represents. /// ExpandAliases, } /// A type printer that does not wrap and indent, but does take into account the /// names that types and modules have been aliased with in the current module. #[derive(Debug)] pub struct Printer<'a> { names: &'a Names, uid: u64, /// Some type variables aren't bound to names, so when trying to print those, /// we need to create our own names which don't overlap with existing type variables. /// These two data structures store a mapping of IDs to created type-variable names, /// to ensure consistent printing, and the set of all printed names so that we don't /// create a type variable name which matches an existing one. /// /// Note: These are stored per printer, not per TypeNames struct, because: /// - It doesn't really matter what these are, as long as they are consistent. /// - We would need mutable access to the names struct, which isn't really possible /// in many contexts. /// printed_type_variables: HashMap, printed_type_variable_names: HashSet, } impl<'a> Printer<'a> { pub fn new(names: &'a Names) -> Self { Printer { names, uid: Default::default(), printed_type_variables: Default::default(), printed_type_variable_names: names.type_variables.values().cloned().collect(), } } /// In the AST, type variables are represented by their IDs, not their names. /// This means that when we are printing a type variable, we either need to /// find its name that was given by the programmer, or generate a new one. /// Type variable names are local to functions, meaning there can be one /// named `a` in one function, and a different one named `a` in another /// function. However, there can't be two named `a` in the same function. /// /// By default, the printer avoids duplicating type variable names entirely. /// This is because we don't have easy access to information about which type /// variables belong to this function. In order to ensure no accidental, /// collisions, we treat all type variables from the module as in scope, even /// though this isn't the case. /// /// When sufficient information is present to ensure type variables are not /// duplicated, `new_without_type_variables` can be used, in combination with /// `register_type_variables` in order to precisely control which variables /// are in scope. /// pub fn new_without_type_variables(names: &'a Names) -> Self { Printer { names, uid: Default::default(), printed_type_variables: Default::default(), printed_type_variable_names: Default::default(), } } /// Clear the registered type variable names. This allows the same `Printer` /// to be used in multiple different scopes, which have different sets of /// type variables. After clearing, the correct variables from the desired /// scope can be registered using `register_type_variable`. pub fn clear_type_variables(&mut self) { self.printed_type_variable_names.clear(); } /// As explained in the documentation for `new_without_type_variables`, it /// it not always possible to determine which type variables are in scope. /// However, when it is possible, this function can be used to manually /// register which type variable names are in scope and cannot be used. pub fn register_type_variable(&mut self, name: EcoString) { _ = self.printed_type_variable_names.insert(name); } pub fn print_type(&mut self, type_: &Type) -> EcoString { let mut buffer = EcoString::new(); self.print(type_, &mut buffer, PrintMode::Normal); buffer } pub fn print_module(&self, module: &str) -> EcoString { match self.names.imported_modules.get(module) { Some((module, _)) => module.clone(), _ => module.split("/").last().unwrap_or(module).into(), } } pub fn print_type_without_aliases(&mut self, type_: &Type) -> EcoString { let mut buffer = EcoString::new(); self.print(type_, &mut buffer, PrintMode::ExpandAliases); buffer } fn print(&mut self, type_: &Type, buffer: &mut EcoString, print_mode: PrintMode) { match type_ { Type::Named { name, arguments, module, .. } => { let (module, name) = match self.names.named_type(module, name, print_mode) { NameContextInformation::Qualified(module, name) => (Some(module), name), NameContextInformation::Unqualified(name) => (None, name), // TODO: indicate that the module is not import and as such // needs to be, as well as how. NameContextInformation::Unimported(module, name) => { (module.split('/').next_back(), name) } }; if let Some(module) = module { buffer.push_str(module); buffer.push('.'); } buffer.push_str(name); if !arguments.is_empty() { buffer.push('('); self.print_arguments(arguments, buffer, print_mode); buffer.push(')'); } } Type::Fn { arguments, return_ } => { buffer.push_str("fn("); self.print_arguments(arguments, buffer, print_mode); buffer.push_str(") -> "); self.print(return_, buffer, print_mode); } Type::Var { type_, .. } => match *type_.borrow() { TypeVar::Link { ref type_, .. } => self.print(type_, buffer, print_mode), TypeVar::Unbound { id, .. } | TypeVar::Generic { id, .. } => { buffer.push_str(&self.type_variable(id)) } }, Type::Tuple { elements, .. } => { buffer.push_str("#("); self.print_arguments(elements, buffer, print_mode); buffer.push(')'); } } } pub fn print_constructor(&mut self, module: &EcoString, name: &EcoString) -> EcoString { let (module, name) = match self.names.named_constructor(module, name) { NameContextInformation::Qualified(module, name) => (Some(module), name), NameContextInformation::Unqualified(name) => (None, name), NameContextInformation::Unimported(module, name) => { (module.split('/').next_back(), name) } }; match module { Some(module) => eco_format!("{module}.{name}"), None => name.into(), } } fn print_arguments( &mut self, arguments: &[Arc], type_str: &mut EcoString, print_mode: PrintMode, ) { for (i, argument) in arguments.iter().enumerate() { self.print(argument, type_str, print_mode); if i < arguments.len() - 1 { type_str.push_str(", "); } } } /// A suitable name of a type variable. pub fn type_variable(&mut self, id: u64) -> EcoString { if let Some(name) = self.names.type_variables.get(&id) { return name.clone(); } if let Some(name) = self.printed_type_variables.get(&id) { return name.clone(); } loop { let name = self.next_letter(); if !self.printed_type_variable_names.contains(&name) { _ = self.printed_type_variable_names.insert(name.clone()); _ = self.printed_type_variables.insert(id, name.clone()); return name; } } } fn next_letter(&mut self) -> EcoString { let alphabet_length = 26; let char_offset = 97; let mut chars = vec![]; let mut n; let mut rest = self.uid; loop { n = rest % alphabet_length; rest /= alphabet_length; chars.push((n as u8 + char_offset) as char); if rest == 0 { break; } rest -= 1 } self.uid += 1; chars.into_iter().rev().collect() } } #[test] fn test_local_type() { let mut names = Names::new(); names.named_type_in_scope("mod".into(), "Tiger".into(), "Cat".into()); let mut printer = Printer::new(&names); let type_ = Type::Named { name: "Tiger".into(), arguments: vec![], module: "mod".into(), publicity: crate::ast::Publicity::Public, package: "".into(), inferred_variant: None, }; assert_eq!(printer.print_type(&type_), "Cat"); } #[test] fn test_prelude_type() { let mut names = Names::new(); names.named_type_in_scope("gleam".into(), "Int".into(), "Int".into()); let mut printer = Printer::new(&names); let type_ = Type::Named { name: "Int".into(), arguments: vec![], module: "gleam".into(), publicity: crate::ast::Publicity::Public, package: "".into(), inferred_variant: None, }; assert_eq!(printer.print_type(&type_), "Int"); } #[test] fn test_shadowed_prelude_type() { let mut names = Names::new(); names.named_type_in_scope("gleam".into(), "Int".into(), "Int".into()); names.named_type_in_scope("mod".into(), "Int".into(), "Int".into()); let mut printer = Printer::new(&names); let type_ = Type::Named { name: "Int".into(), arguments: vec![], module: "gleam".into(), publicity: crate::ast::Publicity::Public, package: "".into(), inferred_variant: None, }; assert_eq!(printer.print_type(&type_), "gleam.Int"); } #[test] fn test_generic_type_annotation() { let mut names = Names::new(); names.type_variable_in_scope(0, "one".into()); let mut printer = Printer::new(&names); let type_ = Type::Var { type_: Arc::new(std::cell::RefCell::new(TypeVar::Generic { id: 0 })), }; assert_eq!(printer.print_type(&type_), "one"); } #[test] fn test_generic_type_var() { let names = Names::new(); let mut printer = Printer::new(&names); let type_ = Type::Var { type_: Arc::new(std::cell::RefCell::new(TypeVar::Unbound { id: 0 })), }; let typ2 = Type::Var { type_: Arc::new(std::cell::RefCell::new(TypeVar::Unbound { id: 1 })), }; assert_eq!(printer.print_type(&type_), "a"); assert_eq!(printer.print_type(&typ2), "b"); } #[test] fn test_tuple_type() { let names = Names::new(); let mut printer = Printer::new(&names); let type_ = Type::Tuple { elements: vec![ Arc::new(Type::Named { name: "Int".into(), arguments: vec![], module: "gleam".into(), publicity: crate::ast::Publicity::Public, package: "".into(), inferred_variant: None, }), Arc::new(Type::Named { name: "String".into(), arguments: vec![], module: "gleam".into(), publicity: crate::ast::Publicity::Public, package: "".into(), inferred_variant: None, }), ], }; assert_eq!(printer.print_type(&type_), "#(gleam.Int, gleam.String)"); } #[test] fn test_fn_type() { let mut names = Names::new(); names.named_type_in_scope("gleam".into(), "Int".into(), "Int".into()); names.named_type_in_scope("gleam".into(), "Bool".into(), "Bool".into()); let mut printer = Printer::new(&names); let type_ = Type::Fn { arguments: vec![ Arc::new(Type::Named { name: "Int".into(), arguments: vec![], module: "gleam".into(), publicity: crate::ast::Publicity::Public, package: "".into(), inferred_variant: None, }), Arc::new(Type::Named { name: "String".into(), arguments: vec![], module: "gleam".into(), publicity: crate::ast::Publicity::Public, package: "".into(), inferred_variant: None, }), ], return_: Arc::new(Type::Named { name: "Bool".into(), arguments: vec![], module: "gleam".into(), publicity: crate::ast::Publicity::Public, package: "".into(), inferred_variant: None, }), }; assert_eq!(printer.print_type(&type_), "fn(Int, gleam.String) -> Bool"); } #[test] fn test_module_alias() { let mut names = Names::new(); assert!( names .imported_module("mod1".into(), "animals".into(), SrcSpan::new(50, 63)) .is_none() ); let mut printer = Printer::new(&names); let type_ = Type::Named { name: "Cat".into(), arguments: vec![], module: "mod1".into(), publicity: crate::ast::Publicity::Public, package: "".into(), inferred_variant: None, }; assert_eq!(printer.print_type(&type_), "animals.Cat"); } #[test] fn test_type_alias_and_generics() { let mut names = Names::new(); names.named_type_in_scope("mod".into(), "Tiger".into(), "Cat".into()); names.type_variable_in_scope(0, "one".into()); let mut printer = Printer::new(&names); let type_ = Type::Named { name: "Tiger".into(), arguments: vec![Arc::new(Type::Var { type_: Arc::new(std::cell::RefCell::new(TypeVar::Generic { id: 0 })), })], module: "mod".into(), publicity: crate::ast::Publicity::Public, package: "".into(), inferred_variant: None, }; assert_eq!(printer.print_type(&type_), "Cat(one)"); } #[test] fn test_unqualified_import_and_generic() { let mut names = Names::new(); names.named_type_in_scope("mod".into(), "Cat".into(), "C".into()); names.type_variable_in_scope(0, "one".into()); let mut printer = Printer::new(&names); let type_ = Type::Named { name: "Cat".into(), arguments: vec![Arc::new(Type::Var { type_: Arc::new(std::cell::RefCell::new(TypeVar::Generic { id: 0 })), })], module: "mod".into(), publicity: crate::ast::Publicity::Public, package: "".into(), inferred_variant: None, }; assert_eq!(printer.print_type(&type_), "C(one)"); } #[test] fn nested_module() { let names = Names::new(); let mut printer = Printer::new(&names); let type_ = Type::Named { name: "Cat".into(), arguments: vec![], module: "one/two/three".into(), publicity: crate::ast::Publicity::Public, package: "".into(), inferred_variant: None, }; assert_eq!(printer.print_type(&type_), "three.Cat"); } #[test] fn test_unqualified_import_and_module_alias() { let mut names = Names::new(); assert!( names .imported_module("mod1".into(), "animals".into(), SrcSpan::new(76, 93)) .is_none() ); let _ = names .local_types .insert(("mod1".into(), "Cat".into()), "C".into()); let mut printer = Printer::new(&names); let type_ = Type::Named { name: "Cat".into(), arguments: vec![], module: "mod1".into(), publicity: crate::ast::Publicity::Public, package: "".into(), inferred_variant: None, }; assert_eq!(printer.print_type(&type_), "C"); } #[test] fn test_module_imports() { let mut names = Names::new(); assert!( names .imported_module("mod".into(), "animals".into(), SrcSpan::new(76, 93)) .is_none() ); let _ = names .local_types .insert(("mod2".into(), "Cat".into()), "Cat".into()); let mut printer = Printer::new(&names); let type_ = Type::Named { name: "Cat".into(), arguments: vec![], module: "mod".into(), publicity: crate::ast::Publicity::Public, package: "".into(), inferred_variant: None, }; let typ1 = Type::Named { name: "Cat".into(), arguments: vec![], module: "mod2".into(), publicity: crate::ast::Publicity::Public, package: "".into(), inferred_variant: None, }; assert_eq!(printer.print_type(&type_), "animals.Cat"); assert_eq!(printer.print_type(&typ1), "Cat"); } #[test] fn test_multiple_generic_annotations() { let mut names = Names::new(); names.type_variable_in_scope(0, "one".into()); names.type_variable_in_scope(1, "two".into()); let mut printer = Printer::new(&names); let type_ = Type::Named { name: "Tiger".into(), arguments: vec![ Arc::new(Type::Var { type_: Arc::new(std::cell::RefCell::new(TypeVar::Generic { id: 0 })), }), Arc::new(Type::Var { type_: Arc::new(std::cell::RefCell::new(TypeVar::Generic { id: 1 })), }), ], module: "tigermodule".into(), publicity: crate::ast::Publicity::Public, package: "".into(), inferred_variant: None, }; let typ1 = Type::Var { type_: Arc::new(std::cell::RefCell::new(TypeVar::Generic { id: 2 })), }; assert_eq!(printer.print_type(&type_), "tigermodule.Tiger(one, two)"); assert_eq!(printer.print_type(&typ1), "a"); } #[test] fn test_variable_name_already_in_scope() { let mut names = Names::new(); names.type_variable_in_scope(1, "a".into()); names.type_variable_in_scope(2, "b".into()); let mut printer = Printer::new(&names); let type_ = |id| Type::Var { type_: Arc::new(std::cell::RefCell::new(TypeVar::Generic { id })), }; assert_eq!(printer.print_type(&type_(0)), "c"); assert_eq!(printer.print_type(&type_(1)), "a"); assert_eq!(printer.print_type(&type_(2)), "b"); assert_eq!(printer.print_type(&type_(3)), "d"); } ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__const_record_update_all_fields.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "pub type Person { Person(name: String, age: Int, city: String) }\n\n pub const base = Person(\"Alice\", 30, \"London\")\n pub const updated = Person(..base, name: \"Bob\", age: 25, city: \"Paris\")" --- ----- SOURCE CODE pub type Person { Person(name: String, age: Int, city: String) } pub const base = Person("Alice", 30, "London") pub const updated = Person(..base, name: "Bob", age: 25, city: "Paris") ----- WARNING warning: Redundant record update ┌─ /src/warning/wrn.gleam:4:29 │ 4 │ pub const updated = Person(..base, name: "Bob", age: 25, city: "Paris") │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This record update specifies all fields Hint: It is better style to use the record creation syntax. ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__const_record_update_field_type_mismatch_error.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "pub type Person { Person(name: String, age: Int) }\n\n pub const alice = Person(\"Alice\", 30)\n pub const bob = Person(..alice, age: \"not a number\")" --- ----- SOURCE CODE pub type Person { Person(name: String, age: Int) } pub const alice = Person("Alice", 30) pub const bob = Person(..alice, age: "not a number") ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:4:46 │ 4 │ pub const bob = Person(..alice, age: "not a number") │ ^^^^^^^^^^^^^^ Expected type: Int Found type: String ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__const_record_update_fieldless_warning.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "pub type Animal { Animal(species: String) }\n\n pub const alice = Animal(\"Cat\")\n pub const dog = Animal(..alice)" --- ----- SOURCE CODE pub type Animal { Animal(species: String) } pub const alice = Animal("Cat") pub const dog = Animal(..alice) ----- WARNING warning: Fieldless record update ┌─ /src/warning/wrn.gleam:4:25 │ 4 │ pub const dog = Animal(..alice) │ ^^^^^^^^^^^^^^^ This record update doesn't change any fields Hint: Add some fields to change or replace it with the record itself. ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__const_record_update_non_record.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "pub type Person { Person(name: String, age: Int) }\n\n pub const number = 42\n pub const bob = Person(..number, name: \"Bob\")" --- ----- SOURCE CODE pub type Person { Person(name: String, age: Int) } pub const number = 42 pub const bob = Person(..number, name: "Bob") ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:4:34 │ 4 │ pub const bob = Person(..number, name: "Bob") │ ^^^^^^ Expected type: Person Found type: Int ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__const_record_update_nonexistent_field.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "pub type Person { Person(name: String, age: Int) }\n\n pub const alice = Person(\"Alice\", 30)\n pub const bob = Person(..alice, nonexistent: \"value\")" --- ----- SOURCE CODE pub type Person { Person(name: String, age: Int) } pub const alice = Person("Alice", 30) pub const bob = Person(..alice, nonexistent: "value") ----- ERROR error: Unknown record field ┌─ /src/one/two.gleam:4:41 │ 4 │ pub const bob = Person(..alice, nonexistent: "value") │ ^^^^^^^^^^^^^^^^^^^^ This field does not exist The value being accessed has this type: Person It has these accessible fields: .age .name ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__const_record_update_type_mismatch_error.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "pub type Person { Person(name: String, age: Int) }\n pub type Animal { Animal(species: String) }\n\n pub const alice = Person(\"Alice\", 30)\n pub const dog = Animal(..alice)" --- ----- SOURCE CODE pub type Person { Person(name: String, age: Int) } pub type Animal { Animal(species: String) } pub const alice = Person("Alice", 30) pub const dog = Animal(..alice) ----- ERROR error: Incorrect record update ┌─ /src/one/two.gleam:5:34 │ 5 │ pub const dog = Animal(..alice) │ ^^^^^ This is a `Person` This value is a `Person` so it cannot be used to build a `Animal`, even if they share some fields. Note: If you want to change one variant of a type into another, you should specify all fields explicitly instead of using the record update syntax. ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__const_record_update_unlabelled_fields.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "pub type Point { Point(Int, Int) }\n\n pub const origin = Point(0, 0)\n pub const point = Point(..origin)" --- ----- SOURCE CODE pub type Point { Point(Int, Int) } pub const origin = Point(0, 0) pub const point = Point(..origin) ----- ERROR error: Invalid record constructor ┌─ /src/one/two.gleam:4:27 │ 4 │ pub const point = Point(..origin) │ ^^^^^ This is not a record constructor Only record constructors can be used with the update syntax. ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__const_record_update_variant_mismatch_error.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "pub type Subject {\n Person(name: String, age: Int)\n Animal(species: String)\n }\n\n pub const alice = Person(\"Alice\", 30)\n pub const dog = Animal(..alice)" --- ----- SOURCE CODE pub type Subject { Person(name: String, age: Int) Animal(species: String) } pub const alice = Person("Alice", 30) pub const dog = Animal(..alice) ----- ERROR error: Incorrect record update ┌─ /src/one/two.gleam:7:34 │ 7 │ pub const dog = Animal(..alice) │ ^^^^^ This is a `Person` This value is a `Person` so it cannot be used to build a `Animal`, even if they share some fields. Note: If you want to change one variant of a type into another, you should specify all fields explicitly instead of using the record update syntax. ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__const_record_update_variant_without_args.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "pub type Status { Active Inactive }\n pub const status1 = Active\n pub const status2 = Active(..status1)" --- ----- SOURCE CODE pub type Status { Active Inactive } pub const status1 = Active pub const status2 = Active(..status1) ----- ERROR error: Invalid record constructor ┌─ /src/one/two.gleam:3:29 │ 3 │ pub const status2 = Active(..status1) │ ^^^^^^ This is not a record constructor Only record constructors can be used with the update syntax. ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__correct_type_check_for_multiple_mutually_recursive_functions.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "\npub fn wibble(id) {\n wobble(id, True)\n}\n\npub fn wobble(id, bool) {\n case bool {\n True -> wibble(id)\n False -> wubble(id, bool)\n }\n}\n\npub fn wubble(id, bool) {\n case bool {\n True -> final(id)\n False -> wobble(id, bool)\n }\n}\n\npub fn final(id) {\n let #(a, b) = id\n echo #(a, b)\n}\n\npub fn main() {\n wibble(#(\"a\", \"b\"))\n wibble(2)\n}\n" --- ----- SOURCE CODE pub fn wibble(id) { wobble(id, True) } pub fn wobble(id, bool) { case bool { True -> wibble(id) False -> wubble(id, bool) } } pub fn wubble(id, bool) { case bool { True -> final(id) False -> wobble(id, bool) } } pub fn final(id) { let #(a, b) = id echo #(a, b) } pub fn main() { wibble(#("a", "b")) wibble(2) } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:27:10 │ 27 │ wibble(2) │ ^ Expected type: #(a, b) Found type: Int ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__function_parameter_errors_do_not_stop_inference.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "\npub fn wibble(x: NonExistent) {\n 1 + False\n}\n" --- ----- SOURCE CODE pub fn wibble(x: NonExistent) { 1 + False } ----- ERROR error: Unknown type ┌─ /src/one/two.gleam:2:18 │ 2 │ pub fn wibble(x: NonExistent) { │ ^^^^^^^^^^^ The type `NonExistent` is not defined or imported in this module. error: Type mismatch ┌─ /src/one/two.gleam:3:7 │ 3 │ 1 + False │ ^^^^^ The + operator expects arguments of this type: Int But this argument has this type: Bool ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__generic_unlabelled_field_in_updated_const_record_wrong_type.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "pub type Wibble(a) {\n Wibble(a, b: Int, c: a)\n }\n\n const w = Wibble(1, 2, 3)\n const w2 = Wibble(..w, c: False)" --- ----- SOURCE CODE pub type Wibble(a) { Wibble(a, b: Int, c: a) } const w = Wibble(1, 2, 3) const w2 = Wibble(..w, c: False) ----- ERROR error: Incomplete record update ┌─ /src/one/two.gleam:6:29 │ 6 │ const w2 = Wibble(..w, c: False) │ ^ This is a `Wibble(Int)` The 1st field of this value is a `Int`, but the arguments given to the record update indicate that it should be a `Bool`. Note: Unlabelled fields cannot be updated in a record update, so either add a label or use a record constructor. ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__pipe_with_annonymous_unannotated_functions_wrong_arity1.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "\npub fn main() {\n let a = 1\n |> fn (x) { #(x, x + 1) }\n |> fn (x, y) { x.0 }\n |> fn (x) { x }\n}\n" --- ----- SOURCE CODE pub fn main() { let a = 1 |> fn (x) { #(x, x + 1) } |> fn (x, y) { x.0 } |> fn (x) { x } } ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:5:9 │ 5 │ |> fn (x, y) { x.0 } │ ^^^^^^^^^^^^^^^^^ Expected 2 arguments, got 1 error: Type mismatch ┌─ /src/one/two.gleam:5:21 │ 5 │ |> fn (x, y) { x.0 } │ ^ What type is this? To index into a tuple we need to know its size, but we don't know anything about this type yet. Please add some type annotations so we can continue. ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__pipe_with_annonymous_unannotated_functions_wrong_arity2.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "\npub fn main() {\n let a = 1\n |> fn (x) { #(x, x + 1) }\n |> fn (x) { x.0 }\n |> fn (x, y) { x }\n}\n" --- ----- SOURCE CODE pub fn main() { let a = 1 |> fn (x) { #(x, x + 1) } |> fn (x) { x.0 } |> fn (x, y) { x } } ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:6:9 │ 6 │ |> fn (x, y) { x } │ ^^^^^^^^^^^^^^^ Expected 2 arguments, got 1 ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__pipe_with_annonymous_unannotated_functions_wrong_arity3.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "\npub fn main() {\n let a = 1\n |> fn (x) { #(x, x + 1) }\n |> fn (x) { x.0 }\n |> fn () { x }\n}\n" --- ----- SOURCE CODE pub fn main() { let a = 1 |> fn (x) { #(x, x + 1) } |> fn (x) { x.0 } |> fn () { x } } ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:6:9 │ 6 │ |> fn () { x } │ ^^^^^^^^^^^ Expected no arguments, got 1 error: Unknown variable ┌─ /src/one/two.gleam:6:17 │ 6 │ |> fn () { x } │ ^ The name `x` is not in scope here. ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__prepend_constant_list_wrong_element_type.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "\nconst list = [3, 4, 5]\npub const full_list = [1.0, 2.0, ..list]\n" --- ----- SOURCE CODE const list = [3, 4, 5] pub const full_list = [1.0, 2.0, ..list] ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:36 │ 3 │ pub const full_list = [1.0, 2.0, ..list] │ ^^^^ Expected type: List(Float) Found type: List(Int) ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__prepend_constant_list_wrong_type.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "\nconst pi = 3.14\npub const full_list = [1.0, 2.0, ..pi]\n" --- ----- SOURCE CODE const pi = 3.14 pub const full_list = [1.0, 2.0, ..pi] ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:36 │ 3 │ pub const full_list = [1.0, 2.0, ..pi] │ ^^ Expected type: List(Float) Found type: Float ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__private_types_not_available_in_other_modules.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "\nimport wibble\n\ntype Wibble {\n Wibble(wibble.Wibble)\n}\n" --- ----- SOURCE CODE -- wibble.gleam type Wibble -- main.gleam import wibble type Wibble { Wibble(wibble.Wibble) } ----- ERROR error: Unknown module type ┌─ /src/one/two.gleam:5:10 │ 5 │ Wibble(wibble.Wibble) │ ^^^^^^^^^^^^^ The module `wibble` does not have a `Wibble` type. ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__record_update_variant_inference_fails_for_several_possible_variants.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "\npub type Vector {\n Vector2(x: Float, y: Float)\n Vector3(x: Float, y: Float, z: Float)\n}\n\npub fn increase_y(vector, by increase) {\n case vector {\n Vector2(y:, ..) as vector | Vector3(y:, ..) as vector ->\n Vector2(..vector, y: y +. increase)\n }\n}\n" --- ----- SOURCE CODE pub type Vector { Vector2(x: Float, y: Float) Vector3(x: Float, y: Float, z: Float) } pub fn increase_y(vector, by increase) { case vector { Vector2(y:, ..) as vector | Vector3(y:, ..) as vector -> Vector2(..vector, y: y +. increase) } } ----- ERROR error: Unsafe record update ┌─ /src/one/two.gleam:10:17 │ 10 │ Vector2(..vector, y: y +. increase) │ ^^^^^^ I'm not sure this is always a `Vector2` This value cannot be used to build an updated `Vector2` as it could be some other variant. Consider pattern matching on it with a case expression and then constructing a new record with its values. ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__record_update_variant_inference_fails_for_several_possible_variants_on_subject_variable.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "\npub type Wibble {\n Wibble(a: Int, b: Int)\n Wobble(a: Int, c: String)\n}\n\npub fn update(wibble: Wibble) -> Wibble {\n case wibble {\n Wibble(..) | Wobble(..) -> Wibble(..wibble, a: 1)\n }\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(a: Int, b: Int) Wobble(a: Int, c: String) } pub fn update(wibble: Wibble) -> Wibble { case wibble { Wibble(..) | Wobble(..) -> Wibble(..wibble, a: 1) } } ----- ERROR error: Unsafe record update ┌─ /src/one/two.gleam:9:41 │ 9 │ Wibble(..) | Wobble(..) -> Wibble(..wibble, a: 1) │ ^^^^^^ I'm not sure this is always a `Wibble` This value cannot be used to build an updated `Wibble` as it could be some other variant. Consider pattern matching on it with a case expression and then constructing a new record with its values. ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__string_concat_ko_1.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: " \"1\" <> 2 " --- ----- SOURCE CODE "1" <> 2 ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:9 │ 1 │ "1" <> 2 │ ^ The <> operator expects arguments of this type: String But this argument has this type: Int ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__string_concat_ko_2.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: " 1 <> \"2\" " --- ----- SOURCE CODE 1 <> "2" ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:2 │ 1 │ 1 <> "2" │ ^ The <> operator expects arguments of this type: String But this argument has this type: Int ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__type_unification_does_not_allow_different_variants_to_be_treated_as_safe.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "\npub type Wibble {\n Wibble(a: Int, b: Int)\n Wobble(a: Int, c: String)\n}\n\npub fn main() {\n let a = case todo {\n Wibble(..) as b -> Wibble(..b, b: 1)\n Wobble(..) as b -> Wobble(..b, c: \"a\")\n }\n\n a.b\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(a: Int, b: Int) Wobble(a: Int, c: String) } pub fn main() { let a = case todo { Wibble(..) as b -> Wibble(..b, b: 1) Wobble(..) as b -> Wobble(..b, c: "a") } a.b } ----- ERROR error: Unknown record field ┌─ /src/one/two.gleam:13:5 │ 13 │ a.b │ ^ Did you mean `a`? The value being accessed has this type: Wibble It has these accessible fields: .a Note: The field you are trying to access is not defined consistently across all variants of this custom type. To fix this, ensure that all variants include the field with the same name, position, and type. ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__type_unification_does_not_allow_lowercase_bools_in_match_clause.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "\npub fn main() {\n case 42 > 42 {\n true -> 1\n false -> 2\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case 42 > 42 { true -> 1 false -> 2 } } ----- ERROR error: Lowercase bool pattern ┌─ /src/one/two.gleam:4:5 │ 4 │ true -> 1 │ ^^^^ This is not a bool See: https://tour.gleam.run/basics/bools/ Hint: In Gleam bool literals are `True` and `False`. error: Lowercase bool pattern ┌─ /src/one/two.gleam:5:5 │ 5 │ false -> 2 │ ^^^^^ This is not a bool See: https://tour.gleam.run/basics/bools/ Hint: In Gleam bool literals are `True` and `False`. ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__type_unification_does_not_cause_false_positives_for_variant_matching.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "\npub type Wibble {\n Wibble(a: Int, b: Int)\n Wobble(a: Int, c: String)\n}\n\npub fn wibbler() { todo }\n\npub fn main() {\n let c = wibbler()\n\n case todo {\n Wibble(..) -> Wibble(..c, b: 1)\n _ -> todo\n }\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(a: Int, b: Int) Wobble(a: Int, c: String) } pub fn wibbler() { todo } pub fn main() { let c = wibbler() case todo { Wibble(..) -> Wibble(..c, b: 1) _ -> todo } } ----- ERROR error: Unsafe record update ┌─ /src/one/two.gleam:13:28 │ 13 │ Wibble(..) -> Wibble(..c, b: 1) │ ^ I'm not sure this is always a `Wibble` This value cannot be used to build an updated `Wibble` as it could be some other variant. Consider pattern matching on it with a case expression and then constructing a new record with its values. ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__type_unification_removes_inferred_variant_in_functions.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "\npub type Either(a, b) {\n Left(value: a)\n Right(value: b)\n}\n\nfn a_or_b(_first: value, second: value) -> value {\n second\n}\n\npub fn main() {\n let func = a_or_b(fn() { Left(1) }, fn() { Right(\"hello\") })\n Left(..func(), value: 10)\n}\n" --- ----- SOURCE CODE pub type Either(a, b) { Left(value: a) Right(value: b) } fn a_or_b(_first: value, second: value) -> value { second } pub fn main() { let func = a_or_b(fn() { Left(1) }, fn() { Right("hello") }) Left(..func(), value: 10) } ----- ERROR error: Unsafe record update ┌─ /src/one/two.gleam:13:10 │ 13 │ Left(..func(), value: 10) │ ^^^^^^ I'm not sure this is always a `Left` This value cannot be used to build an updated `Left` as it could be some other variant. Consider pattern matching on it with a case expression and then constructing a new record with its values. ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__type_unification_removes_inferred_variant_in_nested_type.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "\npub type Box(a) {\n Box(inner: a)\n}\n\npub type Either(a, b) {\n Left(value: a)\n Right(value: b)\n}\n\nfn a_or_b(_first: value, second: value) -> value {\n second\n}\n\npub fn main() {\n let Box(inner) = a_or_b(Box(Left(1)), Box(Right(\"hello\")))\n Left(..inner, value: 10)\n}\n" --- ----- SOURCE CODE pub type Box(a) { Box(inner: a) } pub type Either(a, b) { Left(value: a) Right(value: b) } fn a_or_b(_first: value, second: value) -> value { second } pub fn main() { let Box(inner) = a_or_b(Box(Left(1)), Box(Right("hello"))) Left(..inner, value: 10) } ----- ERROR error: Unsafe record update ┌─ /src/one/two.gleam:17:10 │ 17 │ Left(..inner, value: 10) │ ^^^^^ I'm not sure this is always a `Left` This value cannot be used to build an updated `Left` as it could be some other variant. Consider pattern matching on it with a case expression and then constructing a new record with its values. ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__type_unification_removes_inferred_variant_in_tuples.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "\npub type Either(a, b) {\n Left(value: a)\n Right(value: b)\n}\n\nfn a_or_b(_first: value, second: value) -> value {\n second\n}\n\npub fn main() {\n let #(right) = a_or_b(#(Left(5)), #(Right(\"hello\")))\n Left(..right, value: 10)\n}\n" --- ----- SOURCE CODE pub type Either(a, b) { Left(value: a) Right(value: b) } fn a_or_b(_first: value, second: value) -> value { second } pub fn main() { let #(right) = a_or_b(#(Left(5)), #(Right("hello"))) Left(..right, value: 10) } ----- ERROR error: Unsafe record update ┌─ /src/one/two.gleam:13:10 │ 13 │ Left(..right, value: 10) │ ^^^^^ I'm not sure this is always a `Left` This value cannot be used to build an updated `Left` as it could be some other variant. Consider pattern matching on it with a case expression and then constructing a new record with its values. ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__unlabelled_argument_not_allowed_after_labelled_argument.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "\npub type Bad {\n Bad(labelled: Int, Float)\n}\n" --- ----- SOURCE CODE pub type Bad { Bad(labelled: Int, Float) } ----- ERROR error: Unlabelled argument after labelled argument ┌─ /src/one/two.gleam:3:22 │ 3 │ Bad(labelled: Int, Float) │ ^^^^^ All unlabelled arguments must come before any labelled arguments. ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__variant_inference_does_not_escape_clause_scope.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "\npub type Thingy {\n A(a: Int)\n B(x: Int, b: Int)\n}\n\npub fn fun(x) {\n case x {\n A(..) -> x.a\n B(..) -> x.b\n }\n x.b\n}\n" --- ----- SOURCE CODE pub type Thingy { A(a: Int) B(x: Int, b: Int) } pub fn fun(x) { case x { A(..) -> x.a B(..) -> x.b } x.b } ----- ERROR error: Unknown record field ┌─ /src/one/two.gleam:12:5 │ 12 │ x.b │ ^ This field does not exist The value being accessed has this type: Thingy It does not have fields that are common across all variants. Note: The field you are trying to access is not defined consistently across all variants of this custom type. To fix this, ensure that all variants include the field with the same name, position, and type. ================================================ FILE: compiler-core/src/type_/snapshots/gleam_core__type___tests__variant_inference_on_literal_record.snap ================================================ --- source: compiler-core/src/type_/tests.rs expression: "\npub type Wibble {\n Wibble\n Wobble\n}\n\npub fn main() {\n case Wibble, Wobble {\n Wibble, Wibble -> todo\n }\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble Wobble } pub fn main() { case Wibble, Wobble { Wibble, Wibble -> todo } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:8:3 │ 8 │ ╭ case Wibble, Wobble { 9 │ │ Wibble, Wibble -> todo 10 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Wibble, Wobble ================================================ FILE: compiler-core/src/type_/tests/accessors.rs ================================================ use crate::assert_module_infer; #[test] fn bug_3629() { assert_module_infer!( ("imported", "pub type Wibble"), r#" import imported pub type Exp { One(field: imported.Wibble) Two(field: imported.Wibble) } pub fn main() { let exp = One(todo) exp.field } "#, vec![ ("One", "fn(Wibble) -> Exp"), ("Two", "fn(Wibble) -> Exp"), ("main", "fn() -> Wibble") ], ); } ================================================ FILE: compiler-core/src/type_/tests/assert.rs ================================================ use crate::{assert_error, assert_infer, assert_module_infer, assert_warning}; #[test] fn bool_value() { assert_infer!( " let value = True assert value ", "Nil" ); } #[test] fn equality_check() { assert_infer!( " let value = 10 assert value == 10 ", "Nil" ); } #[test] fn comparison() { assert_infer!( " let value = 4 assert value < 5 ", "Nil" ); } #[test] fn function_call() { assert_module_infer!( " fn bool() { True } pub fn main() { assert bool() } ", vec![("main", "fn() -> Nil")] ); } #[test] fn bool_literal() { assert_warning!( " pub fn main() { assert True } " ); } #[test] fn negation_of_bool_literal() { assert_warning!( " pub fn main() { assert !False } " ); } #[test] fn equality_check_on_literals() { assert_warning!( " pub fn main() { assert 1 == 2 } " ); } #[test] fn comparison_on_literals() { assert_warning!( " pub fn main() { assert 1 < 2 } " ); } #[test] fn with_message() { assert_infer!(r#"assert True as "This should never panic""#, "Nil"); } #[test] fn compound_message() { assert_infer!( r#"assert 1 == 2 as { "one" <> " is never equal to " <> "two" }"#, "Nil" ); } #[test] fn mismatched_types() { assert_error!("assert 10"); } #[test] fn wrong_message_type() { assert_error!("assert True as 10"); } ================================================ FILE: compiler-core/src/type_/tests/assignments.rs ================================================ use crate::assert_infer; #[test] fn let_() { assert_infer!("let x = 1 2", "Int"); } #[test] fn let_1() { assert_infer!("let x = 1 x", "Int"); } #[test] fn let_2() { assert_infer!("let x = 2.0 x", "Float"); } #[test] fn let_3() { assert_infer!("let x = 2 let y = x y", "Int"); } #[test] fn let_4() { assert_infer!( "let #(#(_, _) as x, _) = #(#(0, 1.0), []) x", "#(Int, Float)" ); } #[test] fn let_5() { assert_infer!("let x: String = \"\" x", "String"); } #[test] fn let_6() { assert_infer!("let x: #(Int, Int) = #(5, 5) x", "#(Int, Int)",); } #[test] fn let_7() { assert_infer!("let x: #(Int, Float) = #(5, 5.0) x", "#(Int, Float)",); } #[test] fn let_8() { assert_infer!("let assert [1, 2, ..x]: List(Int) = [1,2,3] x", "List(Int)",); } #[test] fn let_9() { assert_infer!( "let assert #(5, [..x]): #(Int, List(Int)) = #(5, [1,2,3]) x", "List(Int)", ); } #[test] fn let_10() { assert_infer!( "let assert #(5.0, [..x]): #(Float, List(Int)) = #(5.0, [1,2,3]) x", "List(Int)", ); } #[test] fn let_11() { assert_infer!("let x: List(_) = [] x", "List(a)"); } #[test] fn let_12() { assert_infer!("let x: List(_) = [1] x", "List(Int)"); } #[test] fn let_13() { assert_infer!("let assert [a] = [1] a", "Int"); } #[test] fn let_14() { assert_infer!("let assert [a, 2] = [1] a", "Int"); } #[test] fn let_15() { assert_infer!("let assert [a, .. b] = [1] a", "Int"); } #[test] fn let_16() { assert_infer!("let assert [a, .. _] = [1] a", "Int"); } #[test] fn let_17() { assert_infer!("fn(x) { let assert [a] = x a }", "fn(List(a)) -> a"); } #[test] fn let_18() { assert_infer!("fn(x) { let assert [a] = x a + 1 }", "fn(List(Int)) -> Int"); } #[test] fn let_19() { assert_infer!("let _x = 1 2.0", "Float"); } #[test] fn let_20() { assert_infer!("let _ = 1 2.0", "Float"); } #[test] fn let_21() { assert_infer!("let #(tag, x) = #(1.0, 1) x", "Int"); } #[test] fn let_22() { assert_infer!("fn(x) { let #(a, b) = x a }", "fn(#(a, b)) -> a"); } #[test] fn let_23() { assert_infer!("let assert [] = [] 1", "Int"); } #[test] fn let_24() { assert_infer!("let assert Ok(..) = Ok(10)", "Result(Int, a)"); } #[test] fn let_25() { assert_infer!("let assert \"hello\" as a <> _ = \"\" a", "String"); } // https://github.com/gleam-lang/gleam/issues/1991 #[test] fn no_scoped_var_collision() { assert_infer!("let x = 1 { let x = 1.0 } x", "Int"); } ================================================ FILE: compiler-core/src/type_/tests/conditional_compilation.rs ================================================ use crate::assert_module_infer; #[test] fn excluded_error() { assert_module_infer!( "@target(javascript) pub type X = Y pub const x = 1 ", vec![("x", "Int")], ); } #[test] fn alias() { assert_module_infer!( "@target(erlang) pub type X = Int pub const x: X = 1 ", vec![("x", "Int")], ); } #[test] fn alias_in_block() { assert_module_infer!( "@target(erlang) pub type X = Int @target(erlang) pub const x: X = 1 ", vec![("x", "Int")], ); } #[test] fn generalising() { assert_module_infer!( " @target(erlang) pub fn id(x) { x } @target(erlang) pub fn x() { id(1) } ", vec![("id", "fn(a) -> a"), ("x", "fn() -> Int")], ); } #[test] fn excluded_generalising() { assert_module_infer!( " @target(javascript) pub fn id(x) { x } @target(javascript) pub fn x() { id(1) } pub const y = 1 ", vec![("y", "Int")], ); } #[test] fn included_const_ref_earlier() { assert_module_infer!( " @target(erlang) const x = 1 pub fn main() { x } ", vec![("main", "fn() -> Int")], ); } #[test] fn included_const_ref_later() { assert_module_infer!( "pub fn main() { x } @target(erlang) const x = 1 ", vec![("main", "fn() -> Int")], ); } #[test] fn target_does_not_need_to_be_the_first_attribute() { // In previous versions of Gleam the `@target` attribute had to be the // first attribute. assert_module_infer!( r#" @external(erlang, "blah", "wub") @target(erlang) pub fn main() -> Int "#, vec![("main", "fn() -> Int")], ); } ================================================ FILE: compiler-core/src/type_/tests/custom_types.rs ================================================ use crate::{assert_module_error, assert_module_infer, assert_warning}; // https://github.com/gleam-lang/gleam/issues/2215 #[test] fn generic_phantom() { assert_module_infer!( r#" pub type Test(a) { MakeTest(field: Test(Int)) } "#, vec![("MakeTest", "fn(Test(Int)) -> Test(a)")] ); } #[test] fn deprecated_type() { assert_warning!( r#" @deprecated("Dont use this!") pub type Cat { Cat(name: String, cuteness: Int) } pub fn name() -> String { let c = Cat("Numi", 20) c.name } "# ); } #[test] fn deprecated_all_varients_type() { assert_module_error!( r#" pub type Numbers { @deprecated("1") One @deprecated("2") Two } "# ); } #[test] fn deprecated_varients_type() { assert_warning!( r#" pub type Numbers { @deprecated("1") One Two } pub fn num() { let _one = One let _two = Two Nil } "# ); } #[test] fn depreacted_type_deprecate_varient_err() { assert_module_error!( r#" @deprecated("2") pub type Numbers { @deprecated("1") One Two } pub fn num() { let _two = Two Nil } "# ); } #[test] fn fault_tolerance() { // An error in a custom type does not stop analysis assert_module_error!( r#" pub type Cat { Cat(UnknownType) } pub type Kitten = AnotherUnknownType "# ); } #[test] fn duplicate_variable_error_does_not_stop_analysis() { // Both these aliases have errors! We do not stop on the first one. assert_module_error!( r#" type Two(a, a) { Two(a, a) } type Three(a, a) { Three } "# ); } #[test] fn conflict_with_import() { // We cannot declare a type with the same name as an imported type assert_module_error!( ("wibble", "pub type A { B }"), "import wibble.{type A} type A { C }", ); } #[test] fn generic_record_update1() { // A record update on polymorphic types with a field of different type assert_module_infer!( " pub type Box(a) { Box(value: a, i: Int) } pub fn update_box(box: Box(Int), value: String) { Box(..box, value: value) }", vec![ ("Box", "fn(a, Int) -> Box(a)"), ("update_box", "fn(Box(Int), String) -> Box(String)") ] ); } #[test] fn generic_record_update2() { // A record update on polymorphic types with generic fields of a different type assert_module_infer!( " pub type Box(a) { Box(value: a, i: Int) } pub fn update_box(box: Box(a), value: b) { Box(..box, value: value) }", vec![ ("Box", "fn(a, Int) -> Box(a)"), ("update_box", "fn(Box(a), b) -> Box(b)") ] ); } #[test] fn inferred_variant_record_update_change_type_parameter() { assert_module_infer!( r#" pub type Box(a) { Locked(password: String, value: a) Unlocked(password: String, value: a) } pub fn main() { let box = Locked("ungu€$$4bLe", 11) case box { Locked(..) as box -> Locked(..box, value: True) Unlocked(..) as box -> Unlocked(..box, value: False) } } "#, vec![ ("Locked", "fn(String, a) -> Box(a)"), ("Unlocked", "fn(String, a) -> Box(a)"), ("main", "fn() -> Box(Bool)") ] ); } #[test] fn pattern_match_correct_labeled_field() { assert_module_error!( r#" type Fish { Starfish() Jellyfish(name: String, jiggly: Bool) } fn handle_fish(fish: Fish) { case fish { Starfish() -> False Jellyfish(jiggly:) -> jiggly // <- error is here } } "# ); } #[test] fn pattern_match_correct_pos_field() { assert_module_error!( r#" type Fish { Starfish() Jellyfish(String, Bool) } fn handle_fish(fish: Fish) { case fish { Starfish() -> False Jellyfish(jiggly) -> jiggly } } "# ); } ================================================ FILE: compiler-core/src/type_/tests/dead_code_detection.rs ================================================ use crate::{assert_no_warnings, assert_warning}; #[test] fn unused_recursive_function() { assert_warning!( " fn unused(value: Int) -> Int { case value { 0 -> 0 _ -> unused(value - 1) } } " ); } #[test] fn unused_mutually_recursive_functions() { assert_warning!( " fn wibble(value: Int) -> Int { wobble(value) } fn wobble(value: Int) -> Int { wibble(value) } " ); } #[test] fn constant_only_referenced_by_unused_function() { assert_warning!( " const value = 10 fn unused() { value } " ); } #[test] fn constant_only_referenced_by_unused_constant() { assert_warning!( " const value = 10 const value_twice = #(value, value) " ); } #[test] fn constant_referenced_by_public_constant() { assert_no_warnings!( " const value = 10 pub const value_twice = #(value, value) " ); } #[test] fn type_variant_only_referenced_by_unused_function() { assert_warning!( " type Wibble { Wibble Wobble } fn unused() { Wibble } pub fn used() { let _ = Wobble Nil } " ); } #[test] fn type_marked_as_used_if_variant_used() { assert_no_warnings!( " type PrivateType { PrivateConstructor } pub fn public_function() { let _constructor = PrivateConstructor Nil } " ); } #[test] fn type_and_variant_unused() { assert_warning!( " type PrivateType { PrivateConstructor } " ); } #[test] fn type_used_by_public_alias() { assert_no_warnings!( " type PrivateType pub type PublicAlias = PrivateType " ); } #[test] fn imported_module_only_referenced_by_unused_function() { assert_warning!( ( "wibble", " pub type Wibble { Wibble(Int) } " ), " import wibble fn unused() { wibble.Wibble } " ); } #[test] fn imported_module_alias_only_referenced_by_unused_function() { assert_warning!( ( "wibble", " pub type Wibble { Wibble(Int) } " ), " import wibble as wobble fn unused() { wobble.Wibble } " ); } #[test] fn imported_module_alias_only_referenced_by_unused_function_with_unqualified() { assert_warning!( ( "wibble", " pub type Wibble { Wibble(Int) } " ), " import wibble.{type Wibble} as wobble fn unused() { wobble.Wibble } pub fn main() -> Wibble { panic } " ); } #[test] fn imported_module_used_by_public_function() { assert_no_warnings!( ( "thepackage", "wibble", " pub type Wibble { Wibble(Int) } " ), " import wibble pub fn main() { wibble.Wibble(4) } " ); } #[test] fn imported_module_used_in_type() { assert_no_warnings!( ( "thepackage", "wibble", " pub type Wibble { Wibble(Int) } " ), " import wibble pub fn main() -> wibble.Wibble { panic } " ); } #[test] fn imported_module_used_by_public_constant() { assert_no_warnings!( ( "thepackage", "wibble", " pub type Wibble { Wibble(Int) } " ), " import wibble pub const value = wibble.Wibble(42) " ); } #[test] fn imported_module_used_in_type_variant() { assert_no_warnings!( ( "thepackage", "wibble", " pub type Wibble { Wibble(Int) } " ), " import wibble pub type Wobble { Wobble(w: wibble.Wibble) } " ); } #[test] fn imported_module_used_in_type_alias() { assert_no_warnings!( ( "thepackage", "wibble", " pub type Wibble { Wibble(Int) } " ), " import wibble pub type Wobble = wibble.Wibble " ); } #[test] fn imported_value_only_referenced_by_unused_function() { assert_warning!( ( "wibble", " pub type Wibble { Wibble(Int) } " ), " import wibble.{Wibble} fn unused() { Wibble } " ); } #[test] fn imported_type_only_referenced_by_unused_function() { assert_warning!( ( "wibble", " pub type Wibble " ), " import wibble.{type Wibble} fn unused() -> Wibble { panic } " ); } #[test] fn imported_value_used_by_public_function() { assert_no_warnings!( ("thepackage", "wibble", "pub type Wibble { Wibble }"), " import wibble.{Wibble} pub fn main() { Wibble } " ); } #[test] fn imported_type_used_by_public_function() { assert_no_warnings!( ("thepackage", "wibble", "pub type Wibble { Wibble }"), " import wibble.{type Wibble} pub fn main() -> Wibble { wibble.Wibble } " ); } #[test] fn imported_type_used_by_public_function_parameter() { assert_no_warnings!( ("thepackage", "wibble", "pub type Wibble { Wibble }"), " import wibble.{type Wibble} pub fn main(a: Wibble) { a } " ); } #[test] fn unused_type_alias() { assert_warning!( " type Wibble = Int " ); } #[test] fn private_type_alias_only_referenced_by_unused_function() { assert_warning!( " type Wibble = Int fn unused() -> Wibble { 10 } " ); } #[test] fn private_type_alias_underlying_type_referenced_by_public_function() { assert_warning!( " type Wibble = Int pub fn used() -> Int { 10 } " ); } #[test] fn private_type_alias_referenced_by_public_function() { assert_no_warnings!( " type Wibble = Int pub fn used() -> Wibble { 10 } " ); } #[test] fn shadowed_imported_value_marked_unused() { assert_warning!( ( "wibble", " pub const wibble = 1 " ), " import wibble.{wibble} pub const wibble = 2 " ); } #[test] fn used_shadowed_imported_value() { assert_warning!( ( "thepackage", "wibble", " pub const wibble = 1 " ), " import wibble.{wibble} pub const wibble = wibble " ); } #[test] fn imported_module_marked_unused_when_shadowed_in_record_access() { assert_warning!( ( "wibble", " pub const wibble = 1 " ), " import wibble type Wibble { Wibble(wobble: Int) } pub fn main() { let wibble = Wibble(10) // This does not reference the `wibble` module! wibble.wobble } " ); } #[test] fn local_variable_marked_unused_when_shadowed_in_module_access() { assert_warning!( ( "wibble", " pub const wibble = 1 " ), " import wibble pub fn main() { let wibble = 10 // This does not reference the `wibble` variable! wibble.wibble } " ); } #[test] // https://github.com/gleam-lang/gleam/issues/3552 fn constructor_used_if_type_alias_shadows_it() { assert_warning!( ( "wibble", " pub type Wibble { Wibble(String) } " ), r#" import wibble.{Wibble} type Wibble = wibble.Wibble pub fn main() { Wibble("hello") } "# ); } #[test] fn imported_type_and_constructor_with_same_name() { assert_warning!( ( "wibble", " pub type Wibble { Wibble } " ), " import wibble.{type Wibble, Wibble} pub fn main() { Wibble } " ); } #[test] fn imported_type_and_constructor_with_same_name2() { assert_warning!( ( "wibble", " pub type Wibble { Wibble } " ), " import wibble.{type Wibble, Wibble} pub fn main() -> Wibble { wibble.Wibble } " ); } #[test] fn imported_type_and_constructor_with_same_name3() { assert_no_warnings!( ( "thepackage", "wibble", " pub type Wibble { Wibble } " ), " import wibble.{type Wibble, Wibble} pub fn main() -> Wibble { Wibble } " ); } ================================================ FILE: compiler-core/src/type_/tests/echo.rs ================================================ use crate::assert_module_infer; #[test] pub fn echo_has_same_type_as_printed_expression() { assert_module_infer!( r#" pub fn main() { echo 1 } "#, vec![("main", "fn() -> Int")] ); } #[test] pub fn echo_has_same_type_as_printed_expression_2() { assert_module_infer!( r#" pub fn main() { let wibble = todo echo wibble } "#, vec![("main", "fn() -> a")] ); } #[test] pub fn echo_in_pipeline_acts_as_the_identity_function() { assert_module_infer!( r#" pub fn main() { [1, 2, 3] |> echo } "#, vec![("main", "fn() -> List(Int)")] ); } #[test] pub fn echo_in_pipeline_acts_as_the_identity_function_2() { assert_module_infer!( r#" pub fn main() { 1 |> echo |> fn(_: Int) { True } } "#, vec![("main", "fn() -> Bool")] ); } #[test] pub fn echo_in_pipeline_acts_as_the_identity_function_3() { assert_module_infer!( r#" pub fn main() { [1, 2, 3] |> echo |> echo |> wibble } fn wibble(_: List(Int)) -> List(String) { todo } "#, vec![("main", "fn() -> List(String)")] ); } ================================================ FILE: compiler-core/src/type_/tests/errors.rs ================================================ use crate::{ assert_error, assert_internal_module_error, assert_js_module_error, assert_module_error, assert_module_syntax_error, }; #[test] fn bit_array_invalid_type() { assert_module_error!( "fn x() { \"test\" } fn main() { let a = <<1:size(x())>> a }" ); } #[test] fn bit_arrays2() { assert_error!("let <> = <<1>>"); } #[test] fn bit_arrays3() { assert_error!("let <> = <<1>>"); } #[test] fn bit_arrays4() { assert_error!("let <> = <<1>>"); } #[test] fn bit_array_float() { assert_error!("case <<1>> { <> if a > 1 -> 1 _ -> 2 }"); } #[test] fn bit_array_binary() { assert_error!("case <<1>> { <> if a > 1 -> 1 _ -> 2 }"); } #[test] fn bit_array_guard() { assert_error!("case <<1>> { <> if a == \"test\" -> 1 _ -> 2 }"); } #[test] fn bit_array_segment_nosize() { assert_error!("case <<1>> { <<_:bytes, _:bytes>> -> 1 }"); } #[test] fn bit_array_segment_nosize2() { assert_error!("case <<1>> { <<_:bits, _:bytes>> -> 1 }"); } #[test] fn bit_array_segment_nosize3() { assert_error!("case <<1>> { <<_:bytes, _:bits>> -> 1 }"); } #[test] fn bit_array_segment_conflicting_options_int() { assert_error!("let x = <<1:int-bytes>> x"); } #[test] fn bit_array_segment_conflicting_options_bit_array() { assert_error!("case <<1>> { <<1:bits-bytes>> -> 1 }"); } #[test] fn bit_array_segment_conflicting_signedness1() { assert_error!("let x = <<1:signed-unsigned>> x"); } #[test] fn bit_array_segment_conflicting_signedness2() { assert_error!("case <<1>> { <<1:unsigned-signed>> -> 1 }"); } #[test] fn bit_array_segment_conflicting_endianness1() { assert_error!("let x = <<1:big-little>> x"); } #[test] fn bit_array_segment_conflicting_endianness2() { assert_error!("case <<1>> { <<1:native-big>> -> 1 }"); } #[test] fn bit_array_segment_size() { assert_error!("let x = <<1:8-size(5)>> x"); } #[test] fn bit_array_segment_size2() { assert_error!("case <<1>> { <<1:size(2)-size(8)>> -> 1 }"); } #[test] fn bit_array_segment_unit_unit() { assert_error!("let x = <<1:unit(2)-unit(5)>> x"); } #[test] fn bit_array_segment_type_does_not_allow_unit_codepoint_utf8() { assert_error!("let x = <<1:utf8_codepoint-unit(5)>> x"); } #[test] fn bit_array_segment_type_does_not_allow_unit_codepoint_utf16() { assert_error!("let x = <<1:utf16_codepoint-unit(5)>> x"); } #[test] fn bit_array_segment_type_does_not_allow_unit_codepoint_utf32() { assert_error!("case <<1>> { <<1:utf32_codepoint-unit(2)>> -> 1 }"); } #[test] fn bit_array_segment_type_does_not_allow_unit_codepoint_utf8_2() { assert_error!("let x = <<1:utf8_codepoint-size(5)>> x"); } #[test] fn bit_array_segment_type_does_not_allow_unit_codepoint_utf16_2() { assert_error!("let x = <<1:utf16_codepoint-size(5)>> x"); } #[test] fn bit_array_segment_type_does_not_allow_unit_codepoint_utf32_2() { assert_error!("case <<1>> { <<1:utf32_codepoint-size(5)>> -> 1 }"); } #[test] fn bit_array_segment_type_does_not_allow_unit_utf8_2() { assert_error!("let x = <<1:utf8-unit(5)>> x"); } #[test] fn bit_array_segment_type_does_not_allow_unit_utf16() { assert_error!("let x = <<1:utf16-unit(5)>> x"); } #[test] fn bit_array_segment_type_does_not_allow_unit_utf32() { assert_error!("case <<1>> { <<1:utf32-unit(2)>> -> 1 }"); } #[test] fn bit_array_segment_type_does_not_allow_size_utf8() { assert_error!("let x = <<1:utf8-size(5)>> x"); } #[test] fn bit_array_segment_type_does_not_allow_size_utf16() { assert_error!("let x = <<1:utf16-size(5)>> x"); } #[test] fn bit_array_segment_type_does_not_allow_size_utf32() { assert_error!("case <<1>> { <<1:utf32-size(5)>> -> 1 }"); } #[test] fn bit_array_segment_type_does_not_allow_variable_string() { assert_error!("case <<>> { <> -> 1 _ -> 2 }"); } #[test] fn bit_array_segment_type_does_not_allow_aliased_variable_string() { assert_error!("case <<>> { <<_ as a:utf8>> -> 1 _ -> 2 }"); } #[test] fn bit_array_segment_unit_no_size() { assert_error!("let x = <<1:unit(5)>> x"); } #[test] fn bit_array_size_not_int() { assert_error!("let x = <<1:size(\"1\")>> x"); } #[test] fn bit_array_size_not_int_variable() { assert_error!("let a = 2.0 case <<1>> { <<1:size(a)>> -> a }"); } #[test] fn bit_array_float_size() { // float given invalid size assert_error!("let x = <<1:8-float>> x"); } #[test] fn bit_array_bits_option_in_value() { assert_error!("let x = <<<<1:1>>:bytes>> x"); } #[test] fn bit_array_utf8_and_size() { assert_error!(r#"let x = <<"test":size(1)>> x"#); } #[test] fn bit_array_utf8_and_unit() { assert_error!(r#"let x = <<"test":unit(5)>> x"#); } #[test] fn add_int_float() { assert_error!("1 + 1.0"); } #[test] fn add_f_int_float() { assert_error!("1 +. 1.0"); } #[test] fn int_eq_float() { assert_error!("1 == 1.0"); } #[test] fn int_gt_float() { assert_error!("1 > 1.0"); } #[test] fn float_gtf_int() { assert_error!("1.0 >. 1"); } #[test] fn fn0_eq_fn1() { assert_error!("fn() { 1 } == fn(x) { x + 1 }"); } #[test] fn unknown_variable() { assert_error!("x"); } #[test] fn unknown_variable_type() { assert_error!("Int"); } #[test] fn unknown_module() { assert_module_error!("import xpto"); } #[test] fn unknown_variable_2() { assert_error!("case 1 { x -> 1 1 -> x }"); } #[test] fn unknown_variable_3() { assert_error!("let add = fn(x, y) { x + y } 1 |> add(unknown)"); } #[test] fn incorrect_arity_error() { assert_error!("let id = fn(x) { x } id()"); } #[test] fn incorrect_arity_error_2() { assert_error!("let id = fn(x) { x } id(1, 2)"); } #[test] fn case_clause_mismatch() { assert_error!("case 1 { a -> 1 b -> 2.0 }"); } #[test] fn case_subject_pattern_unify() { assert_error!("case 1.0 { 1 -> 1 }"); } #[test] fn case_subject_pattern_unify_2() { assert_error!("case 1 { 1.0 -> 1 }"); } #[test] fn case_operator_unify_situation() { assert_error!("case 1, 2.0 { a, b -> a + b }"); } #[test] fn case_could_not_unify() { assert_error!("case 1, 2.0 { a, b -> a 1, 2 -> 0 }"); } #[test] fn assigned_function_annotation() { assert_error!("let f = fn(x: Int) { x } f(1.0)"); } #[test] fn function_return_annotation() { assert_error!("fn() -> Int { 2.0 }"); } #[test] fn function_arg_and_return_annotation() { assert_error!("fn(x: Int) -> Float { x }"); } // https://github.com/gleam-lang/gleam/issues/1378 #[test] fn function_return_annotation_mismatch_with_pipe() { assert_module_error!( "pub fn main() -> String { 1 |> add_two } fn add_two(i: Int) -> Int { i + 2 }" ); } #[test] fn functions_called_outside_module() { assert_module_syntax_error!("const first = list.at([1], 0)"); } #[test] fn pipe_mismatch_error() { assert_module_error!( "pub fn main() -> String { Orange |> eat_veggie } type Fruit{ Orange } type Veg{ Lettuce } fn eat_veggie(v: Veg) -> String { \"Ok\" }" ); } #[test] fn pipe_value_type_mismatch_error() { assert_module_error!( "pub fn main() -> String { eat_veggie |> Orange } type Fruit{ Orange } type Veg{ Lettuce } fn eat_veggie(v: Veg) -> String { \"Ok\" }" ); } #[test] fn case_tuple_guard() { assert_error!("case #(1, 2, 3) { x if x == #(1, 1.0) -> 1 }"); } #[test] fn case_list_guard() { assert_error!("case [1] { x if x == [1, 2.0] -> 1 _ -> 2 }"); } #[test] fn case_tuple_guard_2() { assert_error!("case #(1, 2) { x if x == #(1, 1.0) -> 1 }"); } #[test] fn case_int_tuple_guard() { assert_error!("case 1 { x if x == #() -> 1 }"); } #[test] fn wrong_number_of_subjects() { assert_error!("case 1 { _, _ -> 1 }"); } #[test] fn wrong_number_of_subjects_alternative_patterns() { assert_error!("case 1 { _, _ | _ | _, _, _ -> 1 }"); } #[test] fn recursive_var() { assert_error!("let id = fn(x) { x(x) } 1"); } #[test] fn true_fn() { assert_error!("let True(x) = 1"); } #[test] fn ok_2_args() { assert_error!("let Ok(1, x) = 1"); } #[test] fn access_int() { assert_error!("let x = 1 x.whatever"); } #[test] fn tuple_2_3() { assert_error!("#(1, 2) == #(1, 2, 3)"); } #[test] fn tuple_int_float() { assert_error!("#(1.0, 2, 3) == #(1, 2, 3)"); } #[test] fn tuple_int() { assert_error!("let #(a, b) = 1"); } #[test] fn int_float_list() { assert_error!("[1.0] == [1]"); } #[test] fn guard_int_float_eq_vars() { assert_error!("let x = 1 let y = 1.0 case x { _ if x == y -> 1 }"); } #[test] fn guard_float_int_eq_vars() { assert_error!("let x = 1.0 let y = 1 case x { _ if x == y -> 1 }"); } #[test] fn guard_if_float() { assert_error!("let x = 1.0 case x { _ if x -> 1 }"); } #[test] fn case() { assert_error!("case #(1, 1.0) { #(x, _) | #(_, x) -> 1 }"); } #[test] fn case2() { assert_error!("case [3.33], 1 { x, y if x > y -> 1 }"); } #[test] fn case3() { assert_error!("case 1, 2.22, \"three\" { x, _, y if x > y -> 1 }"); } #[test] fn case4() { assert_error!("case [3.33], 1 { x, y if x >= y -> 1 }"); } #[test] fn case5() { assert_error!("case 1, 2.22, \"three\" { x, _, y if x >= y -> 1 }"); } #[test] fn case6() { assert_error!("case [3.33], 1 { x, y if x < y -> 1 }"); } #[test] fn case7() { assert_error!("case 1, 2.22, \"three\" { x, _, y if x < y -> 1 }"); } #[test] fn case8() { assert_error!("case [3.33], 1 { x, y if x <= y -> 1 }"); } #[test] fn case9() { assert_error!("case 1, 2.22, \"three\" { x, _, y if x <= y -> 1 }"); } #[test] fn case10() { assert_error!("case [3], 1.1 { x, y if x >. y -> 1 }"); } #[test] fn case11() { assert_error!("case 2.22, 1, \"three\" { x, _, y if x >. y -> 1 }"); } #[test] fn case12() { assert_error!("case [3], 1.1 { x, y if x >=. y -> 1 }"); } #[test] fn case13() { assert_error!("case 2.22, 1, \"three\" { x, _, y if x >=. y -> 1 }"); } #[test] fn case14() { assert_error!("case [3], 1.1 { x, y if x <. y -> 1 }"); } #[test] fn case15() { assert_error!("case 2.22, 1, \"three\" { x, _, y if x <. y -> 1 }"); } #[test] fn case16() { assert_error!("case [3], 1.1 { x, y if x <=. y -> 1 }"); } #[test] fn case17() { assert_error!("case 2.22, 1, \"three\" { x, _, y if x <=. y -> 1 }"); } #[test] fn int_operator_on_floats_in_case_guard() { assert_error!("case 3.0 { x if x > 2.0 -> \"a\" _ -> \"b\" }"); } #[test] fn float_operator_on_ints_in_case_guard() { assert_error!("case 3 { x if x +. 2 == 5.0 -> \"a\" _ -> \"b\" }"); } #[test] fn case18() { assert_error!("case 1 { x if x == \"x\" -> 1 }"); } #[test] fn case19() { assert_error!("case [1] { [x] | x -> 1 }"); } #[test] fn case20() { assert_error!("case [1] { [x] | [] as x -> 1 }"); } #[test] fn extra_var_inalternative() { assert_error!("case [1] { [x] | [x, y] -> 1 }"); } #[test] fn extra_var_inalternative2() { assert_error!("case #(1, 2) { #(1, y) | #(x, y) -> 1 }"); } #[test] fn extra_var_inalternative3() { assert_error!("let x = 1 case #(1, 2) { #(1, y) | #(x, y) -> 1 }"); } #[test] fn tuple_arity() { // https://github.com/gleam-lang/gleam/issues/714 assert_error!("case #(1, 2) { #(1, _, _, _) -> 1 }"); } #[test] fn duplicate_vars() { assert_error!("case #(1, 2) { #(x, x) -> 1 }"); } #[test] fn duplicate_vars_2() { assert_error!("case [3.33], 1 { x, x -> 1 }"); } #[test] fn duplicate_vars_3() { assert_error!("case [1, 2, 3] { [x, x, y] -> 1 }"); } #[test] fn tuple_index_out_of_bounds() { assert_error!("#(0, 1).2"); } #[test] fn tuple_index_not_a_tuple() { assert_error!("Nil.2"); } #[test] fn tuple_index_not_a_tuple_unbound() { assert_error!("fn(a) { a.2 }"); } #[test] fn unknown_accessed_type() { assert_error!("fn(a) { a.field }"); } #[test] fn unknown_field() { assert_error!("fn(a: a) { a.field }"); } #[test] fn field_not_in_all_variants() { assert_module_error!( " pub type Person { Teacher(name: String, age: Int, title: String) Student(name: String, age: Int) } pub fn get_title(person: Person) { person.title }" ); } #[test] fn field_not_in_any_variant() { assert_module_error!( " pub type Person { Teacher(name: String, age: Int, title: String) Student(name: String, age: Int) } pub fn get_height(person: Person) { person.height }" ); } #[test] fn field_type_different_between_variants() { assert_module_error!( " pub type Shape { Square(x: Int, y: Int) Rectangle(x: String, y: String) } pub fn get_x(shape: Shape) { shape.x } " ); } #[test] fn accessor_multiple_variants_multiple_positions() { // We cannot access fields on custom types with multiple variants where they are in different positions e.g. 2nd and 3rd assert_module_error!( " pub type Person { Teacher(name: String, title: String, age: Int) Student(name: String, age: Int) } pub fn get_name(person: Person) { person.name } pub fn get_age(person: Person) { person.age }" ); } #[test] fn accessor_multiple_variants_multiple_positions2() { // We cannot access fields on custom types with multiple variants where they are in different positions e.g. 1st and 3rd assert_module_error!( " pub type Person { Teacher(title: String, age: Int, name: String) Student(name: String, age: Int) } pub fn get_name(person: Person) { person.name } pub fn get_age(person: Person) { person.age }" ); } #[test] fn record_access_on_inferred_variant_when_field_is_in_other_variants() { assert_module_error!( " pub type Wibble { Wibble(wibble: Int) Wobble(wobble: Int) } pub fn main() { let always_wibble = Wibble(10) always_wibble.wobble } " ); } #[test] fn module_could_not_unify() { assert_module_error!("fn go() { 1 + 2.0 }"); } #[test] fn module_could_not_unify2() { assert_module_error!("fn go() { 1 + 2.0 }"); } #[test] fn module_could_not_unify3() { assert_module_error!( " fn id(x: a, y: a) { x } pub fn x() { id(1, 1.0) }" ); } #[test] fn module_could_not_unify4() { assert_module_error!( " fn wobble() -> Int { 5 } fn run(one: fn() -> String) { one() } fn demo() { run(wobble) }" ); } #[test] fn module_could_not_unify5() { assert_module_error!( " fn wobble(x: Int) -> Int { x * 5 } fn run(one: fn(String) -> Int) { one(\"one.\") } fn demo() { run(wobble) }" ); } #[test] fn module_could_not_unify6() { assert_module_error!("fn main() { let x: String = 5 x }"); } #[test] fn module_could_not_unify7() { assert_module_error!("fn main() { let assert 5 = \"\" }"); } #[test] fn module_could_not_unify8() { assert_module_error!("fn main() { let x: #(x, x) = #(5, 5.0) x }"); } #[test] fn module_could_not_unify9() { assert_module_error!("fn main() { let assert [1, 2, ..x]: List(String) = [1,2,3] x }"); } #[test] fn module_could_not_unify10() { assert_module_error!( "fn main() { let #(y, [..x]): #(x, List(x)) = #(\"one\", [1,2,3]) x }" ); } #[test] fn module_could_not_unify11() { assert_module_error!( " pub type Box(inner) { Box(inner) } pub fn create_int_box(value: Int) { let x: Box(Float) = Box(value) x }" ); } #[test] fn module_could_not_unify12() { assert_module_error!( " pub type Person { Person(name: String, age: Int) } pub fn create_person(age: Float) { let x: Person = Person(name: \"Quinn\", age: age) x }" ); } #[test] fn module_arity_error() { assert_module_error!("fn go(x: List(a, b)) -> Int { 1 }"); } #[test] fn module_private_type_leak_1() { assert_module_error!( r#"type PrivateType @external(erlang, "a", "b") pub fn leak_type() -> PrivateType "# ); } #[test] fn module_private_type_leak_2() { assert_module_error!( r#"type PrivateType @external(erlang, "a", "b") fn go() -> PrivateType pub fn leak_type() { go() }"# ); } #[test] fn module_private_type_leak_3() { assert_module_error!( r#"type PrivateType @external(erlang, "a", "b") fn go() -> PrivateType pub fn leak_type() { [go()] }"# ); } #[test] fn module_private_type_leak_4() { assert_module_error!( r#"type PrivateType @external(erlang, "a", "b") pub fn go(x: PrivateType) -> Int"# ); } #[test] fn module_private_type_leak_5() { assert_module_error!( r#"type PrivateType pub type LeakType { Variant(PrivateType) }"# ); } // https://github.com/gleam-lang/gleam/issues/3387 // Private types should not leak even in internal modules #[test] fn module_private_type_leak_6() { assert_internal_module_error!( r#"type PrivateType pub type LeakType { Variant(PrivateType) }"# ); } #[test] fn unexpected_labelled_arg() { assert_module_error!(r#"fn id(x) { x } fn y() { id(x: 4) }"#); } #[test] fn unexpected_labelled_arg_record_constructor() { assert_module_error!(r#"type X { X(Int) } fn y() { X(a: 0) }"#); } #[test] fn unexpected_arg_with_label_shorthand() { assert_module_error!( r#" fn id(x) { x } fn y() { let x = 4 id(x:) } "# ); } #[test] fn positional_argument_after_labelled() { assert_module_error!( r#"type X { X(a: Int, b: Int, c: Int) } fn x() { X(b: 1, a: 1, 1) }"# ); } #[test] fn positional_argument_after_one_using_label_shorthand() { assert_module_error!( r#"type X { X(a: Int, b: Int, c: Int) } fn x() { let b = 1 let a = 1 X(b:, a:, 1) }"# ); } #[test] fn unknown_type() { assert_module_error!(r#"type Thing { Thing(unknown: x) }"#); } #[test] fn unknown_type_in_alias() { // We cannot refer to unknown types in an alias assert_module_error!("type IntMap = IllMap(Int, Int)"); } #[test] fn unknown_type_in_alias2() { // We cannot refer to unknown types in an alias assert_module_error!("type IntMap = Map(Inf, Int)"); } #[test] fn unknown_type_var_in_alias2() { // We cannot use undeclared type vars in a type alias assert_module_error!("type X = List(a)"); } #[test] fn module_non_local_gaurd_var() { assert_module_error!( r#"fn one() { 1 } fn main() { case 1 { _ if one -> 1 } }"# ); } #[test] fn unknown_record_field() { // An unknown field should report the possible fields' labels assert_module_error!( " pub type Box(a) { Box(inner: a) } pub fn main(box: Box(Int)) { box.unknown } " ); } #[test] fn unknown_record_field_2() { // An unknown field should report the possible fields' labels assert_module_error!( " pub type Box(a) { Box(inner: a) } pub fn main(box: Box(Box(Int))) { box.inner.unknown }" ); } #[test] fn unnecessary_spread_operator() { assert_module_error!( " type Triple { Triple(a: Int, b: Int, c: Int) } fn main() { let triple = Triple(1,2,3) let Triple(a, b, c, ..) = triple }" ); } #[test] fn duplicate_var_in_record_pattern() { // Duplicate var in record assert_module_error!( r#"type X { X(a: Int, b: Int, c: Int) } fn x() { case X(1,2,3) { X(x, y, x) -> 1 } }"# ); } #[test] fn duplicate_label_shorthands_in_record_pattern() { // Duplicate var in record assert_module_error!( r#"type X { X(a: Int, b: Int, c: Int) } fn x() { case X(1,2,3) { X(a:, b:, c: a) -> 1 } }"# ); } #[test] fn guard_record_wrong_arity() { // Constructor in guard clause errors assert_module_error!( r#"type X { X(a: Int, b: Float) } fn x() { case X(1, 2.0) { x if x == X(1) -> 1 _ -> 2 } }"# ); } #[test] fn subject_int_float_guard_tuple() { assert_module_error!( r#"type X { X(a: Int, b: Float) } fn x() { case X(1, 2.0) { x if x == X(2.0, 1) -> 1 _ -> 2 } }"# ); } #[test] fn type_variables_in_body() { // Type variables are shared between function annotations and let annotations within their body assert_module_error!( " pub type Box(a) { Box(value: a) } pub fn go(box1: Box(a), box2: Box(b)) { let _: Box(a) = box2 let _: Box(b) = box1 5 }" ); } #[test] fn duplicate_function_names() { // We cannot declare two functions with the same name in a module assert_module_error!( "fn dupe() { 1 } fn dupe() { 2 }" ); } #[test] fn duplicate_function_names_2() { // Different types to force a unify error if we don't detect the // duplicate during refactoring. assert_module_error!( "fn dupe() { 1 } fn dupe() { 2.0 }" ); } #[test] fn duplicate_function_names_3() { assert_module_error!( "fn dupe() { 1 } fn dupe(x) { x }" ); } #[test] fn duplicate_function_names_4() { assert_module_error!( r#"fn dupe() { 1 } @external(erlang, "a", "b") fn dupe(x) -> x "# ); } #[test] fn duplicate_function_names_5() { assert_module_error!( r#" @external(erlang, "a", "b") fn dupe(x) -> x fn dupe() { 1 } "# ); } #[test] fn duplicate_constructors() { // We cannot declare two type constructors with the same name in a module assert_module_error!( "type Box { Box(x: Int) } type Boxy { Box(Int) }" ); } #[test] fn duplicate_constructors2() { // We cannot declare two type constructors with the same name in a module assert_module_error!( "type Boxy { Box(Int) } type Box { Box(x: Int) }" ); } #[test] fn duplicate_constructors3() { // We cannot declare two type constructors with the same name in a module assert_module_error!("type Boxy { Box(Int) Box(Float) }"); } #[test] fn duplicate_alias_names() { // We cannot reuse an alias name in the same module assert_module_error!("type X = Int type X = Int"); } #[test] fn duplicate_custom_type_names() { // We cannot declare two types with the same name in a module assert_module_error!("type DupType { A } type DupType { B }"); } #[test] fn duplicate_const_names() { // We cannot declare two const with the same name in a module assert_module_error!( "const duplicate = 1 pub const duplicate = 1" ); } #[test] fn duplicate_const_and_function_names_const_fn() { // We cannot declare const and functions with the same name in a module // https://github.com/gleam-lang/gleam/issues/2069 assert_module_error!( "const duplicate = 1 fn duplicate() { 2 }" ); } #[test] fn duplicate_const_const() { assert_module_error!( "const wibble = 1 const wibble = 2" ); } #[test] fn duplicate_fn_fn() { assert_module_error!( "fn wibble() { 1 } fn wibble() { 2 }" ); } #[test] fn duplicate_extfn_extfn() { assert_module_error!( r#" @external(erlang, "module1", "function1") fn wibble() -> Float @external(erlang, "module2", "function2") fn wibble() -> Float "# ); } #[test] fn duplicate_extfn_fn() { assert_module_error!( " @external(erlang, \"module1\", \"function1\") fn wibble() -> Float fn wibble() { 2 }" ); } #[test] fn duplicate_fn_extfn() { assert_module_error!( "fn wibble() { 1 } @external(erlang, \"module2\", \"function2\") fn wibble() -> Float " ); } #[test] fn duplicate_const_extfn() { assert_module_error!( "const wibble = 1 @external(erlang, \"module2\", \"function2\") fn wibble() -> Float " ); } #[test] fn duplicate_extfn_const() { assert_module_error!( " @external(erlang, \"module1\", \"function1\") fn wibble() -> Float const wibble = 2" ); } #[test] fn duplicate_const_fn() { assert_module_error!( "const wibble = 1 fn wibble() { 2 }" ); } #[test] fn duplicate_fn_const() { assert_module_error!( "fn wibble() { 1 } const wibble = 2" ); } #[test] fn invalid_const_name() { assert_module_error!("const myInvalid_Constant = 42"); } #[test] fn invalid_parameter_name() { assert_module_error!("fn add(numA: Int, num_b: Int) { numA + num_b }"); } #[test] fn invalid_parameter_name2() { assert_module_error!("fn pass(label paramName: Bool) { paramName }"); } #[test] fn invalid_parameter_name3() { assert_error!("let add = fn(numA: Int, num_b: Int) { numA + num_b }"); } #[test] fn invalid_parameter_discard_name() { assert_module_error!("fn ignore(_ignoreMe: Bool) { 98 }"); } #[test] fn invalid_parameter_discard_name2() { assert_module_error!("fn ignore(labelled_discard _ignoreMe: Bool) { 98 }"); } #[test] fn invalid_parameter_discard_name3() { assert_error!("let ignore = fn(_ignoreMe: Bool) { 98 }"); } #[test] fn invalid_parameter_label() { assert_module_error!("fn func(thisIsALabel param: Int) { param }"); } #[test] fn invalid_parameter_label2() { assert_module_error!("fn ignore(thisIsALabel _ignore: Int) { 25 }"); } #[test] fn invalid_constructor_name() { assert_module_error!("type MyType { Int_Value(Int) }"); } #[test] fn invalid_constructor_arg_name() { assert_module_error!("type IntWrapper { IntWrapper(innerInt: Int) }"); } #[test] fn invalid_custom_type_name() { assert_module_error!("type Boxed_value { Box(Int) }"); } #[test] fn invalid_type_alias_name() { assert_module_error!("type Fancy_Bool = Bool"); } #[test] fn invalid_function_name() { assert_module_error!("fn doStuff() {}"); } #[test] fn invalid_variable_name() { assert_error!("let theAnswer = 42"); } #[test] fn invalid_variable_discard_name() { assert_error!("let _boringNumber = 72"); } #[test] fn invalid_use_name() { assert_module_error!( "fn use_test(f) { f(Nil) } pub fn main() { use useVar <- use_test() }" ); } #[test] fn invalid_use_discard_name() { assert_module_error!( "fn use_test(f) { f(Nil) } pub fn main() { use _discardVar <- use_test() }" ); } #[test] fn invalid_pattern_assignment_name() { assert_error!("let assert 42 as theAnswer = 42"); } #[test] fn invalid_list_pattern_name() { assert_error!("let assert [theElement] = [9.4]"); } #[test] fn invalid_list_pattern_discard_name() { assert_error!("let assert [_elemOne] = [False]"); } #[test] fn invalid_constructor_pattern_name() { assert_module_error!( "pub type Box { Box(Int) } pub fn main() { let Box(innerValue) = Box(203) }" ); } #[test] fn invalid_constructor_pattern_discard_name() { assert_module_error!( "pub type Box { Box(Int) } pub fn main() { let Box(_ignoredInner) = Box(203)}" ); } #[test] fn invalid_tuple_pattern_name() { assert_error!("let #(a, secondValue) = #(1, 2)"); } #[test] fn invalid_tuple_pattern_discard_name() { assert_error!("let #(a, _secondValue) = #(1, 2)"); } #[test] fn invalid_bit_array_pattern_name() { assert_error!("let assert <> = <<73>>"); } #[test] fn invalid_bit_array_pattern_discard_name() { assert_error!("let assert <<_iDontCare>> = <<97>>"); } #[test] fn invalid_string_prefix_pattern_name() { assert_error!(r#"let assert "prefix" <> coolSuffix = "prefix-suffix""#); } #[test] fn invalid_string_prefix_pattern_discard_name() { assert_error!(r#"let assert "prefix" <> _boringSuffix = "prefix-suffix""#); } #[test] fn invalid_string_prefix_pattern_alias() { assert_error!(r#"let assert "prefix" as thePrefix <> _suffix = "prefix-suffix""#); } #[test] fn invalid_case_variable_name() { assert_error!("case 21 { twentyOne -> {Nil} }"); } #[test] fn invalid_case_variable_discard_name() { assert_error!("case 21 { _twentyOne -> {Nil} }"); } #[test] fn invalid_type_parameter_name() { assert_module_error!("type Wrapper(innerType) {}"); } #[test] fn invalid_type_alias_parameter_name() { assert_module_error!("type GleamOption(okType) = Result(okType, Nil)"); } #[test] fn invalid_function_type_parameter_name() { assert_module_error!("fn identity(value: someType) { value }"); } #[test] fn correct_pipe_arity_error_location() { // https://github.com/gleam-lang/gleam/issues/672 assert_module_error!( "fn x(x, y) { x } fn main() { 1 |> x() }" ); } #[test] fn const_annotation_wrong() { assert_module_error!("pub const group_id: Int = \"42\""); } #[test] fn const_annotation_wrong_2() { assert_module_error!("pub const numbers: List(Int) = [1, 2, 2.3]"); } #[test] fn const_annotation_wrong_3() { assert_module_error!("pub const numbers: List(Int) = [1.1, 2.2, 3.3]"); } #[test] fn const_annotation_wrong_4() { assert_module_error!("pub const pair: #(Int, Float) = #(4.1, 1)"); } #[test] fn const_multiple_errors_mismatched_types() { assert_module_error!( "const mismatched_types: String = 7 const invalid_annotation: MyInvalidType = \"str\"" ); } #[test] fn const_multiple_errors_invalid_annotation() { assert_module_error!( "const invalid_annotation: MyInvalidType = \"str\" const invalid_value: String = MyInvalidValue" ); } #[test] fn const_multiple_errors_invalid_value() { assert_module_error!( "const invalid_value: String = MyInvalidValue const invalid_unannotated_value = [1, 2.0]" ); } #[test] fn const_multiple_errors_invalid_unannotated_value() { assert_module_error!( "const invalid_unannotated_value = [1, 2.0] const invalid_everything: MyInvalidType = MyInvalidValue" ); } #[test] fn const_multiple_errors_invalid_annotation_and_value() { assert_module_error!( "const invalid_everything: MyInvalidType = MyInvalidValue const mismatched_types: String = 7" ); } #[test] fn const_multiple_errors_are_local_with_annotation() { assert_module_error!( "const num: String = 7 const tpl: String = #(Ok(1), MyInvalidType, 3) const assignment1: String = num const assignment2: String = tpl" ); } #[test] fn const_multiple_errors_are_local_with_inferred_value() { assert_module_error!( "const str: MyInvalidType = \"str\" const assignment: String = str" ); } #[test] fn const_multiple_errors_are_local_with_unbound_value() { assert_module_error!( "const lst = [1, 2.0] const unbound: MyInvalidType = MyInvalidType const assignment1: String = lst const assignment2: String = unbound" ); } #[test] fn const_usage_wrong() { assert_module_error!( "const pair = #(1, 2.0) fn main() { 1 == pair }" ); } #[test] fn const_heterogenus_list() { assert_module_error!("const pair = [1, 1.0]"); } #[test] fn custom_type_module_constants() { assert_module_error!( r#"type X { X } const x = unknown.X"# ); } #[test] fn unknown_label() { assert_module_error!( r#"type X { X(a: Int, b: Float) } fn x() { let x = X(a: 1, c: 2.0) x }"# ); } #[test] fn unknown_label_shorthand() { assert_module_error!( r#"type X { X(a: Int, b: Float) } fn x() { let c = 2.0 let x = X(a: 1, c:) x }"# ); } #[test] fn wrong_type_var() { // A unification error should show the type var as named by user // See https://github.com/gleam-lang/gleam/issues/1256 assert_module_error!( r#"fn wibble(x: String) { x } fn multi_result(x: some_name) { wibble(x) }"# ); } #[test] fn wrong_type_arg() { assert_module_error!( r#" fn wibble(x: List(Int)) { x } fn main(y: List(something)) { wibble(y) }"# ); } #[test] fn wrong_type_ret() { // See https://github.com/gleam-lang/gleam/pull/1407#issuecomment-1001162876 assert_module_error!( r#"pub fn main(x: something) -> Int { let y = x y }"# ); } #[test] fn wrong_type_update() { // A variable of the wrong type given to a record update assert_module_error!( " pub type Person { Person(name: String, age: Int) } pub type Box(a) { Box(a) } pub fn update_person(person: Person, box: Box(a)) { Person(..box) }" ); } #[test] fn unknown_variable_update() { // An undefined variable given to a record update assert_module_error!( " pub type Person { Person(name: String, age: Int) } pub fn update_person() { Person(..person) }" ); } #[test] fn unknown_field_update() { // An unknown field given to a record update assert_module_error!( " pub type Person { Person(name: String) } pub fn update_person(person: Person) { Person(..person, one: 5) }" ); } #[test] fn unknown_field_update2() { // An unknown field given to a record update assert_module_error!( " pub type Person { Person(name: String, age: Int, size: Int) } pub fn update_person(person: Person) { Person(..person, size: 66, one: 5, age: 3) }" ); } #[test] fn unknown_constructor_update() { // An unknown record constructor being used in a record update assert_module_error!( " pub type Person { Person(name: String, age: Int) } pub fn update_person(person: Person) { NotAPerson(..person) }" ); } #[test] fn not_a_constructor_update() { // Something other than a record constructor being used in a record update assert_module_error!( " pub type Person { Person(name: String, age: Int) } pub fn identity(a) { a } pub fn update_person(person: Person) { identity(..person) }" ); } #[test] fn expression_constructor_update() { // A record update with a constructor returned from an expression assert_module_error!( " pub type Person { Person(name: String, age: Int) } pub fn update_person(person: Person) { let constructor = Person constructor(..person) }" ); } #[test] fn type_vars_must_be_declared() { // https://github.com/gleam-lang/gleam/issues/734 assert_module_error!( r#"type A(a) { A } type B = a"# ); } #[test] fn type_holes1() { // Type holes cannot be used when decaring types or external functions assert_module_error!(r#"type A { A(_) }"#); } #[test] fn type_holes2() { // Type holes cannot be used when decaring types or external functions assert_module_error!( r#" @external(erlang, "a", "b") fn main() -> List(_) "# ); } #[test] fn type_holes3() { // Type holes cannot be used when decaring types or external functions assert_module_error!( r#" @external(erlang, "a", "b") fn main(x: List(_)) -> Nil "# ); } #[test] fn type_holes4() { // Type holes cannot be used when decaring types or external functions assert_module_error!(r#"type X = List(_)"#); } // https://github.com/gleam-lang/gleam/issues/1263 #[test] fn missing_variable_in_alternative_pattern() { assert_error!("case [] { [x] | [] -> x _ -> 0 }"); } #[test] fn type_annotations() { assert_module_error!("fn inc(x: a) { x + 1 }"); } // https://github.com/gleam-lang/gleam/issues/892 #[test] fn case_clause_pipe_diagnostic() { assert_module_error!( r#" pub fn change(x: String) -> String { "" } pub fn parse(input: BitArray) -> String { case input { <<>> -> 1 <<"(":utf8, b:bytes>> -> parse(input) |> change _ -> 3 } }"# ); } #[test] fn pipe_arity_error() { assert_module_error!( r#" fn go(x, y) { x + y } fn main(x) { 1 |> go } "# ); } #[test] fn negate_string() { assert_error!(r#"!"Hello Gleam""#); } #[test] fn ambiguous_type_error() { assert_module_error!( ("wibble", "pub type Thing { Thing }"), "import wibble pub type Thing { Thing } pub fn main() { [Thing] == [wibble.Thing] }", ); } #[test] fn ambiguous_import_error_no_unqualified() { assert_module_error!( ("wibble/sub", "pub fn wobble() { 1 }"), ("wibble2/sub", "pub fn wobble() { 1 }"), " import wibble/sub import wibble2/sub pub fn main() { sub.wobble() } ", ); } #[test] fn ambiguous_import_error_with_unqualified() { assert_module_error!( ("wibble/sub", "pub fn wobble() { 1 }"), ("wibble2/sub", "pub fn wobble() { 1 }"), " import wibble/sub import wibble2/sub.{wobble} pub fn main() { sub.wobble() } ", ); } #[test] fn same_imports_multiple_times() { assert_module_error!( ( "gleam/wibble", " pub fn wobble() { 1 } pub fn zoo() { 1 } " ), " import gleam/wibble.{wobble} import gleam/wibble.{zoo} pub fn go() { wobble() + zoo() } " ); } #[test] fn same_imports_multiple_times_1() { assert_module_error!( ( "one", " pub fn fn1() { 1 } " ), ( "two", " pub fn fn2() { 1 } " ), " import one import two as one " ); } #[test] fn same_imports_multiple_times_2() { assert_module_error!( ( "one", " pub fn fn1() { 1 } " ), ( "two", " pub fn fn2() { 1 } " ), " import one as two import two " ); } #[test] fn same_imports_multiple_times_3() { assert_module_error!( ( "one", " pub fn fn1() { 1 } " ), ( "two", " pub fn fn2() { 1 } " ), " import one as x import two as x " ); } #[test] fn same_imports_multiple_times_4() { assert_module_error!( ( "one", " pub fn fn1() { 1 } " ), ( "two", " pub fn fn2() { 1 } " ), " import one.{fn1} import two.{fn2} as one " ); } #[test] fn same_imports_multiple_times_5() { assert_module_error!( ( "one", " pub fn fn1() { 1 } " ), ( "two", " pub fn fn2() { 1 } " ), " import one.{fn1} as two import two.{fn2} " ); } #[test] fn same_imports_multiple_times_6() { assert_module_error!( ( "one", " pub fn fn1() { 1 } " ), ( "two", " pub fn fn2() { 1 } " ), " import one.{fn1} as x import two.{fn2} as x " ); } #[test] fn same_imports_multiple_times_7() { assert_module_error!( ( "one", " pub fn fn1() { 1 } " ), ( "two", " pub fn fn2() { 1 } " ), " import one.{ fn1 } as x import two.{ fn2 } as x " ); } // https://github.com/gleam-lang/gleam/issues/1705 #[test] fn update_multi_variant_record() { assert_module_error!( " pub type Point { Point2(a: Int, b: Int) Point3(a: Int, b: Int, c: Int) } pub fn main() { Point3(..Point2(a: 1, b: 2)) }" ); } #[test] fn hint_for_method_call() { assert_module_error!( " pub type User { User(id: Int, name: String) } pub fn main(user: User) { user.login() } " ); } #[test] fn no_hint_for_non_method_call() { assert_module_error!( " pub type User { User(id: Int, name: String) } fn login(user: User) { user } pub fn main(user: User) { login(user.wibble) } " ); } #[test] fn unknown_imported_module_type() { assert_module_error!( ("one/two", ""), " import one/two pub fn main(_x: two.Thing) { Nil } " ); } #[test] fn value_imported_as_type() { assert_module_error!( ( "gleam/wibble", "pub type Wibble { Wobble }" ), "import gleam/wibble.{type Wobble}" ); } #[test] fn type_imported_as_value() { assert_module_error!( ( "gleam/wibble", "pub type Wibble { Wobble }" ), "import gleam/wibble.{Wibble}" ); } #[test] fn duplicate_module_function_arguments() { assert_module_error!( " pub fn main(x, x) { Nil } " ); } #[test] fn duplicate_anon_function_arguments() { assert_error!( " fn(x, x) { Nil } " ); } #[test] fn negate_boolean_as_integer() { assert_error!( " fn() { let a = True let b = -a } " ); } #[test] fn negate_float_as_integer() { assert_error!( " fn() { let a = 3.0 let b = -a } " ); } // https://github.com/gleam-lang/gleam/issues/2371 #[test] fn list() { assert_error!("[1, 2.0]"); } #[test] fn mismatched_list_tail() { assert_error!("[\"wibble\", ..[1, 2]]"); } #[test] fn leak_multiple_private_types() { assert_module_error!( " type Private { Private } pub fn ret_private() -> Private { Private } pub fn ret_private2() -> Private { Private } pub fn main() { ret_private() } " ); } #[test] fn const_string_concat_invalid_type() { assert_module_error!( " const some_int = 5 const invalid_concat = some_int <> \"with_string\" " ); } #[test] fn invalid_pattern_label_shorthand() { assert_module_error!( " pub type Wibble { Wibble(arg: Int) } pub fn main() { let Wibble(not_a_label:) = Wibble(1) } " ); } #[test] fn no_crash_on_duplicate_definition() { // This previous caused the compiler to crash assert_module_error!( " pub type Wibble { Wobble Wobble } pub fn main() { let wibble = Wobble case wibble { Wobble -> Nil } } " ); } #[test] fn no_crash_on_duplicate_definition2() { // This also caused a compiler crash, separate to the above test assert_module_error!( " pub type Wibble { Wibble Wobble Wobble Wubble } pub fn main() { let wibble = Wobble case wibble { Wibble -> Nil Wobble -> Nil Wubble -> Nil } } " ); } #[test] fn unknown_module_suggest_import() { assert_module_error!( ("utils", "pub fn helpful() {}"), " pub fn main() { utils.helpful() } ", ); } #[test] fn unknown_module_suggest_typo_for_imported_module() { assert_module_error!( ("wibble", "pub fn wobble() {}"), " import wibble pub fn main() { wible.wobble() } ", ); } #[test] fn unknown_module_suggest_typo_for_unimported_module() { assert_module_error!( ("wibble/wobble", "pub fn wubble() {}"), " pub fn main() { woble.wubble() } ", ); } #[test] fn qualified_type_mismatched_type_error() { assert_module_error!( ("wibble", "pub type Wobble"), " import wibble const my_wobble: wibble.Wobble = Nil " ); } #[test] fn qualified_type_similar_type_name() { assert_module_error!( ("wibble", "pub type Int"), " import wibble const value: wibble.Int = 20 " ); } #[test] fn qualified_type_not_a_function() { assert_module_error!( ("wibble", "pub type Function { Function(fn() -> Nil) }"), " import wibble.{type Function as FuncWrapper} pub fn main(f: FuncWrapper) { f() } " ); } #[test] fn qualified_type_unknown_field() { assert_module_error!( " import gleam type Int { Int(bit_size: gleam.Int, bits: BitArray) } pub fn main(not_a_record: gleam.Int) { not_a_record.bits } " ); } #[test] fn qualified_type_invalid_operands() { assert_module_error!( ("maths", "pub type Vector { Vector(x: Float, y: Float) }"), " import maths as math pub fn add_two_vectors(a: math.Vector, b: math.Vector) { a + b } " ); } #[test] fn qualified_type_invalid_pipe_argument() { assert_module_error!( ( "mod", "pub type Wibble pub fn takes_wibble(value: Wibble) { value }" ), " import mod pub fn main() { Nil |> mod.takes_wibble } " ); } #[test] fn qualified_type_unification_error() { assert_module_error!( " import gleam type Bool { True False } const list_of_bools = [True, False, gleam.False] " ); } #[test] fn qualified_type_not_a_tuple() { assert_module_error!( ("mod", "pub type Pair(a, b) { Pair(a, b) }"), " import mod.{type Pair as Duo} pub fn first(pair: Duo(a, b)) { pair.0 } " ); } #[test] fn qualified_type_not_fn_in_use() { assert_module_error!( ("some_mod", "pub type Function(param1, param2, return)"), " import some_mod as sm pub fn main(func: sm.Function(Int, String, Float)) { use <- func() } " ); } #[test] fn qualified_type_use_fn_without_callback() { assert_module_error!( ( "some_mod", "pub type NotACallback pub fn do_a_thing(a: Int, _b: NotACallback) { a }" ), " import some_mod pub fn main() { use value <- some_mod.do_a_thing(10) } " ); } #[test] fn suggest_unwrapping_a_result_when_types_match() { assert_module_error!( " pub fn main() { let value = Ok(1) add_1(value) } fn add_1(to x) { x + 1 } " ); } #[test] fn unknown_field_that_appears_in_an_imported_variant_has_note() { assert_module_error!( ( "some_mod", "pub type Wibble { Wibble(field: Int) Wobble(not_field: String, field: Int) }" ), " import some_mod pub fn main(wibble: some_mod.Wibble) { wibble.field } " ); } #[test] fn unknown_field_that_appears_in_a_variant_has_note() { assert_module_error!( " pub type Wibble { Wibble(field: Int) Wobble(not_field: String, field: Int) } pub fn main(wibble: Wibble) { wibble.field } " ); } #[test] fn unknown_field_that_does_not_appear_in_variant_has_no_note() { assert_module_error!( " pub type Wibble { Wibble(field: Int) Wobble(not_field: String, field: Int) } pub fn main(wibble: Wibble) { wibble.wibble } " ); } #[test] fn no_note_about_reliable_access_if_the_accessed_type_has_a_single_variant() { assert_module_error!( " pub type User { User(name: String) } pub fn main() { User(\"Jak\").nam } " ); } #[test] fn no_crash_on_duplicate_record_fields() { // https://github.com/gleam-lang/gleam/issues/3713 assert_module_error!( " pub type X { A B(e0: Int, e0: Int) } fn compiler_crash(x: X) { case x { A -> todo _ -> todo } } " ); } #[test] fn record_update_unknown_variant() { assert_module_error!( r#" pub type Wibble { Wibble(wibble: Int, wubble: Bool) Wobble(wobble: Int, wubble: Bool) } pub fn wibble(value: Wibble) { Wibble(..value, wubble: True) } "# ); } #[test] fn record_update_wrong_variant() { assert_module_error!( r#" pub type MyRecord { A(common: Int, other: String) B(common: Int, different: Float) } pub fn b_to_a(value: MyRecord) { case value { A(..) -> value B(..) as b -> A(..b, other: "Hi") } } "# ); } #[test] fn record_update_wrong_variant_imported_type() { assert_module_error!( ( "wibble", " pub type Wibble { Wibble(wibble: Int, wobble: Int) Wobble(wobble: Int, wubble: Int) }" ), " import wibble pub fn main(wibble: wibble.Wibble) { case wibble { wibble.Wibble(..) as w -> wibble.Wobble(..w, wubble: 10) _ -> panic } } " ); } #[test] fn inferred_variant_record_update_change_type_parameter_different_branches() { assert_module_error!( r#" pub type Box(a) { Locked(password: String, value: a) Unlocked(password: String, value: a) } pub fn main() { let box = Locked("ungu€$$4bLe", 11) case box { Locked(..) as box -> Locked(..box, value: True) Unlocked(..) as box -> Unlocked(..box, password: "pwd") } } "# ); } // https://github.com/gleam-lang/gleam/issues/3783 #[test] fn duplicate_fields_in_record_update_reports_error() { assert_module_error!( " pub type Wibble { Wibble(thing: Int, other: Int) } pub fn main() { let wibble = Wibble(1, 2) let wobble = Wibble(..wibble, thing: 1, thing: 2) } " ); } #[test] fn record_update_compatible_fields_wrong_variant() { assert_module_error!( r#" pub type Wibble { A(a: Int, b: Int) B(a: Int, b: Int) } pub fn b_to_a(value: Wibble) { case value { A(..) -> value B(..) as b -> A(..b, b: 3) } } "# ); } #[test] fn record_update_compatible_fields_wrong_type() { assert_module_error!( r#" pub type A { A(a: Int, b: Int) } pub type B { B(a: Int, b: Int) } pub fn b_to_a(value: B) { A(..value, b: 5) } "# ); } #[test] fn record_update_incompatible_but_linked_generics() { assert_module_error!( r#" pub type Wibble(a) { Wibble(a: a, b: a) } pub fn b_to_a(value: Wibble(a)) -> Wibble(Int) { Wibble(..value, a: 5) } "# ); } #[test] // https://github.com/gleam-lang/gleam/issues/3879 fn inexhaustive_use_reports_error() { assert_error!( r#" use [1, 2, 3] <- todo todo "# ); } #[test] fn out_of_range_erlang_float() { assert_error!(r#"1.8e308"#); } #[test] fn out_of_range_erlang_float_in_pattern() { assert_error!(r#"let assert [1.8e308, b] = [x, y]"#); } #[test] fn out_of_range_erlang_float_in_const() { assert_module_error!(r#"const x = 1.8e308"#); } #[test] fn negative_out_of_range_erlang_float() { assert_error!(r#"-1.8e308"#); } #[test] fn negative_out_of_range_erlang_float_in_pattern() { assert_error!(r#"let assert [-1.8e308, b] = [x, y]"#); } #[test] fn negative_out_of_range_erlang_float_in_const() { assert_module_error!(r#"const x = -1.8e308"#); } #[test] fn missing_case_body() { assert_error!("case True"); } #[test] fn suggest_wrapping_a_value_into_ok_if_types_match() { assert_module_error!( " pub fn main() { case todo { 1 -> Ok(2) _ -> 1 } } " ); } #[test] fn suggest_wrapping_a_value_into_ok_if_types_match_2() { assert_module_error!( " pub fn main() { wibble(1) } fn wibble(arg: Result(Int, String)) { todo } " ); } #[test] fn suggest_wrapping_a_value_into_ok_if_types_match_with_block() { assert_module_error!( " pub fn main() { case todo { 1 -> Ok(2) _ -> { todo 1 } } } " ); } #[test] fn suggest_wrapping_a_value_into_ok_with_generic_type() { assert_module_error!( " pub fn first(list: List(a)) -> Result(a, Nil) { case list { [] -> Error(Nil) [first, ..rest] -> first } } " ); } #[test] fn suggest_wrapping_a_value_into_ok_if_types_match_with_multiline_result_in_block() { assert_module_error!( " pub fn main() { case todo { 1 -> Ok(2) _ -> { todo 1 |> add_1 } } } fn add_1(n: Int) { n + 1 } " ); } #[test] fn suggest_wrapping_a_value_into_error_if_types_match() { assert_module_error!( " pub fn main() { case todo { 1 -> Error(1) _ -> 1 } } " ); } #[test] fn suggest_wrapping_a_value_into_error_if_types_match_2() { assert_module_error!( " pub fn main() { wibble(\"a\") } fn wibble(arg: Result(Int, String)) { todo } " ); } #[test] fn suggest_wrapping_a_function_return_value_in_ok() { assert_module_error!( " pub fn main() -> Result(Int, Bool) { 1 } " ); } #[test] fn suggest_wrapping_a_function_return_value_in_error() { assert_module_error!( " pub fn main() -> Result(Int, Bool) { True } " ); } #[test] fn suggest_wrapping_a_use_returned_value_in_ok() { assert_module_error!( " pub fn main() -> Result(Int, Bool) { use <- want_result 1 } pub fn want_result(wibble: fn() -> Result(Int, Bool)) { todo } " ); } #[test] fn suggest_wrapping_a_use_returned_value_in_error() { assert_module_error!( " pub fn main() -> Result(Int, Bool) { use <- want_result False } pub fn want_result(wibble: fn() -> Result(Int, Bool)) { todo } " ); } #[test] // https://github.com/gleam-lang/gleam/issues/4195 fn let_assert_binding_cannot_be_used_in_panic_message() { assert_module_error!( r#" pub fn main() { let assert Ok(message) = Error("Not Message") as { "Uh oh: " <> message } } "# ); } #[test] fn echo_followed_by_no_expression() { assert_error!("echo"); } #[test] fn echo_followed_by_no_expression_and_message() { assert_error!("echo as \"wibble\""); } #[test] fn echo_followed_by_no_expression_and_invalid_message() { assert_error!("echo as 1"); } #[test] fn echo_followed_by_invalid_message() { assert_error!("echo 11 as { True || False }"); } #[test] fn echo_followed_by_no_expression_2() { assert_module_error!( r#" pub fn wibble(a) { a } pub fn main() { wibble(echo) } "# ); } #[test] fn echo_followed_by_no_expression_3() { assert_module_error!( r#" pub fn main() { echo + 1 } "# ); } #[test] fn echo_followed_by_no_expression_4() { assert_module_error!( r#" pub fn main() { "wibble" <> echo } "# ); } #[test] fn echo_followed_by_no_expression_5() { assert_module_error!( r#" pub fn main() { panic as echo } "# ); } #[test] fn echo_followed_by_no_expression_6() { assert_module_error!( r#" pub fn main() { [echo, 1, 2] } "# ); } #[test] fn echo_followed_by_no_expression_7() { assert_module_error!( r#" pub fn main() { #(1, echo) } "# ); } #[test] fn echo_followed_by_no_expression_8() { assert_module_error!( r#" pub fn main() { todo |> fn(_) { echo } |> todo } "# ); } #[test] fn echo_followed_by_no_expression_9() { assert_module_error!( r#" pub fn main() { todo |> { echo } |> todo } "# ); } #[test] fn echo_followed_by_no_expression_10() { assert_module_error!( r#" pub fn main() { echo |> todo } "# ); } #[test] fn function_that_does_not_exist_does_not_produce_error_for_labelled_args() { assert_module_error!( r#" pub fn main() { // We only want to error on `wibble` since it doesn't exist, we don't want // an error on the label at this point! wibble(label: 1) } "# ); } #[test] fn constructor_that_does_not_exist_does_not_produce_error_for_labelled_args() { assert_module_error!( r#" pub fn main() { // We only want to error on `Wibble` since it doesn't exist, we don't want // an error on the label at this point! Wibble(label: 1) } "# ); } #[test] fn float_operator_on_ints() { assert_error!("1 +. 2"); } #[test] fn float_operator_on_ints_2() { assert_error!("1 <. 2"); } #[test] fn int_operator_on_floats() { assert_error!("1.1 + 2.0"); } #[test] fn int_operator_on_floats_2() { assert_error!("1.1 > 2.0"); } #[test] fn add_on_strings() { assert_error!(r#""Hello, " + "Jak""#); } #[test] fn fault_tolerant_list() { assert_module_error!( r#" pub fn main() { [1, "a", 1.0, "a" + 1] } "# ); } #[test] fn fault_tolerant_list_tail() { assert_module_error!( r#" pub fn main() { [1, "a", ..["a", "b"]] } "# ); } #[test] fn fault_tolerant_negate_bool() { assert_module_error!( r#" pub fn main() { !!{ True || a } } "# ); } #[test] fn fault_tolerant_negate_int() { assert_module_error!( r#" pub fn main() { --{ 1 + a } } "# ); } #[test] fn fault_tolerant_tuple() { assert_module_error!( r#" pub fn main() { #(1, 1 + "a", not_in_scope) } "# ); } #[test] fn error_for_missing_type_parameters() { assert_module_error!( r#" type Wibble(a) type Wobble { Wobble(Wibble) } "# ); } #[test] fn double_assignment_in_bit_array() { assert_error!("let assert <> = <<>>"); } #[test] fn negative_size_pattern() { assert_error!("let assert <<1:size(-1)>> = <<>>"); } #[test] fn zero_size_pattern() { assert_error!("let assert <<1:size(0)>> = <<>>"); } // https://github.com/gleam-lang/gleam/issues/3253 #[test] fn bit_array_using_pattern_variables() { assert_error!("let assert #(a, <>) = #(2, <<2:2>>)"); } #[test] fn bit_array_using_pattern_variables_from_other_bit_array() { assert_error!("let assert #(<>, <>) = #(<<2>>, <<2:2>>)"); } #[test] fn non_utf8_string_assignment() { assert_error!(r#"let assert <<"Hello" as message:utf16>> = <<>>"#); } #[test] fn shadowed_function_argument() { assert_module_error!( " pub fn go(_x) { x + 1 } " ); } #[test] fn shadowed_fn_argument() { assert_module_error!( " pub fn go(x) { fn(_y) { y + x } } " ); } #[test] fn shadowed_let_variable() { assert_module_error!( " pub fn go() { let _x = 1 x + 1 } " ); } #[test] fn shadowed_pattern_variable() { assert_module_error!( " pub type Wibble { Wibble(Int) } pub fn go(x) { case x { Wibble(_y) -> y + 1 } } " ); } #[test] fn do_not_suggest_ignored_variable_outside_of_current_scope() { assert_module_error!( " pub fn go() { let _ = { let _y = 1 // <- this shouldn't be highlighted! } y } " ); } // https://github.com/gleam-lang/gleam/issues/4693 #[test] fn pattern_with_incorrect_arity() { assert_module_error!( " pub type Pokemon { Pokemon(name: String, id: Int) } pub fn main() { case todo { Pokemon(name:) -> todo } } " ); } // https://github.com/gleam-lang/gleam/issues/3884 #[test] fn show_only_missing_labels() { assert_module_error!( " fn wibble(a a: Int, b b: Float, c c: String) { todo } pub fn wobble() { wibble(1, 2.0) } " ); } #[test] fn native_endianness_javascript_target() { assert_js_module_error!( " pub fn main() { let assert <> = <<10>> } " ); } #[test] fn utf8_codepoint_javascript_target() { assert_js_module_error!( " pub fn main() { let assert <> = <<10>> } " ); } #[test] fn utf16_codepoint_javascript_target() { assert_js_module_error!( " pub fn main() { let assert <> = <<10>> } " ); } #[test] fn utf32_codepoint_javascript_target() { assert_js_module_error!( " pub fn main() { let assert <> = <<10>> } " ); } #[test] fn private_opaque_type() { assert_module_error!( " opaque type Wibble { Wobble } " ); } #[test] fn src_importing_dev_dependency() { assert_module_error!( ("dev_dependency", "some_module", "pub fn main() { Nil }"), " import some_module pub fn main() { some_module.main() } " ); } #[test] fn missing_type_constructor_arguments_in_type_annotation_1() { assert_module_error!("pub fn main() -> Result() {}"); } #[test] fn missing_type_constructor_arguments_in_type_annotation_2() { assert_module_error!( "pub fn main() { let a: Result() = todo }" ); } #[test] fn type_used_as_a_constructor_1() { assert_module_error!("pub fn main() -> Int() {}"); } #[test] fn type_used_as_a_constructor_2() { assert_module_error!( "pub fn main() { let a: Int() = todo }" ); } #[test] fn type_used_as_a_constructor_with_more_arguments() { assert_module_error!( "pub fn main() { let a: Int(Int, String) = todo }" ); } #[test] fn remembering_record_field_when_type_checking_fails() { assert_module_error!( r#"pub type Wibble { Wibble(x: Int, f: fn(Wobble) -> Int) } pub fn wibble() { Wibble(1, fn(_) { 2 }) } pub fn wobble(wibble: Wibble) { wibble.f } pub fn woo(wibble: Wibble) { Wibble(..wibble, x: 1) }"# ); } #[test] fn external_annotation_on_custom_type_with_constructors() { assert_module_error!( r#" @external(erlang, "gleam_stdlib", "dict") pub type Dict(key, value) { Dict(pairs: List(#(key, value))) } "# ); } #[test] fn generic_unlabelled_field_in_updated_record_wrong_type() { assert_module_error!( " pub type Wibble(a) { Wibble(a, b: Int, c: a) } pub fn main() { let w = Wibble(1, 2, 3) Wibble(..w, c: False) } " ); } // https://github.com/gleam-lang/gleam/issues/5296 #[test] fn no_crash_on_record_update_when_constructor_definition_is_invalid() { assert_module_error!( " pub type Wibble { Wibble(a: Int, b: Int, Bool) } pub fn main() { let one = Wibble(False, a: 5, b: 6) let two = Wibble(..one, b: 1) } " ); } #[test] fn incomplete_pattern_does_not_show_structure_of_internal_type_outside_of_its_module() { assert_module_error!( ( "wibble", "@internal pub type Wibble { Wibble Wobble Woo }" ), "import wibble.{type Wibble} pub fn go(wibble: Wibble) { case wibble {} }" ); } #[test] fn incomplete_pattern_does_not_show_structure_of_internal_type_outside_of_its_module_2() { assert_module_error!( ( "wibble", "@internal pub type Wibble { Wibble Wobble Woo }" ), "import wibble.{type Wibble} pub type Type { Type(wibble: Wibble, list: List(Int)) } pub fn go(value: Type) { case value {} }" ); } #[test] fn record_update_does_not_stop_at_first_invalid_field_1() { assert_module_error!( " pub type Wibble { Wibble(a: Int, b: Bool) } pub fn go(wibble: Wibble) { Wibble(..wibble, c: 1, a: 1.0) }" ); } #[test] fn record_update_does_not_stop_at_first_invalid_field_2() { assert_module_error!( " pub type Wibble { Wibble(a: Int, b: Bool) } pub fn go(wibble: Wibble) { Wibble(..wibble, a: 1.0, a: 2, c: 2) }" ); } #[test] fn record_update_does_not_stop_at_first_invalid_field_3() { assert_module_error!( " pub type Wibble { Wibble(a: Int, b: Bool) } pub fn go(wibble: Wibble) { Wibble(..wibble, a: 1.0, a: 2) }" ); } #[test] fn record_update_does_not_stop_at_first_invalid_field_4() { assert_module_error!( " pub type Wibble { Wibble(a: Int, b: Bool) } pub fn go(wibble: Wibble) { Wibble(..wibble, a: 2, a: 3, b: 1) }" ); } #[test] fn record_update_does_not_stop_at_first_invalid_field_5() { assert_module_error!( " pub type Wibble { Wibble(a: Int, b: Bool) } pub fn go(wibble: Wibble) { Wibble(..wibble, b: 1, a: True, c: 2) }" ); } ================================================ FILE: compiler-core/src/type_/tests/exhaustiveness.rs ================================================ use crate::{assert_error, assert_module_error, assert_no_warnings, assert_warning}; #[test] fn whatever() { assert_no_warnings!( " pub fn main(x) { case x { _ -> 0 } } " ); } #[test] fn nil() { assert_no_warnings!( " pub fn main(x) { case x { Nil -> 0 } } " ); } #[test] fn bool() { assert_no_warnings!( " pub fn main(x) { case x { True -> 1 False -> 0 } } " ); } #[test] fn bool_true() { assert_module_error!( " pub fn main(x) { case x { True -> 1 } } " ); } #[test] fn bool_false() { assert_module_error!( " pub fn main(x) { case x { False -> 1 } } " ); } #[test] fn result() { assert_no_warnings!( " pub fn main(x) { case x { Ok(_) -> 1 Error(_) -> 2 } } " ); } #[test] fn result_ok() { assert_module_error!( " pub fn main(x) { case x { Ok(_) -> 1 } } " ); } #[test] fn result_error() { assert_module_error!( " pub fn main(x) { case x { Error(_) -> 1 } } " ); } #[test] fn result_nil() { assert_no_warnings!( " pub fn main(x) { case x { Ok(Nil) -> 1 Error(Nil) -> 2 } } " ); } #[test] fn result_nil_ok() { assert_module_error!( " pub fn main(x) { case x { Ok(Nil) -> 1 } } " ); } #[test] fn result_nil_error() { assert_module_error!( " pub fn main(x) { case x { Error(Nil) -> 1 } } " ); } #[test] fn result_bool() { assert_no_warnings!( " pub fn main(x) { case x { Ok(True) -> 1 Ok(False) -> 3 Error(True) -> 2 Error(False) -> 4 } } " ); } #[test] fn result_bool_1() { assert_module_error!( " pub fn main(x) { case x { Ok(False) -> 1 Error(True) -> 2 Error(False) -> 3 } } " ); } #[test] fn result_bool_2() { assert_module_error!( " pub fn main(x) { case x { Ok(True) -> 1 Error(True) -> 2 Error(False) -> 3 } } " ); } #[test] fn result_bool_3() { assert_module_error!( " pub fn main(x) { case x { Ok(True) -> 1 Ok(False) -> 2 Error(False) -> 3 } } " ); } #[test] fn result_bool_4() { assert_module_error!( " pub fn main(x) { case x { Ok(True) -> 1 Ok(False) -> 2 Error(True) -> 3 } } " ); } #[test] fn result_bool_5() { assert_module_error!( " pub fn main(x) { case x { Ok(True) -> 1 Ok(False) -> 2 } } " ); } #[test] fn result_bool_6() { assert_module_error!( " pub fn main(x) { case x { Error(True) -> 1 Error(False) -> 2 } } " ); } #[test] fn result_bool_7() { assert_module_error!( " pub fn main(x) { case x { Error(True) -> 1 } } " ); } #[test] fn result_bool_8() { assert_module_error!( " pub fn main(x) { case x { Ok(False) -> 1 } } " ); } #[test] fn list() { assert_no_warnings!( " pub fn main(x) { case x { [_, ..] -> 1 [] -> 2 } } " ); } #[test] fn list_empty() { assert_module_error!( " pub fn main(x) { case x { [] -> 1 } } " ); } #[test] fn list_non_empty() { assert_module_error!( " pub fn main(x) { case x { [_, ..] -> 1 } } " ); } #[test] fn list_one() { assert_module_error!( " pub fn main(x) { case x { [_] -> 1 } } " ); } #[test] fn list_one_two() { assert_module_error!( " pub fn main(x) { case x { [_] -> 1 [_, _] -> 1 } } " ); } #[test] fn list_zero_one_two() { assert_module_error!( " pub fn main(x) { case x { [] -> 1 [_] -> 1 [_, _] -> 1 } } " ); } #[test] fn list_zero_one_two_any() { assert_no_warnings!( " pub fn main(x) { case x { [] -> 1 [_] -> 1 [_, _] -> 1 [_, _, ..] -> 1 } } " ); } #[test] fn list_zero_two_any() { assert_module_error!( " pub fn main(x) { case x { [] -> 1 [_, _] -> 1 [_, _, ..] -> 1 } } " ); } #[test] fn string() { assert_no_warnings!( r#" pub fn main(x) { case x { "" -> 1 "a" -> 1 "b" -> 1 _ -> 1 } } "# ); } #[test] fn string_1() { assert_module_error!( r#" pub fn main(x) { case x { "" -> 1 } } "# ); } #[test] fn string_2() { assert_module_error!( r#" pub fn main(x) { case x { "a" -> 1 } } "# ); } #[test] fn string_3() { assert_module_error!( r#" pub fn main(x) { case x { "a" -> 1 "b" -> 1 } } "# ); } #[test] fn bit_array() { assert_no_warnings!( r#" pub fn main(x) { case x { <<>> -> 1 <<1>> -> 1 <<2>> -> 1 _ -> 1 } } "# ); } #[test] fn bit_array_1() { assert_module_error!( r#" pub fn main(x) { case x { <<>> -> 1 <<1>> -> 1 <<2>> -> 1 } } "# ); } #[test] fn bit_array_2() { assert_module_error!( r#" pub fn main(x) { case x { <<>> -> 1 <<1>> -> 1 } } "# ); } #[test] fn int() { assert_no_warnings!( r#" pub fn main(x) { case x { 0 -> 1 1 -> 1 2 -> 1 _ -> 1 } } "# ); } #[test] fn int_1() { assert_module_error!( r#" pub fn main(x) { case x { 0 -> 1 1 -> 1 2 -> 1 } } "# ); } #[test] fn int_2() { assert_module_error!( r#" pub fn main(x) { case x { 0 -> 1 1 -> 1 } } "# ); } #[test] fn float() { assert_no_warnings!( r#" pub fn main(x) { case x { 0.0 -> 1 1.1 -> 1 2.2 -> 1 _ -> 1 } } "# ); } #[test] fn float_1() { assert_module_error!( r#" pub fn main(x) { case x { 0.0 -> 1 1.1 -> 1 2.2 -> 1 } } "# ); } #[test] fn float_2() { assert_module_error!( r#" pub fn main(x) { case x { 0.0 -> 1 1.1 -> 1 } } "# ); } #[test] fn list_bool_1() { assert_module_error!( r#" pub fn main(x) { case x { [] -> 1 [True] -> 2 [_, _, ..] -> 2 } } "# ); } #[test] fn list_bool_2() { assert_module_error!( r#" pub fn main(x) { case x { [] -> 1 [True] -> 2 [_, False] -> 2 [_, _, _, ..] -> 2 } } "# ); } #[test] fn discard_all_fields() { assert_no_warnings!( r#" pub type Thing { Thing(a: Bool, b: Bool) } pub fn main(x) { case x { Thing(..) -> 1 } } "# ); } #[test] fn discard_1() { assert_no_warnings!( r#" pub type Thing { Thing(a: Bool, b: Bool) } pub fn main(x) { case x { Thing(a: True, ..) -> 1 Thing(a: False, ..) -> 1 } } "# ); } #[test] fn discard_2() { assert_module_error!( r#" pub type Thing { Thing(a: Bool, b: Bool) } pub fn main(x) { case x { Thing(a: True, ..) -> 1 } } "# ); } #[test] fn discard_3() { assert_module_error!( r#" pub type Thing { Thing(a: Bool, b: Bool) } pub fn main(x) { case x { Thing(a: False, ..) -> 1 } } "# ); } #[test] fn discard_4() { assert_module_error!( r#" pub type Thing { Thing(a: Bool, b: Bool) } pub fn main(x) { case x { Thing(a: True, ..) -> 1 } } "# ); } #[test] fn discard_5() { assert_module_error!( r#" pub type Thing { Thing(a: Bool, b: Bool) } pub fn main(x) { case x { Thing(a: False, ..) -> 1 } } "# ); } #[test] fn discard_6() { assert_module_error!( r#" pub type Thing { Thing(a: Bool, b: Bool) } pub fn main(x) { case x { Thing(False, ..) -> 1 } } "# ); } #[test] fn label_1() { assert_module_error!( r#" pub type Thing { Thing(a: Bool, b: Bool) } pub fn main(x) { case x { Thing(a: False, b: True) -> 1 Thing(b: False, a: True) -> 1 } } "# ); } #[test] fn guard() { assert_module_error!( r#" pub fn main(x, y) { case x { _ if y -> 1 } } "# ); } #[test] fn guard_1() { assert_module_error!( r#" pub fn main(x, y) { case x { True if y -> 1 False -> 2 } } "# ); } #[test] fn custom_1() { assert_module_error!( r#" pub type Type { One Two } pub fn main(x) { case x { One -> 1 } } "# ); } #[test] fn custom_2() { assert_module_error!( r#" pub type Type { One Two Three(Type) } pub fn main(x) { case x { One -> 1 Two -> 2 Three(One) -> 4 } } "# ); } #[test] fn redundant_1() { assert_warning!( r#" pub fn main(x) { case x { _ -> 1 _ -> 2 } } "# ); } #[test] fn redundant_2() { assert_warning!( r#" pub fn main(x) { case x { True -> 1 False -> 2 True -> 3 } } "# ); } //https://github.com/gleam-lang/gleam/issues/2651 #[test] fn redundant_3() { assert_warning!( r#" pub fn main(x) { case x { 59 -> "gleew" 14 -> "glabber" 1 -> "" _ -> "glooper" 2 -> "" 3 -> "glen" 4 -> "glew" } } "# ); } #[test] fn redundant_4() { assert_warning!( r#" pub fn main(x) { case x { "P" -> 4 _ -> 3 "geeper!" -> 5 } } "# ); } #[test] fn redundant_5() { assert_warning!( r#" pub fn main(x) { case x { "P" -> 4 "" -> 65 "P" -> 19 _ -> 3 } } "# ); } #[test] fn redundant_int_with_underscores() { assert_warning!( r#" pub fn main(x) { case x { 10 -> "ten" 1_0 -> "also ten" _ -> "other" } } "# ); } #[test] fn redundant_int_with_multiple_underscores() { assert_warning!( r#" pub fn main(x) { case x { 1_000_000 -> "one million" 1000000 -> "also one million" _ -> "other" } } "# ); } #[test] fn redundant_float_with_different_formatting() { assert_warning!( r#" pub fn main(x) { case x { 1.0 -> "one" 1.00 -> "also one" _ -> "other" } } "# ); } #[test] fn redundant_float_with_no_trailing_decimal() { assert_warning!( r#" pub fn main(x) { case x { 1.0 -> "one" 1. -> "another one" _ -> "other" } } "# ); } #[test] fn redundant_float_with_underscore() { assert_warning!( r#" pub fn main(x) { case x { 10.0 -> "ten" 1_0.0 -> "also ten" _ -> "other" } } "# ); } #[test] fn redundant_float_scientific_notation() { assert_warning!( r#" pub fn main(x) { case x { 10.0 -> "ten" 1.0e1 -> "also ten" _ -> "other" } } "# ); } #[test] fn redundant_float_scientific_notation_and_underscore() { assert_warning!( r#" pub fn main(x) { case x { 1.0e2 -> "one hundred" 1_0_0.0 -> "one hundred again" _ -> "other" } } "# ); } #[test] fn let_1() { assert_module_error!( r#" pub fn main(x) { let True = x 0 } "# ); } #[test] fn tuple_0() { assert_module_error!( r#" pub fn main(x, y) { case #(x, y) { #(True, _) -> 1 } } "# ); } // https://github.com/gleam-lang/gleam/issues/2577 #[test] fn nested_type_parameter_usage() { assert_module_error!( r#" pub type Returned(a) { Returned(List(a)) } fn wibble(user: Returned(#())) -> Int { let Returned([#()]) = user 1 } "# ); } #[test] fn empty_case_of_external() { // This external type has no known constructors, and we want to make sure // that an empty case expression is not valid for it. assert_module_error!( r#" pub type Thingy pub fn main(x: Thingy) { case x { } } "# ); } #[test] fn empty_case_of_generic() { // This generic type has no known constructors, and we want to make sure // that an empty case expression is not valid for it. assert_module_error!( r#" pub fn main(x: something) { case x { } } "# ); } #[test] fn reference_absent_type() { // This test is here because this code previously caused the compiler // to crash, and we want to make sure that it doesn't break again assert_module_error!( " type Wibble { One(Int) Two(Absent) } pub fn main(wibble) { case wibble { One(x) -> x } } " ); } #[test] fn case_error_prints_module_names() { assert_module_error!( ("wibble", "pub type Wibble { Wibble Wobble }"), " import wibble pub type Things { Thing1 Thing2(Int) } pub fn main(wobble_thing) { case wobble_thing { #(wibble.Wibble, Thing1) -> Nil } } ", ); } #[test] fn case_error_prints_module_alias() { assert_module_error!( ("wibble", "pub type Wibble { Wibble Wobble }"), " import wibble as wobble pub fn main(wibble) { case wibble { wobble.Wibble -> Nil } } ", ); } #[test] fn case_error_prints_unqualified_value() { assert_module_error!( ("wibble", "pub type Wibble { Wibble Wobble }"), " import wibble.{Wibble, Wobble} pub fn main(wibble) { case wibble { Wibble -> Nil } } ", ); } #[test] fn case_error_prints_aliased_unqualified_value() { assert_module_error!( ("wibble", "pub type Wibble { Wibble Wobble }"), " import wibble.{Wibble, Wobble as Wubble} pub fn main(wibble) { case wibble { Wibble -> Nil } } ", ); } #[test] fn case_error_prints_prelude_module_unqualified() { assert_module_error!( " pub fn main(result: Result(Nil, Nil)) { case result { Ok(Nil) -> Nil } } " ); } #[test] fn case_error_prints_prelude_module_when_shadowed() { assert_module_error!( " import gleam type MyResult { Ok Error } pub fn main(res: Result(Int, Nil)) { case res { gleam.Ok(n) -> Nil } } " ); } #[test] fn case_error_prints_module_when_shadowed() { assert_module_error!( ("mod", "pub type Wibble { Wibble Wobble }"), " import mod.{Wibble} type Wibble { Wibble Wobble } pub fn main() { let wibble = mod.Wibble case wibble { mod.Wobble -> Nil } } " ); } #[test] fn case_error_prints_module_when_aliased_and_shadowed() { assert_module_error!( ("mod", "pub type Wibble { Wibble Wobble }"), " import mod.{Wibble as Wobble} type Wibble { Wobble Wubble } pub fn main() { let wibble = mod.Wibble case wibble { mod.Wobble -> Nil } } " ); } #[test] fn case_error_prints_unqualifed_when_aliased() { assert_module_error!( ("mod", "pub type Wibble { Wibble Wobble }"), " import mod.{Wibble as Wobble} type Wibble { Wibble Wubble } pub fn main() { let wibble = mod.Wibble case wibble { mod.Wobble -> Nil } } " ); } // The following few tests all verify that the compiler provides useful errors // when there are no case arms, instead of just suggesting `_` as it did previously. #[test] fn empty_case_of_bool() { assert_module_error!( " pub fn main(b: Bool) { case b {} } " ); } #[test] fn empty_case_of_custom_type() { assert_module_error!( " pub type Wibble { Wibble Wobble Wubble } pub fn main(wibble: Wibble) { case wibble {} } " ); } #[test] fn empty_case_of_list() { assert_error!( " let list = [] case list {} " ); } #[test] fn empty_case_of_int() { assert_error!( " let num = 24 case num {} " ); } #[test] fn empty_case_of_float() { assert_error!( " let age = 10.6 case age {} " ); } #[test] fn empty_case_of_string() { assert_error!( r#" let name = "John Doe" case name {} "# ); } #[test] fn empty_case_of_multi_pattern() { assert_module_error!( " pub fn main(a: Result(a, b), b: Bool) { case a, b {} } " ); } #[test] fn inexhaustive_multi_pattern() { assert_error!( " let a = Ok(1) let b = True case a, b { Error(_), _ -> Nil } " ); } #[test] fn inexhaustive_multi_pattern2() { assert_module_error!( " pub fn main(a: Result(Int, Nil), b: Bool) { case a, b { Ok(1), True -> Nil } } " ); } #[test] fn inexhaustive_multi_pattern3() { assert_error!( " let a = Ok(1) let b = True case a, b { _, False -> Nil } " ); } #[test] fn inexhaustive_multi_pattern4() { assert_module_error!( " pub fn main(c: Bool) { let a = 12 let b = 3.14 case a, b, c { 1, 2.0, True -> Nil } } " ); } #[test] fn inexhaustive_multi_pattern5() { assert_module_error!( " pub fn main(c: Bool) { let a = 12 let b = 3.14 case a, b, c { 12, _, False -> Nil } } " ); } #[test] fn inferred_variant() { assert_no_warnings!( " pub type Wibble { Wibble(Bool) Wobble(Int) } pub fn main() { let wibble = Wibble(False) case wibble { Wibble(True) -> 1 Wibble(False) -> 0 } } ", ); } #[test] fn inferred_variant2() { assert_no_warnings!( " pub type Wibble { Wibble Wobble } pub fn main(b: Bool) { let wibble = Wibble case wibble, b { Wibble, True -> True Wibble, False -> False } } ", ); } #[test] fn inferred_variant3() { assert_no_warnings!( " pub type Wibble { Wibble(Int, Float, Bool) Wobble(String) } pub fn main() { let wibble = Wibble(1, 3.14, False) let Wibble(_int, _float, _bool) = wibble } ", ); } #[test] fn other_variant_unreachable_when_inferred() { assert_warning!( " pub type Wibble { Wibble Wobble } pub fn main() { let always_wobble = Wobble case always_wobble { Wibble -> panic Wobble -> Nil } } " ); } #[test] fn other_variant_unreachable_when_inferred2() { assert_warning!( " pub type Wibble { Wibble Wobble Wubble } pub fn main() { let always_wobble = Wobble case always_wobble { Wibble | Wubble -> panic Wobble -> Nil } } " ); } #[test] fn unreachable_string_pattern_after_prefix() { assert_warning!( r#"pub fn main() { let string = "" case string { "wib" <> rest -> rest "wibble" -> "a" _ -> "b" } }"# ); } #[test] fn reachable_string_pattern_after_prefix() { assert_no_warnings!( r#"pub fn main() { let string = "" case string { "wib" <> rest if True -> rest "wibble" -> "a" _ -> "b" } }"# ); } #[test] fn reachable_string_pattern_after_prefix_1() { assert_no_warnings!( r#"pub fn main() { let string = "" case string { "wibble" <> rest -> rest "wib" -> "a" _ -> "b" } }"# ); } #[test] fn unreachable_prefix_pattern_after_prefix() { assert_warning!( r#"pub fn main() { let string = "" case string { "wib" <> rest -> rest "wibble" <> rest -> rest _ -> "a" } }"# ); } #[test] fn reachable_prefix_pattern_after_prefix() { assert_no_warnings!( r#"pub fn main() { let string = "" case string { "wib" <> rest if True -> rest "wibble" <> rest -> rest _ -> "a" } }"# ); } #[test] fn reachable_prefix_pattern_after_prefix_1() { assert_no_warnings!( r#"pub fn main() { let string = "" case string { "wibble" <> rest -> rest "wib" <> rest -> rest _ -> "a" } }"# ); } #[test] fn multiple_unreachable_prefix_patterns() { assert_warning!( r#"pub fn main() { let string = "" case string { "wib" <> rest -> rest "wibble" <> rest -> rest "wibblest" <> rest -> rest _ -> "a" } }"# ); } #[test] fn multiple_unreachable_prefix_patterns_1() { assert_warning!( r#"pub fn main() { let string = "" case string { "wib" <> rest if True -> rest "wibble" <> rest -> rest "wibblest" <> rest -> rest _ -> "a" } }"# ); } #[test] fn bit_array_bits_catches_everything() { assert_warning!( r#"pub fn main() { let bit_array = <<>> case bit_array { <<_:bits>> -> 1 <<1>> -> 2 _ -> 2 } }"# ); } #[test] fn bit_array_bytes_needs_catch_all() { assert_module_error!( r#"pub fn main() { let bit_array = <<>> case bit_array { <<_:bytes>> -> 1 } }"# ); } #[test] fn bit_array_overlapping_patterns_are_redundant() { assert_warning!( r#"pub fn main() { let bit_array = <<>> case bit_array { <<1, a:size(16)>> -> a <<1, b:size(8)-unit(2)>> -> b _ -> 2 } }"# ); } #[test] fn bit_array_similar_overlapping_patterns_are_not_redundant() { assert_no_warnings!( r#"pub fn main() { let bit_array = <<>> case bit_array { <<1, a:size(16)>> -> a <<2, b:size(8)-unit(2)>> -> b _ -> 2 } }"# ); } #[test] fn bit_array_overlapping_redundant_patterns_with_variable_size() { assert_warning!( r#"pub fn main() { let bit_array = <<>> let len = 3 case bit_array { <> -> a <<_:size(len), b:size(8)-unit(2)>> -> b _ -> 2 } }"# ); } #[test] fn bit_array_overlapping_redundant_patterns_with_variable_size_2() { assert_warning!( r#"pub fn main() { let bit_array = <<>> case bit_array { <> -> 1 <> -> 2 _ -> 2 } }"# ); } #[test] fn bit_array_overlapping_patterns_with_variable_size_not_redundant() { assert_no_warnings!( r#"pub fn main() { let bit_array = <<>> case bit_array { <> -> 1 <> -> 2 _ -> 2 } }"# ); } #[test] fn bit_array_patterns_with_different_length_with_same_name_are_not_redundant() { assert_no_warnings!( r#"pub fn main() { let bit_array = <<>> let len = 10 case bit_array { <<_, _:size(len)-unit(3)>> -> 1 // Down here len is not the same as the len above, so the branch below is // not redundant! <> -> 2 _ -> 2 } }"# ); } #[test] fn bit_array_patterns_with_different_length_with_same_name_are_not_redundant_1() { assert_no_warnings!( r#"pub fn main() { let bit_array = <<>> let len = 10 case bit_array { <> -> 1 // Down here len is not the same as the len above, so the branch below is // not redundant! <<_, _:size(len)-unit(3)>> -> 2 _ -> 2 } }"# ); } #[test] fn bit_array_patterns_with_different_length_with_same_name_are_not_redundant_2() { assert_no_warnings!( r#"pub fn main() { let bit_array = <<>> case bit_array { <<_, len, _:size(len)>> -> 1 // Down here len is not the same as the len above, so the branch below is // not redundant! <> -> 2 _ -> 2 } }"# ); } #[test] fn same_catch_all_bytes_are_redundant() { assert_warning!( r#"pub fn main() { let bit_array = <<>> case bit_array { <<_:bytes>> -> <<>> <> -> a _ -> <<>> } }"# ); } #[test] fn different_catch_all_bytes_are_not_redundant() { assert_no_warnings!( r#"pub fn main() { let bit_array = <<>> case bit_array { <<_, _:bytes>> -> <<>> <<_:bytes>> -> <<>> _ -> <<>> } }"# ); } // https://github.com/gleam-lang/gleam/issues/2616 #[test] fn duplicated_alternative_patterns() { assert_warning!( " pub fn main() { let x = 1 case x { 2 | 2 -> 2 _ -> panic } } " ); } // https://github.com/gleam-lang/gleam/issues/2616 #[test] fn duplicated_pattern_in_alternative() { assert_warning!( " pub fn main() { let x = 1 case x { 2 -> x 1 | 2 -> x - 4 _ -> panic } } " ); } // https://github.com/gleam-lang/gleam/issues/2616 #[test] fn duplicated_pattern_with_multiple_alternatives() { assert_warning!( " pub fn main() { let x = 1 case x { 1 -> 1 3 -> 3 5 -> 5 1 | 2 | 3 | 4 | 5 -> x - 1 _ -> panic } } " ); } #[test] fn unreachable_multi_pattern() { assert_warning!( " pub fn main() { let x = 1 let y = 2 case x, y { 1, 2 -> True 1, 2 -> False _, _ -> panic } } " ); } #[test] fn unreachable_alternative_multi_pattern() { assert_warning!( " pub fn main() { let x = 1 let y = 2 case x, y { 1, 2 -> True 3, 4 | 1, 2 -> False _, _ -> panic } } " ); } // https://github.com/gleam-lang/gleam/issues/4586 #[test] fn compiler_does_not_crash_when_defining_duplicate_alternative_variables() { assert_error!( " case todo { #(a, b) | #(a, a as b) -> todo } " ); } // https://github.com/gleam-lang/gleam/issues/4626 #[test] fn correct_missing_patterns_for_opaque_type() { assert_module_error!( ( "mod", "pub opaque type Wibble { Wibble(Int) Wobble(String) }" ), " import mod pub fn main(w: mod.Wibble) { case w {} } " ); } #[test] fn correct_missing_patterns_for_opaque_type_in_definition_module() { assert_module_error!( " pub opaque type Wibble { Wibble(Int) Wobble(String) } pub fn main(w: Wibble) { case w {} } " ); } #[test] // https://github.com/gleam-lang/gleam/issues/4278 fn redundant_missing_patterns() { assert_module_error!( r#" fn wibble(b: Bool, i: Int) { case b, i { False, 1 -> todo True, 2 -> todo } } pub fn main() { wibble(False, 1) }"# ); } #[test] // https://github.com/gleam-lang/gleam/issues/5262 fn compiler_does_not_crash_when_matching_on_utfcodepoint() { assert_module_error!( ( "gleam_stdlib", "gleam/string", r#" @external(erlang, "gleam_stdlib", "identity") fn unsafe_int_to_utf_codepoint(a: Int) -> UtfCodepoint pub fn utf_codepoint(value: Int) -> Result(UtfCodepoint, Nil) { case value { i if i > 1_114_111 -> Error(Nil) i if i >= 55_296 && i <= 57_343 -> Error(Nil) i if i < 0 -> Error(Nil) i -> Ok(unsafe_int_to_utf_codepoint(i)) } } "# ), r#" import gleam/string pub fn main() { let assert Ok(wibble) = string.utf_codepoint(71) case wibble { } } "# ); } // https://github.com/gleam-lang/gleam/issues/5286 #[test] fn reachable_bit_array_pattern() { assert_no_warnings!( r#" pub fn main(x) { case x { <<_, "==">> -> 1 <<_, _, "=">> -> 2 // ^^^ This should be reachable _ -> 3 } } "# ); } ================================================ FILE: compiler-core/src/type_/tests/externals.rs ================================================ use crate::{ assert_js_module_error, assert_js_module_infer, assert_module_error, assert_module_infer, }; // https://github.com/gleam-lang/gleam/issues/2324 #[test] fn javascript_only_function_used_by_erlang_module() { let module = r#"@external(javascript, "one", "two") fn js_only() -> Int pub fn main() { js_only() } "#; assert_module_error!(module); assert_js_module_infer!(module, vec![("main", "fn() -> Int")]); } #[test] fn erlang_only_function_used_by_javascript_module() { let module = r#"@external(erlang, "one", "two") fn erlang_only() -> Int pub fn main() { erlang_only() } "#; assert_js_module_error!(module); assert_module_infer!(module, vec![("main", "fn() -> Int")]); } #[test] fn unused_javascript_only_function_is_not_rejected_on_erlang_target() { assert_module_infer!( r#"@external(javascript, "one", "two") fn js_only() -> Int pub fn main() { 10 } "#, vec![("main", "fn() -> Int")] ); } #[test] fn unused_erlang_only_function_is_not_rejected_on_javascript_target() { assert_js_module_infer!( r#"@external(erlang, "one", "two") fn erlang_only() -> Int pub fn main() { 10 } "#, vec![("main", "fn() -> Int")] ); } #[test] fn erlang_only_function_with_javascript_external() { let module = r#" @external(erlang, "one", "two") fn erlang_only() -> Int @external(javascript, "one", "two") fn all_targets() -> Int { erlang_only() } pub fn main() { all_targets() } "#; let expected = vec![("main", "fn() -> Int")]; assert_module_infer!(module, expected.clone()); assert_js_module_infer!(module, expected); } #[test] fn javascript_only_function_with_erlang_external() { let module = r#" @external(javascript, "one", "two") fn javascript_only() -> Int @external(erlang, "one", "two") fn all_targets() -> Int { javascript_only() } pub fn main() { all_targets() } "#; let expected = vec![("main", "fn() -> Int")]; assert_module_infer!(module, expected.clone()); assert_js_module_infer!(module, expected); } #[test] fn javascript_only_function_with_javascript_external() { let module = r#"@external(javascript, "one", "two") fn javascript_only() -> Int @external(javascript, "one", "two") pub fn uh_oh() -> Int { javascript_only() } "#; assert_js_module_infer!(module, vec![("uh_oh", "fn() -> Int")]); assert_module_error!(module); } #[test] fn erlang_only_function_with_erlang_external() { let module = r#"@external(erlang, "one", "two") fn erlang_only() -> Int @external(erlang, "one", "two") pub fn uh_oh() -> Int { erlang_only() } "#; assert_js_module_error!(module); assert_module_infer!(module, vec![("uh_oh", "fn() -> Int")]); } #[test] fn erlang_targeted_function_cant_contain_javascript_only_function() { let module = r#"@target(erlang) pub fn erlang_only() -> Int { javascript_only() } @external(javascript, "one", "two") fn javascript_only() -> Int "#; assert_js_module_infer!(module, vec![]); assert_module_error!(module); } #[test] fn javascript_targeted_function_cant_contain_erlang_only_function() { let module = r#"@target(javascript) pub fn javascript_only() -> Int { erlang_only() } @external(erlang, "one", "two") fn erlang_only() -> Int "#; assert_module_infer!(module, vec![]); assert_js_module_error!(module); } #[test] fn imported_javascript_only_function() { assert_module_error!( ( "module", r#"@external(javascript, "one", "two") pub fn javascript_only() -> Int"# ), "import module pub fn main() { module.javascript_only() }", ); } #[test] fn javascript_only_constant() { assert_module_error!( ( "module", r#"@external(javascript, "one", "two") fn javascript_only() -> Int const constant = javascript_only pub const javascript_only_constant = constant "# ), "import module pub fn main() { module.javascript_only_constant() }", ); } #[test] fn public_javascript_external() { let module = r#"@external(javascript, "one", "two") pub fn main() -> Int "#; assert_module_error!(module); assert_js_module_infer!(module, vec![("main", "fn() -> Int")]); } #[test] fn public_erlang_external() { let module = r#"@external(erlang, "one", "two") pub fn main() -> Int "#; assert_module_infer!(module, vec![("main", "fn() -> Int")]); assert_js_module_error!(module); } #[test] fn unsupported_target_for_unused_import() { // If we import a function which doesn't support the current target, // even if we don't use it, the compiler should error assert_module_error!( ( "mod", r#"@external(javascript, "wibble", "wobble") pub fn wobble()"# ), "import mod.{wobble}" ); } #[test] fn supported_target_for_imported_value() { assert_module_infer!( ( "mod", r#"@external(erlang, "wibble", "wobble") pub fn wobble() -> Int"# ), "import mod.{wobble} pub const wobble = wobble", vec![("wobble", "fn() -> Int")], ); } #[test] fn javascript_mjs() { assert_js_module_infer!( r#"@external(javascript, "one.mjs", "two") pub fn main() -> Int "#, vec![("main", "fn() -> Int")] ); } #[test] fn javascript_cjs() { assert_js_module_infer!( r#"@external(javascript, "one.cjs", "two") pub fn main() -> Int "#, vec![("main", "fn() -> Int")] ); } ================================================ FILE: compiler-core/src/type_/tests/functions.rs ================================================ use crate::{assert_module_error, assert_module_infer}; // https://github.com/gleam-lang/gleam/issues/1860 #[test] fn unlabelled_after_labelled() { assert_module_error!( "fn main(wibble wibber, wobber) { Nil }" ); } // https://github.com/gleam-lang/gleam/issues/1860 #[test] fn unlabelled_after_labelled_with_type() { assert_module_error!( "fn main(wibble wibber, wobber: Int) { Nil }" ); } // https://github.com/gleam-lang/gleam/issues/1860 #[test] fn unlabelled_after_labelled_external() { assert_module_error!( r#" @external(erlang, "", "") fn main(wibble x: Int, y: Int) -> Int "# ); } // https://github.com/gleam-lang/gleam/issues/1860 #[test] fn all_labelled() { assert_module_infer!( r#"pub fn prepend(to list: List(a), this item: a) -> List(a) { [item, ..list] } "#, vec![(r#"prepend"#, r#"fn(List(a), a) -> List(a)"#)] ); } // https://github.com/gleam-lang/gleam/issues/1814 #[test] fn out_of_order_generalisation() { assert_module_infer!( r#" pub fn main() { call(fn() { "Hello" }) } fn call(f: fn() -> a) { f() } "#, vec![(r#"main"#, r#"fn() -> String"#)] ); } // https://github.com/gleam-lang/gleam/issues/2275 #[test] fn bug_2275() { assert_module_infer!( r#" pub fn zero() { one() } fn one() { one() two() } fn two() { two Nil } "#, vec![(r#"zero"#, r#"fn() -> Nil"#)] ); } // https://github.com/gleam-lang/gleam/issues/2275 #[test] fn bug_2275_2_self_references() { assert_module_infer!( r#" pub fn zero() { one() } fn one() { one() two() } fn two() { two two Nil } "#, vec![(r#"zero"#, r#"fn() -> Nil"#)] ); } // https://github.com/gleam-lang/gleam/issues/2275 #[test] fn bug_2275_again() { assert_module_infer!( r#" pub fn aaa(input) { case [] { [] -> input _ -> { let input2 = bbb() aaa(input2) } } } pub fn bbb() { ccc() + bbb() } pub fn ccc() { ccc() + bbb() } "#, vec![ (r#"aaa"#, r#"fn(Int) -> Int"#), (r#"bbb"#, r#"fn() -> Int"#), (r#"ccc"#, r#"fn() -> Int"#), ] ); } #[test] fn deprecated_function() { assert_module_infer!( r#" @deprecated("use wibble instead") pub fn main() { Nil }"#, vec![(r#"main"#, r#"fn() -> Nil"#)] ); } // https://github.com/gleam-lang/gleam/issues/2303 #[test] fn recursive_type() { assert_module_error!( r#" pub fn one(x) { two([x]) } pub fn two(x) { one(x) } "# ); } #[test] fn no_impl_function_fault_tolerance() { // A function not having an implementation does not stop analysis. assert_module_error!( r#" pub fn no_impl() -> Nil pub type X = UnknownType "# ); } #[test] fn bad_body_function_fault_tolerance() { // A function having an invalid body does not stop analysis. assert_module_error!( r#" pub fn bad(x: Int) -> Float { // Invalid body. "" + "" } pub fn user() -> Float { // This checks that the bad function is still usable, the types coming from // its annotations. This function is valid. bad(1) } // Another bad function to make sure that analysis has not stopped. This error // should also be emitted. pub fn bad_2() { bad(Nil) } "# ); } #[test] fn annotation_mismatch_function_fault_tolerance() { // A function having an invalid body does not stop analysis. assert_module_error!( r#" pub fn bad(x: Int) -> Float { // This does not match the return annotation 1 } pub fn user() -> Float { // This checks that the bad function is still usable, the types coming from // its annotations. This function is valid. bad(1) } // Another bad function to make sure that analysis has not stopped. This error // should also be emitted. pub fn bad_2() { bad(Nil) } "# ); } #[test] fn invalid_javascript_external_do_not_stop_analysis() { // Both these have errors! We do not stop on the first one. assert_module_error!( r#" @external(javascript, "somemodule", "() => 123") pub fn one() -> Nil { Nil } pub fn two() -> Nil { "" } "# ); } #[test] fn multiple_bad_statement_assignment_fault_tolerance() { assert_module_error!( r#" pub fn main() { let a = 1 + 2.0 let b = 3 + 4.0 let c = a + b } "# ); } #[test] fn multiple_bad_statement_assignment_with_annotation_fault_tolerance() { assert_module_error!( r#" pub fn main() { let a: Int = "not an int" let b: String = 1 let c = a + 2 } "# ); } #[test] fn multiple_bad_statement_assignment_with_annotation_fault_tolerance2() { assert_module_error!( r#" pub fn main() { // Since the value is invalid the type is the annotation let a: Int = Junk let b: String = 1 let c = a + 2 } "# ); } #[test] fn multiple_bad_statement_assignment_with_pattern_fault_tolerance2() { assert_module_error!( r#" pub fn main() { // Since the pattern is invalid no variable is created let Junk(a) = 7 // Pattern is valid but does not type check let Ok(b) = 1 let c = a + b } "# ); } #[test] fn multiple_bad_statement_expression_fault_tolerance() { assert_module_error!( r#" pub fn main() { 1 + 2.0 3 + 4.0 let c = 1 + 2 } "# ); } #[test] fn function_call_incorrect_arg_types_fault_tolerance() { assert_module_error!( r#" fn add(x: Int, y: Int) { x + y } pub fn main() { add(1.0, 1.0) } "# ); } #[test] fn function_call_incorrect_arity_fault_tolerance() { assert_module_error!( r#" fn add(x: Int, y: Int) { x + y } pub fn main() { add(1.0) } "# ); } #[test] fn function_call_incorrect_arity_with_labels_fault_tolerance() { assert_module_error!( r#" fn wibble(wibble arg1: fn() -> Int, wobble arg2: Int) -> Int { arg1() + arg2 } pub fn main() { wibble(wobble: "") } "# ); } #[test] fn function_call_incorrect_arity_with_label_shorthand_fault_tolerance() { assert_module_error!( r#" fn wibble(wibble arg1: fn() -> Int, wobble arg2: Int) -> Int { arg1() + arg2 } pub fn main() { let wobble = "" wibble(wobble:) } "# ); } #[test] fn function_call_incorrect_arity_with_labels_fault_tolerance2() { assert_module_error!( r#" fn wibble(wibble arg1: fn() -> Int, wobble arg2: Int, wabble arg3: Int) -> Int { arg1() + arg2 + arg3 } pub fn main() { wibble(fn() {""}, wobble: "") } "# ); } #[test] fn function_call_incorrect_arity_with_label_shorthand_fault_tolerance2() { assert_module_error!( r#" fn wibble(wibble arg1: fn() -> Int, wobble arg2: Int, wabble arg3: Int) -> Int { arg1() + arg2 + arg3 } pub fn main() { let wobble = "" wibble(fn() {""}, wobble:) } "# ); } #[test] fn case_clause_pattern_fault_tolerance() { assert_module_error!( r#" pub fn main() { let wibble = True case wibble { True -> 0 Wibble -> 1 Wibble2 -> 2 _ -> 3 } } "# ); } #[test] fn case_clause_guard_fault_tolerance() { assert_module_error!( r#" pub fn main() { let wibble = True case wibble { a if a == Wibble -> 0 b if b == Wibble -> 0 _ -> 1 } } "# ); } #[test] fn case_clause_then_fault_tolerance() { assert_module_error!( r#" pub fn main() { let wibble = True case wibble { True -> { 1.0 + 1.0 } _ -> { 1.0 + 1.0 } } } "# ); } // https://github.com/gleam-lang/gleam/issues/2504 #[test] fn provide_arg_type_to_fn_implicit_ok() { assert_module_infer!( r#" pub fn main() { let z = #(1,2) fn(x) { x.0 }(z) } "#, vec![("main", "fn() -> Int")] ); } #[test] fn provide_arg_type_to_fn_explicit_ok() { assert_module_infer!( r#" pub fn main() { let z = #(1,2) fn(x: #(Int, Int)) { x.0 }(z) } "#, vec![("main", "fn() -> Int")] ); } #[test] fn provide_arg_type_to_fn_implicit_error() { assert_module_error!( r#" pub fn main() { let z = #(1,2) fn(x) { x.2 }(z) } "# ); } #[test] fn provide_arg_type_to_fn_explicit_error() { assert_module_error!( r#" pub fn main() { let z = #(1,2) fn(x: #(Int, Int)) { x.2 }(z) } "# ); } #[test] fn provide_arg_type_to_fn_arg_infer_error() { assert_module_error!( r#" pub fn main() { fn(x) { x.2 }(z) } "# ); } #[test] fn provide_arg_type_to_fn_not_a_tuple() { assert_module_error!( r#" pub fn main() { let z = "not a tuple" fn(x) { x.2 }(z) } "# ); } #[test] fn provide_two_args_type_to_fn() { assert_module_infer!( r#" pub fn main() { let a = #(1,2) let b = #(1,2) fn(x, y) { x.0 + y.1 }(a, b) } "#, vec![("main", "fn() -> Int")] ); } #[test] fn provide_one_arg_type_to_two_args_fn() { assert_module_error!( r#" pub fn main() { let a = #(1,2) fn(x, y) { x.0 + y.1 }(a) } "# ); } #[test] fn provide_two_args_type_to_fn_wrong_types() { assert_module_error!( r#" pub fn main() { let a = 1 let b = "not an int" fn(x, y) { x + y }(a, b) } "# ); } ================================================ FILE: compiler-core/src/type_/tests/guards.rs ================================================ use crate::{assert_module_error, assert_module_infer}; #[test] fn nested_record_access() { assert_module_infer!( r#" pub type A { A(b: B) } pub type B { B(c: C) } pub type C { C(d: Bool) } pub fn a(a: A) { case a { _ if a.b.c.d -> 1 _ -> 0 } } "#, vec![ ("A", "fn(B) -> A"), ("B", "fn(C) -> B"), ("C", "fn(Bool) -> C"), ("a", "fn(A) -> Int"), ], ); } #[test] fn string_variable_access() { assert_module_error!( r#" pub fn a(a: String) { case a { _ if a.b -> 1 _ -> 0 } } "# ); } #[test] fn qualified_record() { assert_module_infer!( ("wibble", "pub type Wibble { Wibble Wobble }"), " import wibble pub fn main(wibble: wibble.Wibble) { case wibble { w if w == wibble.Wobble -> True _ -> False } } ", vec![("main", "fn(Wibble) -> Bool")] ); } #[test] fn qualified_record_with_arguments() { assert_module_infer!( ( "wibble", "pub type Wibble { Wibble(Int) Wobble(Int, Float) }" ), " import wibble pub fn main(wibble: wibble.Wibble) { case wibble { w if w == wibble.Wobble(1, 3.8) -> True _ -> False } } ", vec![("main", "fn(Wibble) -> Bool")] ); } ================================================ FILE: compiler-core/src/type_/tests/imports.rs ================================================ use crate::{assert_module_error, assert_module_infer}; // https://github.com/gleam-lang/gleam/issues/1760 #[test] fn import_value_with_same_name_as_imported_module() { assert_module_infer!( ("other", "pub const other = 1"), " import other.{other} pub const a = other ", vec![("a", "Int")], ); } #[test] fn imported_constant_record() { assert_module_infer!( ("one/two", "pub type Thing { Thing(Int) }"), " import one/two pub const a = two.Thing(1) ", vec![("a", "Thing")], ); } #[test] fn using_private_constructor() { assert_module_error!( ("one", "type Two { Two }"), "import one pub fn main() { one.Two }", ); } #[test] fn using_private_constructor_pattern() { assert_module_error!( ("one", "type Two { Two }"), "import one pub fn main(x) { let one.Two = x }", ); } #[test] fn using_opaque_constructor() { assert_module_error!( ("one", "pub opaque type Two { Two }"), "import one pub fn main() { one.Two }", ); } #[test] fn using_private_function() { assert_module_error!( ("one", "fn two() { 2 }"), "import one pub fn main() { one.two }", ); } #[test] fn using_private_type_alias() { assert_module_error!( ("one", "type X = Int"), "import one pub fn main() { one.X }", ); } #[test] fn using_private_unqualified_type_alias() { assert_module_error!( ("one", "type X = Int"), "import one.{X} pub fn main() { X }", ); } #[test] fn using_private_external_type() { assert_module_error!( ("one", "type X"), "import one pub fn main() { one.X }", ); } #[test] fn using_private_unqualified_external_type() { assert_module_error!( ("one", "type X"), "import one.{X} pub fn main() { X }", ); } #[test] fn using_private_custom_type() { assert_module_error!( ("one", "type X { Y }"), "import one pub fn main() { one.X }", ); } #[test] fn using_private_unqualified_custom_type() { assert_module_error!( ("one", "type X { Y }"), "import one.{X} pub fn main() { X }", ); } #[test] fn unqualified_using_private_constructor() { assert_module_error!( ("one", "type Two { Two }"), "import one.{Two} pub fn main() { Two }", ); } #[test] fn unqualified_using_private_constructor_pattern() { assert_module_error!( ("one", "type Two { Two }"), "import one.{Two} pub fn main(x) { let Two = x }", ); } #[test] fn unqualified_using_opaque_constructor() { assert_module_error!( ("one", "pub opaque type Two { Two }"), "import one.{Two} pub fn main() { Two }", ); } #[test] fn unqualified_using_private_function() { assert_module_error!( ("one", "fn two() { 2 }"), "import one.{two} pub fn main() { two }", ); } #[test] fn import_type() { assert_module_infer!( ("one", "pub type One = Int"), "import one.{type One} pub fn main() -> One { todo } ", vec![("main", "fn() -> Int")], ); } #[test] fn import_type_duplicate() { assert_module_error!( ("one", "pub type One = Int"), "import one.{One, type One} pub fn main() -> One { todo } ", ); } #[test] fn import_type_duplicate_with_as() { assert_module_error!( ("one", "pub type One = Int"), "import one.{type One as MyOne, type One as MyOne} pub type X = One ", ); } #[test] fn import_type_duplicate_with_as_multiline() { assert_module_error!( ("one", "pub type One = Int"), "import one.{ type One as MyOne, type One as MyOne } pub type X = One ", ); } // https://github.com/gleam-lang/gleam/issues/2379 #[test] fn deprecated_type_import_conflict() { assert_module_infer!( ("one", "pub type X { X }"), "import one.{X, type X}", vec![] ); } #[test] fn aliased_unqualified_type_and_value() { assert_module_infer!( ("one", "pub type X { X }"), "import one.{X as XX, type X as XX}", vec![] ); } #[test] fn deprecated_type_import_conflict_two_modules() { assert_module_infer!( ("one", "pub type X { X }"), ("two", "pub type X { X }"), " import one.{type X as Y} import two.{X} ", vec![] ); } #[test] fn imported_constructor_instead_of_type() { assert_module_error!( ("module", "pub type Wibble { Wibble }"), "import module.{Wibble} pub fn main(x: Wibble) { todo }", ); } #[test] fn import_errors_do_not_block_analysis() { // An error in an import doesn't stop the rest of the module being analysed assert_module_error!( "import unknown_module pub fn main() { 1 + Nil }" ); } #[test] fn unqualified_import_errors_do_not_block_later_unqualified() { assert_module_error!( "import gleam.{Unknown, type Int as Integer} pub fn main() -> Integer { Nil }" ); } #[test] fn module_alias_used_as_a_name() { assert_module_error!( ("one/two", ""), " import one/two pub fn main() { two } " ); } ================================================ FILE: compiler-core/src/type_/tests/let_assert.rs ================================================ use crate::{assert_error, assert_infer}; #[test] fn empty_list() { assert_infer!("let assert [] = [] 1", "Int"); } #[test] fn list_one() { assert_infer!("let assert [a] = [1] a", "Int"); } #[test] fn list_two() { assert_infer!("let assert [a, 2] = [1] a", "Int"); } #[test] fn list_spread() { assert_infer!("let assert [a, ..] = [1] a", "Int"); } #[test] fn list_spread_discard() { assert_infer!("let assert [a, .._] = [1] a", "Int"); } #[test] fn list_spread_discard_comma_after() { assert_infer!("let assert [a, .._,] = [1] a", "Int"); } #[test] fn in_fn() { assert_infer!("fn(x) { let assert [a] = x a }", "fn(List(a)) -> a"); } #[test] fn in_fn_list_int() { assert_infer!("fn(x) { let assert [a] = x a + 1 }", "fn(List(Int)) -> Int"); } #[test] fn discard_named() { assert_infer!("let assert _x = 1 2.0", "Float"); } #[test] fn discard() { assert_infer!("let assert _ = 1 2.0", "Float"); } #[test] fn tuple() { assert_infer!("let assert #(tag, x) = #(1.0, 1) x", "Int"); } #[test] fn tuple_in_fn() { assert_infer!("fn(x) { let assert #(a, b) = x a }", "fn(#(a, b)) -> a"); } #[test] fn annotation() { assert_infer!("let assert 5: Int = 5 5", "Int"); } #[test] fn new_syntax() { assert_infer!("let assert Ok(x) = Error(1)", "Result(a, Int)"); } #[test] fn expression() { assert_infer!("let assert x = 1", "Int"); } #[test] fn expression1() { assert_infer!("let assert x = { let assert x = 1 }", "Int"); } #[test] fn expression2() { assert_infer!("let assert x = { let assert x = 1. }", "Float"); } #[test] fn expression3() { assert_infer!("let assert 1 = 1", "Int"); } #[test] fn message() { assert_infer!( r#" let assert Ok(inner) = Ok(10) as "This clearly never fails" inner "#, "Int" ); } #[test] fn non_string_message() { assert_error!("let assert 1 = 2 as 3"); } ================================================ FILE: compiler-core/src/type_/tests/pipes.rs ================================================ use crate::{assert_module_error, assert_module_infer, assert_no_warnings}; // https://github.com/gleam-lang/gleam/issues/2392 #[test] fn empty_list() { assert_module_infer!( " pub fn a() { fn(_) { Nil } } pub fn b(_) { fn(_) { Nil } } pub fn c() { Nil |> b( Nil |> a(), ) }", vec![ ("a", "fn() -> fn(a) -> Nil"), ("b", "fn(a) -> fn(b) -> Nil"), ("c", "fn() -> Nil"), ] ); } // https://github.com/gleam-lang/gleam/pull/3406#discussion_r1683068647 #[test] fn pipe_rewrite_with_missing_argument() { assert_module_infer!( r#" pub fn main() { let f = fn(a, b) { fn(c) { a + b + c } } 1 |> f(2) } "#, vec![("main", "fn() -> fn(Int) -> Int")] ); } #[test] fn pipe_regression_gh3515() { // https://github.com/gleam-lang/gleam/issues/3515 assert_module_infer!( r#" fn relu(t) { fn(theta: String) { // use t and theta and return a Float 0.0 } } pub fn k_relu(k: Int) { fn(t: Float) { fn(theta: String) { case k { 0 -> t _ -> { // following code is OK on gleam 1.3.2, // but raised error on gleam 1.4.1 // The key here is that it is not a direct function call, // but a "var" call, which points to the same function. let next_layer = theta |> relu(t) |> k_relu(k - 1) theta |> next_layer } } } } }"#, vec![("k_relu", "fn(Int) -> fn(Float) -> fn(String) -> Float")], ); } #[test] fn pipe_callback_var_function1() { assert_module_infer!( r#" pub fn main() { let f = fn(a) { fn(b) { #(a, b) } } let x = 1 |> f() } "#, vec![("main", "fn() -> fn(a) -> #(Int, a)")], ); } #[test] fn pipe_callback_var_function2() { assert_module_infer!( r#" pub fn main() { let f = fn(a) { fn(b) { #(a, b) } } let x = 1 |> f(1) } "#, vec![("main", "fn() -> #(Int, Int)")], ); } #[test] fn pipe_callback_correct_arity1() { assert_module_infer!( r#" fn callback(a: Int) { fn() -> String { "Called" } } pub fn main() { let x = 1 |> callback() } "#, vec![("main", "fn() -> fn() -> String")], ); } #[test] fn pipe_callback_correct_arity2() { assert_module_infer!( r#" fn callback(a: Float) { fn(b: Int) -> String { "Called" } } pub fn main() { let x = 1 |> callback(2.5) } "#, vec![("main", "fn() -> String")], ); } #[test] fn pipe_callback_wrong_arity() { assert_module_error!( r#" fn callback(a: Int) { fn() -> String { "Called" } } pub fn main() { let x = 1 |> callback(2) } "# ); } #[test] fn no_warnings_when_piping_into_labelled_capture_as_first_argument() { assert_no_warnings!( " fn wibble(label1 a, label2 b, lots c, of d, labels e) { a + b * c - d / e } pub fn main() { 1 |> wibble(label1: _, label2: 2, lots: 3, of: 4, labels: 5) } " ); } #[test] fn no_warnings_when_piping_into_labelled_capture_as_only_argument() { assert_no_warnings!( " fn wibble(descriptive_label value) { value } pub fn main() { 42 |> wibble(descriptive_label: _) } " ); } ================================================ FILE: compiler-core/src/type_/tests/pretty.rs ================================================ use std::sync::Arc; use crate::type_::{ Type, prelude::{bool, int, tuple}, pretty::Printer, }; use super::Publicity; fn print(type_: Arc) -> String { Printer::new().pretty_print(&type_, 0) } fn custom_bool() -> Arc { Arc::new(Type::Named { publicity: Publicity::Public, package: "wibble".into(), module: "one/two".into(), name: "Bool".into(), arguments: vec![], inferred_variant: None, }) } #[test] fn repeated_prelude_type() { insta::assert_snapshot!(print(tuple(vec![int(), int(), int()]))); } #[test] fn prelude_type_clash_prelude_first() { insta::assert_snapshot!(print(tuple(vec![bool(), custom_bool()]))); } #[test] fn prelude_type_clash_custom_first() { insta::assert_snapshot!(print(tuple(vec![custom_bool(), bool()]))); } ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__assert__bool_literal.snap ================================================ --- source: compiler-core/src/type_/tests/assert.rs expression: "\npub fn main() {\n assert True\n}\n" --- ----- SOURCE CODE pub fn main() { assert True } ----- WARNING warning: Assertion of a literal value ┌─ /src/warning/wrn.gleam:3:10 │ 3 │ assert True │ ^^^^ Asserting on a literal bool is redundant since you can already tell whether it will be `True` or `False`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__assert__comparison_on_literals.snap ================================================ --- source: compiler-core/src/type_/tests/assert.rs expression: "\npub fn main() {\n assert 1 < 2\n}\n" --- ----- SOURCE CODE pub fn main() { assert 1 < 2 } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:3:10 │ 3 │ assert 1 < 2 │ ^^^^^ This is always `True` This comparison is redundant since it always succeeds. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__assert__equality_check_on_literals.snap ================================================ --- source: compiler-core/src/type_/tests/assert.rs expression: "\npub fn main() {\n assert 1 == 2\n}\n" --- ----- SOURCE CODE pub fn main() { assert 1 == 2 } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:3:10 │ 3 │ assert 1 == 2 │ ^^^^^^ This is always `False` This comparison is redundant since it always fails. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__assert__mismatched_types.snap ================================================ --- source: compiler-core/src/type_/tests/assert.rs expression: assert 10 --- ----- SOURCE CODE assert 10 ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:8 │ 1 │ assert 10 │ ^^ Expected type: Bool Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__assert__negation_of_bool_literal.snap ================================================ --- source: compiler-core/src/type_/tests/assert.rs expression: "\npub fn main() {\n assert !False\n}\n" --- ----- SOURCE CODE pub fn main() { assert !False } ----- WARNING warning: Assertion of a literal value ┌─ /src/warning/wrn.gleam:3:10 │ 3 │ assert !False │ ^^^^^^ Asserting on a literal bool is redundant since you can already tell whether it will be `True` or `False`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__assert__wrong_message_type.snap ================================================ --- source: compiler-core/src/type_/tests/assert.rs expression: assert True as 10 --- ----- SOURCE CODE assert True as 10 ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:16 │ 1 │ assert True as 10 │ ^^ Expected type: String Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__custom_types__conflict_with_import.snap ================================================ --- source: compiler-core/src/type_/tests/custom_types.rs expression: "import wibble.{type A} type A { C }" --- ----- SOURCE CODE -- wibble.gleam pub type A { B } -- main.gleam import wibble.{type A} type A { C } ----- ERROR error: Duplicate type definition ┌─ /src/one/two.gleam:1:24 │ 1 │ import wibble.{type A} type A { C } │ ------ ^^^^^^ Redefined here │ │ │ First defined here The type `A` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__custom_types__depreacted_type_deprecate_varient_err.snap ================================================ --- source: compiler-core/src/type_/tests/custom_types.rs expression: "\n@deprecated(\"2\")\npub type Numbers {\n @deprecated(\"1\")\n One\n Two\n}\n\npub fn num() {\n let _two = Two\n Nil\n}\n" --- ----- SOURCE CODE @deprecated("2") pub type Numbers { @deprecated("1") One Two } pub fn num() { let _two = Two Nil } ----- ERROR error: Custom type already deprecated ┌─ /src/one/two.gleam:5:3 │ 5 │ One │ ^^^ This custom type has already been deprecated, so deprecating one of its variants does nothing. Consider removing the deprecation attribute on the variant. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__custom_types__deprecated_all_varients_type.snap ================================================ --- source: compiler-core/src/type_/tests/custom_types.rs expression: "\npub type Numbers {\n @deprecated(\"1\")\n One\n @deprecated(\"2\")\n Two\n}\n" --- ----- SOURCE CODE pub type Numbers { @deprecated("1") One @deprecated("2") Two } ----- ERROR error: All variants of custom type deprecated. ┌─ /src/one/two.gleam:2:1 │ 2 │ pub type Numbers { │ ^^^^^^^^^^^^^^^^ Consider deprecating the type as a whole. @deprecated("message") type Wibble { Wobble1 Wobble2 } ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__custom_types__deprecated_type.snap ================================================ --- source: compiler-core/src/type_/tests/custom_types.rs expression: "\n@deprecated(\"Dont use this!\")\npub type Cat {\n Cat(name: String, cuteness: Int)\n}\n\npub fn name() -> String {\n let c = Cat(\"Numi\", 20)\n c.name\n}\n " --- ----- SOURCE CODE @deprecated("Dont use this!") pub type Cat { Cat(name: String, cuteness: Int) } pub fn name() -> String { let c = Cat("Numi", 20) c.name } ----- WARNING warning: Deprecated value used ┌─ /src/warning/wrn.gleam:8:11 │ 8 │ let c = Cat("Numi", 20) │ ^^^ This value has been deprecated It was deprecated with this message: Dont use this! ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__custom_types__deprecated_varients_type.snap ================================================ --- source: compiler-core/src/type_/tests/custom_types.rs expression: "\npub type Numbers {\n @deprecated(\"1\")\n One\n Two\n}\n\npub fn num() {\n let _one = One\n let _two = Two\n Nil\n}\n" --- ----- SOURCE CODE pub type Numbers { @deprecated("1") One Two } pub fn num() { let _one = One let _two = Two Nil } ----- WARNING warning: Deprecated value used ┌─ /src/warning/wrn.gleam:9:14 │ 9 │ let _one = One │ ^^^ This value has been deprecated It was deprecated with this message: 1 ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__custom_types__duplicate_variable_error_does_not_stop_analysis.snap ================================================ --- source: compiler-core/src/type_/tests/custom_types.rs expression: "\ntype Two(a, a) {\n Two(a, a)\n}\n\ntype Three(a, a) {\n Three\n}\n" --- ----- SOURCE CODE type Two(a, a) { Two(a, a) } type Three(a, a) { Three } ----- ERROR error: Duplicate type parameter ┌─ /src/one/two.gleam:2:13 │ 2 │ type Two(a, a) { │ ^ This definition has multiple type parameters named `a`. Rename or remove one of them. error: Duplicate type parameter ┌─ /src/one/two.gleam:6:15 │ 6 │ type Three(a, a) { │ ^ This definition has multiple type parameters named `a`. Rename or remove one of them. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__custom_types__fault_tolerance.snap ================================================ --- source: compiler-core/src/type_/tests/custom_types.rs expression: "\npub type Cat {\n Cat(UnknownType)\n}\n\npub type Kitten = AnotherUnknownType\n " --- ----- SOURCE CODE pub type Cat { Cat(UnknownType) } pub type Kitten = AnotherUnknownType ----- ERROR error: Unknown type ┌─ /src/one/two.gleam:3:7 │ 3 │ Cat(UnknownType) │ ^^^^^^^^^^^ The type `UnknownType` is not defined or imported in this module. error: Unknown type ┌─ /src/one/two.gleam:6:19 │ 6 │ pub type Kitten = AnotherUnknownType │ ^^^^^^^^^^^^^^^^^^ The type `AnotherUnknownType` is not defined or imported in this module. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__custom_types__pattern_match_correct_labeled_field.snap ================================================ --- source: compiler-core/src/type_/tests/custom_types.rs expression: "\ntype Fish {\n Starfish()\n Jellyfish(name: String, jiggly: Bool)\n}\n\nfn handle_fish(fish: Fish) {\n case fish {\n Starfish() -> False\n Jellyfish(jiggly:) -> jiggly // <- error is here\n }\n}\n" --- ----- SOURCE CODE type Fish { Starfish() Jellyfish(name: String, jiggly: Bool) } fn handle_fish(fish: Fish) { case fish { Starfish() -> False Jellyfish(jiggly:) -> jiggly // <- error is here } } ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:10:5 │ 10 │ Jellyfish(jiggly:) -> jiggly // <- error is here │ ^^^^^^^^^^^^^^^^^^ Expected 2 arguments, got 1 This pattern accepts these additional labelled arguments: - name ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__custom_types__pattern_match_correct_pos_field.snap ================================================ --- source: compiler-core/src/type_/tests/custom_types.rs expression: "\ntype Fish {\n Starfish()\n Jellyfish(String, Bool)\n}\n\nfn handle_fish(fish: Fish) {\n case fish {\n Starfish() -> False\n Jellyfish(jiggly) -> jiggly\n }\n}\n" --- ----- SOURCE CODE type Fish { Starfish() Jellyfish(String, Bool) } fn handle_fish(fish: Fish) { case fish { Starfish() -> False Jellyfish(jiggly) -> jiggly } } ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:10:5 │ 10 │ Jellyfish(jiggly) -> jiggly │ ^^^^^^^^^^^^^^^^^ Expected 2 arguments, got 1 error: Type mismatch ┌─ /src/one/two.gleam:10:5 │ 10 │ Jellyfish(jiggly) -> jiggly │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ This case clause was found to return a different type than the previous one, but all case clauses must return the same type. Expected type: Bool Found type: String ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__dead_code_detection__constant_only_referenced_by_unused_constant.snap ================================================ --- source: compiler-core/src/type_/tests/dead_code_detection.rs expression: "\nconst value = 10\n\nconst value_twice = #(value, value)\n" --- ----- SOURCE CODE const value = 10 const value_twice = #(value, value) ----- WARNING warning: Unused private constant ┌─ /src/warning/wrn.gleam:2:1 │ 2 │ const value = 10 │ ^^^^^^^^^^^ This private constant is never used Hint: You can safely remove it. warning: Unused private constant ┌─ /src/warning/wrn.gleam:4:1 │ 4 │ const value_twice = #(value, value) │ ^^^^^^^^^^^^^^^^^ This private constant is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__dead_code_detection__constant_only_referenced_by_unused_function.snap ================================================ --- source: compiler-core/src/type_/tests/dead_code_detection.rs expression: "\nconst value = 10\n\nfn unused() {\n value\n}\n" --- ----- SOURCE CODE const value = 10 fn unused() { value } ----- WARNING warning: Unused private constant ┌─ /src/warning/wrn.gleam:2:1 │ 2 │ const value = 10 │ ^^^^^^^^^^^ This private constant is never used Hint: You can safely remove it. warning: Unused private function ┌─ /src/warning/wrn.gleam:4:1 │ 4 │ fn unused() { │ ^^^^^^^^^^^ This private function is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__dead_code_detection__constructor_used_if_type_alias_shadows_it.snap ================================================ --- source: compiler-core/src/type_/tests/dead_code_detection.rs expression: "\nimport wibble.{Wibble}\n\ntype Wibble =\n wibble.Wibble\n\npub fn main() {\n Wibble(\"hello\")\n}\n" --- ----- SOURCE CODE -- wibble.gleam pub type Wibble { Wibble(String) } -- main.gleam import wibble.{Wibble} type Wibble = wibble.Wibble pub fn main() { Wibble("hello") } ----- WARNING warning: Unused private type ┌─ /src/warning/wrn.gleam:4:1 │ 4 │ ╭ type Wibble = 5 │ │ wibble.Wibble │ ╰───────────────^ This private type is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__dead_code_detection__imported_module_alias_only_referenced_by_unused_function.snap ================================================ --- source: compiler-core/src/type_/tests/dead_code_detection.rs expression: "\nimport wibble as wobble\n\nfn unused() {\n wobble.Wibble\n}\n" --- ----- SOURCE CODE -- wibble.gleam pub type Wibble { Wibble(Int) } -- main.gleam import wibble as wobble fn unused() { wobble.Wibble } ----- WARNING warning: Unused private function ┌─ /src/warning/wrn.gleam:4:1 │ 4 │ fn unused() { │ ^^^^^^^^^^^ This private function is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__dead_code_detection__imported_module_alias_only_referenced_by_unused_function_with_unqualified.snap ================================================ --- source: compiler-core/src/type_/tests/dead_code_detection.rs expression: "\nimport wibble.{type Wibble} as wobble\n\nfn unused() {\n wobble.Wibble\n}\n\npub fn main() -> Wibble {\n panic\n}\n" --- ----- SOURCE CODE -- wibble.gleam pub type Wibble { Wibble(Int) } -- main.gleam import wibble.{type Wibble} as wobble fn unused() { wobble.Wibble } pub fn main() -> Wibble { panic } ----- WARNING warning: Unused private function ┌─ /src/warning/wrn.gleam:4:1 │ 4 │ fn unused() { │ ^^^^^^^^^^^ This private function is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__dead_code_detection__imported_module_marked_unused_when_shadowed_in_record_access.snap ================================================ --- source: compiler-core/src/type_/tests/dead_code_detection.rs expression: "\nimport wibble\n\ntype Wibble {\n Wibble(wobble: Int)\n}\n\npub fn main() {\n let wibble = Wibble(10)\n // This does not reference the `wibble` module!\n wibble.wobble\n}\n" --- ----- SOURCE CODE -- wibble.gleam pub const wibble = 1 -- main.gleam import wibble type Wibble { Wibble(wobble: Int) } pub fn main() { let wibble = Wibble(10) // This does not reference the `wibble` module! wibble.wobble } ----- WARNING warning: Unused imported module ┌─ /src/warning/wrn.gleam:2:1 │ 2 │ import wibble │ ^^^^^^^^^^^^^ This imported module is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__dead_code_detection__imported_module_only_referenced_by_unused_function.snap ================================================ --- source: compiler-core/src/type_/tests/dead_code_detection.rs expression: "\nimport wibble\n\nfn unused() {\n wibble.Wibble\n}\n" --- ----- SOURCE CODE -- wibble.gleam pub type Wibble { Wibble(Int) } -- main.gleam import wibble fn unused() { wibble.Wibble } ----- WARNING warning: Unused private function ┌─ /src/warning/wrn.gleam:4:1 │ 4 │ fn unused() { │ ^^^^^^^^^^^ This private function is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__dead_code_detection__imported_type_and_constructor_with_same_name.snap ================================================ --- source: compiler-core/src/type_/tests/dead_code_detection.rs expression: "\nimport wibble.{type Wibble, Wibble}\n\npub fn main() {\n Wibble\n}\n" --- ----- SOURCE CODE -- wibble.gleam pub type Wibble { Wibble } -- main.gleam import wibble.{type Wibble, Wibble} pub fn main() { Wibble } ----- WARNING warning: Unused imported type ┌─ /src/warning/wrn.gleam:2:16 │ 2 │ import wibble.{type Wibble, Wibble} │ ^^^^^^^^^^^ This imported type is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__dead_code_detection__imported_type_and_constructor_with_same_name2.snap ================================================ --- source: compiler-core/src/type_/tests/dead_code_detection.rs expression: "\nimport wibble.{type Wibble, Wibble}\n\npub fn main() -> Wibble {\n wibble.Wibble\n}\n" --- ----- SOURCE CODE -- wibble.gleam pub type Wibble { Wibble } -- main.gleam import wibble.{type Wibble, Wibble} pub fn main() -> Wibble { wibble.Wibble } ----- WARNING warning: Unused imported item ┌─ /src/warning/wrn.gleam:2:29 │ 2 │ import wibble.{type Wibble, Wibble} │ ^^^^^^ This imported constructor is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__dead_code_detection__imported_type_only_referenced_by_unused_function.snap ================================================ --- source: compiler-core/src/type_/tests/dead_code_detection.rs expression: "\nimport wibble.{type Wibble}\n\nfn unused() -> Wibble {\n panic\n}\n" --- ----- SOURCE CODE -- wibble.gleam pub type Wibble -- main.gleam import wibble.{type Wibble} fn unused() -> Wibble { panic } ----- WARNING warning: Unused imported module ┌─ /src/warning/wrn.gleam:2:1 │ 2 │ import wibble.{type Wibble} │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ This imported module is never used Hint: You can safely remove it. warning: Unused private function ┌─ /src/warning/wrn.gleam:4:1 │ 4 │ fn unused() -> Wibble { │ ^^^^^^^^^^^^^^^^^^^^^ This private function is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__dead_code_detection__imported_value_only_referenced_by_unused_function.snap ================================================ --- source: compiler-core/src/type_/tests/dead_code_detection.rs expression: "\nimport wibble.{Wibble}\n\nfn unused() {\n Wibble\n}\n" --- ----- SOURCE CODE -- wibble.gleam pub type Wibble { Wibble(Int) } -- main.gleam import wibble.{Wibble} fn unused() { Wibble } ----- WARNING warning: Unused imported module ┌─ /src/warning/wrn.gleam:2:1 │ 2 │ import wibble.{Wibble} │ ^^^^^^^^^^^^^^^^^^^^^^ This imported module is never used Hint: You can safely remove it. warning: Unused private function ┌─ /src/warning/wrn.gleam:4:1 │ 4 │ fn unused() { │ ^^^^^^^^^^^ This private function is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__dead_code_detection__local_variable_marked_unused_when_shadowed_in_module_access.snap ================================================ --- source: compiler-core/src/type_/tests/dead_code_detection.rs expression: "\nimport wibble\n\npub fn main() {\n let wibble = 10\n // This does not reference the `wibble` variable!\n wibble.wibble\n}\n" --- ----- SOURCE CODE -- wibble.gleam pub const wibble = 1 -- main.gleam import wibble pub fn main() { let wibble = 10 // This does not reference the `wibble` variable! wibble.wibble } ----- WARNING warning: Unused variable ┌─ /src/warning/wrn.gleam:5:7 │ 5 │ let wibble = 10 │ ^^^^^^ This variable is never used Hint: You can ignore it with an underscore: `_wibble`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__dead_code_detection__private_type_alias_only_referenced_by_unused_function.snap ================================================ --- source: compiler-core/src/type_/tests/dead_code_detection.rs expression: "\ntype Wibble = Int\n\nfn unused() -> Wibble {\n 10\n}\n" --- ----- SOURCE CODE type Wibble = Int fn unused() -> Wibble { 10 } ----- WARNING warning: Unused private type ┌─ /src/warning/wrn.gleam:2:1 │ 2 │ type Wibble = Int │ ^^^^^^^^^^^^^^^^^ This private type is never used Hint: You can safely remove it. warning: Unused private function ┌─ /src/warning/wrn.gleam:4:1 │ 4 │ fn unused() -> Wibble { │ ^^^^^^^^^^^^^^^^^^^^^ This private function is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__dead_code_detection__private_type_alias_underlying_type_referenced_by_public_function.snap ================================================ --- source: compiler-core/src/type_/tests/dead_code_detection.rs expression: "\ntype Wibble = Int\n\npub fn used() -> Int {\n 10\n}\n" --- ----- SOURCE CODE type Wibble = Int pub fn used() -> Int { 10 } ----- WARNING warning: Unused private type ┌─ /src/warning/wrn.gleam:2:1 │ 2 │ type Wibble = Int │ ^^^^^^^^^^^^^^^^^ This private type is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__dead_code_detection__shadowed_imported_value_marked_unused.snap ================================================ --- source: compiler-core/src/type_/tests/dead_code_detection.rs expression: "\nimport wibble.{wibble}\n\npub const wibble = 2\n" --- ----- SOURCE CODE -- wibble.gleam pub const wibble = 1 -- main.gleam import wibble.{wibble} pub const wibble = 2 ----- WARNING warning: Unused imported module ┌─ /src/warning/wrn.gleam:2:1 │ 2 │ import wibble.{wibble} │ ^^^^^^^^^^^^^^^^^^^^^^ This imported module is never used Hint: You can safely remove it. warning: Shadowed Import ┌─ /src/warning/wrn.gleam:4:1 │ 4 │ pub const wibble = 2 │ ^^^^^^^^^^^^^^^^ `wibble` is defined here Definition of wibble shadows an imported value. The imported value could not be used in this module anyway. Hint: Either rename the definition or remove the import. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__dead_code_detection__type_and_variant_unused.snap ================================================ --- source: compiler-core/src/type_/tests/dead_code_detection.rs expression: "\ntype PrivateType {\n PrivateConstructor\n}\n" --- ----- SOURCE CODE type PrivateType { PrivateConstructor } ----- WARNING warning: Unused private type ┌─ /src/warning/wrn.gleam:2:1 │ 2 │ type PrivateType { │ ^^^^^^^^^^^^^^^^ This private type is never used Hint: You can safely remove it. warning: Unused private constructor ┌─ /src/warning/wrn.gleam:3:3 │ 3 │ PrivateConstructor │ ^^^^^^^^^^^^^^^^^^ This private constructor is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__dead_code_detection__type_variant_only_referenced_by_unused_function.snap ================================================ --- source: compiler-core/src/type_/tests/dead_code_detection.rs expression: "\ntype Wibble {\n Wibble\n Wobble\n}\n\nfn unused() {\n Wibble\n}\n\npub fn used() {\n let _ = Wobble\n Nil\n}\n" --- ----- SOURCE CODE type Wibble { Wibble Wobble } fn unused() { Wibble } pub fn used() { let _ = Wobble Nil } ----- WARNING warning: Unused private constructor ┌─ /src/warning/wrn.gleam:3:3 │ 3 │ Wibble │ ^^^^^^ This private constructor is never used Hint: You can safely remove it. warning: Unused private function ┌─ /src/warning/wrn.gleam:7:1 │ 7 │ fn unused() { │ ^^^^^^^^^^^ This private function is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__dead_code_detection__unused_mutually_recursive_functions.snap ================================================ --- source: compiler-core/src/type_/tests/dead_code_detection.rs expression: "\nfn wibble(value: Int) -> Int {\n wobble(value)\n}\n\nfn wobble(value: Int) -> Int {\n wibble(value)\n}\n" --- ----- SOURCE CODE fn wibble(value: Int) -> Int { wobble(value) } fn wobble(value: Int) -> Int { wibble(value) } ----- WARNING warning: Unused private function ┌─ /src/warning/wrn.gleam:2:1 │ 2 │ fn wibble(value: Int) -> Int { │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This private function is never used Hint: You can safely remove it. warning: Unused private function ┌─ /src/warning/wrn.gleam:6:1 │ 6 │ fn wobble(value: Int) -> Int { │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This private function is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__dead_code_detection__unused_recursive_function.snap ================================================ --- source: compiler-core/src/type_/tests/dead_code_detection.rs expression: "\nfn unused(value: Int) -> Int {\n case value {\n 0 -> 0\n _ -> unused(value - 1)\n }\n}\n" --- ----- SOURCE CODE fn unused(value: Int) -> Int { case value { 0 -> 0 _ -> unused(value - 1) } } ----- WARNING warning: Unused private function ┌─ /src/warning/wrn.gleam:2:1 │ 2 │ fn unused(value: Int) -> Int { │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This private function is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__dead_code_detection__unused_type_alias.snap ================================================ --- source: compiler-core/src/type_/tests/dead_code_detection.rs expression: "\ntype Wibble = Int\n" --- ----- SOURCE CODE type Wibble = Int ----- WARNING warning: Unused private type ┌─ /src/warning/wrn.gleam:2:1 │ 2 │ type Wibble = Int │ ^^^^^^^^^^^^^^^^^ This private type is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__dead_code_detection__used_shadowed_imported_value.snap ================================================ --- source: compiler-core/src/type_/tests/dead_code_detection.rs expression: "\nimport wibble.{wibble}\n\npub const wibble = wibble\n" --- ----- SOURCE CODE -- wibble.gleam pub const wibble = 1 -- main.gleam import wibble.{wibble} pub const wibble = wibble ----- WARNING warning: Shadowed Import ┌─ /src/warning/wrn.gleam:4:1 │ 4 │ pub const wibble = wibble │ ^^^^^^^^^^^^^^^^ `wibble` is defined here Definition of wibble shadows an imported value. The imported value could not be used in this module anyway. Hint: Either rename the definition or remove the import. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__access_int.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: let x = 1 x.whatever --- ----- SOURCE CODE let x = 1 x.whatever ----- ERROR error: Unknown record field ┌─ /src/one/two.gleam:1:13 │ 1 │ let x = 1 x.whatever │ ^^^^^^^^ This field does not exist The value being accessed has this type: Int It does not have any fields. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__accessor_multiple_variants_multiple_positions.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Person {\n Teacher(name: String, title: String, age: Int)\n Student(name: String, age: Int)\n}\npub fn get_name(person: Person) { person.name }\npub fn get_age(person: Person) { person.age }" --- ----- SOURCE CODE pub type Person { Teacher(name: String, title: String, age: Int) Student(name: String, age: Int) } pub fn get_name(person: Person) { person.name } pub fn get_age(person: Person) { person.age } ----- ERROR error: Unknown record field ┌─ /src/one/two.gleam:7:41 │ 7 │ pub fn get_age(person: Person) { person.age } │ ^^^ Did you mean `name`? The value being accessed has this type: Person It has these accessible fields: .name Note: The field you are trying to access is not defined consistently across all variants of this custom type. To fix this, ensure that all variants include the field with the same name, position, and type. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__accessor_multiple_variants_multiple_positions2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Person {\n Teacher(title: String, age: Int, name: String)\n Student(name: String, age: Int)\n}\npub fn get_name(person: Person) { person.name }\npub fn get_age(person: Person) { person.age }" --- ----- SOURCE CODE pub type Person { Teacher(title: String, age: Int, name: String) Student(name: String, age: Int) } pub fn get_name(person: Person) { person.name } pub fn get_age(person: Person) { person.age } ----- ERROR error: Unknown record field ┌─ /src/one/two.gleam:6:42 │ 6 │ pub fn get_name(person: Person) { person.name } │ ^^^^ Did you mean `age`? The value being accessed has this type: Person It has these accessible fields: .age Note: The field you are trying to access is not defined consistently across all variants of this custom type. To fix this, ensure that all variants include the field with the same name, position, and type. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__add_f_int_float.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: 1 +. 1.0 --- ----- SOURCE CODE 1 +. 1.0 ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:1 │ 1 │ 1 +. 1.0 │ ^ The +. operator expects arguments of this type: Float But this argument has this type: Int Hint: the + operator can be used with Ints ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__add_int_float.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: 1 + 1.0 --- ----- SOURCE CODE 1 + 1.0 ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:5 │ 1 │ 1 + 1.0 │ ^^^ The + operator expects arguments of this type: Int But this argument has this type: Float Hint: the +. operator can be used with Floats ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__add_on_strings.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\"Hello, \" + \"Jak\"" --- ----- SOURCE CODE "Hello, " + "Jak" ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:11 │ 1 │ "Hello, " + "Jak" │ ^ Use <> instead The + operator can only be used on Ints. To join two strings together you can use the <> operator. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__ambiguous_import_error_no_unqualified.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n import wibble/sub\n import wibble2/sub\n pub fn main() {\n sub.wobble()\n }\n " --- ----- SOURCE CODE -- wibble/sub.gleam pub fn wobble() { 1 } -- wibble2/sub.gleam pub fn wobble() { 1 } -- main.gleam import wibble/sub import wibble2/sub pub fn main() { sub.wobble() } ----- ERROR error: Duplicate import ┌─ /src/one/two.gleam:3:9 │ 2 │ import wibble/sub │ ----------------- First imported here 3 │ import wibble2/sub │ ^^^^^^^^^^^^^^^^^^ Reimported here `sub` has been imported multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__ambiguous_import_error_with_unqualified.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n import wibble/sub\n import wibble2/sub.{wobble}\n pub fn main() {\n sub.wobble()\n }\n " --- ----- SOURCE CODE -- wibble/sub.gleam pub fn wobble() { 1 } -- wibble2/sub.gleam pub fn wobble() { 1 } -- main.gleam import wibble/sub import wibble2/sub.{wobble} pub fn main() { sub.wobble() } ----- ERROR error: Duplicate import ┌─ /src/one/two.gleam:3:9 │ 2 │ import wibble/sub │ ----------------- First imported here 3 │ import wibble2/sub.{wobble} │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Reimported here `sub` has been imported multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__ambiguous_type_error.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "import wibble pub type Thing { Thing }\n pub fn main() {\n [Thing] == [wibble.Thing]\n }" --- ----- SOURCE CODE -- wibble.gleam pub type Thing { Thing } -- main.gleam import wibble pub type Thing { Thing } pub fn main() { [Thing] == [wibble.Thing] } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:24 │ 3 │ [Thing] == [wibble.Thing] │ ^^^^^^^^^^^^^^ Expected type: List(Thing) Found type: List(wibble.Thing) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__assigned_function_annotation.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let f = fn(x: Int) { x } f(1.0)" --- ----- SOURCE CODE let f = fn(x: Int) { x } f(1.0) ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:28 │ 1 │ let f = fn(x: Int) { x } f(1.0) │ ^^^ Expected type: Int Found type: Float ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_binary.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case <<1>> { <> if a > 1 -> 1 _ -> 2 }" --- ----- SOURCE CODE case <<1>> { <> if a > 1 -> 1 _ -> 2 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:29 │ 1 │ case <<1>> { <> if a > 1 -> 1 _ -> 2 } │ ^ Expected type: Int Found type: BitArray ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_bits_option_in_value.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let x = <<<<1:1>>:bytes>> x" --- ----- SOURCE CODE let x = <<<<1:1>>:bytes>> x ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:19 │ 1 │ let x = <<<<1:1>>:bytes>> x │ ^^^^^ This option is only allowed in BitArray patterns Hint: This option has no effect in BitArray values. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_float.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case <<1>> { <> if a > 1 -> 1 _ -> 2 }" --- ----- SOURCE CODE case <<1>> { <> if a > 1 -> 1 _ -> 2 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:29 │ 1 │ case <<1>> { <> if a > 1 -> 1 _ -> 2 } │ ^ Expected type: Int Found type: Float ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_float_size.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let x = <<1:8-float>> x" --- ----- SOURCE CODE let x = <<1:8-float>> x ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:13 │ 1 │ let x = <<1:8-float>> x │ ^ Invalid float size Hint: floats have an exact size of 16/32/64 bits. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_guard.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case <<1>> { <> if a == \"test\" -> 1 _ -> 2 }" --- ----- SOURCE CODE case <<1>> { <> if a == "test" -> 1 _ -> 2 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:39 │ 1 │ case <<1>> { <> if a == "test" -> 1 _ -> 2 } │ ^^^^^^^^^^^ Expected type: UtfCodepoint Found type: String ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_invalid_type.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn x() { \"test\" }\n\nfn main() {\n let a = <<1:size(x())>>\n a\n}" --- ----- SOURCE CODE fn x() { "test" } fn main() { let a = <<1:size(x())>> a } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:4:22 │ 4 │ let a = <<1:size(x())>> │ ^^^ Expected type: Int Found type: String ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_conflicting_endianness1.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let x = <<1:big-little>> x" --- ----- SOURCE CODE let x = <<1:big-little>> x ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:17 │ 1 │ let x = <<1:big-little>> x │ ^^^^^^ This is an extra endianness specifier Hint: This segment already has an endianness of big. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_conflicting_endianness2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case <<1>> { <<1:native-big>> -> 1 }" --- ----- SOURCE CODE case <<1>> { <<1:native-big>> -> 1 } ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:25 │ 1 │ case <<1>> { <<1:native-big>> -> 1 } │ ^^^ This is an extra endianness specifier Hint: This segment already has an endianness of native. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_conflicting_options_bit_array.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case <<1>> { <<1:bits-bytes>> -> 1 }" --- ----- SOURCE CODE case <<1>> { <<1:bits-bytes>> -> 1 } ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:23 │ 1 │ case <<1>> { <<1:bits-bytes>> -> 1 } │ ^^^^^ This is an extra type specifier Hint: This segment already has the type bits. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_conflicting_options_int.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let x = <<1:int-bytes>> x" --- ----- SOURCE CODE let x = <<1:int-bytes>> x ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:17 │ 1 │ let x = <<1:int-bytes>> x │ ^^^^^ This is an extra type specifier Hint: This segment already has the type int. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_conflicting_signedness1.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let x = <<1:signed-unsigned>> x" --- ----- SOURCE CODE let x = <<1:signed-unsigned>> x ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:20 │ 1 │ let x = <<1:signed-unsigned>> x │ ^^^^^^^^ This is an extra signedness specifier Hint: This segment already has a signedness of signed. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_conflicting_signedness2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case <<1>> { <<1:unsigned-signed>> -> 1 }" --- ----- SOURCE CODE case <<1>> { <<1:unsigned-signed>> -> 1 } ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:27 │ 1 │ case <<1>> { <<1:unsigned-signed>> -> 1 } │ ^^^^^^ This is an extra signedness specifier Hint: This segment already has a signedness of unsigned. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_nosize.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case <<1>> { <<_:bytes, _:bytes>> -> 1 }" --- ----- SOURCE CODE case <<1>> { <<_:bytes, _:bytes>> -> 1 } ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:18 │ 1 │ case <<1>> { <<_:bytes, _:bytes>> -> 1 } │ ^^^^^ This segment has no size Hint: Bit array segments without a size are only allowed at the end of a bin pattern. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_nosize2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case <<1>> { <<_:bits, _:bytes>> -> 1 }" --- ----- SOURCE CODE case <<1>> { <<_:bits, _:bytes>> -> 1 } ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:18 │ 1 │ case <<1>> { <<_:bits, _:bytes>> -> 1 } │ ^^^^ This segment has no size Hint: Bit array segments without a size are only allowed at the end of a bin pattern. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_nosize3.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case <<1>> { <<_:bytes, _:bits>> -> 1 }" --- ----- SOURCE CODE case <<1>> { <<_:bytes, _:bits>> -> 1 } ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:18 │ 1 │ case <<1>> { <<_:bytes, _:bits>> -> 1 } │ ^^^^^ This segment has no size Hint: Bit array segments without a size are only allowed at the end of a bin pattern. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_size.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let x = <<1:8-size(5)>> x" --- ----- SOURCE CODE let x = <<1:8-size(5)>> x ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:15 │ 1 │ let x = <<1:8-size(5)>> x │ ^^^^^^^ This is an extra size specifier Hint: This segment already has a size. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_size2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case <<1>> { <<1:size(2)-size(8)>> -> 1 }" --- ----- SOURCE CODE case <<1>> { <<1:size(2)-size(8)>> -> 1 } ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:26 │ 1 │ case <<1>> { <<1:size(2)-size(8)>> -> 1 } │ ^^^^^^^ This is an extra size specifier Hint: This segment already has a size. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_aliased_variable_string.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case <<>> { <<_ as a:utf8>> -> 1 _ -> 2 }" --- ----- SOURCE CODE case <<>> { <<_ as a:utf8>> -> 1 _ -> 2 } ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:15 │ 1 │ case <<>> { <<_ as a:utf8>> -> 1 _ -> 2 } │ ^^^^^^^^^^^ This cannot be a variable Hint: in patterns utf8, utf16, and utf32 must be an exact string. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_size_utf16.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let x = <<1:utf16-size(5)>> x" --- ----- SOURCE CODE let x = <<1:utf16-size(5)>> x ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:19 │ 1 │ let x = <<1:utf16-size(5)>> x │ ^^^^^^^ Size cannot be specified here Hint: utf16 segments have an automatic size. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_size_utf32.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case <<1>> { <<1:utf32-size(5)>> -> 1 }" --- ----- SOURCE CODE case <<1>> { <<1:utf32-size(5)>> -> 1 } ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:24 │ 1 │ case <<1>> { <<1:utf32-size(5)>> -> 1 } │ ^^^^^^^ Size cannot be specified here Hint: utf32 segments have an automatic size. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_size_utf8.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let x = <<1:utf8-size(5)>> x" --- ----- SOURCE CODE let x = <<1:utf8-size(5)>> x ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:18 │ 1 │ let x = <<1:utf8-size(5)>> x │ ^^^^^^^ Size cannot be specified here Hint: utf8 segments have an automatic size. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_unit_codepoint_utf16.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let x = <<1:utf16_codepoint-unit(5)>> x" --- ----- SOURCE CODE let x = <<1:utf16_codepoint-unit(5)>> x ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:29 │ 1 │ let x = <<1:utf16_codepoint-unit(5)>> x │ ^^^^^^^ Unit cannot be specified here Hint: utf16_codepoint segments are sized based on their value and cannot have a unit. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_unit_codepoint_utf16_2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let x = <<1:utf16_codepoint-size(5)>> x" --- ----- SOURCE CODE let x = <<1:utf16_codepoint-size(5)>> x ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:29 │ 1 │ let x = <<1:utf16_codepoint-size(5)>> x │ ^^^^^^^ Size cannot be specified here Hint: utf16_codepoint segments have an automatic size. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_unit_codepoint_utf32.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case <<1>> { <<1:utf32_codepoint-unit(2)>> -> 1 }" --- ----- SOURCE CODE case <<1>> { <<1:utf32_codepoint-unit(2)>> -> 1 } ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:34 │ 1 │ case <<1>> { <<1:utf32_codepoint-unit(2)>> -> 1 } │ ^^^^^^^ Unit cannot be specified here Hint: utf32_codepoint segments are sized based on their value and cannot have a unit. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_unit_codepoint_utf32_2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case <<1>> { <<1:utf32_codepoint-size(5)>> -> 1 }" --- ----- SOURCE CODE case <<1>> { <<1:utf32_codepoint-size(5)>> -> 1 } ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:34 │ 1 │ case <<1>> { <<1:utf32_codepoint-size(5)>> -> 1 } │ ^^^^^^^ Size cannot be specified here Hint: utf32_codepoint segments have an automatic size. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_unit_codepoint_utf8.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let x = <<1:utf8_codepoint-unit(5)>> x" --- ----- SOURCE CODE let x = <<1:utf8_codepoint-unit(5)>> x ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:28 │ 1 │ let x = <<1:utf8_codepoint-unit(5)>> x │ ^^^^^^^ Unit cannot be specified here Hint: utf8_codepoint segments are sized based on their value and cannot have a unit. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_unit_codepoint_utf8_2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let x = <<1:utf8_codepoint-size(5)>> x" --- ----- SOURCE CODE let x = <<1:utf8_codepoint-size(5)>> x ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:28 │ 1 │ let x = <<1:utf8_codepoint-size(5)>> x │ ^^^^^^^ Size cannot be specified here Hint: utf8_codepoint segments have an automatic size. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_unit_utf16.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let x = <<1:utf16-unit(5)>> x" --- ----- SOURCE CODE let x = <<1:utf16-unit(5)>> x ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:19 │ 1 │ let x = <<1:utf16-unit(5)>> x │ ^^^^^^^ Unit cannot be specified here Hint: utf16 segments are sized based on their value and cannot have a unit. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_unit_utf32.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case <<1>> { <<1:utf32-unit(2)>> -> 1 }" --- ----- SOURCE CODE case <<1>> { <<1:utf32-unit(2)>> -> 1 } ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:24 │ 1 │ case <<1>> { <<1:utf32-unit(2)>> -> 1 } │ ^^^^^^^ Unit cannot be specified here Hint: utf32 segments are sized based on their value and cannot have a unit. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_unit_utf8_2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let x = <<1:utf8-unit(5)>> x" --- ----- SOURCE CODE let x = <<1:utf8-unit(5)>> x ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:18 │ 1 │ let x = <<1:utf8-unit(5)>> x │ ^^^^^^^ Unit cannot be specified here Hint: utf8 segments are sized based on their value and cannot have a unit. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_type_does_not_allow_variable_string.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case <<>> { <> -> 1 _ -> 2 }" --- ----- SOURCE CODE case <<>> { <> -> 1 _ -> 2 } ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:15 │ 1 │ case <<>> { <> -> 1 _ -> 2 } │ ^^^^^^ This cannot be a variable Hint: in patterns utf8, utf16, and utf32 must be an exact string. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_unit_no_size.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let x = <<1:unit(5)>> x" --- ----- SOURCE CODE let x = <<1:unit(5)>> x ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:13 │ 1 │ let x = <<1:unit(5)>> x │ ^^^^^^^ This needs an explicit size Hint: If you specify unit() you must also specify size(). See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_segment_unit_unit.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let x = <<1:unit(2)-unit(5)>> x" --- ----- SOURCE CODE let x = <<1:unit(2)-unit(5)>> x ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:21 │ 1 │ let x = <<1:unit(2)-unit(5)>> x │ ^^^^^^^ This is an extra unit specifier Hint: A BitArray segment can have at most 1 unit. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_size_not_int.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let x = <<1:size(\"1\")>> x" --- ----- SOURCE CODE let x = <<1:size("1")>> x ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:18 │ 1 │ let x = <<1:size("1")>> x │ ^^^ Expected type: Int Found type: String ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_size_not_int_variable.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let a = 2.0 case <<1>> { <<1:size(a)>> -> a }" --- ----- SOURCE CODE let a = 2.0 case <<1>> { <<1:size(a)>> -> a } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:35 │ 1 │ let a = 2.0 case <<1>> { <<1:size(a)>> -> a } │ ^ Expected type: Int Found type: Float ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_using_pattern_variables.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let assert #(a, <>) = #(2, <<2:2>>)" --- ----- SOURCE CODE let assert #(a, <>) = #(2, <<2:2>>) ----- ERROR error: Unknown variable ┌─ /src/one/two.gleam:1:26 │ 1 │ let assert #(a, <>) = #(2, <<2:2>>) │ ^ The name `a` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_using_pattern_variables_from_other_bit_array.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let assert #(<>, <>) = #(<<2>>, <<2:2>>)" --- ----- SOURCE CODE let assert #(<>, <>) = #(<<2>>, <<2:2>>) ----- ERROR error: Unknown variable ┌─ /src/one/two.gleam:1:30 │ 1 │ let assert #(<>, <>) = #(<<2>>, <<2:2>>) │ ^ The name `a` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_utf8_and_size.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let x = <<\"test\":size(1)>> x" --- ----- SOURCE CODE let x = <<"test":size(1)>> x ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:18 │ 1 │ let x = <<"test":size(1)>> x │ ^^^^^^^ Size cannot be specified here Hint: utf8 segments have an automatic size. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_array_utf8_and_unit.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let x = <<\"test\":unit(5)>> x" --- ----- SOURCE CODE let x = <<"test":unit(5)>> x ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:18 │ 1 │ let x = <<"test":unit(5)>> x │ ^^^^^^^ Unit cannot be specified here Hint: utf8 segments are sized based on their value and cannot have a unit. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_arrays2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let <> = <<1>>" --- ----- SOURCE CODE let <> = <<1>> ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:7 │ 1 │ let <> = <<1>> │ ^^^^^^ This cannot be a variable Hint: in patterns utf8, utf16, and utf32 must be an exact string. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_arrays3.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let <> = <<1>>" --- ----- SOURCE CODE let <> = <<1>> ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:7 │ 1 │ let <> = <<1>> │ ^^^^^^^ This cannot be a variable Hint: in patterns utf8, utf16, and utf32 must be an exact string. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__bit_arrays4.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let <> = <<1>>" --- ----- SOURCE CODE let <> = <<1>> ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:7 │ 1 │ let <> = <<1>> │ ^^^^^^^ This cannot be a variable Hint: in patterns utf8, utf16, and utf32 must be an exact string. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case #(1, 1.0) { #(x, _) | #(_, x) -> 1 }" --- ----- SOURCE CODE case #(1, 1.0) { #(x, _) | #(_, x) -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:33 │ 1 │ case #(1, 1.0) { #(x, _) | #(_, x) -> 1 } │ ^ Expected type: Int Found type: Float ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case10.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case [3], 1.1 { x, y if x >. y -> 1 }" --- ----- SOURCE CODE case [3], 1.1 { x, y if x >. y -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:25 │ 1 │ case [3], 1.1 { x, y if x >. y -> 1 } │ ^ Expected type: Float Found type: List(Int) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case11.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case 2.22, 1, \"three\" { x, _, y if x >. y -> 1 }" --- ----- SOURCE CODE case 2.22, 1, "three" { x, _, y if x >. y -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:41 │ 1 │ case 2.22, 1, "three" { x, _, y if x >. y -> 1 } │ ^ Expected type: Float Found type: String ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case12.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case [3], 1.1 { x, y if x >=. y -> 1 }" --- ----- SOURCE CODE case [3], 1.1 { x, y if x >=. y -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:25 │ 1 │ case [3], 1.1 { x, y if x >=. y -> 1 } │ ^ Expected type: Float Found type: List(Int) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case13.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case 2.22, 1, \"three\" { x, _, y if x >=. y -> 1 }" --- ----- SOURCE CODE case 2.22, 1, "three" { x, _, y if x >=. y -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:42 │ 1 │ case 2.22, 1, "three" { x, _, y if x >=. y -> 1 } │ ^ Expected type: Float Found type: String ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case14.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case [3], 1.1 { x, y if x <. y -> 1 }" --- ----- SOURCE CODE case [3], 1.1 { x, y if x <. y -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:25 │ 1 │ case [3], 1.1 { x, y if x <. y -> 1 } │ ^ Expected type: Float Found type: List(Int) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case15.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case 2.22, 1, \"three\" { x, _, y if x <. y -> 1 }" --- ----- SOURCE CODE case 2.22, 1, "three" { x, _, y if x <. y -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:41 │ 1 │ case 2.22, 1, "three" { x, _, y if x <. y -> 1 } │ ^ Expected type: Float Found type: String ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case16.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case [3], 1.1 { x, y if x <=. y -> 1 }" --- ----- SOURCE CODE case [3], 1.1 { x, y if x <=. y -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:25 │ 1 │ case [3], 1.1 { x, y if x <=. y -> 1 } │ ^ Expected type: Float Found type: List(Int) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case17.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case 2.22, 1, \"three\" { x, _, y if x <=. y -> 1 }" --- ----- SOURCE CODE case 2.22, 1, "three" { x, _, y if x <=. y -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:42 │ 1 │ case 2.22, 1, "three" { x, _, y if x <=. y -> 1 } │ ^ Expected type: Float Found type: String ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case18.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case 1 { x if x == \"x\" -> 1 }" --- ----- SOURCE CODE case 1 { x if x == "x" -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:15 │ 1 │ case 1 { x if x == "x" -> 1 } │ ^^^^^^^^ Expected type: Int Found type: String ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case19.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case [1] { [x] | x -> 1 }" --- ----- SOURCE CODE case [1] { [x] | x -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:18 │ 1 │ case [1] { [x] | x -> 1 } │ ^ Expected type: Int Found type: List(Int) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case [3.33], 1 { x, y if x > y -> 1 }" --- ----- SOURCE CODE case [3.33], 1 { x, y if x > y -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:26 │ 1 │ case [3.33], 1 { x, y if x > y -> 1 } │ ^ Expected type: Int Found type: List(Float) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case20.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case [1] { [x] | [] as x -> 1 }" --- ----- SOURCE CODE case [1] { [x] | [] as x -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:24 │ 1 │ case [1] { [x] | [] as x -> 1 } │ ^ Expected type: Int Found type: List(Int) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case3.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case 1, 2.22, \"three\" { x, _, y if x > y -> 1 }" --- ----- SOURCE CODE case 1, 2.22, "three" { x, _, y if x > y -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:40 │ 1 │ case 1, 2.22, "three" { x, _, y if x > y -> 1 } │ ^ Expected type: Int Found type: String ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case4.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case [3.33], 1 { x, y if x >= y -> 1 }" --- ----- SOURCE CODE case [3.33], 1 { x, y if x >= y -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:26 │ 1 │ case [3.33], 1 { x, y if x >= y -> 1 } │ ^ Expected type: Int Found type: List(Float) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case5.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case 1, 2.22, \"three\" { x, _, y if x >= y -> 1 }" --- ----- SOURCE CODE case 1, 2.22, "three" { x, _, y if x >= y -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:41 │ 1 │ case 1, 2.22, "three" { x, _, y if x >= y -> 1 } │ ^ Expected type: Int Found type: String ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case6.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case [3.33], 1 { x, y if x < y -> 1 }" --- ----- SOURCE CODE case [3.33], 1 { x, y if x < y -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:26 │ 1 │ case [3.33], 1 { x, y if x < y -> 1 } │ ^ Expected type: Int Found type: List(Float) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case7.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case 1, 2.22, \"three\" { x, _, y if x < y -> 1 }" --- ----- SOURCE CODE case 1, 2.22, "three" { x, _, y if x < y -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:40 │ 1 │ case 1, 2.22, "three" { x, _, y if x < y -> 1 } │ ^ Expected type: Int Found type: String ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case8.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case [3.33], 1 { x, y if x <= y -> 1 }" --- ----- SOURCE CODE case [3.33], 1 { x, y if x <= y -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:26 │ 1 │ case [3.33], 1 { x, y if x <= y -> 1 } │ ^ Expected type: Int Found type: List(Float) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case9.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case 1, 2.22, \"three\" { x, _, y if x <= y -> 1 }" --- ----- SOURCE CODE case 1, 2.22, "three" { x, _, y if x <= y -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:41 │ 1 │ case 1, 2.22, "three" { x, _, y if x <= y -> 1 } │ ^ Expected type: Int Found type: String ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case_clause_mismatch.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case 1 { a -> 1 b -> 2.0 }" --- ----- SOURCE CODE case 1 { a -> 1 b -> 2.0 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:17 │ 1 │ case 1 { a -> 1 b -> 2.0 } │ ^^^^^^^^ This case clause was found to return a different type than the previous one, but all case clauses must return the same type. Expected type: Int Found type: Float ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case_clause_pipe_diagnostic.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn change(x: String) -> String {\n \"\"\n}\n\npub fn parse(input: BitArray) -> String {\n case input {\n <<>> -> 1\n <<\"(\":utf8, b:bytes>> ->\n parse(input)\n |> change\n _ -> 3\n }\n}" --- ----- SOURCE CODE pub fn change(x: String) -> String { "" } pub fn parse(input: BitArray) -> String { case input { <<>> -> 1 <<"(":utf8, b:bytes>> -> parse(input) |> change _ -> 3 } } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:7:3 │ 7 │ ╭ case input { 8 │ │ <<>> -> 1 9 │ │ <<"(":utf8, b:bytes>> -> 10 │ │ parse(input) 11 │ │ |> change 12 │ │ _ -> 3 13 │ │ } │ ╰───^ The type of this returned value doesn't match the return type annotation of this function. Expected type: String Found type: Int error: Type mismatch ┌─ /src/one/two.gleam:9:5 │ 9 │ ╭ <<"(":utf8, b:bytes>> -> 10 │ │ parse(input) 11 │ │ |> change │ ╰───────────────^ This case clause was found to return a different type than the previous one, but all case clauses must return the same type. Expected type: Int Found type: String ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case_could_not_unify.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case 1, 2.0 { a, b -> a 1, 2 -> 0 }" --- ----- SOURCE CODE case 1, 2.0 { a, b -> a 1, 2 -> 0 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:28 │ 1 │ case 1, 2.0 { a, b -> a 1, 2 -> 0 } │ ^ Expected type: Float Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case_int_tuple_guard.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case 1 { x if x == #() -> 1 }" --- ----- SOURCE CODE case 1 { x if x == #() -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:15 │ 1 │ case 1 { x if x == #() -> 1 } │ ^^^^^^^^ Expected type: Int Found type: #() ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case_list_guard.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case [1] { x if x == [1, 2.0] -> 1 _ -> 2 }" --- ----- SOURCE CODE case [1] { x if x == [1, 2.0] -> 1 _ -> 2 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:26 │ 1 │ case [1] { x if x == [1, 2.0] -> 1 _ -> 2 } │ ^^^ Expected type: Int Found type: Float ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case_operator_unify_situation.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case 1, 2.0 { a, b -> a + b }" --- ----- SOURCE CODE case 1, 2.0 { a, b -> a + b } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:27 │ 1 │ case 1, 2.0 { a, b -> a + b } │ ^ The + operator expects arguments of this type: Int But this argument has this type: Float Hint: the +. operator can be used with Floats ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case_subject_pattern_unify.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case 1.0 { 1 -> 1 }" --- ----- SOURCE CODE case 1.0 { 1 -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:12 │ 1 │ case 1.0 { 1 -> 1 } │ ^ Expected type: Float Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case_subject_pattern_unify_2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case 1 { 1.0 -> 1 }" --- ----- SOURCE CODE case 1 { 1.0 -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:10 │ 1 │ case 1 { 1.0 -> 1 } │ ^^^ Expected type: Int Found type: Float ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case_tuple_guard.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case #(1, 2, 3) { x if x == #(1, 1.0) -> 1 }" --- ----- SOURCE CODE case #(1, 2, 3) { x if x == #(1, 1.0) -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:24 │ 1 │ case #(1, 2, 3) { x if x == #(1, 1.0) -> 1 } │ ^^^^^^^^^^^^^^ Expected type: #(Int, Int, Int) Found type: #(Int, Float) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__case_tuple_guard_2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case #(1, 2) { x if x == #(1, 1.0) -> 1 }" --- ----- SOURCE CODE case #(1, 2) { x if x == #(1, 1.0) -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:21 │ 1 │ case #(1, 2) { x if x == #(1, 1.0) -> 1 } │ ^^^^^^^^^^^^^^ Expected type: #(Int, Int) Found type: #(Int, Float) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__const_annotation_wrong.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "pub const group_id: Int = \"42\"" --- ----- SOURCE CODE pub const group_id: Int = "42" ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:27 │ 1 │ pub const group_id: Int = "42" │ ^^^^ Expected type: Int Found type: String ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__const_annotation_wrong_2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "pub const numbers: List(Int) = [1, 2, 2.3]" --- ----- SOURCE CODE pub const numbers: List(Int) = [1, 2, 2.3] ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:39 │ 1 │ pub const numbers: List(Int) = [1, 2, 2.3] │ ^^^ Expected type: Int Found type: Float ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__const_annotation_wrong_3.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "pub const numbers: List(Int) = [1.1, 2.2, 3.3]" --- ----- SOURCE CODE pub const numbers: List(Int) = [1.1, 2.2, 3.3] ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:32 │ 1 │ pub const numbers: List(Int) = [1.1, 2.2, 3.3] │ ^^^^^^^^^^^^^^^ Expected type: List(Int) Found type: List(Float) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__const_annotation_wrong_4.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "pub const pair: #(Int, Float) = #(4.1, 1)" --- ----- SOURCE CODE pub const pair: #(Int, Float) = #(4.1, 1) ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:33 │ 1 │ pub const pair: #(Int, Float) = #(4.1, 1) │ ^^^^^^^^^ Expected type: #(Int, Float) Found type: #(Float, Int) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__const_heterogenus_list.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "const pair = [1, 1.0]" --- ----- SOURCE CODE const pair = [1, 1.0] ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:18 │ 1 │ const pair = [1, 1.0] │ ^^^ Expected type: Int Found type: Float ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__const_multiple_errors_are_local_with_annotation.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "const num: String = 7\nconst tpl: String = #(Ok(1), MyInvalidType, 3)\nconst assignment1: String = num\nconst assignment2: String = tpl" --- ----- SOURCE CODE const num: String = 7 const tpl: String = #(Ok(1), MyInvalidType, 3) const assignment1: String = num const assignment2: String = tpl ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:21 │ 1 │ const num: String = 7 │ ^ Expected type: String Found type: Int error: Type mismatch ┌─ /src/one/two.gleam:2:21 │ 2 │ const tpl: String = #(Ok(1), MyInvalidType, 3) │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ Expected type: String Found type: #(Result(Int, a), b, Int) error: Unknown variable ┌─ /src/one/two.gleam:2:30 │ 2 │ const tpl: String = #(Ok(1), MyInvalidType, 3) │ ^^^^^^^^^^^^^ The custom type variant constructor `MyInvalidType` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__const_multiple_errors_are_local_with_inferred_value.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "const str: MyInvalidType = \"str\"\nconst assignment: String = str" --- ----- SOURCE CODE const str: MyInvalidType = "str" const assignment: String = str ----- ERROR error: Unknown type ┌─ /src/one/two.gleam:1:12 │ 1 │ const str: MyInvalidType = "str" │ ^^^^^^^^^^^^^ The type `MyInvalidType` is not defined or imported in this module. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__const_multiple_errors_are_local_with_unbound_value.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "const lst = [1, 2.0]\nconst unbound: MyInvalidType = MyInvalidType\nconst assignment1: String = lst\nconst assignment2: String = unbound" --- ----- SOURCE CODE const lst = [1, 2.0] const unbound: MyInvalidType = MyInvalidType const assignment1: String = lst const assignment2: String = unbound ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:17 │ 1 │ const lst = [1, 2.0] │ ^^^ Expected type: Int Found type: Float error: Unknown type ┌─ /src/one/two.gleam:2:16 │ 2 │ const unbound: MyInvalidType = MyInvalidType │ ^^^^^^^^^^^^^ The type `MyInvalidType` is not defined or imported in this module. error: Unknown variable ┌─ /src/one/two.gleam:2:32 │ 2 │ const unbound: MyInvalidType = MyInvalidType │ ^^^^^^^^^^^^^ The custom type variant constructor `MyInvalidType` is not in scope here. error: Type mismatch ┌─ /src/one/two.gleam:3:29 │ 3 │ const assignment1: String = lst │ ^^^ Expected type: String Found type: List(Int) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__const_multiple_errors_invalid_annotation.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "const invalid_annotation: MyInvalidType = \"str\"\nconst invalid_value: String = MyInvalidValue" --- ----- SOURCE CODE const invalid_annotation: MyInvalidType = "str" const invalid_value: String = MyInvalidValue ----- ERROR error: Unknown type ┌─ /src/one/two.gleam:1:27 │ 1 │ const invalid_annotation: MyInvalidType = "str" │ ^^^^^^^^^^^^^ The type `MyInvalidType` is not defined or imported in this module. error: Unknown variable ┌─ /src/one/two.gleam:2:31 │ 2 │ const invalid_value: String = MyInvalidValue │ ^^^^^^^^^^^^^^ The custom type variant constructor `MyInvalidValue` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__const_multiple_errors_invalid_annotation_and_value.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "const invalid_everything: MyInvalidType = MyInvalidValue\nconst mismatched_types: String = 7" --- ----- SOURCE CODE const invalid_everything: MyInvalidType = MyInvalidValue const mismatched_types: String = 7 ----- ERROR error: Unknown type ┌─ /src/one/two.gleam:1:27 │ 1 │ const invalid_everything: MyInvalidType = MyInvalidValue │ ^^^^^^^^^^^^^ The type `MyInvalidType` is not defined or imported in this module. error: Unknown variable ┌─ /src/one/two.gleam:1:43 │ 1 │ const invalid_everything: MyInvalidType = MyInvalidValue │ ^^^^^^^^^^^^^^ The custom type variant constructor `MyInvalidValue` is not in scope here. error: Type mismatch ┌─ /src/one/two.gleam:2:34 │ 2 │ const mismatched_types: String = 7 │ ^ Expected type: String Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__const_multiple_errors_invalid_unannotated_value.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "const invalid_unannotated_value = [1, 2.0]\nconst invalid_everything: MyInvalidType = MyInvalidValue" --- ----- SOURCE CODE const invalid_unannotated_value = [1, 2.0] const invalid_everything: MyInvalidType = MyInvalidValue ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:39 │ 1 │ const invalid_unannotated_value = [1, 2.0] │ ^^^ Expected type: Int Found type: Float error: Unknown type ┌─ /src/one/two.gleam:2:27 │ 2 │ const invalid_everything: MyInvalidType = MyInvalidValue │ ^^^^^^^^^^^^^ The type `MyInvalidType` is not defined or imported in this module. error: Unknown variable ┌─ /src/one/two.gleam:2:43 │ 2 │ const invalid_everything: MyInvalidType = MyInvalidValue │ ^^^^^^^^^^^^^^ The custom type variant constructor `MyInvalidValue` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__const_multiple_errors_invalid_value.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "const invalid_value: String = MyInvalidValue\nconst invalid_unannotated_value = [1, 2.0]" --- ----- SOURCE CODE const invalid_value: String = MyInvalidValue const invalid_unannotated_value = [1, 2.0] ----- ERROR error: Unknown variable ┌─ /src/one/two.gleam:1:31 │ 1 │ const invalid_value: String = MyInvalidValue │ ^^^^^^^^^^^^^^ The custom type variant constructor `MyInvalidValue` is not in scope here. error: Type mismatch ┌─ /src/one/two.gleam:2:39 │ 2 │ const invalid_unannotated_value = [1, 2.0] │ ^^^ Expected type: Int Found type: Float ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__const_multiple_errors_mismatched_types.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "const mismatched_types: String = 7\nconst invalid_annotation: MyInvalidType = \"str\"" --- ----- SOURCE CODE const mismatched_types: String = 7 const invalid_annotation: MyInvalidType = "str" ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:34 │ 1 │ const mismatched_types: String = 7 │ ^ Expected type: String Found type: Int error: Unknown type ┌─ /src/one/two.gleam:2:27 │ 2 │ const invalid_annotation: MyInvalidType = "str" │ ^^^^^^^^^^^^^ The type `MyInvalidType` is not defined or imported in this module. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__const_string_concat_invalid_type.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nconst some_int = 5\nconst invalid_concat = some_int <> \"with_string\"\n" --- ----- SOURCE CODE const some_int = 5 const invalid_concat = some_int <> "with_string" ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:24 │ 3 │ const invalid_concat = some_int <> "with_string" │ ^^^^^^^^ The <> operator expects arguments of this type: String But this argument has this type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__const_usage_wrong.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "const pair = #(1, 2.0)\nfn main() { 1 == pair }" --- ----- SOURCE CODE const pair = #(1, 2.0) fn main() { 1 == pair } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:2:18 │ 2 │ fn main() { 1 == pair } │ ^^^^ Expected type: Int Found type: #(Int, Float) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__constructor_that_does_not_exist_does_not_produce_error_for_labelled_args.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() {\n // We only want to error on `Wibble` since it doesn't exist, we don't want\n // an error on the label at this point!\n Wibble(label: 1)\n}\n" --- ----- SOURCE CODE pub fn main() { // We only want to error on `Wibble` since it doesn't exist, we don't want // an error on the label at this point! Wibble(label: 1) } ----- ERROR error: Unknown variable ┌─ /src/one/two.gleam:5:3 │ 5 │ Wibble(label: 1) │ ^^^^^^ The custom type variant constructor `Wibble` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__correct_pipe_arity_error_location.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn x(x, y) { x }\nfn main() { 1 |> x() }" --- ----- SOURCE CODE fn x(x, y) { x } fn main() { 1 |> x() } ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:2:18 │ 2 │ fn main() { 1 |> x() } │ ^^^ Expected 2 arguments, got 1 ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__custom_type_module_constants.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type X { X }\nconst x = unknown.X" --- ----- SOURCE CODE type X { X } const x = unknown.X ----- ERROR error: Unknown module ┌─ /src/one/two.gleam:2:11 │ 2 │ const x = unknown.X │ ^^^^^^^ No module has been found with the name `unknown`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__do_not_suggest_ignored_variable_outside_of_current_scope.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn go() {\n let _ = {\n let _y = 1 // <- this shouldn't be highlighted!\n }\n y\n}\n" --- ----- SOURCE CODE pub fn go() { let _ = { let _y = 1 // <- this shouldn't be highlighted! } y } ----- ERROR error: Unknown variable ┌─ /src/one/two.gleam:6:3 │ 6 │ y │ ^ The name `y` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__double_assignment_in_bit_array.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: let assert <> = <<>> --- ----- SOURCE CODE let assert <> = <<>> ----- ERROR error: Double variable assignment ┌─ /src/one/two.gleam:1:19 │ 1 │ let assert <> = <<>> │ ^ This pattern assigns to two different variables at once, which is not possible in bit arrays. Hint: Remove the `as` assignment. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_alias_names.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: type X = Int type X = Int --- ----- SOURCE CODE type X = Int type X = Int ----- ERROR error: Duplicate type definition ┌─ /src/one/two.gleam:1:14 │ 1 │ type X = Int type X = Int │ ------------ ^^^^^^^^^^^^ Redefined here │ │ │ First defined here The type `X` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_anon_function_arguments.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nfn(x, x) {\n Nil\n}\n" --- ----- SOURCE CODE fn(x, x) { Nil } ----- ERROR error: Argument name already used ┌─ /src/one/two.gleam:2:7 │ 2 │ fn(x, x) { │ ^ Two `x` arguments have been defined for this function. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_const_and_function_names_const_fn.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "const duplicate = 1\nfn duplicate() { 2 }" --- ----- SOURCE CODE const duplicate = 1 fn duplicate() { 2 } ----- ERROR error: Duplicate definition ┌─ /src/one/two.gleam:2:1 │ 1 │ const duplicate = 1 │ --------------- First defined here 2 │ fn duplicate() { 2 } │ ^^^^^^^^^^^^^^ Redefined here `duplicate` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_const_const.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "const wibble = 1\nconst wibble = 2" --- ----- SOURCE CODE const wibble = 1 const wibble = 2 ----- ERROR error: Duplicate definition ┌─ /src/one/two.gleam:2:1 │ 1 │ const wibble = 1 │ ------------ First defined here 2 │ const wibble = 2 │ ^^^^^^^^^^^^ Redefined here `wibble` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_const_extfn.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "const wibble = 1\n\n@external(erlang, \"module2\", \"function2\")\nfn wibble() -> Float\n" --- ----- SOURCE CODE const wibble = 1 @external(erlang, "module2", "function2") fn wibble() -> Float ----- ERROR error: Duplicate definition ┌─ /src/one/two.gleam:4:1 │ 1 │ const wibble = 1 │ ------------ First defined here · 4 │ fn wibble() -> Float │ ^^^^^^^^^^^ Redefined here `wibble` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_const_fn.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "const wibble = 1\nfn wibble() { 2 }" --- ----- SOURCE CODE const wibble = 1 fn wibble() { 2 } ----- ERROR error: Duplicate definition ┌─ /src/one/two.gleam:2:1 │ 1 │ const wibble = 1 │ ------------ First defined here 2 │ fn wibble() { 2 } │ ^^^^^^^^^^^ Redefined here `wibble` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_const_names.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "const duplicate = 1\npub const duplicate = 1" --- ----- SOURCE CODE const duplicate = 1 pub const duplicate = 1 ----- ERROR error: Duplicate definition ┌─ /src/one/two.gleam:2:1 │ 1 │ const duplicate = 1 │ --------------- First defined here 2 │ pub const duplicate = 1 │ ^^^^^^^^^^^^^^^^^^^ Redefined here `duplicate` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_constructors.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type Box { Box(x: Int) }\ntype Boxy { Box(Int) }" --- ----- SOURCE CODE type Box { Box(x: Int) } type Boxy { Box(Int) } ----- ERROR error: Duplicate definition ┌─ /src/one/two.gleam:2:13 │ 1 │ type Box { Box(x: Int) } │ ----------- First defined here 2 │ type Boxy { Box(Int) } │ ^^^^^^^^ Redefined here `Box` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_constructors2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type Boxy { Box(Int) }\ntype Box { Box(x: Int) }" --- ----- SOURCE CODE type Boxy { Box(Int) } type Box { Box(x: Int) } ----- ERROR error: Duplicate definition ┌─ /src/one/two.gleam:2:12 │ 1 │ type Boxy { Box(Int) } │ -------- First defined here 2 │ type Box { Box(x: Int) } │ ^^^^^^^^^^^ Redefined here `Box` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_constructors3.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type Boxy { Box(Int) Box(Float) }" --- ----- SOURCE CODE type Boxy { Box(Int) Box(Float) } ----- ERROR error: Duplicate definition ┌─ /src/one/two.gleam:1:22 │ 1 │ type Boxy { Box(Int) Box(Float) } │ -------- ^^^^^^^^^^ Redefined here │ │ │ First defined here `Box` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_custom_type_names.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type DupType { A } type DupType { B }" --- ----- SOURCE CODE type DupType { A } type DupType { B } ----- ERROR error: Duplicate type definition ┌─ /src/one/two.gleam:1:20 │ 1 │ type DupType { A } type DupType { B } │ ------------ ^^^^^^^^^^^^ Redefined here │ │ │ First defined here The type `DupType` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_extfn_const.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n@external(erlang, \"module1\", \"function1\")\nfn wibble() -> Float\n\nconst wibble = 2" --- ----- SOURCE CODE @external(erlang, "module1", "function1") fn wibble() -> Float const wibble = 2 ----- ERROR error: Duplicate definition ┌─ /src/one/two.gleam:5:1 │ 3 │ fn wibble() -> Float │ ----------- First defined here 4 │ 5 │ const wibble = 2 │ ^^^^^^^^^^^^ Redefined here `wibble` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_extfn_extfn.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n@external(erlang, \"module1\", \"function1\")\nfn wibble() -> Float\n@external(erlang, \"module2\", \"function2\")\nfn wibble() -> Float\n" --- ----- SOURCE CODE @external(erlang, "module1", "function1") fn wibble() -> Float @external(erlang, "module2", "function2") fn wibble() -> Float ----- ERROR error: Duplicate definition ┌─ /src/one/two.gleam:5:1 │ 3 │ fn wibble() -> Float │ ----------- First defined here 4 │ @external(erlang, "module2", "function2") 5 │ fn wibble() -> Float │ ^^^^^^^^^^^ Redefined here `wibble` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_extfn_fn.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n@external(erlang, \"module1\", \"function1\")\nfn wibble() -> Float\n\nfn wibble() { 2 }" --- ----- SOURCE CODE @external(erlang, "module1", "function1") fn wibble() -> Float fn wibble() { 2 } ----- ERROR error: Duplicate definition ┌─ /src/one/two.gleam:5:1 │ 3 │ fn wibble() -> Float │ ----------- First defined here 4 │ 5 │ fn wibble() { 2 } │ ^^^^^^^^^^^ Redefined here `wibble` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_fields_in_record_update_reports_error.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Wibble { Wibble(thing: Int, other: Int) }\n\npub fn main() {\n let wibble = Wibble(1, 2)\n let wobble = Wibble(..wibble, thing: 1, thing: 2)\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(thing: Int, other: Int) } pub fn main() { let wibble = Wibble(1, 2) let wobble = Wibble(..wibble, thing: 1, thing: 2) } ----- ERROR error: Duplicate argument ┌─ /src/one/two.gleam:6:43 │ 6 │ let wobble = Wibble(..wibble, thing: 1, thing: 2) │ ^^^^^^^^ The labelled argument `thing` has already been supplied. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_fn_const.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn wibble() { 1 }\nconst wibble = 2" --- ----- SOURCE CODE fn wibble() { 1 } const wibble = 2 ----- ERROR error: Duplicate definition ┌─ /src/one/two.gleam:2:1 │ 1 │ fn wibble() { 1 } │ ----------- First defined here 2 │ const wibble = 2 │ ^^^^^^^^^^^^ Redefined here `wibble` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_fn_extfn.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn wibble() { 1 }\n\n@external(erlang, \"module2\", \"function2\")\nfn wibble() -> Float\n" --- ----- SOURCE CODE fn wibble() { 1 } @external(erlang, "module2", "function2") fn wibble() -> Float ----- ERROR error: Duplicate definition ┌─ /src/one/two.gleam:4:1 │ 1 │ fn wibble() { 1 } │ ----------- First defined here · 4 │ fn wibble() -> Float │ ^^^^^^^^^^^ Redefined here `wibble` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_fn_fn.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn wibble() { 1 }\nfn wibble() { 2 }" --- ----- SOURCE CODE fn wibble() { 1 } fn wibble() { 2 } ----- ERROR error: Duplicate definition ┌─ /src/one/two.gleam:2:1 │ 1 │ fn wibble() { 1 } │ ----------- First defined here 2 │ fn wibble() { 2 } │ ^^^^^^^^^^^ Redefined here `wibble` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_function_names.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn dupe() { 1 }\nfn dupe() { 2 }" --- ----- SOURCE CODE fn dupe() { 1 } fn dupe() { 2 } ----- ERROR error: Duplicate definition ┌─ /src/one/two.gleam:2:1 │ 1 │ fn dupe() { 1 } │ --------- First defined here 2 │ fn dupe() { 2 } │ ^^^^^^^^^ Redefined here `dupe` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_function_names_2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn dupe() { 1 }\nfn dupe() { 2.0 }" --- ----- SOURCE CODE fn dupe() { 1 } fn dupe() { 2.0 } ----- ERROR error: Duplicate definition ┌─ /src/one/two.gleam:2:1 │ 1 │ fn dupe() { 1 } │ --------- First defined here 2 │ fn dupe() { 2.0 } │ ^^^^^^^^^ Redefined here `dupe` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_function_names_3.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn dupe() { 1 }\nfn dupe(x) { x }" --- ----- SOURCE CODE fn dupe() { 1 } fn dupe(x) { x } ----- ERROR error: Duplicate definition ┌─ /src/one/two.gleam:2:1 │ 1 │ fn dupe() { 1 } │ --------- First defined here 2 │ fn dupe(x) { x } │ ^^^^^^^^^^ Redefined here `dupe` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_function_names_4.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn dupe() { 1 }\n@external(erlang, \"a\", \"b\")\nfn dupe(x) -> x\n" --- ----- SOURCE CODE fn dupe() { 1 } @external(erlang, "a", "b") fn dupe(x) -> x ----- ERROR error: Duplicate definition ┌─ /src/one/two.gleam:3:1 │ 1 │ fn dupe() { 1 } │ --------- First defined here 2 │ @external(erlang, "a", "b") 3 │ fn dupe(x) -> x │ ^^^^^^^^^^ Redefined here `dupe` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_function_names_5.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n@external(erlang, \"a\", \"b\")\nfn dupe(x) -> x\nfn dupe() { 1 }\n" --- ----- SOURCE CODE @external(erlang, "a", "b") fn dupe(x) -> x fn dupe() { 1 } ----- ERROR error: Duplicate definition ┌─ /src/one/two.gleam:4:1 │ 3 │ fn dupe(x) -> x │ ---------- First defined here 4 │ fn dupe() { 1 } │ ^^^^^^^^^ Redefined here `dupe` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_label_shorthands_in_record_pattern.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type X { X(a: Int, b: Int, c: Int) }\nfn x() {\n case X(1,2,3) { X(a:, b:, c: a) -> 1 }\n}" --- ----- SOURCE CODE type X { X(a: Int, b: Int, c: Int) } fn x() { case X(1,2,3) { X(a:, b:, c: a) -> 1 } } ----- ERROR error: Duplicate variable in pattern ┌─ /src/one/two.gleam:3:32 │ 3 │ case X(1,2,3) { X(a:, b:, c: a) -> 1 } │ ^ This has already been used Variables can only be used once per pattern. This variable `a` appears multiple times. If you used the same variable twice deliberately in order to check for equality please use a guard clause instead. e.g. (x, y) if x == y -> ... ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_module_function_arguments.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main(x, x) {\n Nil\n}\n" --- ----- SOURCE CODE pub fn main(x, x) { Nil } ----- ERROR error: Argument name already used ┌─ /src/one/two.gleam:2:16 │ 2 │ pub fn main(x, x) { │ ^ Two `x` arguments have been defined for this function. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_var_in_record_pattern.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type X { X(a: Int, b: Int, c: Int) }\nfn x() {\n case X(1,2,3) { X(x, y, x) -> 1 }\n}" --- ----- SOURCE CODE type X { X(a: Int, b: Int, c: Int) } fn x() { case X(1,2,3) { X(x, y, x) -> 1 } } ----- ERROR error: Duplicate variable in pattern ┌─ /src/one/two.gleam:3:27 │ 3 │ case X(1,2,3) { X(x, y, x) -> 1 } │ ^ This has already been used Variables can only be used once per pattern. This variable `x` appears multiple times. If you used the same variable twice deliberately in order to check for equality please use a guard clause instead. e.g. (x, y) if x == y -> ... ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_vars.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case #(1, 2) { #(x, x) -> 1 }" --- ----- SOURCE CODE case #(1, 2) { #(x, x) -> 1 } ----- ERROR error: Duplicate variable in pattern ┌─ /src/one/two.gleam:1:21 │ 1 │ case #(1, 2) { #(x, x) -> 1 } │ ^ This has already been used Variables can only be used once per pattern. This variable `x` appears multiple times. If you used the same variable twice deliberately in order to check for equality please use a guard clause instead. e.g. (x, y) if x == y -> ... ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_vars_2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case [3.33], 1 { x, x -> 1 }" --- ----- SOURCE CODE case [3.33], 1 { x, x -> 1 } ----- ERROR error: Duplicate variable in pattern ┌─ /src/one/two.gleam:1:21 │ 1 │ case [3.33], 1 { x, x -> 1 } │ ^ This has already been used Variables can only be used once per pattern. This variable `x` appears multiple times. If you used the same variable twice deliberately in order to check for equality please use a guard clause instead. e.g. (x, y) if x == y -> ... ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__duplicate_vars_3.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case [1, 2, 3] { [x, x, y] -> 1 }" --- ----- SOURCE CODE case [1, 2, 3] { [x, x, y] -> 1 } ----- ERROR error: Duplicate variable in pattern ┌─ /src/one/two.gleam:1:22 │ 1 │ case [1, 2, 3] { [x, x, y] -> 1 } │ ^ This has already been used Variables can only be used once per pattern. This variable `x` appears multiple times. If you used the same variable twice deliberately in order to check for equality please use a guard clause instead. e.g. (x, y) if x == y -> ... ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__echo_followed_by_invalid_message.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "echo 11 as { True || False }" --- ----- SOURCE CODE echo 11 as { True || False } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:12 │ 1 │ echo 11 as { True || False } │ ^^^^^^^^^^^^^^^^^ Expected type: String Found type: Bool ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__echo_followed_by_no_expression.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: echo --- ----- SOURCE CODE echo ----- ERROR error: Invalid echo use ┌─ /src/one/two.gleam:1:1 │ 1 │ echo │ ^^^^ I was expecting a value after this The `echo` keyword should be followed by a value to print. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__echo_followed_by_no_expression_10.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() {\n echo\n |> todo\n}\n" --- ----- SOURCE CODE pub fn main() { echo |> todo } ----- ERROR error: Invalid echo use ┌─ /src/one/two.gleam:3:3 │ 3 │ echo │ ^^^^ I was expecting a value after this The `echo` keyword should be followed by a value to print. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__echo_followed_by_no_expression_2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n pub fn wibble(a) { a }\n\n pub fn main() {\n wibble(echo)\n }\n" --- ----- SOURCE CODE pub fn wibble(a) { a } pub fn main() { wibble(echo) } ----- ERROR error: Invalid echo use ┌─ /src/one/two.gleam:5:12 │ 5 │ wibble(echo) │ ^^^^ I was expecting a value after this The `echo` keyword should be followed by a value to print. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__echo_followed_by_no_expression_3.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n pub fn main() {\n echo + 1\n }\n" --- ----- SOURCE CODE pub fn main() { echo + 1 } ----- ERROR error: Invalid echo use ┌─ /src/one/two.gleam:3:5 │ 3 │ echo + 1 │ ^^^^ I was expecting a value after this The `echo` keyword should be followed by a value to print. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__echo_followed_by_no_expression_4.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n pub fn main() {\n \"wibble\" <> echo\n }\n" --- ----- SOURCE CODE pub fn main() { "wibble" <> echo } ----- ERROR error: Invalid echo use ┌─ /src/one/two.gleam:3:17 │ 3 │ "wibble" <> echo │ ^^^^ I was expecting a value after this The `echo` keyword should be followed by a value to print. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__echo_followed_by_no_expression_5.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() {\n panic as echo\n}\n" --- ----- SOURCE CODE pub fn main() { panic as echo } ----- ERROR error: Invalid echo use ┌─ /src/one/two.gleam:3:12 │ 3 │ panic as echo │ ^^^^ I was expecting a value after this The `echo` keyword should be followed by a value to print. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__echo_followed_by_no_expression_6.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() {\n [echo, 1, 2]\n}\n" --- ----- SOURCE CODE pub fn main() { [echo, 1, 2] } ----- ERROR error: Invalid echo use ┌─ /src/one/two.gleam:3:4 │ 3 │ [echo, 1, 2] │ ^^^^ I was expecting a value after this The `echo` keyword should be followed by a value to print. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__echo_followed_by_no_expression_7.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() {\n #(1, echo)\n}\n" --- ----- SOURCE CODE pub fn main() { #(1, echo) } ----- ERROR error: Invalid echo use ┌─ /src/one/two.gleam:3:8 │ 3 │ #(1, echo) │ ^^^^ I was expecting a value after this The `echo` keyword should be followed by a value to print. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__echo_followed_by_no_expression_8.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() {\n todo\n |> fn(_) { echo }\n |> todo\n}\n" --- ----- SOURCE CODE pub fn main() { todo |> fn(_) { echo } |> todo } ----- ERROR error: Invalid echo use ┌─ /src/one/two.gleam:4:14 │ 4 │ |> fn(_) { echo } │ ^^^^ I was expecting a value after this The `echo` keyword should be followed by a value to print. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__echo_followed_by_no_expression_9.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() {\n todo\n |> { echo }\n |> todo\n}\n" --- ----- SOURCE CODE pub fn main() { todo |> { echo } |> todo } ----- ERROR error: Invalid echo use ┌─ /src/one/two.gleam:4:8 │ 4 │ |> { echo } │ ^^^^ I was expecting a value after this The `echo` keyword should be followed by a value to print. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__echo_followed_by_no_expression_and_invalid_message.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: echo as 1 --- ----- SOURCE CODE echo as 1 ----- ERROR error: Invalid echo use ┌─ /src/one/two.gleam:1:1 │ 1 │ echo as 1 │ ^^^^ I was expecting a value after this The `echo` keyword should be followed by a value to print. error: Type mismatch ┌─ /src/one/two.gleam:1:9 │ 1 │ echo as 1 │ ^ Expected type: String Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__echo_followed_by_no_expression_and_message.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "echo as \"wibble\"" --- ----- SOURCE CODE echo as "wibble" ----- ERROR error: Invalid echo use ┌─ /src/one/two.gleam:1:1 │ 1 │ echo as "wibble" │ ^^^^ I was expecting a value after this The `echo` keyword should be followed by a value to print. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__error_for_missing_type_parameters.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\ntype Wibble(a)\n\ntype Wobble {\n Wobble(Wibble)\n}\n" --- ----- SOURCE CODE type Wibble(a) type Wobble { Wobble(Wibble) } ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:5:10 │ 5 │ Wobble(Wibble) │ ^^^^^^ Expected 1 type argument, got 0 `Wibble` requires 1 type argument but none where provided. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__expression_constructor_update.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Person {\n Person(name: String, age: Int)\n}\npub fn update_person(person: Person) {\n let constructor = Person\n constructor(..person)\n}" --- ----- SOURCE CODE pub type Person { Person(name: String, age: Int) } pub fn update_person(person: Person) { let constructor = Person constructor(..person) } ----- ERROR error: Invalid record constructor ┌─ /src/one/two.gleam:7:3 │ 7 │ constructor(..person) │ ^^^^^^^^^^^ This is not a record constructor Only record constructors can be used with the update syntax. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__external_annotation_on_custom_type_with_constructors.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n@external(erlang, \"gleam_stdlib\", \"dict\")\npub type Dict(key, value) {\n Dict(pairs: List(#(key, value)))\n}\n" --- ----- SOURCE CODE @external(erlang, "gleam_stdlib", "dict") pub type Dict(key, value) { Dict(pairs: List(#(key, value))) } ----- ERROR error: External type with constructors ┌─ /src/one/two.gleam:3:1 │ 3 │ pub type Dict(key, value) { │ ^^^^^^^^^^^^^^^^^^^^^^^^^ This type is annotated with the `@external` annotation, but it has constructors. The `@external` annotation is only for external types with no constructors. Hint: Remove the `@external` annotation ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__extra_var_inalternative.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case [1] { [x] | [x, y] -> 1 }" --- ----- SOURCE CODE case [1] { [x] | [x, y] -> 1 } ----- ERROR error: Extra alternative pattern variable ┌─ /src/one/two.gleam:1:22 │ 1 │ case [1] { [x] | [x, y] -> 1 } │ ^ Has not been previously defined All alternative patterns must define the same variables as the initial pattern. This variable `y` has not been previously defined. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__extra_var_inalternative2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case #(1, 2) { #(1, y) | #(x, y) -> 1 }" --- ----- SOURCE CODE case #(1, 2) { #(1, y) | #(x, y) -> 1 } ----- ERROR error: Extra alternative pattern variable ┌─ /src/one/two.gleam:1:28 │ 1 │ case #(1, 2) { #(1, y) | #(x, y) -> 1 } │ ^ Has not been previously defined All alternative patterns must define the same variables as the initial pattern. This variable `x` has not been previously defined. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__extra_var_inalternative3.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let x = 1 case #(1, 2) { #(1, y) | #(x, y) -> 1 }" --- ----- SOURCE CODE let x = 1 case #(1, 2) { #(1, y) | #(x, y) -> 1 } ----- ERROR error: Extra alternative pattern variable ┌─ /src/one/two.gleam:1:38 │ 1 │ let x = 1 case #(1, 2) { #(1, y) | #(x, y) -> 1 } │ ^ Has not been previously defined All alternative patterns must define the same variables as the initial pattern. This variable `x` has not been previously defined. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__fault_tolerant_list.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() {\n [1, \"a\", 1.0, \"a\" + 1]\n}\n" --- ----- SOURCE CODE pub fn main() { [1, "a", 1.0, "a" + 1] } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:7 │ 3 │ [1, "a", 1.0, "a" + 1] │ ^^^ All elements of a list must be the same type, but this one doesn't match the one before it. Expected type: Int Found type: String error: Type mismatch ┌─ /src/one/two.gleam:3:12 │ 3 │ [1, "a", 1.0, "a" + 1] │ ^^^ All elements of a list must be the same type, but this one doesn't match the one before it. Expected type: Int Found type: Float error: Type mismatch ┌─ /src/one/two.gleam:3:17 │ 3 │ [1, "a", 1.0, "a" + 1] │ ^^^ The + operator expects arguments of this type: Int But this argument has this type: String Hint: Strings can be joined using the `<>` operator. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__fault_tolerant_list_tail.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() {\n [1, \"a\", ..[\"a\", \"b\"]]\n}\n" --- ----- SOURCE CODE pub fn main() { [1, "a", ..["a", "b"]] } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:7 │ 3 │ [1, "a", ..["a", "b"]] │ ^^^ All elements of a list must be the same type, but this one doesn't match the one before it. Expected type: Int Found type: String error: Type mismatch ┌─ /src/one/two.gleam:3:14 │ 3 │ [1, "a", ..["a", "b"]] │ ^^^^^^^^^^ All elements in a list must have the same type, but the elements of this list don't match the type of the elements being prepended to it. Expected type: List(Int) Found type: List(String) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__fault_tolerant_negate_bool.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() {\n !!{ True || a }\n}\n" --- ----- SOURCE CODE pub fn main() { !!{ True || a } } ----- ERROR error: Unknown variable ┌─ /src/one/two.gleam:3:15 │ 3 │ !!{ True || a } │ ^ The name `a` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__fault_tolerant_negate_int.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() {\n --{ 1 + a }\n}\n" --- ----- SOURCE CODE pub fn main() { --{ 1 + a } } ----- ERROR error: Unknown variable ┌─ /src/one/two.gleam:3:11 │ 3 │ --{ 1 + a } │ ^ The name `a` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__fault_tolerant_tuple.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() {\n #(1, 1 + \"a\", not_in_scope)\n}\n" --- ----- SOURCE CODE pub fn main() { #(1, 1 + "a", not_in_scope) } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:12 │ 3 │ #(1, 1 + "a", not_in_scope) │ ^^^ The + operator expects arguments of this type: Int But this argument has this type: String Hint: Strings can be joined using the `<>` operator. error: Unknown variable ┌─ /src/one/two.gleam:3:17 │ 3 │ #(1, 1 + "a", not_in_scope) │ ^^^^^^^^^^^^ The name `not_in_scope` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__field_not_in_all_variants.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Person {\n Teacher(name: String, age: Int, title: String)\n Student(name: String, age: Int)\n}\npub fn get_title(person: Person) { person.title }" --- ----- SOURCE CODE pub type Person { Teacher(name: String, age: Int, title: String) Student(name: String, age: Int) } pub fn get_title(person: Person) { person.title } ----- ERROR error: Unknown record field ┌─ /src/one/two.gleam:6:43 │ 6 │ pub fn get_title(person: Person) { person.title } │ ^^^^^ This field does not exist The value being accessed has this type: Person It has these accessible fields: .age .name Note: The field you are trying to access is not defined consistently across all variants of this custom type. To fix this, ensure that all variants include the field with the same name, position, and type. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__field_not_in_any_variant.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Person {\n Teacher(name: String, age: Int, title: String)\n Student(name: String, age: Int)\n}\npub fn get_height(person: Person) { person.height }" --- ----- SOURCE CODE pub type Person { Teacher(name: String, age: Int, title: String) Student(name: String, age: Int) } pub fn get_height(person: Person) { person.height } ----- ERROR error: Unknown record field ┌─ /src/one/two.gleam:6:44 │ 6 │ pub fn get_height(person: Person) { person.height } │ ^^^^^^ This field does not exist The value being accessed has this type: Person It has these accessible fields: .age .name ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__field_type_different_between_variants.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Shape {\n Square(x: Int, y: Int)\n Rectangle(x: String, y: String)\n}\npub fn get_x(shape: Shape) { shape.x }\n" --- ----- SOURCE CODE pub type Shape { Square(x: Int, y: Int) Rectangle(x: String, y: String) } pub fn get_x(shape: Shape) { shape.x } ----- ERROR error: Unknown record field ┌─ /src/one/two.gleam:6:36 │ 6 │ pub fn get_x(shape: Shape) { shape.x } │ ^ This field does not exist The value being accessed has this type: Shape It does not have fields that are common across all variants. Note: The field you are trying to access is not defined consistently across all variants of this custom type. To fix this, ensure that all variants include the field with the same name, position, and type. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__float_gtf_int.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: 1.0 >. 1 --- ----- SOURCE CODE 1.0 >. 1 ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:8 │ 1 │ 1.0 >. 1 │ ^ The >. operator expects arguments of this type: Float But this argument has this type: Int Hint: the > operator can be used with Ints ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__float_operator_on_ints.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: 1 +. 2 --- ----- SOURCE CODE 1 +. 2 ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:3 │ 1 │ 1 +. 2 │ ^^ Use + instead The +. operator can only be used on Floats. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__float_operator_on_ints_2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: 1 <. 2 --- ----- SOURCE CODE 1 <. 2 ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:3 │ 1 │ 1 <. 2 │ ^^ Use < instead The <. operator can only be used on Floats. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__float_operator_on_ints_in_case_guard.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case 3 { x if x +. 2 == 5.0 -> \"a\" _ -> \"b\" }" --- ----- SOURCE CODE case 3 { x if x +. 2 == 5.0 -> "a" _ -> "b" } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:15 │ 1 │ case 3 { x if x +. 2 == 5.0 -> "a" _ -> "b" } │ ^^^^^^ Use + instead The +. operator can only be used on Floats. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__fn0_eq_fn1.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn() { 1 } == fn(x) { x + 1 }" --- ----- SOURCE CODE fn() { 1 } == fn(x) { x + 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:15 │ 1 │ fn() { 1 } == fn(x) { x + 1 } │ ^^^^^^^^^^^^^^^ Expected type: fn() -> Int Found type: fn(Int) -> Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__function_arg_and_return_annotation.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn(x: Int) -> Float { x }" --- ----- SOURCE CODE fn(x: Int) -> Float { x } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:23 │ 1 │ fn(x: Int) -> Float { x } │ ^ The type of this returned value doesn't match the return type annotation of this function. Expected type: Float Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__function_return_annotation.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn() -> Int { 2.0 }" --- ----- SOURCE CODE fn() -> Int { 2.0 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:15 │ 1 │ fn() -> Int { 2.0 } │ ^^^ The type of this returned value doesn't match the return type annotation of this function. Expected type: Int Found type: Float ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__function_return_annotation_mismatch_with_pipe.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "pub fn main() -> String {\n 1\n |> add_two\n }\n\n fn add_two(i: Int) -> Int {\n i + 2\n }" --- ----- SOURCE CODE pub fn main() -> String { 1 |> add_two } fn add_two(i: Int) -> Int { i + 2 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:2:13 │ 2 │ ╭ 1 3 │ │ |> add_two │ ╰──────────────────────^ The type of this returned value doesn't match the return type annotation of this function. Expected type: String Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__function_that_does_not_exist_does_not_produce_error_for_labelled_args.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() {\n // We only want to error on `wibble` since it doesn't exist, we don't want\n // an error on the label at this point!\n wibble(label: 1)\n}\n" --- ----- SOURCE CODE pub fn main() { // We only want to error on `wibble` since it doesn't exist, we don't want // an error on the label at this point! wibble(label: 1) } ----- ERROR error: Unknown variable ┌─ /src/one/two.gleam:5:3 │ 5 │ wibble(label: 1) │ ^^^^^^ The name `wibble` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__functions_called_outside_module.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "const first = list.at([1], 0)" --- ----- SOURCE CODE const first = list.at([1], 0) ----- ERROR error: Syntax error ┌─ /src/one/two.gleam:1:15 │ 1 │ const first = list.at([1], 0) │ ^^^^^^^^ Functions can only be called within other functions ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__generic_unlabelled_field_in_updated_record_wrong_type.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Wibble(a) {\n Wibble(a, b: Int, c: a)\n}\n\npub fn main() {\n let w = Wibble(1, 2, 3)\n Wibble(..w, c: False)\n}\n" --- ----- SOURCE CODE pub type Wibble(a) { Wibble(a, b: Int, c: a) } pub fn main() { let w = Wibble(1, 2, 3) Wibble(..w, c: False) } ----- ERROR error: Incomplete record update ┌─ /src/one/two.gleam:8:12 │ 8 │ Wibble(..w, c: False) │ ^ This is a `Wibble(Int)` The 1st field of this value is a `Int`, but the arguments given to the record update indicate that it should be a `Bool`. Note: Unlabelled fields cannot be updated in a record update, so either add a label or use a record constructor. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__guard_float_int_eq_vars.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let x = 1.0 let y = 1 case x { _ if x == y -> 1 }" --- ----- SOURCE CODE let x = 1.0 let y = 1 case x { _ if x == y -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:37 │ 1 │ let x = 1.0 let y = 1 case x { _ if x == y -> 1 } │ ^^^^^^ Expected type: Float Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__guard_if_float.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let x = 1.0 case x { _ if x -> 1 }" --- ----- SOURCE CODE let x = 1.0 case x { _ if x -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:27 │ 1 │ let x = 1.0 case x { _ if x -> 1 } │ ^ Expected type: Bool Found type: Float ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__guard_int_float_eq_vars.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let x = 1 let y = 1.0 case x { _ if x == y -> 1 }" --- ----- SOURCE CODE let x = 1 let y = 1.0 case x { _ if x == y -> 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:37 │ 1 │ let x = 1 let y = 1.0 case x { _ if x == y -> 1 } │ ^^^^^^ Expected type: Int Found type: Float ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__guard_record_wrong_arity.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type X { X(a: Int, b: Float) }\nfn x() {\n case X(1, 2.0) { x if x == X(1) -> 1 _ -> 2 }\n}" --- ----- SOURCE CODE type X { X(a: Int, b: Float) } fn x() { case X(1, 2.0) { x if x == X(1) -> 1 _ -> 2 } } ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:3:30 │ 3 │ case X(1, 2.0) { x if x == X(1) -> 1 _ -> 2 } │ ^^^^ Expected 2 arguments, got 1 This call accepts these additional labelled arguments: - b ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__hint_for_method_call.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type User {\n User(id: Int, name: String)\n}\n\npub fn main(user: User) {\n user.login()\n}\n" --- ----- SOURCE CODE pub type User { User(id: Int, name: String) } pub fn main(user: User) { user.login() } ----- ERROR error: Unknown record field ┌─ /src/one/two.gleam:7:8 │ 7 │ user.login() │ ^^^^^ This field does not exist The value being accessed has this type: User It has these accessible fields: .id .name Gleam is not object oriented, so if you are trying to call a method on this value you may want to use the function syntax instead. login(value) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__incomplete_pattern_does_not_show_structure_of_internal_type_outside_of_its_module.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "import wibble.{type Wibble}\n\npub fn go(wibble: Wibble) {\n case wibble {}\n}" --- ----- SOURCE CODE -- wibble.gleam @internal pub type Wibble { Wibble Wobble Woo } -- main.gleam import wibble.{type Wibble} pub fn go(wibble: Wibble) { case wibble {} } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:4:3 │ 4 │ case wibble {} │ ^^^^^^^^^^^^^^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: _ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__incomplete_pattern_does_not_show_structure_of_internal_type_outside_of_its_module_2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "import wibble.{type Wibble}\n\npub type Type {\n Type(wibble: Wibble, list: List(Int))\n}\n\npub fn go(value: Type) {\n case value {}\n}" --- ----- SOURCE CODE -- wibble.gleam @internal pub type Wibble { Wibble Wobble Woo } -- main.gleam import wibble.{type Wibble} pub type Type { Type(wibble: Wibble, list: List(Int)) } pub fn go(value: Type) { case value {} } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:8:3 │ 8 │ case value {} │ ^^^^^^^^^^^^^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Type(wibble:, list:) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__incorrect_arity_error.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let id = fn(x) { x } id()" --- ----- SOURCE CODE let id = fn(x) { x } id() ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:1:22 │ 1 │ let id = fn(x) { x } id() │ ^^^^ Expected 1 argument, got 0 ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__incorrect_arity_error_2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let id = fn(x) { x } id(1, 2)" --- ----- SOURCE CODE let id = fn(x) { x } id(1, 2) ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:1:22 │ 1 │ let id = fn(x) { x } id(1, 2) │ ^^^^^^^^ Expected 1 argument, got 2 ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__inexhaustive_use_reports_error.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nuse [1, 2, 3] <- todo\ntodo\n" --- ----- SOURCE CODE use [1, 2, 3] <- todo todo ----- ERROR error: Inexhaustive pattern ┌─ /src/one/two.gleam:2:5 │ 2 │ use [1, 2, 3] <- todo │ ^^^^^^^^^ This assignment uses a pattern that does not match all possible values. If one of the other values is used then the assignment will crash. The missing patterns are: [] [_] [_, _] [_, _, _] [_, _, _, _, ..] Hint: Use a more general pattern or use `let assert` instead. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__inferred_variant_record_update_change_type_parameter_different_branches.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Box(a) {\n Locked(password: String, value: a)\n Unlocked(password: String, value: a)\n}\n\npub fn main() {\n let box = Locked(\"ungu€$$4bLe\", 11)\n case box {\n Locked(..) as box -> Locked(..box, value: True)\n Unlocked(..) as box -> Unlocked(..box, password: \"pwd\")\n }\n}\n" --- ----- SOURCE CODE pub type Box(a) { Locked(password: String, value: a) Unlocked(password: String, value: a) } pub fn main() { let box = Locked("ungu€$$4bLe", 11) case box { Locked(..) as box -> Locked(..box, value: True) Unlocked(..) as box -> Unlocked(..box, password: "pwd") } } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:11:5 │ 11 │ Unlocked(..) as box -> Unlocked(..box, password: "pwd") │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This case clause was found to return a different type than the previous one, but all case clauses must return the same type. Expected type: Box(Bool) Found type: Box(Int) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__int_eq_float.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: 1 == 1.0 --- ----- SOURCE CODE 1 == 1.0 ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:6 │ 1 │ 1 == 1.0 │ ^^^ Expected type: Int Found type: Float ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__int_float_list.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "[1.0] == [1]" --- ----- SOURCE CODE [1.0] == [1] ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:10 │ 1 │ [1.0] == [1] │ ^^^ Expected type: List(Float) Found type: List(Int) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__int_gt_float.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: 1 > 1.0 --- ----- SOURCE CODE 1 > 1.0 ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:5 │ 1 │ 1 > 1.0 │ ^^^ The > operator expects arguments of this type: Int But this argument has this type: Float Hint: the >. operator can be used with Floats ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__int_operator_on_floats.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: 1.1 + 2.0 --- ----- SOURCE CODE 1.1 + 2.0 ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:5 │ 1 │ 1.1 + 2.0 │ ^ Use +. instead The + operator can only be used on Ints. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__int_operator_on_floats_2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: 1.1 > 2.0 --- ----- SOURCE CODE 1.1 > 2.0 ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:5 │ 1 │ 1.1 > 2.0 │ ^ Use >. instead The > operator can only be used on Ints. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__int_operator_on_floats_in_case_guard.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case 3.0 { x if x > 2.0 -> \"a\" _ -> \"b\" }" --- ----- SOURCE CODE case 3.0 { x if x > 2.0 -> "a" _ -> "b" } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:17 │ 1 │ case 3.0 { x if x > 2.0 -> "a" _ -> "b" } │ ^^^^^^^ Use >. instead The > operator can only be used on Ints. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_bit_array_pattern_discard_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: let assert <<_iDontCare>> = <<97>> --- ----- SOURCE CODE let assert <<_iDontCare>> = <<97>> ----- ERROR error: Invalid discard name ┌─ /src/one/two.gleam:1:14 │ 1 │ let assert <<_iDontCare>> = <<97>> │ ^^^^^^^^^^ This is not a valid discard name Hint: Discard names start with _ and contain a-z, 0-9, or _. Try: _i_dont_care ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_bit_array_pattern_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: let assert <> = <<73>> --- ----- SOURCE CODE let assert <> = <<73>> ----- ERROR error: Invalid variable name ┌─ /src/one/two.gleam:1:14 │ 1 │ let assert <> = <<73>> │ ^^^^^^^^ This is not a valid variable name Hint: Variable names start with a lowercase letter and contain a-z, 0-9, or _. Try: bit_value ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_case_variable_discard_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case 21 { _twentyOne -> {Nil} }" --- ----- SOURCE CODE case 21 { _twentyOne -> {Nil} } ----- ERROR error: Invalid discard name ┌─ /src/one/two.gleam:1:11 │ 1 │ case 21 { _twentyOne -> {Nil} } │ ^^^^^^^^^^ This is not a valid discard name Hint: Discard names start with _ and contain a-z, 0-9, or _. Try: _twenty_one ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_case_variable_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case 21 { twentyOne -> {Nil} }" --- ----- SOURCE CODE case 21 { twentyOne -> {Nil} } ----- ERROR error: Invalid variable name ┌─ /src/one/two.gleam:1:11 │ 1 │ case 21 { twentyOne -> {Nil} } │ ^^^^^^^^^ This is not a valid variable name Hint: Variable names start with a lowercase letter and contain a-z, 0-9, or _. Try: twenty_one ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_const_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: const myInvalid_Constant = 42 --- ----- SOURCE CODE const myInvalid_Constant = 42 ----- ERROR error: Invalid constant name ┌─ /src/one/two.gleam:1:7 │ 1 │ const myInvalid_Constant = 42 │ ^^^^^^^^^^^^^^^^^^ This is not a valid constant name Hint: Constant names start with a lowercase letter and contain a-z, 0-9, or _. Try: my_invalid_constant ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_constructor_arg_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type IntWrapper { IntWrapper(innerInt: Int) }" --- ----- SOURCE CODE type IntWrapper { IntWrapper(innerInt: Int) } ----- ERROR error: Invalid label name ┌─ /src/one/two.gleam:1:30 │ 1 │ type IntWrapper { IntWrapper(innerInt: Int) } │ ^^^^^^^^ This is not a valid label name Hint: Label names start with a lowercase letter and contain a-z, 0-9, or _. Try: inner_int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_constructor_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type MyType { Int_Value(Int) }" --- ----- SOURCE CODE type MyType { Int_Value(Int) } ----- ERROR error: Invalid type variant name ┌─ /src/one/two.gleam:1:15 │ 1 │ type MyType { Int_Value(Int) } │ ^^^^^^^^^ This is not a valid type variant name Hint: Type variant names start with an uppercase letter and contain only lowercase letters, numbers, and uppercase letters. Try: IntValue ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_constructor_pattern_discard_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "pub type Box { Box(Int) } pub fn main() { let Box(_ignoredInner) = Box(203)}" --- ----- SOURCE CODE pub type Box { Box(Int) } pub fn main() { let Box(_ignoredInner) = Box(203)} ----- ERROR error: Invalid discard name ┌─ /src/one/two.gleam:1:51 │ 1 │ pub type Box { Box(Int) } pub fn main() { let Box(_ignoredInner) = Box(203)} │ ^^^^^^^^^^^^^ This is not a valid discard name Hint: Discard names start with _ and contain a-z, 0-9, or _. Try: _ignored_inner ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_constructor_pattern_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "pub type Box { Box(Int) } pub fn main() { let Box(innerValue) = Box(203) }" --- ----- SOURCE CODE pub type Box { Box(Int) } pub fn main() { let Box(innerValue) = Box(203) } ----- ERROR error: Invalid variable name ┌─ /src/one/two.gleam:1:51 │ 1 │ pub type Box { Box(Int) } pub fn main() { let Box(innerValue) = Box(203) } │ ^^^^^^^^^^ This is not a valid variable name Hint: Variable names start with a lowercase letter and contain a-z, 0-9, or _. Try: inner_value ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_custom_type_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type Boxed_value { Box(Int) }" --- ----- SOURCE CODE type Boxed_value { Box(Int) } ----- ERROR error: Invalid type name ┌─ /src/one/two.gleam:1:6 │ 1 │ type Boxed_value { Box(Int) } │ ^^^^^^^^^^^ This is not a valid type name Hint: Type names start with an uppercase letter and contain only lowercase letters, numbers, and uppercase letters. Try: BoxedValue ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_function_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn doStuff() {}" --- ----- SOURCE CODE fn doStuff() {} ----- ERROR error: Invalid function name ┌─ /src/one/two.gleam:1:4 │ 1 │ fn doStuff() {} │ ^^^^^^^ This is not a valid function name Hint: Function names start with a lowercase letter and contain a-z, 0-9, or _. Try: do_stuff ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_function_type_parameter_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn identity(value: someType) { value }" --- ----- SOURCE CODE fn identity(value: someType) { value } ----- ERROR error: Invalid type variable name ┌─ /src/one/two.gleam:1:20 │ 1 │ fn identity(value: someType) { value } │ ^^^^^^^^ This is not a valid type variable name Hint: Type variable names start with a lowercase letter and contain a-z, 0-9, or _. Try: some_type ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_list_pattern_discard_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let assert [_elemOne] = [False]" --- ----- SOURCE CODE let assert [_elemOne] = [False] ----- ERROR error: Invalid discard name ┌─ /src/one/two.gleam:1:13 │ 1 │ let assert [_elemOne] = [False] │ ^^^^^^^^ This is not a valid discard name Hint: Discard names start with _ and contain a-z, 0-9, or _. Try: _elem_one ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_list_pattern_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let assert [theElement] = [9.4]" --- ----- SOURCE CODE let assert [theElement] = [9.4] ----- ERROR error: Invalid variable name ┌─ /src/one/two.gleam:1:13 │ 1 │ let assert [theElement] = [9.4] │ ^^^^^^^^^^ This is not a valid variable name Hint: Variable names start with a lowercase letter and contain a-z, 0-9, or _. Try: the_element ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_parameter_discard_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn ignore(_ignoreMe: Bool) { 98 }" --- ----- SOURCE CODE fn ignore(_ignoreMe: Bool) { 98 } ----- ERROR error: Invalid discard name ┌─ /src/one/two.gleam:1:11 │ 1 │ fn ignore(_ignoreMe: Bool) { 98 } │ ^^^^^^^^^ This is not a valid discard name Hint: Discard names start with _ and contain a-z, 0-9, or _. Try: _ignore_me ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_parameter_discard_name2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn ignore(labelled_discard _ignoreMe: Bool) { 98 }" --- ----- SOURCE CODE fn ignore(labelled_discard _ignoreMe: Bool) { 98 } ----- ERROR error: Invalid discard name ┌─ /src/one/two.gleam:1:28 │ 1 │ fn ignore(labelled_discard _ignoreMe: Bool) { 98 } │ ^^^^^^^^^ This is not a valid discard name Hint: Discard names start with _ and contain a-z, 0-9, or _. Try: _ignore_me ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_parameter_discard_name3.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let ignore = fn(_ignoreMe: Bool) { 98 }" --- ----- SOURCE CODE let ignore = fn(_ignoreMe: Bool) { 98 } ----- ERROR error: Invalid discard name ┌─ /src/one/two.gleam:1:17 │ 1 │ let ignore = fn(_ignoreMe: Bool) { 98 } │ ^^^^^^^^^ This is not a valid discard name Hint: Discard names start with _ and contain a-z, 0-9, or _. Try: _ignore_me ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_parameter_label.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn func(thisIsALabel param: Int) { param }" --- ----- SOURCE CODE fn func(thisIsALabel param: Int) { param } ----- ERROR error: Invalid label name ┌─ /src/one/two.gleam:1:9 │ 1 │ fn func(thisIsALabel param: Int) { param } │ ^^^^^^^^^^^^ This is not a valid label name Hint: Label names start with a lowercase letter and contain a-z, 0-9, or _. Try: this_is_a_label ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_parameter_label2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn ignore(thisIsALabel _ignore: Int) { 25 }" --- ----- SOURCE CODE fn ignore(thisIsALabel _ignore: Int) { 25 } ----- ERROR error: Invalid label name ┌─ /src/one/two.gleam:1:11 │ 1 │ fn ignore(thisIsALabel _ignore: Int) { 25 } │ ^^^^^^^^^^^^ This is not a valid label name Hint: Label names start with a lowercase letter and contain a-z, 0-9, or _. Try: this_is_a_label ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_parameter_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn add(numA: Int, num_b: Int) { numA + num_b }" --- ----- SOURCE CODE fn add(numA: Int, num_b: Int) { numA + num_b } ----- ERROR error: Invalid argument name ┌─ /src/one/two.gleam:1:8 │ 1 │ fn add(numA: Int, num_b: Int) { numA + num_b } │ ^^^^ This is not a valid argument name Hint: Argument names start with a lowercase letter and contain a-z, 0-9, or _. Try: num_a ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_parameter_name2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn pass(label paramName: Bool) { paramName }" --- ----- SOURCE CODE fn pass(label paramName: Bool) { paramName } ----- ERROR error: Invalid argument name ┌─ /src/one/two.gleam:1:15 │ 1 │ fn pass(label paramName: Bool) { paramName } │ ^^^^^^^^^ This is not a valid argument name Hint: Argument names start with a lowercase letter and contain a-z, 0-9, or _. Try: param_name ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_parameter_name3.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let add = fn(numA: Int, num_b: Int) { numA + num_b }" --- ----- SOURCE CODE let add = fn(numA: Int, num_b: Int) { numA + num_b } ----- ERROR error: Invalid argument name ┌─ /src/one/two.gleam:1:14 │ 1 │ let add = fn(numA: Int, num_b: Int) { numA + num_b } │ ^^^^ This is not a valid argument name Hint: Argument names start with a lowercase letter and contain a-z, 0-9, or _. Try: num_a ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_pattern_assignment_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: let assert 42 as theAnswer = 42 --- ----- SOURCE CODE let assert 42 as theAnswer = 42 ----- ERROR error: Invalid variable name ┌─ /src/one/two.gleam:1:18 │ 1 │ let assert 42 as theAnswer = 42 │ ^^^^^^^^^ This is not a valid variable name Hint: Variable names start with a lowercase letter and contain a-z, 0-9, or _. Try: the_answer ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_pattern_label_shorthand.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Wibble { Wibble(arg: Int) }\npub fn main() {\n let Wibble(not_a_label:) = Wibble(1)\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(arg: Int) } pub fn main() { let Wibble(not_a_label:) = Wibble(1) } ----- ERROR error: Unknown label ┌─ /src/one/two.gleam:4:14 │ 4 │ let Wibble(not_a_label:) = Wibble(1) │ ^^^^^^^^^^^^ Did you mean `arg`? It accepts these labels: arg ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_string_prefix_pattern_alias.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let assert \"prefix\" as thePrefix <> _suffix = \"prefix-suffix\"" --- ----- SOURCE CODE let assert "prefix" as thePrefix <> _suffix = "prefix-suffix" ----- ERROR error: Invalid variable name ┌─ /src/one/two.gleam:1:24 │ 1 │ let assert "prefix" as thePrefix <> _suffix = "prefix-suffix" │ ^^^^^^^^^ This is not a valid variable name Hint: Variable names start with a lowercase letter and contain a-z, 0-9, or _. Try: the_prefix ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_string_prefix_pattern_discard_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let assert \"prefix\" <> _boringSuffix = \"prefix-suffix\"" --- ----- SOURCE CODE let assert "prefix" <> _boringSuffix = "prefix-suffix" ----- ERROR error: Invalid discard name ┌─ /src/one/two.gleam:1:24 │ 1 │ let assert "prefix" <> _boringSuffix = "prefix-suffix" │ ^^^^^^^^^^^^^ This is not a valid discard name Hint: Discard names start with _ and contain a-z, 0-9, or _. Try: _boring_suffix ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_string_prefix_pattern_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let assert \"prefix\" <> coolSuffix = \"prefix-suffix\"" --- ----- SOURCE CODE let assert "prefix" <> coolSuffix = "prefix-suffix" ----- ERROR error: Invalid variable name ┌─ /src/one/two.gleam:1:24 │ 1 │ let assert "prefix" <> coolSuffix = "prefix-suffix" │ ^^^^^^^^^^ This is not a valid variable name Hint: Variable names start with a lowercase letter and contain a-z, 0-9, or _. Try: cool_suffix ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_tuple_pattern_discard_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let #(a, _secondValue) = #(1, 2)" --- ----- SOURCE CODE let #(a, _secondValue) = #(1, 2) ----- ERROR error: Invalid discard name ┌─ /src/one/two.gleam:1:10 │ 1 │ let #(a, _secondValue) = #(1, 2) │ ^^^^^^^^^^^^ This is not a valid discard name Hint: Discard names start with _ and contain a-z, 0-9, or _. Try: _second_value ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_tuple_pattern_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let #(a, secondValue) = #(1, 2)" --- ----- SOURCE CODE let #(a, secondValue) = #(1, 2) ----- ERROR error: Invalid variable name ┌─ /src/one/two.gleam:1:10 │ 1 │ let #(a, secondValue) = #(1, 2) │ ^^^^^^^^^^^ This is not a valid variable name Hint: Variable names start with a lowercase letter and contain a-z, 0-9, or _. Try: second_value ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_type_alias_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: type Fancy_Bool = Bool --- ----- SOURCE CODE type Fancy_Bool = Bool ----- ERROR error: Invalid type alias name ┌─ /src/one/two.gleam:1:6 │ 1 │ type Fancy_Bool = Bool │ ^^^^^^^^^^ This is not a valid type alias name Hint: Type alias names start with an uppercase letter and contain only lowercase letters, numbers, and uppercase letters. Try: FancyBool ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_type_alias_parameter_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type GleamOption(okType) = Result(okType, Nil)" --- ----- SOURCE CODE type GleamOption(okType) = Result(okType, Nil) ----- ERROR error: Invalid type variable name ┌─ /src/one/two.gleam:1:18 │ 1 │ type GleamOption(okType) = Result(okType, Nil) │ ^^^^^^ This is not a valid type variable name Hint: Type variable names start with a lowercase letter and contain a-z, 0-9, or _. Try: ok_type ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_type_parameter_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type Wrapper(innerType) {}" --- ----- SOURCE CODE type Wrapper(innerType) {} ----- ERROR error: Invalid type variable name ┌─ /src/one/two.gleam:1:14 │ 1 │ type Wrapper(innerType) {} │ ^^^^^^^^^ This is not a valid type variable name Hint: Type variable names start with a lowercase letter and contain a-z, 0-9, or _. Try: inner_type ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_use_discard_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn use_test(f) { f(Nil) }\npub fn main() { use _discardVar <- use_test() }" --- ----- SOURCE CODE fn use_test(f) { f(Nil) } pub fn main() { use _discardVar <- use_test() } ----- ERROR error: Invalid discard name ┌─ /src/one/two.gleam:2:21 │ 2 │ pub fn main() { use _discardVar <- use_test() } │ ^^^^^^^^^^^ This is not a valid discard name Hint: Discard names start with _ and contain a-z, 0-9, or _. Try: _discard_var ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_use_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn use_test(f) { f(Nil) }\npub fn main() { use useVar <- use_test() }" --- ----- SOURCE CODE fn use_test(f) { f(Nil) } pub fn main() { use useVar <- use_test() } ----- ERROR error: Invalid argument name ┌─ /src/one/two.gleam:2:21 │ 2 │ pub fn main() { use useVar <- use_test() } │ ^^^^^^ This is not a valid argument name Hint: Argument names start with a lowercase letter and contain a-z, 0-9, or _. Try: use_var ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_variable_discard_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: let _boringNumber = 72 --- ----- SOURCE CODE let _boringNumber = 72 ----- ERROR error: Invalid discard name ┌─ /src/one/two.gleam:1:5 │ 1 │ let _boringNumber = 72 │ ^^^^^^^^^^^^^ This is not a valid discard name Hint: Discard names start with _ and contain a-z, 0-9, or _. Try: _boring_number ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__invalid_variable_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: let theAnswer = 42 --- ----- SOURCE CODE let theAnswer = 42 ----- ERROR error: Invalid variable name ┌─ /src/one/two.gleam:1:5 │ 1 │ let theAnswer = 42 │ ^^^^^^^^^ This is not a valid variable name Hint: Variable names start with a lowercase letter and contain a-z, 0-9, or _. Try: the_answer ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__leak_multiple_private_types.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n type Private {\n Private\n }\n\n pub fn ret_private() -> Private {\n Private\n }\n\n pub fn ret_private2() -> Private {\n Private\n }\n\n pub fn main() {\n ret_private()\n }\n " --- ----- SOURCE CODE type Private { Private } pub fn ret_private() -> Private { Private } pub fn ret_private2() -> Private { Private } pub fn main() { ret_private() } ----- ERROR error: Private type used in public interface ┌─ /src/one/two.gleam:6:9 │ 6 │ pub fn ret_private() -> Private { │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The following type is private, but is being used by this public export. Private Private types can only be used within the module that defines them. error: Private type used in public interface ┌─ /src/one/two.gleam:10:9 │ 10 │ pub fn ret_private2() -> Private { │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The following type is private, but is being used by this public export. Private Private types can only be used within the module that defines them. error: Private type used in public interface ┌─ /src/one/two.gleam:14:9 │ 14 │ pub fn main() { │ ^^^^^^^^^^^^^ The following type is private, but is being used by this public export. Private Private types can only be used within the module that defines them. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__let_assert_binding_cannot_be_used_in_panic_message.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() {\n let assert Ok(message) = Error(\"Not Message\") as { \"Uh oh: \" <> message }\n}\n" --- ----- SOURCE CODE pub fn main() { let assert Ok(message) = Error("Not Message") as { "Uh oh: " <> message } } ----- ERROR error: Unknown variable ┌─ /src/one/two.gleam:3:67 │ 3 │ let assert Ok(message) = Error("Not Message") as { "Uh oh: " <> message } │ ^^^^^^^ The name `message` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__list.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "[1, 2.0]" --- ----- SOURCE CODE [1, 2.0] ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:5 │ 1 │ [1, 2.0] │ ^^^ All elements of a list must be the same type, but this one doesn't match the one before it. Expected type: Int Found type: Float ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__mismatched_list_tail.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "[\"wibble\", ..[1, 2]]" --- ----- SOURCE CODE ["wibble", ..[1, 2]] ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:14 │ 1 │ ["wibble", ..[1, 2]] │ ^^^^^^ All elements in a list must have the same type, but the elements of this list don't match the type of the elements being prepended to it. Expected type: List(String) Found type: List(Int) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__missing_case_body.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: case True --- ----- SOURCE CODE case True ----- ERROR error: Missing case body ┌─ /src/one/two.gleam:1:1 │ 1 │ case True │ ^^^^^^^^^ This case expression is missing its body. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__missing_type_constructor_arguments_in_type_annotation_1.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "pub fn main() -> Result() {}" --- ----- SOURCE CODE pub fn main() -> Result() {} ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:1:18 │ 1 │ pub fn main() -> Result() {} │ ^^^^^^^^ Expected 2 type arguments, got 0 `Result` requires 2 type arguments but none where provided. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__missing_type_constructor_arguments_in_type_annotation_2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "pub fn main() {\n let a: Result() = todo\n}" --- ----- SOURCE CODE pub fn main() { let a: Result() = todo } ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:2:10 │ 2 │ let a: Result() = todo │ ^^^^^^^^ Expected 2 type arguments, got 0 `Result` requires 2 type arguments but none where provided. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__missing_variable_in_alternative_pattern.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case [] { [x] | [] -> x _ -> 0 }" --- ----- SOURCE CODE case [] { [x] | [] -> x _ -> 0 } ----- ERROR error: Missing alternative pattern variable ┌─ /src/one/two.gleam:1:11 │ 1 │ case [] { [x] | [] -> x _ -> 0 } │ ^^^^^^^^^^^^^ This does not define all required variables All alternative patterns must define the same variables as the initial pattern, but the `x` variable is missing. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__module_arity_error.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn go(x: List(a, b)) -> Int { 1 }" --- ----- SOURCE CODE fn go(x: List(a, b)) -> Int { 1 } ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:1:10 │ 1 │ fn go(x: List(a, b)) -> Int { 1 } │ ^^^^^^^^^^ Expected 1 type argument, got 2 `List` requires 1 type argument but 2 where provided. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__module_could_not_unify.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn go() { 1 + 2.0 }" --- ----- SOURCE CODE fn go() { 1 + 2.0 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:15 │ 1 │ fn go() { 1 + 2.0 } │ ^^^ The + operator expects arguments of this type: Int But this argument has this type: Float Hint: the +. operator can be used with Floats ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__module_could_not_unify10.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn main() {\n let #(y, [..x]): #(x, List(x)) = #(\"one\", [1,2,3])\n x\n }" --- ----- SOURCE CODE fn main() { let #(y, [..x]): #(x, List(x)) = #("one", [1,2,3]) x } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:2:46 │ 2 │ let #(y, [..x]): #(x, List(x)) = #("one", [1,2,3]) │ ^^^^^^^^^^^^^^^^^ Expected type: #(x, List(x)) Found type: #(String, List(Int)) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__module_could_not_unify11.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n pub type Box(inner) {\n Box(inner)\n }\n\n pub fn create_int_box(value: Int) {\n let x: Box(Float) = Box(value)\n x\n }" --- ----- SOURCE CODE pub type Box(inner) { Box(inner) } pub fn create_int_box(value: Int) { let x: Box(Float) = Box(value) x } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:7:33 │ 7 │ let x: Box(Float) = Box(value) │ ^^^^^^^^^^ Expected type: Box(Float) Found type: Box(Int) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__module_could_not_unify12.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n pub type Person {\n Person(name: String, age: Int)\n }\n\n pub fn create_person(age: Float) {\n let x: Person = Person(name: \"Quinn\", age: age)\n x\n }" --- ----- SOURCE CODE pub type Person { Person(name: String, age: Int) } pub fn create_person(age: Float) { let x: Person = Person(name: "Quinn", age: age) x } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:7:56 │ 7 │ let x: Person = Person(name: "Quinn", age: age) │ ^^^ Expected type: Int Found type: Float ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__module_could_not_unify2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn go() { 1 + 2.0 }" --- ----- SOURCE CODE fn go() { 1 + 2.0 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:15 │ 1 │ fn go() { 1 + 2.0 } │ ^^^ The + operator expects arguments of this type: Int But this argument has this type: Float Hint: the +. operator can be used with Floats ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__module_could_not_unify3.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nfn id(x: a, y: a) { x }\npub fn x() { id(1, 1.0) }" --- ----- SOURCE CODE fn id(x: a, y: a) { x } pub fn x() { id(1, 1.0) } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:20 │ 3 │ pub fn x() { id(1, 1.0) } │ ^^^ Expected type: Int Found type: Float ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__module_could_not_unify4.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nfn wobble() -> Int {\n 5\n}\n\nfn run(one: fn() -> String) {\n one()\n}\n\nfn demo() {\n run(wobble)\n}" --- ----- SOURCE CODE fn wobble() -> Int { 5 } fn run(one: fn() -> String) { one() } fn demo() { run(wobble) } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:11:9 │ 11 │ run(wobble) │ ^^^^^^ Expected type: fn() -> String Found type: fn() -> Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__module_could_not_unify5.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nfn wobble(x: Int) -> Int {\n x * 5\n}\n\nfn run(one: fn(String) -> Int) {\n one(\"one.\")\n}\n\nfn demo() {\n run(wobble)\n}" --- ----- SOURCE CODE fn wobble(x: Int) -> Int { x * 5 } fn run(one: fn(String) -> Int) { one("one.") } fn demo() { run(wobble) } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:11:9 │ 11 │ run(wobble) │ ^^^^^^ Expected type: fn(String) -> Int Found type: fn(Int) -> Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__module_could_not_unify6.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn main() { let x: String = 5 x }" --- ----- SOURCE CODE fn main() { let x: String = 5 x } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:29 │ 1 │ fn main() { let x: String = 5 x } │ ^ Expected type: String Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__module_could_not_unify7.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn main() { let assert 5 = \"\" }" --- ----- SOURCE CODE fn main() { let assert 5 = "" } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:24 │ 1 │ fn main() { let assert 5 = "" } │ ^ Expected type: String Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__module_could_not_unify8.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn main() { let x: #(x, x) = #(5, 5.0) x }" --- ----- SOURCE CODE fn main() { let x: #(x, x) = #(5, 5.0) x } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:30 │ 1 │ fn main() { let x: #(x, x) = #(5, 5.0) x } │ ^^^^^^^^^ Expected type: #(x, x) Found type: #(Int, Float) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__module_could_not_unify9.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn main() { let assert [1, 2, ..x]: List(String) = [1,2,3] x }" --- ----- SOURCE CODE fn main() { let assert [1, 2, ..x]: List(String) = [1,2,3] x } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:52 │ 1 │ fn main() { let assert [1, 2, ..x]: List(String) = [1,2,3] x } │ ^^^^^^^ Expected type: List(String) Found type: List(Int) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__module_non_local_gaurd_var.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn one() { 1 }\nfn main() { case 1 { _ if one -> 1 } }" --- ----- SOURCE CODE fn one() { 1 } fn main() { case 1 { _ if one -> 1 } } ----- ERROR error: Invalid guard variable ┌─ /src/one/two.gleam:2:27 │ 2 │ fn main() { case 1 { _ if one -> 1 } } │ ^^^ Is not locally defined Variables used in guards must be either defined in the function, or be an argument to the function. The variable `one` is not defined locally. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__module_private_type_leak_1.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type PrivateType\n\n@external(erlang, \"a\", \"b\")\npub fn leak_type() -> PrivateType\n" --- ----- SOURCE CODE type PrivateType @external(erlang, "a", "b") pub fn leak_type() -> PrivateType ----- ERROR error: Private type used in public interface ┌─ /src/one/two.gleam:4:1 │ 4 │ pub fn leak_type() -> PrivateType │ ^^^^^^^^^^^^^^^^^^ The following type is private, but is being used by this public export. PrivateType Private types can only be used within the module that defines them. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__module_private_type_leak_2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type PrivateType\n\n@external(erlang, \"a\", \"b\")\nfn go() -> PrivateType\n\npub fn leak_type() { go() }" --- ----- SOURCE CODE type PrivateType @external(erlang, "a", "b") fn go() -> PrivateType pub fn leak_type() { go() } ----- ERROR error: Private type used in public interface ┌─ /src/one/two.gleam:6:1 │ 6 │ pub fn leak_type() { go() } │ ^^^^^^^^^^^^^^^^^^ The following type is private, but is being used by this public export. PrivateType Private types can only be used within the module that defines them. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__module_private_type_leak_3.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type PrivateType\n@external(erlang, \"a\", \"b\")\nfn go() -> PrivateType\npub fn leak_type() { [go()] }" --- ----- SOURCE CODE type PrivateType @external(erlang, "a", "b") fn go() -> PrivateType pub fn leak_type() { [go()] } ----- ERROR error: Private type used in public interface ┌─ /src/one/two.gleam:4:1 │ 4 │ pub fn leak_type() { [go()] } │ ^^^^^^^^^^^^^^^^^^ The following type is private, but is being used by this public export. PrivateType Private types can only be used within the module that defines them. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__module_private_type_leak_4.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type PrivateType\n@external(erlang, \"a\", \"b\")\npub fn go(x: PrivateType) -> Int" --- ----- SOURCE CODE type PrivateType @external(erlang, "a", "b") pub fn go(x: PrivateType) -> Int ----- ERROR error: Private type used in public interface ┌─ /src/one/two.gleam:3:1 │ 3 │ pub fn go(x: PrivateType) -> Int │ ^^^^^^^^^^^^^^^^^^^^^^^^^ The following type is private, but is being used by this public export. PrivateType Private types can only be used within the module that defines them. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__module_private_type_leak_5.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type PrivateType\npub type LeakType { Variant(PrivateType) }" --- ----- SOURCE CODE type PrivateType pub type LeakType { Variant(PrivateType) } ----- ERROR error: Private type used in public interface ┌─ /src/one/two.gleam:2:21 │ 2 │ pub type LeakType { Variant(PrivateType) } │ ^^^^^^^^^^^^^^^^^^^^ The following type is private, but is being used by this public export. PrivateType Private types can only be used within the module that defines them. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__module_private_type_leak_6.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type PrivateType\npub type LeakType { Variant(PrivateType) }" --- ----- SOURCE CODE type PrivateType pub type LeakType { Variant(PrivateType) } ----- ERROR error: Private type used in public interface ┌─ /src/one/two.gleam:2:21 │ 2 │ pub type LeakType { Variant(PrivateType) } │ ^^^^^^^^^^^^^^^^^^^^ The following type is private, but is being used by this public export. PrivateType Private types can only be used within the module that defines them. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__native_endianness_javascript_target.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs assertion_line: 3260 expression: "\npub fn main() {\n let assert <> = <<10>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn main() { let assert <> = <<10>> } ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:3:18 │ 3 │ let assert <> = <<10>> │ ^^^^^^ Unsupported endianness The JavaScript target does not support the `native` endianness option. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__negate_boolean_as_integer.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nfn() {\n let a = True\n let b = -a\n}\n" --- ----- SOURCE CODE fn() { let a = True let b = -a } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:4:12 │ 4 │ let b = -a │ ^ Expected type: Int Found type: Bool ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__negate_float_as_integer.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nfn() {\n let a = 3.0\n let b = -a\n}\n" --- ----- SOURCE CODE fn() { let a = 3.0 let b = -a } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:4:12 │ 4 │ let b = -a │ ^ Expected type: Int Found type: Float ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__negate_string.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "!\"Hello Gleam\"" --- ----- SOURCE CODE !"Hello Gleam" ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:2 │ 1 │ !"Hello Gleam" │ ^^^^^^^^^^^^^ Expected type: Bool Found type: String ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__negative_out_of_range_erlang_float.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "-1.8e308" --- ----- SOURCE CODE -1.8e308 ----- ERROR error: Float outside of valid range ┌─ /src/one/two.gleam:1:1 │ 1 │ -1.8e308 │ ^^^^^^^^ This float value is too large to be represented by a floating point type: float values must be in the range -1.7976931348623157e308 - 1.7976931348623157e308. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__negative_out_of_range_erlang_float_in_const.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: const x = -1.8e308 --- ----- SOURCE CODE const x = -1.8e308 ----- ERROR error: Float outside of valid range ┌─ /src/one/two.gleam:1:11 │ 1 │ const x = -1.8e308 │ ^^^^^^^^ This float value is too large to be represented by a floating point type: float values must be in the range -1.7976931348623157e308 - 1.7976931348623157e308. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__negative_out_of_range_erlang_float_in_pattern.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let assert [-1.8e308, b] = [x, y]" --- ----- SOURCE CODE let assert [-1.8e308, b] = [x, y] ----- ERROR error: Unknown variable ┌─ /src/one/two.gleam:1:29 │ 1 │ let assert [-1.8e308, b] = [x, y] │ ^ The name `x` is not in scope here. error: Unknown variable ┌─ /src/one/two.gleam:1:32 │ 1 │ let assert [-1.8e308, b] = [x, y] │ ^ The name `y` is not in scope here. error: Float outside of valid range ┌─ /src/one/two.gleam:1:13 │ 1 │ let assert [-1.8e308, b] = [x, y] │ ^^^^^^^^ This float value is too large to be represented by a floating point type: float values must be in the range -1.7976931348623157e308 - 1.7976931348623157e308. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__negative_size_pattern.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let assert <<1:size(-1)>> = <<>>" --- ----- SOURCE CODE let assert <<1:size(-1)>> = <<>> ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:16 │ 1 │ let assert <<1:size(-1)>> = <<>> │ ^^^^^^^^ A constant size must be a positive number See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__no_crash_on_duplicate_definition.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Wibble {\n Wobble\n Wobble\n}\n\npub fn main() {\n let wibble = Wobble\n case wibble {\n Wobble -> Nil\n }\n}\n" --- ----- SOURCE CODE pub type Wibble { Wobble Wobble } pub fn main() { let wibble = Wobble case wibble { Wobble -> Nil } } ----- ERROR error: Duplicate definition ┌─ /src/one/two.gleam:4:3 │ 3 │ Wobble │ ------ First defined here 4 │ Wobble │ ^^^^^^ Redefined here `Wobble` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__no_crash_on_duplicate_definition2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Wibble {\n Wibble\n Wobble\n Wobble\n Wubble\n}\n\npub fn main() {\n let wibble = Wobble\n case wibble {\n Wibble -> Nil\n Wobble -> Nil\n Wubble -> Nil\n }\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble Wobble Wobble Wubble } pub fn main() { let wibble = Wobble case wibble { Wibble -> Nil Wobble -> Nil Wubble -> Nil } } ----- ERROR error: Duplicate definition ┌─ /src/one/two.gleam:5:3 │ 4 │ Wobble │ ------ First defined here 5 │ Wobble │ ^^^^^^ Redefined here `Wobble` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__no_crash_on_duplicate_record_fields.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type X {\n A\n B(e0: Int, e0: Int)\n}\n\nfn compiler_crash(x: X) {\n case x {\n A -> todo\n _ -> todo\n }\n}\n " --- ----- SOURCE CODE pub type X { A B(e0: Int, e0: Int) } fn compiler_crash(x: X) { case x { A -> todo _ -> todo } } ----- ERROR error: Duplicate label ┌─ /src/one/two.gleam:4:14 │ 4 │ B(e0: Int, e0: Int) │ ^^ The label `e0` has already been defined. Rename this label. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__no_crash_on_record_update_when_constructor_definition_is_invalid.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Wibble {\n Wibble(a: Int, b: Int, Bool)\n}\n\npub fn main() {\n let one = Wibble(False, a: 5, b: 6)\n let two = Wibble(..one, b: 1)\n}\n " --- ----- SOURCE CODE pub type Wibble { Wibble(a: Int, b: Int, Bool) } pub fn main() { let one = Wibble(False, a: 5, b: 6) let two = Wibble(..one, b: 1) } ----- ERROR error: Unlabelled argument after labelled argument ┌─ /src/one/two.gleam:3:26 │ 3 │ Wibble(a: Int, b: Int, Bool) │ ^^^^ All unlabelled arguments must come before any labelled arguments. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__no_hint_for_non_method_call.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type User {\n User(id: Int, name: String)\n}\n\nfn login(user: User) {\n user\n}\n\npub fn main(user: User) {\n login(user.wibble)\n}\n" --- ----- SOURCE CODE pub type User { User(id: Int, name: String) } fn login(user: User) { user } pub fn main(user: User) { login(user.wibble) } ----- ERROR error: Unknown record field ┌─ /src/one/two.gleam:11:14 │ 11 │ login(user.wibble) │ ^^^^^^ This field does not exist The value being accessed has this type: User It has these accessible fields: .id .name ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__no_note_about_reliable_access_if_the_accessed_type_has_a_single_variant.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type User {\n User(name: String)\n}\n\npub fn main() {\n User(\"Jak\").nam\n}\n" --- ----- SOURCE CODE pub type User { User(name: String) } pub fn main() { User("Jak").nam } ----- ERROR error: Unknown record field ┌─ /src/one/two.gleam:7:15 │ 7 │ User("Jak").nam │ ^^^ Did you mean `name`? The value being accessed has this type: User It has these accessible fields: .name ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__non_utf8_string_assignment.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let assert <<\"Hello\" as message:utf16>> = <<>>" --- ----- SOURCE CODE let assert <<"Hello" as message:utf16>> = <<>> ----- ERROR error: Non UTF-8 string assignment ┌─ /src/one/two.gleam:1:25 │ 1 │ let assert <<"Hello" as message:utf16>> = <<>> │ ^^^^^^^ This pattern assigns a non UTF-8 string to a variable in a bit array. This is planned to be supported in the future, but we are unsure of the desired behaviour. Please go to https://github.com/gleam-lang/gleam/issues/4566 and explain your usecase for this pattern, and how you would expect it to behave. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__not_a_constructor_update.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Person {\n Person(name: String, age: Int)\n}\npub fn identity(a) { a }\npub fn update_person(person: Person) {\n identity(..person)\n}" --- ----- SOURCE CODE pub type Person { Person(name: String, age: Int) } pub fn identity(a) { a } pub fn update_person(person: Person) { identity(..person) } ----- ERROR error: Invalid record constructor ┌─ /src/one/two.gleam:7:3 │ 7 │ identity(..person) │ ^^^^^^^^ This is not a record constructor Only record constructors can be used with the update syntax. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__ok_2_args.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let Ok(1, x) = 1" --- ----- SOURCE CODE let Ok(1, x) = 1 ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:5 │ 1 │ let Ok(1, x) = 1 │ ^^^^^^^^ Expected type: Int Found type: Result(Int, a) error: Incorrect arity ┌─ /src/one/two.gleam:1:5 │ 1 │ let Ok(1, x) = 1 │ ^^^^^^^^ Expected 1 argument, got 2 ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__out_of_range_erlang_float.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "1.8e308" --- ----- SOURCE CODE 1.8e308 ----- ERROR error: Float outside of valid range ┌─ /src/one/two.gleam:1:1 │ 1 │ 1.8e308 │ ^^^^^^^ This float value is too large to be represented by a floating point type: float values must be in the range -1.7976931348623157e308 - 1.7976931348623157e308. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__out_of_range_erlang_float_in_const.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: const x = 1.8e308 --- ----- SOURCE CODE const x = 1.8e308 ----- ERROR error: Float outside of valid range ┌─ /src/one/two.gleam:1:11 │ 1 │ const x = 1.8e308 │ ^^^^^^^ This float value is too large to be represented by a floating point type: float values must be in the range -1.7976931348623157e308 - 1.7976931348623157e308. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__out_of_range_erlang_float_in_pattern.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let assert [1.8e308, b] = [x, y]" --- ----- SOURCE CODE let assert [1.8e308, b] = [x, y] ----- ERROR error: Unknown variable ┌─ /src/one/two.gleam:1:28 │ 1 │ let assert [1.8e308, b] = [x, y] │ ^ The name `x` is not in scope here. error: Unknown variable ┌─ /src/one/two.gleam:1:31 │ 1 │ let assert [1.8e308, b] = [x, y] │ ^ The name `y` is not in scope here. error: Float outside of valid range ┌─ /src/one/two.gleam:1:13 │ 1 │ let assert [1.8e308, b] = [x, y] │ ^^^^^^^ This float value is too large to be represented by a floating point type: float values must be in the range -1.7976931348623157e308 - 1.7976931348623157e308. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__pattern_with_incorrect_arity.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Pokemon { Pokemon(name: String, id: Int) }\n\npub fn main() {\n case todo {\n Pokemon(name:) -> todo\n }\n}\n" --- ----- SOURCE CODE pub type Pokemon { Pokemon(name: String, id: Int) } pub fn main() { case todo { Pokemon(name:) -> todo } } ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:6:5 │ 6 │ Pokemon(name:) -> todo │ ^^^^^^^^^^^^^^ Expected 2 arguments, got 1 This pattern accepts these additional labelled arguments: - id ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__pipe_arity_error.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nfn go(x, y) {\n x + y\n}\n\nfn main(x) {\n 1\n |> go\n}\n" --- ----- SOURCE CODE fn go(x, y) { x + y } fn main(x) { 1 |> go } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:8:6 │ 8 │ |> go │ ^^ Expected type: fn(Int) -> a Found type: fn(Int, Int) -> Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__pipe_mismatch_error.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "pub fn main() -> String {\n Orange\n |> eat_veggie\n }\n\n type Fruit{ Orange }\n type Veg{ Lettuce }\n\n fn eat_veggie(v: Veg) -> String {\n \"Ok\"\n }" --- ----- SOURCE CODE pub fn main() -> String { Orange |> eat_veggie } type Fruit{ Orange } type Veg{ Lettuce } fn eat_veggie(v: Veg) -> String { "Ok" } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:16 │ 3 │ |> eat_veggie │ ^^^^^^^^^^ This function does not accept the piped type The argument is: Fruit But function expects: Veg ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__pipe_value_type_mismatch_error.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "pub fn main() -> String {\n eat_veggie\n |> Orange\n }\n\n type Fruit{ Orange }\n type Veg{ Lettuce }\n\n fn eat_veggie(v: Veg) -> String {\n \"Ok\"\n }" --- ----- SOURCE CODE pub fn main() -> String { eat_veggie |> Orange } type Fruit{ Orange } type Veg{ Lettuce } fn eat_veggie(v: Veg) -> String { "Ok" } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:16 │ 3 │ |> Orange │ ^^^^^^ Expected type: fn(fn(Veg) -> String) -> String Found type: Fruit ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__positional_argument_after_labelled.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type X { X(a: Int, b: Int, c: Int) }\nfn x() { X(b: 1, a: 1, 1) }" --- ----- SOURCE CODE type X { X(a: Int, b: Int, c: Int) } fn x() { X(b: 1, a: 1, 1) } ----- ERROR error: Unexpected positional argument ┌─ /src/one/two.gleam:2:24 │ 2 │ fn x() { X(b: 1, a: 1, 1) } │ ^ This unlabeled argument has been supplied after a labelled argument. Once a labelled argument has been supplied all following arguments must also be labelled. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__positional_argument_after_one_using_label_shorthand.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type X { X(a: Int, b: Int, c: Int) }\nfn x() {\n let b = 1\n let a = 1\n X(b:, a:, 1)\n}" --- ----- SOURCE CODE type X { X(a: Int, b: Int, c: Int) } fn x() { let b = 1 let a = 1 X(b:, a:, 1) } ----- ERROR error: Unexpected positional argument ┌─ /src/one/two.gleam:5:13 │ 5 │ X(b:, a:, 1) │ ^ This unlabeled argument has been supplied after a labelled argument. Once a labelled argument has been supplied all following arguments must also be labelled. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__private_opaque_type.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nopaque type Wibble {\n Wobble\n}\n" --- ----- SOURCE CODE opaque type Wibble { Wobble } ----- ERROR error: Private opaque type ┌─ /src/one/two.gleam:2:1 │ 2 │ opaque type Wibble { │ ^^^^^^ You can safely remove this. Only a public type can be opaque. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__qualified_type_invalid_operands.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nimport maths as math\npub fn add_two_vectors(a: math.Vector, b: math.Vector) {\n a + b\n}\n" --- ----- SOURCE CODE -- maths.gleam pub type Vector { Vector(x: Float, y: Float) } -- main.gleam import maths as math pub fn add_two_vectors(a: math.Vector, b: math.Vector) { a + b } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:4:3 │ 4 │ a + b │ ^ The + operator expects arguments of this type: Int But this argument has this type: math.Vector error: Type mismatch ┌─ /src/one/two.gleam:4:7 │ 4 │ a + b │ ^ The + operator expects arguments of this type: Int But this argument has this type: math.Vector ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__qualified_type_invalid_pipe_argument.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nimport mod\npub fn main() {\n Nil |> mod.takes_wibble\n}\n" --- ----- SOURCE CODE -- mod.gleam pub type Wibble pub fn takes_wibble(value: Wibble) { value } -- main.gleam import mod pub fn main() { Nil |> mod.takes_wibble } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:4:10 │ 4 │ Nil |> mod.takes_wibble │ ^^^^^^^^^^^^^^^^ This function does not accept the piped type The argument is: Nil But function expects: mod.Wibble ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__qualified_type_mismatched_type_error.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nimport wibble\nconst my_wobble: wibble.Wobble = Nil\n" --- ----- SOURCE CODE -- wibble.gleam pub type Wobble -- main.gleam import wibble const my_wobble: wibble.Wobble = Nil ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:34 │ 3 │ const my_wobble: wibble.Wobble = Nil │ ^^^ Expected type: wibble.Wobble Found type: Nil ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__qualified_type_not_a_function.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nimport wibble.{type Function as FuncWrapper}\npub fn main(f: FuncWrapper) {\n f()\n}\n" --- ----- SOURCE CODE -- wibble.gleam pub type Function { Function(fn() -> Nil) } -- main.gleam import wibble.{type Function as FuncWrapper} pub fn main(f: FuncWrapper) { f() } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:4:3 │ 4 │ f() │ ^ This value is being called as a function but its type is: FuncWrapper ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__qualified_type_not_a_tuple.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nimport mod.{type Pair as Duo}\npub fn first(pair: Duo(a, b)) {\n pair.0\n}\n" --- ----- SOURCE CODE -- mod.gleam pub type Pair(a, b) { Pair(a, b) } -- main.gleam import mod.{type Pair as Duo} pub fn first(pair: Duo(a, b)) { pair.0 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:4:3 │ 4 │ pair.0 │ ^^^^ This is not a tuple To index into this value it needs to be a tuple, however it has this type: Duo(a, b) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__qualified_type_not_fn_in_use.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nimport some_mod as sm\npub fn main(func: sm.Function(Int, String, Float)) {\n use <- func()\n}\n" --- ----- SOURCE CODE -- some_mod.gleam pub type Function(param1, param2, return) -- main.gleam import some_mod as sm pub fn main(func: sm.Function(Int, String, Float)) { use <- func() } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:4:10 │ 4 │ use <- func() │ ^^^^^^ In a use expression, there should be a function on the right hand side of `<-`, but this value has type: sm.Function(Int, String, Float) See: https://tour.gleam.run/advanced-features/use/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__qualified_type_similar_type_name.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nimport wibble\nconst value: wibble.Int = 20\n" --- ----- SOURCE CODE -- wibble.gleam pub type Int -- main.gleam import wibble const value: wibble.Int = 20 ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:27 │ 3 │ const value: wibble.Int = 20 │ ^^ Expected type: wibble.Int Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__qualified_type_unification_error.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nimport gleam\n\ntype Bool {\n True\n False\n}\n\nconst list_of_bools = [True, False, gleam.False]\n" --- ----- SOURCE CODE import gleam type Bool { True False } const list_of_bools = [True, False, gleam.False] ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:9:37 │ 9 │ const list_of_bools = [True, False, gleam.False] │ ^^^^^^^^^^^ Expected type: Bool Found type: gleam.Bool ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__qualified_type_unknown_field.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nimport gleam\ntype Int {\n Int(bit_size: gleam.Int, bits: BitArray)\n}\n\npub fn main(not_a_record: gleam.Int) {\n not_a_record.bits\n}\n" --- ----- SOURCE CODE import gleam type Int { Int(bit_size: gleam.Int, bits: BitArray) } pub fn main(not_a_record: gleam.Int) { not_a_record.bits } ----- ERROR error: Unknown record field ┌─ /src/one/two.gleam:8:16 │ 8 │ not_a_record.bits │ ^^^^ This field does not exist The value being accessed has this type: gleam.Int It does not have any fields. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__qualified_type_use_fn_without_callback.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nimport some_mod\npub fn main() {\n use value <- some_mod.do_a_thing(10)\n}\n" --- ----- SOURCE CODE -- some_mod.gleam pub type NotACallback pub fn do_a_thing(a: Int, _b: NotACallback) { a } -- main.gleam import some_mod pub fn main() { use value <- some_mod.do_a_thing(10) } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:4:16 │ 4 │ use value <- some_mod.do_a_thing(10) │ ^^^^^^^^^^^^^^^^^^^^^^^ The function on the right hand side of `<-` has to take a callback function as its last argument. But the last argument of this function has type: some_mod.NotACallback See: https://tour.gleam.run/advanced-features/use/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__record_access_on_inferred_variant_when_field_is_in_other_variants.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Wibble {\n Wibble(wibble: Int)\n Wobble(wobble: Int)\n}\n\npub fn main() {\n let always_wibble = Wibble(10)\n always_wibble.wobble\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(wibble: Int) Wobble(wobble: Int) } pub fn main() { let always_wibble = Wibble(10) always_wibble.wobble } ----- ERROR error: Unknown record field ┌─ /src/one/two.gleam:9:17 │ 9 │ always_wibble.wobble │ ^^^^^^ Did you mean `wibble`? The value being accessed has this type: Wibble It has these accessible fields: .wibble Note: The field exists in this custom type but is not defined for the current variant. Ensure that you are accessing the field on a variant where it is valid. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__record_update_compatible_fields_wrong_type.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type A {\n A(a: Int, b: Int)\n}\n\npub type B {\n B(a: Int, b: Int)\n}\n\npub fn b_to_a(value: B) {\n A(..value, b: 5)\n}\n" --- ----- SOURCE CODE pub type A { A(a: Int, b: Int) } pub type B { B(a: Int, b: Int) } pub fn b_to_a(value: B) { A(..value, b: 5) } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:11:7 │ 11 │ A(..value, b: 5) │ ^^^^^ Expected type: A Found type: B ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__record_update_compatible_fields_wrong_variant.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Wibble {\n A(a: Int, b: Int)\n B(a: Int, b: Int)\n}\n\npub fn b_to_a(value: Wibble) {\n case value {\n A(..) -> value\n B(..) as b -> A(..b, b: 3)\n }\n}\n" --- ----- SOURCE CODE pub type Wibble { A(a: Int, b: Int) B(a: Int, b: Int) } pub fn b_to_a(value: Wibble) { case value { A(..) -> value B(..) as b -> A(..b, b: 3) } } ----- ERROR error: Incorrect record update ┌─ /src/one/two.gleam:10:23 │ 10 │ B(..) as b -> A(..b, b: 3) │ ^ This is a `B` This value is a `B` so it cannot be used to build a `A`, even if they share some fields. Note: If you want to change one variant of a type into another, you should specify all fields explicitly instead of using the record update syntax. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__record_update_does_not_stop_at_first_invalid_field_1.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Wibble {\n Wibble(a: Int, b: Bool)\n}\n\npub fn go(wibble: Wibble) {\n Wibble(..wibble, c: 1, a: 1.0)\n}" --- ----- SOURCE CODE pub type Wibble { Wibble(a: Int, b: Bool) } pub fn go(wibble: Wibble) { Wibble(..wibble, c: 1, a: 1.0) } ----- ERROR error: Unknown record field ┌─ /src/one/two.gleam:7:22 │ 7 │ Wibble(..wibble, c: 1, a: 1.0) │ ^^^^ Did you mean `a`? The value being accessed has this type: Wibble It has these accessible fields: .a .b error: Type mismatch ┌─ /src/one/two.gleam:7:28 │ 7 │ Wibble(..wibble, c: 1, a: 1.0) │ ^^^^^^ Expected type: Int Found type: Float ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__record_update_does_not_stop_at_first_invalid_field_2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Wibble {\n Wibble(a: Int, b: Bool)\n}\n\npub fn go(wibble: Wibble) {\n Wibble(..wibble, a: 1.0, a: 2, c: 2)\n}" --- ----- SOURCE CODE pub type Wibble { Wibble(a: Int, b: Bool) } pub fn go(wibble: Wibble) { Wibble(..wibble, a: 1.0, a: 2, c: 2) } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:7:22 │ 7 │ Wibble(..wibble, a: 1.0, a: 2, c: 2) │ ^^^^^^ Expected type: Int Found type: Float error: Duplicate argument ┌─ /src/one/two.gleam:7:30 │ 7 │ Wibble(..wibble, a: 1.0, a: 2, c: 2) │ ^^^^ The labelled argument `a` has already been supplied. error: Unknown record field ┌─ /src/one/two.gleam:7:36 │ 7 │ Wibble(..wibble, a: 1.0, a: 2, c: 2) │ ^^^^ Did you mean `a`? The value being accessed has this type: Wibble It has these accessible fields: .a .b ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__record_update_does_not_stop_at_first_invalid_field_3.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Wibble {\n Wibble(a: Int, b: Bool)\n}\n\npub fn go(wibble: Wibble) {\n Wibble(..wibble, a: 1.0, a: 2)\n}" --- ----- SOURCE CODE pub type Wibble { Wibble(a: Int, b: Bool) } pub fn go(wibble: Wibble) { Wibble(..wibble, a: 1.0, a: 2) } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:7:22 │ 7 │ Wibble(..wibble, a: 1.0, a: 2) │ ^^^^^^ Expected type: Int Found type: Float error: Duplicate argument ┌─ /src/one/two.gleam:7:30 │ 7 │ Wibble(..wibble, a: 1.0, a: 2) │ ^^^^ The labelled argument `a` has already been supplied. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__record_update_does_not_stop_at_first_invalid_field_4.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Wibble {\n Wibble(a: Int, b: Bool)\n}\n\npub fn go(wibble: Wibble) {\n Wibble(..wibble, a: 2, a: 3, b: 1)\n}" --- ----- SOURCE CODE pub type Wibble { Wibble(a: Int, b: Bool) } pub fn go(wibble: Wibble) { Wibble(..wibble, a: 2, a: 3, b: 1) } ----- ERROR error: Duplicate argument ┌─ /src/one/two.gleam:7:28 │ 7 │ Wibble(..wibble, a: 2, a: 3, b: 1) │ ^^^^ The labelled argument `a` has already been supplied. error: Type mismatch ┌─ /src/one/two.gleam:7:34 │ 7 │ Wibble(..wibble, a: 2, a: 3, b: 1) │ ^^^^ Expected type: Bool Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__record_update_does_not_stop_at_first_invalid_field_5.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Wibble {\n Wibble(a: Int, b: Bool)\n}\n\npub fn go(wibble: Wibble) {\n Wibble(..wibble, b: 1, a: True, c: 2)\n}" --- ----- SOURCE CODE pub type Wibble { Wibble(a: Int, b: Bool) } pub fn go(wibble: Wibble) { Wibble(..wibble, b: 1, a: True, c: 2) } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:7:22 │ 7 │ Wibble(..wibble, b: 1, a: True, c: 2) │ ^^^^ Expected type: Bool Found type: Int error: Type mismatch ┌─ /src/one/two.gleam:7:28 │ 7 │ Wibble(..wibble, b: 1, a: True, c: 2) │ ^^^^^^^ Expected type: Int Found type: Bool error: Unknown record field ┌─ /src/one/two.gleam:7:37 │ 7 │ Wibble(..wibble, b: 1, a: True, c: 2) │ ^^^^ Did you mean `a`? The value being accessed has this type: Wibble It has these accessible fields: .a .b ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__record_update_incompatible_but_linked_generics.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Wibble(a) {\n Wibble(a: a, b: a)\n}\n\npub fn b_to_a(value: Wibble(a)) -> Wibble(Int) {\n Wibble(..value, a: 5)\n}\n" --- ----- SOURCE CODE pub type Wibble(a) { Wibble(a: a, b: a) } pub fn b_to_a(value: Wibble(a)) -> Wibble(Int) { Wibble(..value, a: 5) } ----- ERROR error: Incomplete record update ┌─ /src/one/two.gleam:7:12 │ 7 │ Wibble(..value, a: 5) │ ^^^^^ This is a `Wibble(a)` The `b` field of this value is a `a`, but the arguments given to the record update indicate that it should be a `Int`. Note: If the same type variable is used for multiple fields, all those fields need to be updated at the same time if their type changes. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__record_update_unknown_variant.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Wibble {\n Wibble(wibble: Int, wubble: Bool)\n Wobble(wobble: Int, wubble: Bool)\n}\n\npub fn wibble(value: Wibble) {\n Wibble(..value, wubble: True)\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(wibble: Int, wubble: Bool) Wobble(wobble: Int, wubble: Bool) } pub fn wibble(value: Wibble) { Wibble(..value, wubble: True) } ----- ERROR error: Unsafe record update ┌─ /src/one/two.gleam:8:12 │ 8 │ Wibble(..value, wubble: True) │ ^^^^^ I'm not sure this is always a `Wibble` This value cannot be used to build an updated `Wibble` as it could be some other variant. Consider pattern matching on it with a case expression and then constructing a new record with its values. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__record_update_wrong_variant.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type MyRecord {\n A(common: Int, other: String)\n B(common: Int, different: Float)\n}\n\npub fn b_to_a(value: MyRecord) {\n case value {\n A(..) -> value\n B(..) as b -> A(..b, other: \"Hi\")\n }\n}\n" --- ----- SOURCE CODE pub type MyRecord { A(common: Int, other: String) B(common: Int, different: Float) } pub fn b_to_a(value: MyRecord) { case value { A(..) -> value B(..) as b -> A(..b, other: "Hi") } } ----- ERROR error: Incorrect record update ┌─ /src/one/two.gleam:10:23 │ 10 │ B(..) as b -> A(..b, other: "Hi") │ ^ This is a `B` This value is a `B` so it cannot be used to build a `A`, even if they share some fields. Note: If you want to change one variant of a type into another, you should specify all fields explicitly instead of using the record update syntax. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__record_update_wrong_variant_imported_type.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nimport wibble\n\npub fn main(wibble: wibble.Wibble) {\n case wibble {\n wibble.Wibble(..) as w -> wibble.Wobble(..w, wubble: 10)\n _ -> panic\n }\n}\n" --- ----- SOURCE CODE -- wibble.gleam pub type Wibble { Wibble(wibble: Int, wobble: Int) Wobble(wobble: Int, wubble: Int) } -- main.gleam import wibble pub fn main(wibble: wibble.Wibble) { case wibble { wibble.Wibble(..) as w -> wibble.Wobble(..w, wubble: 10) _ -> panic } } ----- ERROR error: Incorrect record update ┌─ /src/one/two.gleam:6:47 │ 6 │ wibble.Wibble(..) as w -> wibble.Wobble(..w, wubble: 10) │ ^ This is a `Wibble` This value is a `Wibble` so it cannot be used to build a `Wobble`, even if they share some fields. Note: If you want to change one variant of a type into another, you should specify all fields explicitly instead of using the record update syntax. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__recursive_var.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let id = fn(x) { x(x) } 1" --- ----- SOURCE CODE let id = fn(x) { x(x) } 1 ----- ERROR error: Recursive type ┌─ /src/one/two.gleam:1:20 │ 1 │ let id = fn(x) { x(x) } 1 │ ^ I don't know how to work out what type this value has. It seems to be defined in terms of itself. Hint: Add some type annotations and try again. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__remembering_record_field_when_type_checking_fails.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "pub type Wibble {\n Wibble(x: Int, f: fn(Wobble) -> Int)\n}\n\npub fn wibble() {\n Wibble(1, fn(_) { 2 })\n}\n\npub fn wobble(wibble: Wibble) {\n wibble.f\n}\n\npub fn woo(wibble: Wibble) {\n Wibble(..wibble, x: 1)\n}" --- ----- SOURCE CODE pub type Wibble { Wibble(x: Int, f: fn(Wobble) -> Int) } pub fn wibble() { Wibble(1, fn(_) { 2 }) } pub fn wobble(wibble: Wibble) { wibble.f } pub fn woo(wibble: Wibble) { Wibble(..wibble, x: 1) } ----- ERROR error: Unknown type ┌─ /src/one/two.gleam:2:24 │ 2 │ Wibble(x: Int, f: fn(Wobble) -> Int) │ ^^^^^^ Did you mean `Wibble`? The type `Wobble` is not defined or imported in this module. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__same_imports_multiple_times.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n import gleam/wibble.{wobble}\n import gleam/wibble.{zoo}\n pub fn go() { wobble() + zoo() }\n " --- ----- SOURCE CODE -- gleam/wibble.gleam pub fn wobble() { 1 } pub fn zoo() { 1 } -- main.gleam import gleam/wibble.{wobble} import gleam/wibble.{zoo} pub fn go() { wobble() + zoo() } ----- ERROR error: Duplicate import ┌─ /src/one/two.gleam:3:9 │ 2 │ import gleam/wibble.{wobble} │ ---------------------------- First imported here 3 │ import gleam/wibble.{zoo} │ ^^^^^^^^^^^^^^^^^^^^^^^^^ Reimported here `wibble` has been imported multiple times. Names in a Gleam module must be unique so one will need to be renamed. error: Unknown variable ┌─ /src/one/two.gleam:4:34 │ 4 │ pub fn go() { wobble() + zoo() } │ ^^^ The name `zoo` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__same_imports_multiple_times_1.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n import one\n import two as one\n " --- ----- SOURCE CODE -- one.gleam pub fn fn1() { 1 } -- two.gleam pub fn fn2() { 1 } -- main.gleam import one import two as one ----- ERROR error: Duplicate import ┌─ /src/one/two.gleam:3:9 │ 2 │ import one │ ---------- First imported here 3 │ import two as one │ ^^^^^^^^^^^^^^^^^ Reimported here `one` has been imported multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__same_imports_multiple_times_2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n import one as two\n import two\n " --- ----- SOURCE CODE -- one.gleam pub fn fn1() { 1 } -- two.gleam pub fn fn2() { 1 } -- main.gleam import one as two import two ----- ERROR error: Duplicate import ┌─ /src/one/two.gleam:3:9 │ 2 │ import one as two │ ----------------- First imported here 3 │ import two │ ^^^^^^^^^^ Reimported here `two` has been imported multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__same_imports_multiple_times_3.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n import one as x\n import two as x\n " --- ----- SOURCE CODE -- one.gleam pub fn fn1() { 1 } -- two.gleam pub fn fn2() { 1 } -- main.gleam import one as x import two as x ----- ERROR error: Duplicate import ┌─ /src/one/two.gleam:3:9 │ 2 │ import one as x │ --------------- First imported here 3 │ import two as x │ ^^^^^^^^^^^^^^^ Reimported here `x` has been imported multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__same_imports_multiple_times_4.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n import one.{fn1}\n import two.{fn2} as one\n " --- ----- SOURCE CODE -- one.gleam pub fn fn1() { 1 } -- two.gleam pub fn fn2() { 1 } -- main.gleam import one.{fn1} import two.{fn2} as one ----- ERROR error: Duplicate import ┌─ /src/one/two.gleam:3:9 │ 2 │ import one.{fn1} │ ---------------- First imported here 3 │ import two.{fn2} as one │ ^^^^^^^^^^^^^^^^^^^^^^^ Reimported here `one` has been imported multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__same_imports_multiple_times_5.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n import one.{fn1} as two\n import two.{fn2}\n " --- ----- SOURCE CODE -- one.gleam pub fn fn1() { 1 } -- two.gleam pub fn fn2() { 1 } -- main.gleam import one.{fn1} as two import two.{fn2} ----- ERROR error: Duplicate import ┌─ /src/one/two.gleam:3:9 │ 2 │ import one.{fn1} as two │ ----------------------- First imported here 3 │ import two.{fn2} │ ^^^^^^^^^^^^^^^^ Reimported here `two` has been imported multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__same_imports_multiple_times_6.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n import one.{fn1} as x\n import two.{fn2} as x\n " --- ----- SOURCE CODE -- one.gleam pub fn fn1() { 1 } -- two.gleam pub fn fn2() { 1 } -- main.gleam import one.{fn1} as x import two.{fn2} as x ----- ERROR error: Duplicate import ┌─ /src/one/two.gleam:3:9 │ 2 │ import one.{fn1} as x │ --------------------- First imported here 3 │ import two.{fn2} as x │ ^^^^^^^^^^^^^^^^^^^^^ Reimported here `x` has been imported multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__same_imports_multiple_times_7.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n import one.{\n fn1\n } as x\n import two.{\n fn2\n } as x\n " --- ----- SOURCE CODE -- one.gleam pub fn fn1() { 1 } -- two.gleam pub fn fn2() { 1 } -- main.gleam import one.{ fn1 } as x import two.{ fn2 } as x ----- ERROR error: Duplicate import ┌─ /src/one/two.gleam:5:9 │ 2 │ ╭ import one.{ 3 │ │ fn1 4 │ │ } as x │ ╰──────────────' First imported here 5 │ ╭ import two.{ 6 │ │ fn2 7 │ │ } as x │ ╰────────────────^ Reimported here `x` has been imported multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__shadowed_fn_argument.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn go(x) {\n fn(_y) {\n y + x\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { fn(_y) { y + x } } ----- ERROR error: Unknown variable ┌─ /src/one/two.gleam:4:5 │ 3 │ fn(_y) { │ -- This value is discarded 4 │ y + x │ ^ So this is not in scope Hint: Change `_y` to `y` or reference another variable ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__shadowed_function_argument.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn go(_x) {\n x + 1\n}\n" --- ----- SOURCE CODE pub fn go(_x) { x + 1 } ----- ERROR error: Unknown variable ┌─ /src/one/two.gleam:3:3 │ 2 │ pub fn go(_x) { │ -- This value is discarded 3 │ x + 1 │ ^ So this is not in scope Hint: Change `_x` to `x` or reference another variable ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__shadowed_let_variable.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn go() {\n let _x = 1\n x + 1\n}\n" --- ----- SOURCE CODE pub fn go() { let _x = 1 x + 1 } ----- ERROR error: Unknown variable ┌─ /src/one/two.gleam:4:3 │ 3 │ let _x = 1 │ -- This value is discarded 4 │ x + 1 │ ^ So this is not in scope Hint: Change `_x` to `x` or reference another variable ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__shadowed_pattern_variable.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Wibble {\n Wibble(Int)\n}\n\npub fn go(x) {\n case x {\n Wibble(_y) -> y + 1\n }\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(Int) } pub fn go(x) { case x { Wibble(_y) -> y + 1 } } ----- ERROR error: Unknown variable ┌─ /src/one/two.gleam:8:19 │ 8 │ Wibble(_y) -> y + 1 │ -- ^ So this is not in scope │ │ │ This value is discarded Hint: Change `_y` to `y` or reference another variable ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__show_only_missing_labels.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nfn wibble(a a: Int, b b: Float, c c: String) {\n todo\n}\n\npub fn wobble() {\n wibble(1, 2.0)\n}\n" --- ----- SOURCE CODE fn wibble(a a: Int, b b: Float, c c: String) { todo } pub fn wobble() { wibble(1, 2.0) } ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:7:5 │ 7 │ wibble(1, 2.0) │ ^^^^^^^^^^^^^^ Expected 3 arguments, got 2 This call accepts these additional labelled arguments: - c ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__src_importing_dev_dependency.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nimport some_module\n\npub fn main() {\n some_module.main()\n}\n" --- ----- SOURCE CODE -- some_module.gleam pub fn main() { Nil } -- main.gleam import some_module pub fn main() { some_module.main() } ----- ERROR error: App importing dev dependency ┌─ /src/one/two.gleam:2:1 │ 2 │ import some_module │ ^^^^^^^^^^^^^^^^^^ The application module `themodule` is importing the module `some_module`, but `dev_dependency`, the package it belongs to, is a dev dependency. Dev dependencies are not included in production builds so application modules should not import them. Perhaps change `dev_dependency` to a regular dependency. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__subject_int_float_guard_tuple.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type X { X(a: Int, b: Float) }\nfn x() { case X(1, 2.0) { x if x == X(2.0, 1) -> 1 _ -> 2 } }" --- ----- SOURCE CODE type X { X(a: Int, b: Float) } fn x() { case X(1, 2.0) { x if x == X(2.0, 1) -> 1 _ -> 2 } } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:2:39 │ 2 │ fn x() { case X(1, 2.0) { x if x == X(2.0, 1) -> 1 _ -> 2 } } │ ^^^ Expected type: Int Found type: Float error: Type mismatch ┌─ /src/one/two.gleam:2:44 │ 2 │ fn x() { case X(1, 2.0) { x if x == X(2.0, 1) -> 1 _ -> 2 } } │ ^ Expected type: Float Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__suggest_unwrapping_a_result_when_types_match.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() {\n let value = Ok(1)\n add_1(value)\n}\n\nfn add_1(to x) { x + 1 }\n" --- ----- SOURCE CODE pub fn main() { let value = Ok(1) add_1(value) } fn add_1(to x) { x + 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:4:9 │ 4 │ add_1(value) │ ^^^^^ Expected type: Int Found type: Result(Int, a) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__suggest_wrapping_a_function_return_value_in_error.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() -> Result(Int, Bool) {\n True\n}\n" --- ----- SOURCE CODE pub fn main() -> Result(Int, Bool) { True } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:3 │ 3 │ True │ ^^^^ Did you mean to wrap this in an `Error`? The type of this returned value doesn't match the return type annotation of this function. Expected type: Result(Int, Bool) Found type: Bool ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__suggest_wrapping_a_function_return_value_in_ok.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() -> Result(Int, Bool) {\n 1\n}\n" --- ----- SOURCE CODE pub fn main() -> Result(Int, Bool) { 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:3 │ 3 │ 1 │ ^ Did you mean to wrap this in an `Ok`? The type of this returned value doesn't match the return type annotation of this function. Expected type: Result(Int, Bool) Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__suggest_wrapping_a_use_returned_value_in_error.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() -> Result(Int, Bool) {\n use <- want_result\n False\n}\n\npub fn want_result(wibble: fn() -> Result(Int, Bool)) {\n todo\n}\n" --- ----- SOURCE CODE pub fn main() -> Result(Int, Bool) { use <- want_result False } pub fn want_result(wibble: fn() -> Result(Int, Bool)) { todo } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:4:3 │ 4 │ False │ ^^^^^ Did you mean to wrap this in an `Error`? Expected type: Result(Int, Bool) Found type: Bool ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__suggest_wrapping_a_use_returned_value_in_ok.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() -> Result(Int, Bool) {\n use <- want_result\n 1\n}\n\npub fn want_result(wibble: fn() -> Result(Int, Bool)) {\n todo\n}\n" --- ----- SOURCE CODE pub fn main() -> Result(Int, Bool) { use <- want_result 1 } pub fn want_result(wibble: fn() -> Result(Int, Bool)) { todo } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:4:3 │ 4 │ 1 │ ^ Did you mean to wrap this in an `Ok`? Expected type: Result(Int, Bool) Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__suggest_wrapping_a_value_into_error_if_types_match.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() {\n case todo {\n 1 -> Error(1)\n _ -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case todo { 1 -> Error(1) _ -> 1 } } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:5:5 │ 5 │ _ -> 1 │ ^^^^^^ This case clause was found to return a different type than the previous one, but all case clauses must return the same type. Expected type: Result(a, Int) Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__suggest_wrapping_a_value_into_error_if_types_match_2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() {\n wibble(\"a\")\n}\n\nfn wibble(arg: Result(Int, String)) { todo }\n" --- ----- SOURCE CODE pub fn main() { wibble("a") } fn wibble(arg: Result(Int, String)) { todo } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:12 │ 3 │ wibble("a") │ ^^^ Did you mean to wrap this in an `Error`? Expected type: Result(Int, String) Found type: String ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__suggest_wrapping_a_value_into_ok_if_types_match.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() {\n case todo {\n 1 -> Ok(2)\n _ -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case todo { 1 -> Ok(2) _ -> 1 } } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:5:5 │ 5 │ _ -> 1 │ ^^^^^^ This case clause was found to return a different type than the previous one, but all case clauses must return the same type. Expected type: Result(Int, a) Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__suggest_wrapping_a_value_into_ok_if_types_match_2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() {\n wibble(1)\n}\n\nfn wibble(arg: Result(Int, String)) { todo }\n" --- ----- SOURCE CODE pub fn main() { wibble(1) } fn wibble(arg: Result(Int, String)) { todo } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:10 │ 3 │ wibble(1) │ ^ Did you mean to wrap this in an `Ok`? Expected type: Result(Int, String) Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__suggest_wrapping_a_value_into_ok_if_types_match_with_block.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() {\n case todo {\n 1 -> Ok(2)\n _ -> {\n todo\n 1\n }\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case todo { 1 -> Ok(2) _ -> { todo 1 } } } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:5:5 │ 5 │ ╭ _ -> { 6 │ │ todo 7 │ │ 1 8 │ │ } │ ╰─────^ This case clause was found to return a different type than the previous one, but all case clauses must return the same type. Expected type: Result(Int, a) Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__suggest_wrapping_a_value_into_ok_if_types_match_with_multiline_result_in_block.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() {\n case todo {\n 1 -> Ok(2)\n _ -> {\n todo\n 1\n |> add_1\n }\n }\n}\n\nfn add_1(n: Int) { n + 1 }\n" --- ----- SOURCE CODE pub fn main() { case todo { 1 -> Ok(2) _ -> { todo 1 |> add_1 } } } fn add_1(n: Int) { n + 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:5:5 │ 5 │ ╭ _ -> { 6 │ │ todo 7 │ │ 1 8 │ │ |> add_1 9 │ │ } │ ╰─────^ This case clause was found to return a different type than the previous one, but all case clauses must return the same type. Expected type: Result(Int, a) Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__suggest_wrapping_a_value_into_ok_with_generic_type.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn first(list: List(a)) -> Result(a, Nil) {\n case list {\n [] -> Error(Nil)\n [first, ..rest] -> first\n }\n}\n" --- ----- SOURCE CODE pub fn first(list: List(a)) -> Result(a, Nil) { case list { [] -> Error(Nil) [first, ..rest] -> first } } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:5:5 │ 5 │ [first, ..rest] -> first │ ^^^^^^^^^^^^^^^^^^^^^^^^ This case clause was found to return a different type than the previous one, but all case clauses must return the same type. Expected type: Result(a, Nil) Found type: a ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__true_fn.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: let True(x) = 1 --- ----- SOURCE CODE let True(x) = 1 ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:5 │ 1 │ let True(x) = 1 │ ^^^^^^^ Expected type: Int Found type: Bool error: Incorrect arity ┌─ /src/one/two.gleam:1:5 │ 1 │ let True(x) = 1 │ ^^^^^^^ Expected no arguments, got 1 ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__tuple_2_3.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "#(1, 2) == #(1, 2, 3)" --- ----- SOURCE CODE #(1, 2) == #(1, 2, 3) ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:12 │ 1 │ #(1, 2) == #(1, 2, 3) │ ^^^^^^^^^^ Expected type: #(Int, Int) Found type: #(Int, Int, Int) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__tuple_arity.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case #(1, 2) { #(1, _, _, _) -> 1 }" --- ----- SOURCE CODE case #(1, 2) { #(1, _, _, _) -> 1 } ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:1:16 │ 1 │ case #(1, 2) { #(1, _, _, _) -> 1 } │ ^^^^^^^^^^^^^ Expected 2 arguments, got 4 ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__tuple_index_not_a_tuple.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: Nil.2 --- ----- SOURCE CODE Nil.2 ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:1 │ 1 │ Nil.2 │ ^^^ This is not a tuple To index into this value it needs to be a tuple, however it has this type: Nil ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__tuple_index_not_a_tuple_unbound.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn(a) { a.2 }" --- ----- SOURCE CODE fn(a) { a.2 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:9 │ 1 │ fn(a) { a.2 } │ ^ What type is this? To index into a tuple we need to know its size, but we don't know anything about this type yet. Please add some type annotations so we can continue. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__tuple_index_out_of_bounds.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "#(0, 1).2" --- ----- SOURCE CODE #(0, 1).2 ----- ERROR error: Out of bounds tuple index ┌─ /src/one/two.gleam:1:8 │ 1 │ #(0, 1).2 │ ^^ This index is too large The index being accessed for this tuple is 2, but this tuple has 2 elements so the highest valid index is 1. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__tuple_int.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let #(a, b) = 1" --- ----- SOURCE CODE let #(a, b) = 1 ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:5 │ 1 │ let #(a, b) = 1 │ ^^^^^^^ Expected type: Int Found type: #(a, b) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__tuple_int_float.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "#(1.0, 2, 3) == #(1, 2, 3)" --- ----- SOURCE CODE #(1.0, 2, 3) == #(1, 2, 3) ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:17 │ 1 │ #(1.0, 2, 3) == #(1, 2, 3) │ ^^^^^^^^^^ Expected type: #(Float, Int, Int) Found type: #(Int, Int, Int) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__type_annotations.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn inc(x: a) { x + 1 }" --- ----- SOURCE CODE fn inc(x: a) { x + 1 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:16 │ 1 │ fn inc(x: a) { x + 1 } │ ^ The + operator expects arguments of this type: Int But this argument has this type: a ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__type_holes1.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type A { A(_) }" --- ----- SOURCE CODE type A { A(_) } ----- ERROR error: Unexpected type hole ┌─ /src/one/two.gleam:1:12 │ 1 │ type A { A(_) } │ ^ I need to know what this is We need to know the exact type here so type holes cannot be used. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__type_holes2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n@external(erlang, \"a\", \"b\")\nfn main() -> List(_)\n" --- ----- SOURCE CODE @external(erlang, "a", "b") fn main() -> List(_) ----- ERROR error: Unexpected type hole ┌─ /src/one/two.gleam:3:19 │ 3 │ fn main() -> List(_) │ ^ I need to know what this is We need to know the exact type here so type holes cannot be used. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__type_holes3.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n@external(erlang, \"a\", \"b\")\nfn main(x: List(_)) -> Nil\n" --- ----- SOURCE CODE @external(erlang, "a", "b") fn main(x: List(_)) -> Nil ----- ERROR error: Unexpected type hole ┌─ /src/one/two.gleam:3:17 │ 3 │ fn main(x: List(_)) -> Nil │ ^ I need to know what this is We need to know the exact type here so type holes cannot be used. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__type_holes4.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: type X = List(_) --- ----- SOURCE CODE type X = List(_) ----- ERROR error: Unexpected type hole ┌─ /src/one/two.gleam:1:15 │ 1 │ type X = List(_) │ ^ I need to know what this is We need to know the exact type here so type holes cannot be used. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__type_imported_as_value.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "import gleam/wibble.{Wibble}" --- ----- SOURCE CODE -- gleam/wibble.gleam pub type Wibble { Wobble } -- main.gleam import gleam/wibble.{Wibble} ----- ERROR error: Unknown module value ┌─ /src/one/two.gleam:1:22 │ 1 │ import gleam/wibble.{Wibble} │ ^^^^^^ Did you mean `type Wibble`? `Wibble` is only a type, it cannot be imported as a value. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__type_used_as_a_constructor_1.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "pub fn main() -> Int() {}" --- ----- SOURCE CODE pub fn main() -> Int() {} ----- ERROR error: Type used as a type constructor ┌─ /src/one/two.gleam:1:21 │ 1 │ pub fn main() -> Int() {} │ ^^ You can remove this `Int` is a type with no parameters, but here it's being used as a type constructor. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__type_used_as_a_constructor_2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "pub fn main() {\n let a: Int() = todo\n}" --- ----- SOURCE CODE pub fn main() { let a: Int() = todo } ----- ERROR error: Type used as a type constructor ┌─ /src/one/two.gleam:2:13 │ 2 │ let a: Int() = todo │ ^^ You can remove this `Int` is a type with no parameters, but here it's being used as a type constructor. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__type_used_as_a_constructor_with_more_arguments.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "pub fn main() {\n let a: Int(Int, String) = todo\n}" --- ----- SOURCE CODE pub fn main() { let a: Int(Int, String) = todo } ----- ERROR error: Type used as a type constructor ┌─ /src/one/two.gleam:2:13 │ 2 │ let a: Int(Int, String) = todo │ ^^^^^^^^^^^^^ You can remove this `Int` is a type with no parameters, but here it's being used as a type constructor. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__type_variables_in_body.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Box(a) {\n Box(value: a)\n}\npub fn go(box1: Box(a), box2: Box(b)) {\n let _: Box(a) = box2\n let _: Box(b) = box1\n 5\n}" --- ----- SOURCE CODE pub type Box(a) { Box(value: a) } pub fn go(box1: Box(a), box2: Box(b)) { let _: Box(a) = box2 let _: Box(b) = box1 5 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:6:19 │ 6 │ let _: Box(a) = box2 │ ^^^^ Expected type: Box(a) Found type: Box(b) error: Type mismatch ┌─ /src/one/two.gleam:7:19 │ 7 │ let _: Box(b) = box1 │ ^^^^ Expected type: Box(b) Found type: Box(a) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__type_vars_must_be_declared.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type A(a) { A }\ntype B = a" --- ----- SOURCE CODE type A(a) { A } type B = a ----- ERROR error: Unknown type ┌─ /src/one/two.gleam:2:10 │ 2 │ type B = a │ ^ Did you mean `A`? The type `a` is not defined or imported in this module. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unexpected_arg_with_label_shorthand.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n fn id(x) { x }\n fn y() {\n let x = 4\n id(x:)\n }\n" --- ----- SOURCE CODE fn id(x) { x } fn y() { let x = 4 id(x:) } ----- ERROR error: Unexpected labelled argument ┌─ /src/one/two.gleam:5:12 │ 5 │ id(x:) │ ^^ This argument has been given a label but the function does not expect any. Please remove the label `x`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unexpected_labelled_arg.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn id(x) { x } fn y() { id(x: 4) }" --- ----- SOURCE CODE fn id(x) { x } fn y() { id(x: 4) } ----- ERROR error: Unexpected labelled argument ┌─ /src/one/two.gleam:1:28 │ 1 │ fn id(x) { x } fn y() { id(x: 4) } │ ^^^^ This argument has been given a label but the function does not expect any. Please remove the label `x`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unexpected_labelled_arg_record_constructor.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type X { X(Int) } fn y() { X(a: 0) }" --- ----- SOURCE CODE type X { X(Int) } fn y() { X(a: 0) } ----- ERROR error: Unexpected labelled argument ┌─ /src/one/two.gleam:1:30 │ 1 │ type X { X(Int) } fn y() { X(a: 0) } │ ^^^^ This argument has been given a label but the record constructor does not expect any. Please remove the label `a`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_accessed_type.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn(a) { a.field }" --- ----- SOURCE CODE fn(a) { a.field } ----- ERROR error: Unknown type for record access ┌─ /src/one/two.gleam:1:9 │ 1 │ fn(a) { a.field } │ ^ I don't know what type this is In order to access a record field we need to know what type it is, but I can't tell the type here. Try adding type annotations to your function and try again. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_constructor_update.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Person {\n Person(name: String, age: Int)\n}\npub fn update_person(person: Person) {\n NotAPerson(..person)\n}" --- ----- SOURCE CODE pub type Person { Person(name: String, age: Int) } pub fn update_person(person: Person) { NotAPerson(..person) } ----- ERROR error: Unknown variable ┌─ /src/one/two.gleam:6:4 │ 6 │ NotAPerson(..person) │ ^^^^^^^^^^ Did you mean `Person`? The custom type variant constructor `NotAPerson` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_field.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn(a: a) { a.field }" --- ----- SOURCE CODE fn(a: a) { a.field } ----- ERROR error: Unknown record field ┌─ /src/one/two.gleam:1:14 │ 1 │ fn(a: a) { a.field } │ ^^^^^ This field does not exist The value being accessed has this type: a It does not have any fields. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_field_that_appears_in_a_variant_has_note.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Wibble {\n Wibble(field: Int)\n Wobble(not_field: String, field: Int)\n}\n\npub fn main(wibble: Wibble) {\n wibble.field\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(field: Int) Wobble(not_field: String, field: Int) } pub fn main(wibble: Wibble) { wibble.field } ----- ERROR error: Unknown record field ┌─ /src/one/two.gleam:8:10 │ 8 │ wibble.field │ ^^^^^ This field does not exist The value being accessed has this type: Wibble It does not have fields that are common across all variants. Note: The field you are trying to access is not defined consistently across all variants of this custom type. To fix this, ensure that all variants include the field with the same name, position, and type. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_field_that_appears_in_an_imported_variant_has_note.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nimport some_mod\npub fn main(wibble: some_mod.Wibble) {\n wibble.field\n}\n" --- ----- SOURCE CODE -- some_mod.gleam pub type Wibble { Wibble(field: Int) Wobble(not_field: String, field: Int) } -- main.gleam import some_mod pub fn main(wibble: some_mod.Wibble) { wibble.field } ----- ERROR error: Unknown record field ┌─ /src/one/two.gleam:4:10 │ 4 │ wibble.field │ ^^^^^ This field does not exist The value being accessed has this type: some_mod.Wibble It does not have fields that are common across all variants. Note: The field you are trying to access is not defined consistently across all variants of this custom type. To fix this, ensure that all variants include the field with the same name, position, and type. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_field_that_does_not_appear_in_variant_has_no_note.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Wibble {\n Wibble(field: Int)\n Wobble(not_field: String, field: Int)\n}\n\npub fn main(wibble: Wibble) {\n wibble.wibble\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(field: Int) Wobble(not_field: String, field: Int) } pub fn main(wibble: Wibble) { wibble.wibble } ----- ERROR error: Unknown record field ┌─ /src/one/two.gleam:8:10 │ 8 │ wibble.wibble │ ^^^^^^ This field does not exist The value being accessed has this type: Wibble It does not have fields that are common across all variants. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_field_update.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n pub type Person {\n Person(name: String)\n }\n pub fn update_person(person: Person) {\n Person(..person, one: 5)\n }" --- ----- SOURCE CODE pub type Person { Person(name: String) } pub fn update_person(person: Person) { Person(..person, one: 5) } ----- ERROR error: Unknown record field ┌─ /src/one/two.gleam:6:21 │ 6 │ Person(..person, one: 5) │ ^^^^^^ Did you mean `name`? The value being accessed has this type: Person It has these accessible fields: .name ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_field_update2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n pub type Person {\n Person(name: String, age: Int, size: Int)\n }\n pub fn update_person(person: Person) {\n Person(..person, size: 66, one: 5, age: 3)\n }" --- ----- SOURCE CODE pub type Person { Person(name: String, age: Int, size: Int) } pub fn update_person(person: Person) { Person(..person, size: 66, one: 5, age: 3) } ----- ERROR error: Unknown record field ┌─ /src/one/two.gleam:6:31 │ 6 │ Person(..person, size: 66, one: 5, age: 3) │ ^^^^^^ This field does not exist The value being accessed has this type: Person It has these accessible fields: .age .name .size ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_imported_module_type.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nimport one/two\n\npub fn main(_x: two.Thing) {\n Nil\n}\n" --- ----- SOURCE CODE -- one/two.gleam -- main.gleam import one/two pub fn main(_x: two.Thing) { Nil } ----- ERROR error: Unknown module type ┌─ /src/one/two.gleam:4:17 │ 4 │ pub fn main(_x: two.Thing) { │ ^^^^^^^^^ The module `one/two` does not have a `Thing` type. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_label.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type X { X(a: Int, b: Float) }\nfn x() {\n let x = X(a: 1, c: 2.0)\n x\n}" --- ----- SOURCE CODE type X { X(a: Int, b: Float) } fn x() { let x = X(a: 1, c: 2.0) x } ----- ERROR error: Unknown label ┌─ /src/one/two.gleam:3:19 │ 3 │ let x = X(a: 1, c: 2.0) │ ^^^^^^ Did you mean `b`? It accepts these labels: b ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_label_shorthand.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type X { X(a: Int, b: Float) }\nfn x() {\n let c = 2.0\n let x = X(a: 1, c:)\n x\n}" --- ----- SOURCE CODE type X { X(a: Int, b: Float) } fn x() { let c = 2.0 let x = X(a: 1, c:) x } ----- ERROR error: Unknown label ┌─ /src/one/two.gleam:4:19 │ 4 │ let x = X(a: 1, c:) │ ^^ Did you mean `b`? It accepts these labels: b ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_module.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: import xpto --- ----- SOURCE CODE import xpto ----- ERROR error: Unknown module ┌─ /src/one/two.gleam:1:1 │ 1 │ import xpto │ ^^^^^^^^^^^ No module has been found with the name `xpto`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_module_suggest_import.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() {\n utils.helpful()\n}\n" --- ----- SOURCE CODE -- utils.gleam pub fn helpful() {} -- main.gleam pub fn main() { utils.helpful() } ----- ERROR error: Unknown module ┌─ /src/one/two.gleam:3:3 │ 3 │ utils.helpful() │ ^^^^^ No module has been found with the name `utils`. Hint: Did you mean to import `utils`? ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_module_suggest_typo_for_imported_module.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nimport wibble\npub fn main() {\n wible.wobble()\n}\n" --- ----- SOURCE CODE -- wibble.gleam pub fn wobble() {} -- main.gleam import wibble pub fn main() { wible.wobble() } ----- ERROR error: Unknown module ┌─ /src/one/two.gleam:4:3 │ 4 │ wible.wobble() │ ^^^^^ No module has been found with the name `wible`. Hint: Did you mean `wibble`? ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_module_suggest_typo_for_unimported_module.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub fn main() {\n woble.wubble()\n}\n" --- ----- SOURCE CODE -- wibble/wobble.gleam pub fn wubble() {} -- main.gleam pub fn main() { woble.wubble() } ----- ERROR error: Unknown module ┌─ /src/one/two.gleam:3:3 │ 3 │ woble.wubble() │ ^^^^^ No module has been found with the name `woble`. Hint: Did you mean to import `wibble/wobble` and reference `wobble`? ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_record_field.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Box(a) { Box(inner: a) }\npub fn main(box: Box(Int)) { box.unknown }\n" --- ----- SOURCE CODE pub type Box(a) { Box(inner: a) } pub fn main(box: Box(Int)) { box.unknown } ----- ERROR error: Unknown record field ┌─ /src/one/two.gleam:3:34 │ 3 │ pub fn main(box: Box(Int)) { box.unknown } │ ^^^^^^^ Did you mean `inner`? The value being accessed has this type: Box(Int) It has these accessible fields: .inner ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_record_field_2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Box(a) { Box(inner: a) }\npub fn main(box: Box(Box(Int))) { box.inner.unknown }" --- ----- SOURCE CODE pub type Box(a) { Box(inner: a) } pub fn main(box: Box(Box(Int))) { box.inner.unknown } ----- ERROR error: Unknown record field ┌─ /src/one/two.gleam:3:45 │ 3 │ pub fn main(box: Box(Box(Int))) { box.inner.unknown } │ ^^^^^^^ Did you mean `inner`? The value being accessed has this type: Box(Int) It has these accessible fields: .inner ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_type.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type Thing { Thing(unknown: x) }" --- ----- SOURCE CODE type Thing { Thing(unknown: x) } ----- ERROR error: Unknown type ┌─ /src/one/two.gleam:1:29 │ 1 │ type Thing { Thing(unknown: x) } │ ^ The type `x` is not defined or imported in this module. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_type_in_alias.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type IntMap = IllMap(Int, Int)" --- ----- SOURCE CODE type IntMap = IllMap(Int, Int) ----- ERROR error: Unknown type ┌─ /src/one/two.gleam:1:15 │ 1 │ type IntMap = IllMap(Int, Int) │ ^^^^^^^^^^^^^^^^ The type `IllMap` is not defined or imported in this module. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_type_in_alias2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "type IntMap = Map(Inf, Int)" --- ----- SOURCE CODE type IntMap = Map(Inf, Int) ----- ERROR error: Unknown type ┌─ /src/one/two.gleam:1:19 │ 1 │ type IntMap = Map(Inf, Int) │ ^^^ Did you mean `Int`? The type `Inf` is not defined or imported in this module. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_type_var_in_alias2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: type X = List(a) --- ----- SOURCE CODE type X = List(a) ----- ERROR error: Unknown type ┌─ /src/one/two.gleam:1:15 │ 1 │ type X = List(a) │ ^ The type `a` is not defined or imported in this module. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: x --- ----- SOURCE CODE x ----- ERROR error: Unknown variable ┌─ /src/one/two.gleam:1:1 │ 1 │ x │ ^ The name `x` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_2.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case 1 { x -> 1 1 -> x }" --- ----- SOURCE CODE case 1 { x -> 1 1 -> x } ----- ERROR error: Unknown variable ┌─ /src/one/two.gleam:1:22 │ 1 │ case 1 { x -> 1 1 -> x } │ ^ The name `x` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_3.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let add = fn(x, y) { x + y } 1 |> add(unknown)" --- ----- SOURCE CODE let add = fn(x, y) { x + y } 1 |> add(unknown) ----- ERROR error: Unknown variable ┌─ /src/one/two.gleam:1:39 │ 1 │ let add = fn(x, y) { x + y } 1 |> add(unknown) │ ^^^^^^^ The name `unknown` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_type.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: Int --- ----- SOURCE CODE Int ----- ERROR error: Unknown variable ┌─ /src/one/two.gleam:1:1 │ 1 │ Int │ ^^^ `Int` is a type, it cannot be used as a value. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unknown_variable_update.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Person {\n Person(name: String, age: Int)\n}\npub fn update_person() {\n Person(..person)\n}" --- ----- SOURCE CODE pub type Person { Person(name: String, age: Int) } pub fn update_person() { Person(..person) } ----- ERROR error: Unknown variable ┌─ /src/one/two.gleam:6:12 │ 6 │ Person(..person) │ ^^^^^^ Did you mean `Person`? The name `person` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__unnecessary_spread_operator.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\ntype Triple {\n Triple(a: Int, b: Int, c: Int)\n}\n\nfn main() {\n let triple = Triple(1,2,3)\n let Triple(a, b, c, ..) = triple\n}" --- ----- SOURCE CODE type Triple { Triple(a: Int, b: Int, c: Int) } fn main() { let triple = Triple(1,2,3) let Triple(a, b, c, ..) = triple } ----- ERROR error: Unnecessary spread operator ┌─ /src/one/two.gleam:8:23 │ 8 │ let Triple(a, b, c, ..) = triple │ ^^ This record has 3 fields and you have already assigned variables to all of them. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__update_multi_variant_record.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\npub type Point {\n Point2(a: Int, b: Int)\n Point3(a: Int, b: Int, c: Int)\n}\n\npub fn main() {\n Point3(..Point2(a: 1, b: 2))\n}" --- ----- SOURCE CODE pub type Point { Point2(a: Int, b: Int) Point3(a: Int, b: Int, c: Int) } pub fn main() { Point3(..Point2(a: 1, b: 2)) } ----- ERROR error: Incorrect record update ┌─ /src/one/two.gleam:8:12 │ 8 │ Point3(..Point2(a: 1, b: 2)) │ ^^^^^^^^^^^^^^^^^^ This is a `Point2` This value is a `Point2` so it cannot be used to build a `Point3`, even if they share some fields. Note: If you want to change one variant of a type into another, you should specify all fields explicitly instead of using the record update syntax. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__utf16_codepoint_javascript_target.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs assertion_line: 3282 expression: "\npub fn main() {\n let assert <> = <<10>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn main() { let assert <> = <<10>> } ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:3:18 │ 3 │ let assert <> = <<10>> │ ^^^^^^^^^^^^^^^ UTF-codepoint pattern matching is not supported The JavaScript target does not support UTF-codepoint pattern matching. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__utf32_codepoint_javascript_target.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs assertion_line: 3293 expression: "\npub fn main() {\n let assert <> = <<10>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn main() { let assert <> = <<10>> } ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:3:18 │ 3 │ let assert <> = <<10>> │ ^^^^^^^^^^^^^^^ UTF-codepoint pattern matching is not supported The JavaScript target does not support UTF-codepoint pattern matching. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__utf8_codepoint_javascript_target.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs assertion_line: 3271 expression: "\npub fn main() {\n let assert <> = <<10>>\n}\n" snapshot_kind: text --- ----- SOURCE CODE pub fn main() { let assert <> = <<10>> } ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:3:18 │ 3 │ let assert <> = <<10>> │ ^^^^^^^^^^^^^^ UTF-codepoint pattern matching is not supported The JavaScript target does not support UTF-codepoint pattern matching. See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__value_imported_as_type.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "import gleam/wibble.{type Wobble}" --- ----- SOURCE CODE -- gleam/wibble.gleam pub type Wibble { Wobble } -- main.gleam import gleam/wibble.{type Wobble} ----- ERROR error: Unknown module type ┌─ /src/one/two.gleam:1:22 │ 1 │ import gleam/wibble.{type Wobble} │ ^^^^^^^^^^^ Did you mean `Wobble`? `Wobble` is only a value, it cannot be imported as a type. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__wrong_number_of_subjects.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case 1 { _, _ -> 1 }" --- ----- SOURCE CODE case 1 { _, _ -> 1 } ----- ERROR error: Incorrect number of patterns ┌─ /src/one/two.gleam:1:10 │ 1 │ case 1 { _, _ -> 1 } │ ^^^^ Expected 1 pattern, got 2 This case expression has 1 subject, but this pattern matches 2. Each clause must have a pattern for every subject value. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__wrong_number_of_subjects_alternative_patterns.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "case 1 { _, _ | _ | _, _, _ -> 1 }" --- ----- SOURCE CODE case 1 { _, _ | _ | _, _, _ -> 1 } ----- ERROR error: Incorrect number of patterns ┌─ /src/one/two.gleam:1:10 │ 1 │ case 1 { _, _ | _ | _, _, _ -> 1 } │ ^^^^ Expected 1 pattern, got 2 This case expression has 1 subject, but this pattern matches 2. Each clause must have a pattern for every subject value. error: Incorrect number of patterns ┌─ /src/one/two.gleam:1:21 │ 1 │ case 1 { _, _ | _ | _, _, _ -> 1 } │ ^^^^^^^ Expected 1 pattern, got 3 This case expression has 1 subject, but this pattern matches 3. Each clause must have a pattern for every subject value. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__wrong_type_arg.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\nfn wibble(x: List(Int)) { x }\nfn main(y: List(something)) {\n wibble(y)\n}" --- ----- SOURCE CODE fn wibble(x: List(Int)) { x } fn main(y: List(something)) { wibble(y) } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:4:10 │ 4 │ wibble(y) │ ^ Expected type: List(Int) Found type: List(something) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__wrong_type_ret.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "pub fn main(x: something) -> Int {\n let y = x\n y\n}" --- ----- SOURCE CODE pub fn main(x: something) -> Int { let y = x y } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:3 │ 3 │ y │ ^ The type of this returned value doesn't match the return type annotation of this function. Expected type: Int Found type: something ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__wrong_type_update.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "\n pub type Person {\n Person(name: String, age: Int)\n }\n pub type Box(a) {\n Box(a)\n }\n pub fn update_person(person: Person, box: Box(a)) {\n Person(..box)\n }" --- ----- SOURCE CODE pub type Person { Person(name: String, age: Int) } pub type Box(a) { Box(a) } pub fn update_person(person: Person, box: Box(a)) { Person(..box) } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:9:13 │ 9 │ Person(..box) │ ^^^ Expected type: Person Found type: Box(a) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__wrong_type_var.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "fn wibble(x: String) { x }\nfn multi_result(x: some_name) {\n wibble(x)\n}" --- ----- SOURCE CODE fn wibble(x: String) { x } fn multi_result(x: some_name) { wibble(x) } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:10 │ 3 │ wibble(x) │ ^ Expected type: String Found type: some_name ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__errors__zero_size_pattern.snap ================================================ --- source: compiler-core/src/type_/tests/errors.rs expression: "let assert <<1:size(0)>> = <<>>" --- ----- SOURCE CODE let assert <<1:size(0)>> = <<>> ----- ERROR error: Invalid bit array segment ┌─ /src/one/two.gleam:1:16 │ 1 │ let assert <<1:size(0)>> = <<>> │ ^^^^^^^ A constant size must be a positive number See: https://tour.gleam.run/data-types/bit-arrays/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__bit_array_1.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n <<>> -> 1\n <<1>> -> 1\n <<2>> -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { <<>> -> 1 <<1>> -> 1 <<2>> -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ <<>> -> 1 5 │ │ <<1>> -> 1 6 │ │ <<2>> -> 1 7 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: _ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__bit_array_2.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n <<>> -> 1\n <<1>> -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { <<>> -> 1 <<1>> -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ <<>> -> 1 5 │ │ <<1>> -> 1 6 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: _ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__bit_array_bits_catches_everything.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "pub fn main() {\n let bit_array = <<>>\n case bit_array {\n <<_:bits>> -> 1\n <<1>> -> 2\n _ -> 2\n }\n}" --- ----- SOURCE CODE pub fn main() { let bit_array = <<>> case bit_array { <<_:bits>> -> 1 <<1>> -> 2 _ -> 2 } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:5:5 │ 5 │ <<1>> -> 2 │ ^^^^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:6:5 │ 6 │ _ -> 2 │ ^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__bit_array_bytes_needs_catch_all.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "pub fn main() {\n let bit_array = <<>>\n case bit_array {\n <<_:bytes>> -> 1\n }\n}" --- ----- SOURCE CODE pub fn main() { let bit_array = <<>> case bit_array { <<_:bytes>> -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case bit_array { 4 │ │ <<_:bytes>> -> 1 5 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: _ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__bit_array_overlapping_patterns_are_redundant.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "pub fn main() {\n let bit_array = <<>>\n case bit_array {\n <<1, a:size(16)>> -> a\n <<1, b:size(8)-unit(2)>> -> b\n _ -> 2\n }\n}" --- ----- SOURCE CODE pub fn main() { let bit_array = <<>> case bit_array { <<1, a:size(16)>> -> a <<1, b:size(8)-unit(2)>> -> b _ -> 2 } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:5:5 │ 5 │ <<1, b:size(8)-unit(2)>> -> b │ ^^^^^^^^^^^^^^^^^^^^^^^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__bit_array_overlapping_redundant_patterns_with_variable_size.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "pub fn main() {\n let bit_array = <<>>\n let len = 3\n case bit_array {\n <> -> a\n <<_:size(len), b:size(8)-unit(2)>> -> b\n _ -> 2\n }\n}" --- ----- SOURCE CODE pub fn main() { let bit_array = <<>> let len = 3 case bit_array { <> -> a <<_:size(len), b:size(8)-unit(2)>> -> b _ -> 2 } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:6:5 │ 6 │ <<_:size(len), b:size(8)-unit(2)>> -> b │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__bit_array_overlapping_redundant_patterns_with_variable_size_2.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "pub fn main() {\n let bit_array = <<>>\n case bit_array {\n <> -> 1\n <> -> 2\n _ -> 2\n }\n}" --- ----- SOURCE CODE pub fn main() { let bit_array = <<>> case bit_array { <> -> 1 <> -> 2 _ -> 2 } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:5:5 │ 5 │ <> -> 2 │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__bool_false.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n False -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { False -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ False -> 1 5 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: True ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__bool_true.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n True -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { True -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ True -> 1 5 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: False ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__case_error_prints_aliased_unqualified_value.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\nimport wibble.{Wibble, Wobble as Wubble}\npub fn main(wibble) {\n case wibble {\n Wibble -> Nil\n }\n}\n" --- ----- SOURCE CODE -- wibble.gleam pub type Wibble { Wibble Wobble } -- main.gleam import wibble.{Wibble, Wobble as Wubble} pub fn main(wibble) { case wibble { Wibble -> Nil } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:4:5 │ 4 │ ╭ case wibble { 5 │ │ Wibble -> Nil 6 │ │ } │ ╰─────^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Wubble ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__case_error_prints_module_alias.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\nimport wibble as wobble\npub fn main(wibble) {\n case wibble {\n wobble.Wibble -> Nil\n }\n}\n" --- ----- SOURCE CODE -- wibble.gleam pub type Wibble { Wibble Wobble } -- main.gleam import wibble as wobble pub fn main(wibble) { case wibble { wobble.Wibble -> Nil } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:4:5 │ 4 │ ╭ case wibble { 5 │ │ wobble.Wibble -> Nil 6 │ │ } │ ╰─────^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: wobble.Wobble ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__case_error_prints_module_names.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\nimport wibble\npub type Things { Thing1 Thing2(Int) }\npub fn main(wobble_thing) {\n case wobble_thing {\n #(wibble.Wibble, Thing1) -> Nil\n }\n}\n" --- ----- SOURCE CODE -- wibble.gleam pub type Wibble { Wibble Wobble } -- main.gleam import wibble pub type Things { Thing1 Thing2(Int) } pub fn main(wobble_thing) { case wobble_thing { #(wibble.Wibble, Thing1) -> Nil } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:5:5 │ 5 │ ╭ case wobble_thing { 6 │ │ #(wibble.Wibble, Thing1) -> Nil 7 │ │ } │ ╰─────^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: #(wibble.Wibble, Thing2(_)) #(wibble.Wobble, _) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__case_error_prints_module_when_aliased_and_shadowed.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\nimport mod.{Wibble as Wobble}\ntype Wibble { Wobble Wubble }\npub fn main() {\n let wibble = mod.Wibble\n case wibble {\n mod.Wobble -> Nil\n }\n}\n" --- ----- SOURCE CODE -- mod.gleam pub type Wibble { Wibble Wobble } -- main.gleam import mod.{Wibble as Wobble} type Wibble { Wobble Wubble } pub fn main() { let wibble = mod.Wibble case wibble { mod.Wobble -> Nil } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:6:3 │ 6 │ ╭ case wibble { 7 │ │ mod.Wobble -> Nil 8 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: mod.Wibble ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__case_error_prints_module_when_shadowed.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\nimport mod.{Wibble}\ntype Wibble { Wibble Wobble }\npub fn main() {\n let wibble = mod.Wibble\n case wibble {\n mod.Wobble -> Nil\n }\n}\n" --- ----- SOURCE CODE -- mod.gleam pub type Wibble { Wibble Wobble } -- main.gleam import mod.{Wibble} type Wibble { Wibble Wobble } pub fn main() { let wibble = mod.Wibble case wibble { mod.Wobble -> Nil } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:6:3 │ 6 │ ╭ case wibble { 7 │ │ mod.Wobble -> Nil 8 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: mod.Wibble ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__case_error_prints_prelude_module_unqualified.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(result: Result(Nil, Nil)) {\n case result {\n Ok(Nil) -> Nil\n }\n}\n" --- ----- SOURCE CODE pub fn main(result: Result(Nil, Nil)) { case result { Ok(Nil) -> Nil } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case result { 4 │ │ Ok(Nil) -> Nil 5 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Error(_) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__case_error_prints_prelude_module_when_shadowed.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\nimport gleam\ntype MyResult { Ok Error }\npub fn main(res: Result(Int, Nil)) {\n case res {\n gleam.Ok(n) -> Nil\n }\n}\n" --- ----- SOURCE CODE import gleam type MyResult { Ok Error } pub fn main(res: Result(Int, Nil)) { case res { gleam.Ok(n) -> Nil } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:5:3 │ 5 │ ╭ case res { 6 │ │ gleam.Ok(n) -> Nil 7 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: gleam.Error(_) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__case_error_prints_unqualifed_when_aliased.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\nimport mod.{Wibble as Wobble}\ntype Wibble { Wibble Wubble }\npub fn main() {\n let wibble = mod.Wibble\n case wibble {\n mod.Wobble -> Nil\n }\n}\n" --- ----- SOURCE CODE -- mod.gleam pub type Wibble { Wibble Wobble } -- main.gleam import mod.{Wibble as Wobble} type Wibble { Wibble Wubble } pub fn main() { let wibble = mod.Wibble case wibble { mod.Wobble -> Nil } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:6:3 │ 6 │ ╭ case wibble { 7 │ │ mod.Wobble -> Nil 8 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Wobble ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__case_error_prints_unqualified_value.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\nimport wibble.{Wibble, Wobble}\npub fn main(wibble) {\n case wibble {\n Wibble -> Nil\n }\n}\n" --- ----- SOURCE CODE -- wibble.gleam pub type Wibble { Wibble Wobble } -- main.gleam import wibble.{Wibble, Wobble} pub fn main(wibble) { case wibble { Wibble -> Nil } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:4:5 │ 4 │ ╭ case wibble { 5 │ │ Wibble -> Nil 6 │ │ } │ ╰─────^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Wobble ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__compiler_does_not_crash_when_defining_duplicate_alternative_variables.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\ncase todo {\n #(a, b) | #(a, a as b) -> todo\n}\n" --- ----- SOURCE CODE case todo { #(a, b) | #(a, a as b) -> todo } ----- ERROR error: Duplicate variable in pattern ┌─ /src/one/two.gleam:3:18 │ 3 │ #(a, b) | #(a, a as b) -> todo │ ^ This has already been used Variables can only be used once per pattern. This variable `a` appears multiple times. If you used the same variable twice deliberately in order to check for equality please use a guard clause instead. e.g. (x, y) if x == y -> ... ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__compiler_does_not_crash_when_matching_on_utfcodepoint.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\nimport gleam/string\n\npub fn main() {\n let assert Ok(wibble) = string.utf_codepoint(71)\n case wibble {\n }\n} " --- ----- SOURCE CODE -- gleam/string.gleam @external(erlang, "gleam_stdlib", "identity") fn unsafe_int_to_utf_codepoint(a: Int) -> UtfCodepoint pub fn utf_codepoint(value: Int) -> Result(UtfCodepoint, Nil) { case value { i if i > 1_114_111 -> Error(Nil) i if i >= 55_296 && i <= 57_343 -> Error(Nil) i if i < 0 -> Error(Nil) i -> Ok(unsafe_int_to_utf_codepoint(i)) } } -- main.gleam import gleam/string pub fn main() { let assert Ok(wibble) = string.utf_codepoint(71) case wibble { } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:6:3 │ 6 │ ╭ case wibble { 7 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: _ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__correct_missing_patterns_for_opaque_type.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\nimport mod\n\npub fn main(w: mod.Wibble) {\n case w {}\n}\n" --- ----- SOURCE CODE -- mod.gleam pub opaque type Wibble { Wibble(Int) Wobble(String) } -- main.gleam import mod pub fn main(w: mod.Wibble) { case w {} } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:5:3 │ 5 │ case w {} │ ^^^^^^^^^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: _ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__correct_missing_patterns_for_opaque_type_in_definition_module.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub opaque type Wibble { Wibble(Int) Wobble(String) }\n\npub fn main(w: Wibble) {\n case w {}\n}\n" --- ----- SOURCE CODE pub opaque type Wibble { Wibble(Int) Wobble(String) } pub fn main(w: Wibble) { case w {} } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:5:3 │ 5 │ case w {} │ ^^^^^^^^^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Wibble(_) Wobble(_) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__custom_1.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub type Type {\n One\n Two\n}\n\npub fn main(x) {\n case x {\n One -> 1\n }\n}\n" --- ----- SOURCE CODE pub type Type { One Two } pub fn main(x) { case x { One -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:8:3 │ 8 │ ╭ case x { 9 │ │ One -> 1 10 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Two ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__custom_2.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub type Type {\n One\n Two\n Three(Type)\n}\n\npub fn main(x) {\n case x {\n One -> 1\n Two -> 2\n Three(One) -> 4\n }\n}\n" --- ----- SOURCE CODE pub type Type { One Two Three(Type) } pub fn main(x) { case x { One -> 1 Two -> 2 Three(One) -> 4 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:9:3 │ 9 │ ╭ case x { 10 │ │ One -> 1 11 │ │ Two -> 2 12 │ │ Three(One) -> 4 13 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Three(Two) Three(Three(_)) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__discard_2.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub type Thing {\n Thing(a: Bool, b: Bool)\n}\n\npub fn main(x) {\n case x {\n Thing(a: True, ..) -> 1\n }\n}\n" --- ----- SOURCE CODE pub type Thing { Thing(a: Bool, b: Bool) } pub fn main(x) { case x { Thing(a: True, ..) -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:7:3 │ 7 │ ╭ case x { 8 │ │ Thing(a: True, ..) -> 1 9 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Thing(a: False, b:) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__discard_3.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub type Thing {\n Thing(a: Bool, b: Bool)\n}\n\npub fn main(x) {\n case x {\n Thing(a: False, ..) -> 1\n }\n}\n" --- ----- SOURCE CODE pub type Thing { Thing(a: Bool, b: Bool) } pub fn main(x) { case x { Thing(a: False, ..) -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:7:3 │ 7 │ ╭ case x { 8 │ │ Thing(a: False, ..) -> 1 9 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Thing(a: True, b:) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__discard_4.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub type Thing {\n Thing(a: Bool, b: Bool)\n}\n\npub fn main(x) {\n case x {\n Thing(a: True, ..) -> 1\n }\n}\n" --- ----- SOURCE CODE pub type Thing { Thing(a: Bool, b: Bool) } pub fn main(x) { case x { Thing(a: True, ..) -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:7:3 │ 7 │ ╭ case x { 8 │ │ Thing(a: True, ..) -> 1 9 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Thing(a: False, b:) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__discard_5.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub type Thing {\n Thing(a: Bool, b: Bool)\n}\n\npub fn main(x) {\n case x {\n Thing(a: False, ..) -> 1\n }\n}\n" --- ----- SOURCE CODE pub type Thing { Thing(a: Bool, b: Bool) } pub fn main(x) { case x { Thing(a: False, ..) -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:7:3 │ 7 │ ╭ case x { 8 │ │ Thing(a: False, ..) -> 1 9 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Thing(a: True, b:) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__discard_6.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub type Thing {\n Thing(a: Bool, b: Bool)\n}\n\npub fn main(x) {\n case x {\n Thing(False, ..) -> 1\n }\n}\n" --- ----- SOURCE CODE pub type Thing { Thing(a: Bool, b: Bool) } pub fn main(x) { case x { Thing(False, ..) -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:7:3 │ 7 │ ╭ case x { 8 │ │ Thing(False, ..) -> 1 9 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Thing(a: True, b:) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__duplicated_alternative_patterns.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main() {\n let x = 1\n case x {\n 2 | 2 -> 2\n _ -> panic\n }\n}\n" --- ----- SOURCE CODE pub fn main() { let x = 1 case x { 2 | 2 -> 2 _ -> panic } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:5:9 │ 5 │ 2 | 2 -> 2 │ ^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__duplicated_pattern_in_alternative.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main() {\n let x = 1\n case x {\n 2 -> x\n 1 | 2 -> x - 4\n _ -> panic\n }\n}\n" --- ----- SOURCE CODE pub fn main() { let x = 1 case x { 2 -> x 1 | 2 -> x - 4 _ -> panic } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:6:9 │ 6 │ 1 | 2 -> x - 4 │ ^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__duplicated_pattern_with_multiple_alternatives.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main() {\n let x = 1\n case x {\n 1 -> 1\n 3 -> 3\n 5 -> 5\n 1 | 2 | 3 | 4 | 5 -> x - 1\n _ -> panic\n }\n}\n" --- ----- SOURCE CODE pub fn main() { let x = 1 case x { 1 -> 1 3 -> 3 5 -> 5 1 | 2 | 3 | 4 | 5 -> x - 1 _ -> panic } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:8:5 │ 8 │ 1 | 2 | 3 | 4 | 5 -> x - 1 │ ^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:8:13 │ 8 │ 1 | 2 | 3 | 4 | 5 -> x - 1 │ ^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:8:21 │ 8 │ 1 | 2 | 3 | 4 | 5 -> x - 1 │ ^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__empty_case_of_bool.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(b: Bool) {\n case b {}\n}\n" --- ----- SOURCE CODE pub fn main(b: Bool) { case b {} } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ case b {} │ ^^^^^^^^^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: True False ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__empty_case_of_custom_type.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub type Wibble { Wibble Wobble Wubble }\npub fn main(wibble: Wibble) {\n case wibble {}\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble Wobble Wubble } pub fn main(wibble: Wibble) { case wibble {} } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:4:3 │ 4 │ case wibble {} │ ^^^^^^^^^^^^^^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Wibble Wobble Wubble ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__empty_case_of_external.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub type Thingy\n\npub fn main(x: Thingy) {\n case x {\n }\n}\n" --- ----- SOURCE CODE pub type Thingy pub fn main(x: Thingy) { case x { } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:5:3 │ 5 │ ╭ case x { 6 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: _ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__empty_case_of_float.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\nlet age = 10.6\ncase age {}\n" --- ----- SOURCE CODE let age = 10.6 case age {} ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:1 │ 3 │ case age {} │ ^^^^^^^^^^^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: _ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__empty_case_of_generic.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x: something) {\n case x {\n }\n}\n" --- ----- SOURCE CODE pub fn main(x: something) { case x { } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: _ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__empty_case_of_int.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\nlet num = 24\ncase num {}\n" --- ----- SOURCE CODE let num = 24 case num {} ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:1 │ 3 │ case num {} │ ^^^^^^^^^^^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: _ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__empty_case_of_list.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\nlet list = []\ncase list {}\n" --- ----- SOURCE CODE let list = [] case list {} ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:1 │ 3 │ case list {} │ ^^^^^^^^^^^^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: [] [_, ..] ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__empty_case_of_multi_pattern.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(a: Result(a, b), b: Bool) {\n case a, b {}\n}\n" --- ----- SOURCE CODE pub fn main(a: Result(a, b), b: Bool) { case a, b {} } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ case a, b {} │ ^^^^^^^^^^^^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Ok(_), _ Error(_), _ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__empty_case_of_string.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\nlet name = \"John Doe\"\ncase name {}\n" --- ----- SOURCE CODE let name = "John Doe" case name {} ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:1 │ 3 │ case name {} │ ^^^^^^^^^^^^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: _ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__float_1.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n 0.0 -> 1\n 1.1 -> 1\n 2.2 -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { 0.0 -> 1 1.1 -> 1 2.2 -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ 0.0 -> 1 5 │ │ 1.1 -> 1 6 │ │ 2.2 -> 1 7 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: _ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__float_2.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n 0.0 -> 1\n 1.1 -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { 0.0 -> 1 1.1 -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ 0.0 -> 1 5 │ │ 1.1 -> 1 6 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: _ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__guard.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x, y) {\n case x {\n _ if y -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x, y) { case x { _ if y -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ _ if y -> 1 5 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: _ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__guard_1.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x, y) {\n case x {\n True if y -> 1\n False -> 2\n }\n}\n" --- ----- SOURCE CODE pub fn main(x, y) { case x { True if y -> 1 False -> 2 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ True if y -> 1 5 │ │ False -> 2 6 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: True ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__inexhaustive_multi_pattern.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\nlet a = Ok(1)\nlet b = True\ncase a, b {\n Error(_), _ -> Nil\n}\n" --- ----- SOURCE CODE let a = Ok(1) let b = True case a, b { Error(_), _ -> Nil } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:4:1 │ 4 │ ╭ case a, b { 5 │ │ Error(_), _ -> Nil 6 │ │ } │ ╰─^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Ok(_), _ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__inexhaustive_multi_pattern2.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(a: Result(Int, Nil), b: Bool) {\n case a, b {\n Ok(1), True -> Nil\n }\n}\n" --- ----- SOURCE CODE pub fn main(a: Result(Int, Nil), b: Bool) { case a, b { Ok(1), True -> Nil } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case a, b { 4 │ │ Ok(1), True -> Nil 5 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Ok(_), True Ok(_), False Error(_), _ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__inexhaustive_multi_pattern3.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\nlet a = Ok(1)\nlet b = True\ncase a, b {\n _, False -> Nil\n}\n" --- ----- SOURCE CODE let a = Ok(1) let b = True case a, b { _, False -> Nil } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:4:1 │ 4 │ ╭ case a, b { 5 │ │ _, False -> Nil 6 │ │ } │ ╰─^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: _, True ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__inexhaustive_multi_pattern4.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(c: Bool) {\n let a = 12\n let b = 3.14\n case a, b, c {\n 1, 2.0, True -> Nil\n }\n}\n" --- ----- SOURCE CODE pub fn main(c: Bool) { let a = 12 let b = 3.14 case a, b, c { 1, 2.0, True -> Nil } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:5:3 │ 5 │ ╭ case a, b, c { 6 │ │ 1, 2.0, True -> Nil 7 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: _, _, True _, _, False ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__inexhaustive_multi_pattern5.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(c: Bool) {\n let a = 12\n let b = 3.14\n case a, b, c {\n 12, _, False -> Nil\n }\n}\n" --- ----- SOURCE CODE pub fn main(c: Bool) { let a = 12 let b = 3.14 case a, b, c { 12, _, False -> Nil } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:5:3 │ 5 │ ╭ case a, b, c { 6 │ │ 12, _, False -> Nil 7 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: _, _, False _, _, True ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__int_1.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n 0 -> 1\n 1 -> 1\n 2 -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { 0 -> 1 1 -> 1 2 -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ 0 -> 1 5 │ │ 1 -> 1 6 │ │ 2 -> 1 7 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: _ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__int_2.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n 0 -> 1\n 1 -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { 0 -> 1 1 -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ 0 -> 1 5 │ │ 1 -> 1 6 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: _ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__label_1.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub type Thing {\n Thing(a: Bool, b: Bool)\n}\n\npub fn main(x) {\n case x {\n Thing(a: False, b: True) -> 1\n Thing(b: False, a: True) -> 1\n }\n}\n" --- ----- SOURCE CODE pub type Thing { Thing(a: Bool, b: Bool) } pub fn main(x) { case x { Thing(a: False, b: True) -> 1 Thing(b: False, a: True) -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:7:3 │ 7 │ ╭ case x { 8 │ │ Thing(a: False, b: True) -> 1 9 │ │ Thing(b: False, a: True) -> 1 10 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Thing(a: True, b: True) Thing(a: False, b: False) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__let_1.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n let True = x\n 0\n}\n" --- ----- SOURCE CODE pub fn main(x) { let True = x 0 } ----- ERROR error: Inexhaustive pattern ┌─ /src/one/two.gleam:3:3 │ 3 │ let True = x │ ^^^^^^^^^^^^ This assignment uses a pattern that does not match all possible values. If one of the other values is used then the assignment will crash. The missing patterns are: False Hint: Use a more general pattern or use `let assert` instead. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__list_bool_1.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n [] -> 1\n [True] -> 2\n [_, _, ..] -> 2\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { [] -> 1 [True] -> 2 [_, _, ..] -> 2 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ [] -> 1 5 │ │ [True] -> 2 6 │ │ [_, _, ..] -> 2 7 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: [False] ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__list_bool_2.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n [] -> 1\n [True] -> 2\n [_, False] -> 2\n [_, _, _, ..] -> 2\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { [] -> 1 [True] -> 2 [_, False] -> 2 [_, _, _, ..] -> 2 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ [] -> 1 5 │ │ [True] -> 2 6 │ │ [_, False] -> 2 7 │ │ [_, _, _, ..] -> 2 8 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: [False] [_, True] ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__list_empty.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n [] -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { [] -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ [] -> 1 5 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: [_, ..] ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__list_non_empty.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n [_, ..] -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { [_, ..] -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ [_, ..] -> 1 5 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: [] ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__list_one.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n [_] -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { [_] -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ [_] -> 1 5 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: [] [_, _, ..] ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__list_one_two.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n [_] -> 1\n [_, _] -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { [_] -> 1 [_, _] -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ [_] -> 1 5 │ │ [_, _] -> 1 6 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: [] [_, _, _, ..] ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__list_zero_one_two.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n [] -> 1\n [_] -> 1\n [_, _] -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { [] -> 1 [_] -> 1 [_, _] -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ [] -> 1 5 │ │ [_] -> 1 6 │ │ [_, _] -> 1 7 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: [_, _, _, ..] ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__list_zero_two_any.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n [] -> 1\n [_, _] -> 1\n [_, _, ..] -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { [] -> 1 [_, _] -> 1 [_, _, ..] -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ [] -> 1 5 │ │ [_, _] -> 1 6 │ │ [_, _, ..] -> 1 7 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: [_] ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__multiple_unreachable_prefix_patterns.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "pub fn main() {\n let string = \"\"\n case string {\n \"wib\" <> rest -> rest\n \"wibble\" <> rest -> rest\n \"wibblest\" <> rest -> rest\n _ -> \"a\"\n }\n}" --- ----- SOURCE CODE pub fn main() { let string = "" case string { "wib" <> rest -> rest "wibble" <> rest -> rest "wibblest" <> rest -> rest _ -> "a" } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:5:5 │ 5 │ "wibble" <> rest -> rest │ ^^^^^^^^^^^^^^^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:6:5 │ 6 │ "wibblest" <> rest -> rest │ ^^^^^^^^^^^^^^^^^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__multiple_unreachable_prefix_patterns_1.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "pub fn main() {\n let string = \"\"\n case string {\n \"wib\" <> rest if True -> rest\n \"wibble\" <> rest -> rest\n \"wibblest\" <> rest -> rest\n _ -> \"a\"\n }\n}" --- ----- SOURCE CODE pub fn main() { let string = "" case string { "wib" <> rest if True -> rest "wibble" <> rest -> rest "wibblest" <> rest -> rest _ -> "a" } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:6:5 │ 6 │ "wibblest" <> rest -> rest │ ^^^^^^^^^^^^^^^^^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__nested_type_parameter_usage.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub type Returned(a) {\n Returned(List(a))\n}\n\nfn wibble(user: Returned(#())) -> Int {\n let Returned([#()]) = user\n 1\n}\n" --- ----- SOURCE CODE pub type Returned(a) { Returned(List(a)) } fn wibble(user: Returned(#())) -> Int { let Returned([#()]) = user 1 } ----- ERROR error: Inexhaustive pattern ┌─ /src/one/two.gleam:7:3 │ 7 │ let Returned([#()]) = user │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ This assignment uses a pattern that does not match all possible values. If one of the other values is used then the assignment will crash. The missing patterns are: Returned([]) Returned([#(), _, ..]) Hint: Use a more general pattern or use `let assert` instead. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__other_variant_unreachable_when_inferred.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub type Wibble {\n Wibble\n Wobble\n}\n\npub fn main() {\n let always_wobble = Wobble\n case always_wobble {\n Wibble -> panic\n Wobble -> Nil\n }\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble Wobble } pub fn main() { let always_wobble = Wobble case always_wobble { Wibble -> panic Wobble -> Nil } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:10:5 │ 10 │ Wibble -> panic │ ^^^^^^ This pattern cannot be reached as it matches on a variant of a type which is never present. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__other_variant_unreachable_when_inferred2.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub type Wibble {\n Wibble\n Wobble\n Wubble\n}\n\npub fn main() {\n let always_wobble = Wobble\n case always_wobble {\n Wibble | Wubble -> panic\n Wobble -> Nil\n }\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble Wobble Wubble } pub fn main() { let always_wobble = Wobble case always_wobble { Wibble | Wubble -> panic Wobble -> Nil } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:11:5 │ 11 │ Wibble | Wubble -> panic │ ^^^^^^ This pattern cannot be reached as it matches on a variant of a type which is never present. Hint: It can be safely removed. warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:11:14 │ 11 │ Wibble | Wubble -> panic │ ^^^^^^ This pattern cannot be reached as it matches on a variant of a type which is never present. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_1.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n _ -> 1\n _ -> 2\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { _ -> 1 _ -> 2 } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:5:5 │ 5 │ _ -> 2 │ ^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_2.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n True -> 1\n False -> 2\n True -> 3\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { True -> 1 False -> 2 True -> 3 } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:6:5 │ 6 │ True -> 3 │ ^^^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_3.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\ncase x {\n59 -> \"gleew\"\n14 -> \"glabber\"\n1 -> \"\"\n_ -> \"glooper\"\n2 -> \"\"\n3 -> \"glen\"\n4 -> \"glew\"\n}\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { 59 -> "gleew" 14 -> "glabber" 1 -> "" _ -> "glooper" 2 -> "" 3 -> "glen" 4 -> "glew" } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:8:1 │ 8 │ 2 -> "" │ ^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:9:1 │ 9 │ 3 -> "glen" │ ^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:10:1 │ 10 │ 4 -> "glew" │ ^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_4.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\ncase x {\n\"P\" -> 4\n_ -> 3\n\"geeper!\" -> 5\n}\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { "P" -> 4 _ -> 3 "geeper!" -> 5 } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:6:1 │ 6 │ "geeper!" -> 5 │ ^^^^^^^^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_5.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\ncase x {\n\"P\" -> 4\n\"\" -> 65\n\"P\" -> 19\n_ -> 3\n}\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { "P" -> 4 "" -> 65 "P" -> 19 _ -> 3 } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:6:1 │ 6 │ "P" -> 19 │ ^^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_float_scientific_notation.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n 10.0 -> \"ten\"\n 1.0e1 -> \"also ten\"\n _ -> \"other\"\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { 10.0 -> "ten" 1.0e1 -> "also ten" _ -> "other" } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:5:5 │ 5 │ 1.0e1 -> "also ten" │ ^^^^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_float_scientific_notation_and_underscore.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n 1.0e2 -> \"one hundred\"\n 1_0_0.0 -> \"one hundred again\"\n _ -> \"other\"\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { 1.0e2 -> "one hundred" 1_0_0.0 -> "one hundred again" _ -> "other" } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:5:5 │ 5 │ 1_0_0.0 -> "one hundred again" │ ^^^^^^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_float_with_different_formatting.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n 1.0 -> \"one\"\n 1.00 -> \"also one\"\n _ -> \"other\"\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { 1.0 -> "one" 1.00 -> "also one" _ -> "other" } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:5:5 │ 5 │ 1.00 -> "also one" │ ^^^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_float_with_no_trailing_decimal.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n 1.0 -> \"one\"\n 1. -> \"another one\"\n _ -> \"other\"\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { 1.0 -> "one" 1. -> "another one" _ -> "other" } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:5:5 │ 5 │ 1. -> "another one" │ ^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_float_with_underscore.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n 10.0 -> \"ten\"\n 1_0.0 -> \"also ten\"\n _ -> \"other\"\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { 10.0 -> "ten" 1_0.0 -> "also ten" _ -> "other" } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:5:5 │ 5 │ 1_0.0 -> "also ten" │ ^^^^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_int_with_multiple_underscores.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n 1_000_000 -> \"one million\"\n 1000000 -> \"also one million\"\n _ -> \"other\"\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { 1_000_000 -> "one million" 1000000 -> "also one million" _ -> "other" } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:5:5 │ 5 │ 1000000 -> "also one million" │ ^^^^^^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_int_with_underscores.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n 10 -> \"ten\"\n 1_0 -> \"also ten\"\n _ -> \"other\"\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { 10 -> "ten" 1_0 -> "also ten" _ -> "other" } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:5:5 │ 5 │ 1_0 -> "also ten" │ ^^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_missing_patterns.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\nfn wibble(b: Bool, i: Int) {\n case b, i {\n False, 1 -> todo\n True, 2 -> todo\n }\n}\n\npub fn main() { wibble(False, 1) }" --- ----- SOURCE CODE fn wibble(b: Bool, i: Int) { case b, i { False, 1 -> todo True, 2 -> todo } } pub fn main() { wibble(False, 1) } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case b, i { 4 │ │ False, 1 -> todo 5 │ │ True, 2 -> todo 6 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: True, _ False, _ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__reference_absent_type.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\ntype Wibble {\n One(Int)\n Two(Absent)\n}\n\npub fn main(wibble) {\n case wibble {\n One(x) -> x\n }\n}\n" --- ----- SOURCE CODE type Wibble { One(Int) Two(Absent) } pub fn main(wibble) { case wibble { One(x) -> x } } ----- ERROR error: Unknown type ┌─ /src/one/two.gleam:4:9 │ 4 │ Two(Absent) │ ^^^^^^ The type `Absent` is not defined or imported in this module. error: Private type used in public interface ┌─ /src/one/two.gleam:7:1 │ 7 │ pub fn main(wibble) { │ ^^^^^^^^^^^^^^^^^^^ The following type is private, but is being used by this public export. Wibble Private types can only be used within the module that defines them. error: Inexhaustive patterns ┌─ /src/one/two.gleam:8:5 │ 8 │ ╭ case wibble { 9 │ │ One(x) -> x 10 │ │ } │ ╰─────^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Two(_) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__result_bool_1.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n Ok(False) -> 1\n Error(True) -> 2\n Error(False) -> 3\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { Ok(False) -> 1 Error(True) -> 2 Error(False) -> 3 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ Ok(False) -> 1 5 │ │ Error(True) -> 2 6 │ │ Error(False) -> 3 7 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Ok(True) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__result_bool_2.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n Ok(True) -> 1\n Error(True) -> 2\n Error(False) -> 3\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { Ok(True) -> 1 Error(True) -> 2 Error(False) -> 3 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ Ok(True) -> 1 5 │ │ Error(True) -> 2 6 │ │ Error(False) -> 3 7 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Ok(False) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__result_bool_3.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n Ok(True) -> 1\n Ok(False) -> 2\n Error(False) -> 3\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { Ok(True) -> 1 Ok(False) -> 2 Error(False) -> 3 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ Ok(True) -> 1 5 │ │ Ok(False) -> 2 6 │ │ Error(False) -> 3 7 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Error(True) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__result_bool_4.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n Ok(True) -> 1\n Ok(False) -> 2\n Error(True) -> 3\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { Ok(True) -> 1 Ok(False) -> 2 Error(True) -> 3 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ Ok(True) -> 1 5 │ │ Ok(False) -> 2 6 │ │ Error(True) -> 3 7 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Error(False) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__result_bool_5.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n Ok(True) -> 1\n Ok(False) -> 2\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { Ok(True) -> 1 Ok(False) -> 2 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ Ok(True) -> 1 5 │ │ Ok(False) -> 2 6 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Error(_) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__result_bool_6.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n Error(True) -> 1\n Error(False) -> 2\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { Error(True) -> 1 Error(False) -> 2 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ Error(True) -> 1 5 │ │ Error(False) -> 2 6 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Ok(_) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__result_bool_7.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n Error(True) -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { Error(True) -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ Error(True) -> 1 5 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Error(False) Ok(_) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__result_bool_8.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n Ok(False) -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { Ok(False) -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ Ok(False) -> 1 5 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Ok(True) Error(_) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__result_error.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n Error(_) -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { Error(_) -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ Error(_) -> 1 5 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Ok(_) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__result_nil_error.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n Error(Nil) -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { Error(Nil) -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ Error(Nil) -> 1 5 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Ok(_) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__result_nil_ok.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n Ok(Nil) -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { Ok(Nil) -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ Ok(Nil) -> 1 5 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Error(_) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__result_ok.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n Ok(_) -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { Ok(_) -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ Ok(_) -> 1 5 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: Error(_) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__same_catch_all_bytes_are_redundant.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "pub fn main() {\n let bit_array = <<>>\n case bit_array {\n <<_:bytes>> -> <<>>\n <> -> a\n _ -> <<>>\n }\n}" --- ----- SOURCE CODE pub fn main() { let bit_array = <<>> case bit_array { <<_:bytes>> -> <<>> <> -> a _ -> <<>> } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:5:5 │ 5 │ <> -> a │ ^^^^^^^^^^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__string_1.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n \"\" -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { "" -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ "" -> 1 5 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: _ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__string_2.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n \"a\" -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { "a" -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ "a" -> 1 5 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: _ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__string_3.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x) {\n case x {\n \"a\" -> 1\n \"b\" -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x) { case x { "a" -> 1 "b" -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ "a" -> 1 5 │ │ "b" -> 1 6 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: _ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__tuple_0.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main(x, y) {\n case #(x, y) {\n #(True, _) -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main(x, y) { case #(x, y) { #(True, _) -> 1 } } ----- ERROR error: Inexhaustive patterns ┌─ /src/one/two.gleam:3:3 │ 3 │ ╭ case #(x, y) { 4 │ │ #(True, _) -> 1 5 │ │ } │ ╰───^ This case expression does not have a pattern for all possible values. If it is run on one of the values without a pattern then it will crash. The missing patterns are: #(False, _) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__unreachable_alternative_multi_pattern.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main() {\n let x = 1\n let y = 2\n case x, y {\n 1, 2 -> True\n 3, 4 | 1, 2 -> False\n _, _ -> panic\n }\n}\n" --- ----- SOURCE CODE pub fn main() { let x = 1 let y = 2 case x, y { 1, 2 -> True 3, 4 | 1, 2 -> False _, _ -> panic } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:7:12 │ 7 │ 3, 4 | 1, 2 -> False │ ^^^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__unreachable_multi_pattern.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "\npub fn main() {\n let x = 1\n let y = 2\n case x, y {\n 1, 2 -> True\n 1, 2 -> False\n _, _ -> panic\n }\n}\n" --- ----- SOURCE CODE pub fn main() { let x = 1 let y = 2 case x, y { 1, 2 -> True 1, 2 -> False _, _ -> panic } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:7:5 │ 7 │ 1, 2 -> False │ ^^^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__unreachable_prefix_pattern_after_prefix.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "pub fn main() {\n let string = \"\"\n case string {\n \"wib\" <> rest -> rest\n \"wibble\" <> rest -> rest\n _ -> \"a\"\n }\n}" --- ----- SOURCE CODE pub fn main() { let string = "" case string { "wib" <> rest -> rest "wibble" <> rest -> rest _ -> "a" } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:5:5 │ 5 │ "wibble" <> rest -> rest │ ^^^^^^^^^^^^^^^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__unreachable_string_pattern_after_prefix.snap ================================================ --- source: compiler-core/src/type_/tests/exhaustiveness.rs expression: "pub fn main() {\n let string = \"\"\n case string {\n \"wib\" <> rest -> rest\n \"wibble\" -> \"a\"\n _ -> \"b\"\n }\n}" --- ----- SOURCE CODE pub fn main() { let string = "" case string { "wib" <> rest -> rest "wibble" -> "a" _ -> "b" } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:5:5 │ 5 │ "wibble" -> "a" │ ^^^^^^^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__externals__erlang_only_function_used_by_javascript_module.snap ================================================ --- source: compiler-core/src/type_/tests/externals.rs expression: "@external(erlang, \"one\", \"two\")\nfn erlang_only() -> Int\n\npub fn main() {\n erlang_only()\n}\n" --- ----- SOURCE CODE @external(erlang, "one", "two") fn erlang_only() -> Int pub fn main() { erlang_only() } ----- ERROR error: Unsupported target ┌─ /src/one/two.gleam:5:3 │ 5 │ erlang_only() │ ^^^^^^^^^^^ This value is not available as it is defined using externals, and there is no implementation for the JavaScript target. Hint: Did you mean to build for a different target? ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__externals__erlang_only_function_with_erlang_external.snap ================================================ --- source: compiler-core/src/type_/tests/externals.rs expression: "@external(erlang, \"one\", \"two\")\nfn erlang_only() -> Int\n\n@external(erlang, \"one\", \"two\")\npub fn uh_oh() -> Int {\n erlang_only()\n}\n" --- ----- SOURCE CODE @external(erlang, "one", "two") fn erlang_only() -> Int @external(erlang, "one", "two") pub fn uh_oh() -> Int { erlang_only() } ----- ERROR error: Unsupported target ┌─ /src/one/two.gleam:6:3 │ 6 │ erlang_only() │ ^^^^^^^^^^^ This value is not available as it is defined using externals, and there is no implementation for the JavaScript target. Hint: Did you mean to build for a different target? ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__externals__erlang_targeted_function_cant_contain_javascript_only_function.snap ================================================ --- source: compiler-core/src/type_/tests/externals.rs expression: "@target(erlang)\npub fn erlang_only() -> Int {\n javascript_only()\n}\n\n@external(javascript, \"one\", \"two\")\nfn javascript_only() -> Int\n " --- ----- SOURCE CODE @target(erlang) pub fn erlang_only() -> Int { javascript_only() } @external(javascript, "one", "two") fn javascript_only() -> Int ----- ERROR error: Unsupported target ┌─ /src/one/two.gleam:3:3 │ 3 │ javascript_only() │ ^^^^^^^^^^^^^^^ This value is not available as it is defined using externals, and there is no implementation for the Erlang target. Hint: Did you mean to build for a different target? ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__externals__imported_javascript_only_function.snap ================================================ --- source: compiler-core/src/type_/tests/externals.rs expression: "import module\npub fn main() {\n module.javascript_only()\n}" --- ----- SOURCE CODE -- module.gleam @external(javascript, "one", "two") pub fn javascript_only() -> Int -- main.gleam import module pub fn main() { module.javascript_only() } ----- ERROR error: Unsupported target ┌─ /src/one/two.gleam:3:10 │ 3 │ module.javascript_only() │ ^^^^^^^^^^^^^^^ This value is not available as it is defined using externals, and there is no implementation for the Erlang target. Hint: Did you mean to build for a different target? ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__externals__javascript_only_constant.snap ================================================ --- source: compiler-core/src/type_/tests/externals.rs expression: "import module\npub fn main() {\n module.javascript_only_constant()\n}" --- ----- SOURCE CODE -- module.gleam @external(javascript, "one", "two") fn javascript_only() -> Int const constant = javascript_only pub const javascript_only_constant = constant -- main.gleam import module pub fn main() { module.javascript_only_constant() } ----- ERROR error: Unsupported target ┌─ /src/one/two.gleam:3:10 │ 3 │ module.javascript_only_constant() │ ^^^^^^^^^^^^^^^^^^^^^^^^ This value is not available as it is defined using externals, and there is no implementation for the Erlang target. Hint: Did you mean to build for a different target? ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__externals__javascript_only_function_used_by_erlang_module.snap ================================================ --- source: compiler-core/src/type_/tests/externals.rs expression: "@external(javascript, \"one\", \"two\")\nfn js_only() -> Int\n\npub fn main() {\n js_only()\n}\n" --- ----- SOURCE CODE @external(javascript, "one", "two") fn js_only() -> Int pub fn main() { js_only() } ----- ERROR error: Unsupported target ┌─ /src/one/two.gleam:5:3 │ 5 │ js_only() │ ^^^^^^^ This value is not available as it is defined using externals, and there is no implementation for the Erlang target. Hint: Did you mean to build for a different target? ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__externals__javascript_only_function_with_javascript_external.snap ================================================ --- source: compiler-core/src/type_/tests/externals.rs expression: "@external(javascript, \"one\", \"two\")\nfn javascript_only() -> Int\n\n@external(javascript, \"one\", \"two\")\npub fn uh_oh() -> Int {\n javascript_only()\n}\n" --- ----- SOURCE CODE @external(javascript, "one", "two") fn javascript_only() -> Int @external(javascript, "one", "two") pub fn uh_oh() -> Int { javascript_only() } ----- ERROR error: Unsupported target ┌─ /src/one/two.gleam:6:3 │ 6 │ javascript_only() │ ^^^^^^^^^^^^^^^ This value is not available as it is defined using externals, and there is no implementation for the Erlang target. Hint: Did you mean to build for a different target? ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__externals__javascript_targeted_function_cant_contain_erlang_only_function.snap ================================================ --- source: compiler-core/src/type_/tests/externals.rs expression: "@target(javascript)\npub fn javascript_only() -> Int {\n erlang_only()\n}\n\n@external(erlang, \"one\", \"two\")\nfn erlang_only() -> Int\n " --- ----- SOURCE CODE @target(javascript) pub fn javascript_only() -> Int { erlang_only() } @external(erlang, "one", "two") fn erlang_only() -> Int ----- ERROR error: Unsupported target ┌─ /src/one/two.gleam:3:3 │ 3 │ erlang_only() │ ^^^^^^^^^^^ This value is not available as it is defined using externals, and there is no implementation for the JavaScript target. Hint: Did you mean to build for a different target? ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__externals__public_erlang_external.snap ================================================ --- source: compiler-core/src/type_/tests/externals.rs expression: "@external(erlang, \"one\", \"two\")\npub fn main() -> Int\n" --- ----- SOURCE CODE @external(erlang, "one", "two") pub fn main() -> Int ----- ERROR error: Unsupported target ┌─ /src/one/two.gleam:2:1 │ 2 │ pub fn main() -> Int │ ^^^^^^^^^^^^^ The `main` function is public but doesn't have an implementation for the JavaScript target. All public functions of a package must be able to compile for a module to be valid. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__externals__public_javascript_external.snap ================================================ --- source: compiler-core/src/type_/tests/externals.rs expression: "@external(javascript, \"one\", \"two\")\npub fn main() -> Int\n" --- ----- SOURCE CODE @external(javascript, "one", "two") pub fn main() -> Int ----- ERROR error: Unsupported target ┌─ /src/one/two.gleam:2:1 │ 2 │ pub fn main() -> Int │ ^^^^^^^^^^^^^ The `main` function is public but doesn't have an implementation for the Erlang target. All public functions of a package must be able to compile for a module to be valid. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__externals__unsupported_target_for_unused_import.snap ================================================ --- source: compiler-core/src/type_/tests/externals.rs expression: "import mod.{wobble}" --- ----- SOURCE CODE -- mod.gleam @external(javascript, "wibble", "wobble") pub fn wobble() -- main.gleam import mod.{wobble} ----- ERROR error: Unsupported target ┌─ /src/one/two.gleam:1:13 │ 1 │ import mod.{wobble} │ ^^^^^^ This value is not available as it is defined using externals, and there is no implementation for the Erlang target. Hint: Did you mean to build for a different target? ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__annotation_mismatch_function_fault_tolerance.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\npub fn bad(x: Int) -> Float {\n // This does not match the return annotation\n 1\n}\n\npub fn user() -> Float {\n // This checks that the bad function is still usable, the types coming from\n // its annotations. This function is valid.\n bad(1)\n}\n\n// Another bad function to make sure that analysis has not stopped. This error\n// should also be emitted.\npub fn bad_2() {\n bad(Nil)\n}\n" --- ----- SOURCE CODE pub fn bad(x: Int) -> Float { // This does not match the return annotation 1 } pub fn user() -> Float { // This checks that the bad function is still usable, the types coming from // its annotations. This function is valid. bad(1) } // Another bad function to make sure that analysis has not stopped. This error // should also be emitted. pub fn bad_2() { bad(Nil) } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:4:3 │ 4 │ 1 │ ^ The type of this returned value doesn't match the return type annotation of this function. Expected type: Float Found type: Int error: Type mismatch ┌─ /src/one/two.gleam:16:7 │ 16 │ bad(Nil) │ ^^^ Expected type: Int Found type: Nil ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__bad_body_function_fault_tolerance.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\npub fn bad(x: Int) -> Float {\n // Invalid body.\n \"\" + \"\"\n}\n\npub fn user() -> Float {\n // This checks that the bad function is still usable, the types coming from\n // its annotations. This function is valid.\n bad(1)\n}\n\n// Another bad function to make sure that analysis has not stopped. This error\n// should also be emitted.\npub fn bad_2() {\n bad(Nil)\n}\n" --- ----- SOURCE CODE pub fn bad(x: Int) -> Float { // Invalid body. "" + "" } pub fn user() -> Float { // This checks that the bad function is still usable, the types coming from // its annotations. This function is valid. bad(1) } // Another bad function to make sure that analysis has not stopped. This error // should also be emitted. pub fn bad_2() { bad(Nil) } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:4:3 │ 4 │ "" + "" │ ^^^^^^^ The type of this returned value doesn't match the return type annotation of this function. Expected type: Float Found type: Int error: Type mismatch ┌─ /src/one/two.gleam:4:6 │ 4 │ "" + "" │ ^ Use <> instead The + operator can only be used on Ints. To join two strings together you can use the <> operator. error: Type mismatch ┌─ /src/one/two.gleam:16:7 │ 16 │ bad(Nil) │ ^^^ Expected type: Int Found type: Nil ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__case_clause_guard_fault_tolerance.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\npub fn main() {\n let wibble = True\n case wibble {\n a if a == Wibble -> 0\n b if b == Wibble -> 0\n _ -> 1\n }\n}\n" --- ----- SOURCE CODE pub fn main() { let wibble = True case wibble { a if a == Wibble -> 0 b if b == Wibble -> 0 _ -> 1 } } ----- ERROR error: Unknown variable ┌─ /src/one/two.gleam:5:15 │ 5 │ a if a == Wibble -> 0 │ ^^^^^^ Did you mean `wibble`? The custom type variant constructor `Wibble` is not in scope here. error: Unknown variable ┌─ /src/one/two.gleam:6:15 │ 6 │ b if b == Wibble -> 0 │ ^^^^^^ Did you mean `wibble`? The custom type variant constructor `Wibble` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__case_clause_pattern_fault_tolerance.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\npub fn main() {\n let wibble = True\n case wibble {\n True -> 0\n Wibble -> 1\n Wibble2 -> 2\n _ -> 3\n }\n}\n" --- ----- SOURCE CODE pub fn main() { let wibble = True case wibble { True -> 0 Wibble -> 1 Wibble2 -> 2 _ -> 3 } } ----- ERROR error: Unknown variable ┌─ /src/one/two.gleam:6:5 │ 6 │ Wibble -> 1 │ ^^^^^^ Did you mean `wibble`? The custom type variant constructor `Wibble` is not in scope here. error: Unknown variable ┌─ /src/one/two.gleam:7:5 │ 7 │ Wibble2 -> 2 │ ^^^^^^^ Did you mean `wibble`? The custom type variant constructor `Wibble2` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__case_clause_then_fault_tolerance.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\npub fn main() {\n let wibble = True\n case wibble {\n True -> {\n 1.0 + 1.0\n }\n _ -> {\n 1.0 + 1.0\n }\n }\n}\n" --- ----- SOURCE CODE pub fn main() { let wibble = True case wibble { True -> { 1.0 + 1.0 } _ -> { 1.0 + 1.0 } } } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:6:11 │ 6 │ 1.0 + 1.0 │ ^ Use +. instead The + operator can only be used on Ints. error: Type mismatch ┌─ /src/one/two.gleam:9:11 │ 9 │ 1.0 + 1.0 │ ^ Use +. instead The + operator can only be used on Ints. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__function_call_incorrect_arg_types_fault_tolerance.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\nfn add(x: Int, y: Int) {\n x + y\n}\n\npub fn main() {\n add(1.0, 1.0)\n}\n" --- ----- SOURCE CODE fn add(x: Int, y: Int) { x + y } pub fn main() { add(1.0, 1.0) } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:7:7 │ 7 │ add(1.0, 1.0) │ ^^^ Expected type: Int Found type: Float error: Type mismatch ┌─ /src/one/two.gleam:7:12 │ 7 │ add(1.0, 1.0) │ ^^^ Expected type: Int Found type: Float ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__function_call_incorrect_arity_fault_tolerance.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\nfn add(x: Int, y: Int) {\n x + y\n}\n\npub fn main() {\n add(1.0)\n}\n" --- ----- SOURCE CODE fn add(x: Int, y: Int) { x + y } pub fn main() { add(1.0) } ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:7:3 │ 7 │ add(1.0) │ ^^^^^^^^ Expected 2 arguments, got 1 error: Type mismatch ┌─ /src/one/two.gleam:7:7 │ 7 │ add(1.0) │ ^^^ Expected type: Int Found type: Float ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__function_call_incorrect_arity_with_label_shorthand_fault_tolerance.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\nfn wibble(wibble arg1: fn() -> Int, wobble arg2: Int) -> Int {\n arg1() + arg2\n}\n\npub fn main() {\n let wobble = \"\"\n wibble(wobble:)\n}\n" --- ----- SOURCE CODE fn wibble(wibble arg1: fn() -> Int, wobble arg2: Int) -> Int { arg1() + arg2 } pub fn main() { let wobble = "" wibble(wobble:) } ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:8:3 │ 8 │ wibble(wobble:) │ ^^^^^^^^^^^^^^^ Expected 2 arguments, got 1 This call accepts these additional labelled arguments: - wibble ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__function_call_incorrect_arity_with_label_shorthand_fault_tolerance2.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\nfn wibble(wibble arg1: fn() -> Int, wobble arg2: Int, wabble arg3: Int) -> Int {\n arg1() + arg2 + arg3\n}\n\npub fn main() {\n let wobble = \"\"\n wibble(fn() {\"\"}, wobble:)\n}\n" --- ----- SOURCE CODE fn wibble(wibble arg1: fn() -> Int, wobble arg2: Int, wabble arg3: Int) -> Int { arg1() + arg2 + arg3 } pub fn main() { let wobble = "" wibble(fn() {""}, wobble:) } ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:8:3 │ 8 │ wibble(fn() {""}, wobble:) │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ Expected 3 arguments, got 2 This call accepts these additional labelled arguments: - wabble error: Type mismatch ┌─ /src/one/two.gleam:8:10 │ 8 │ wibble(fn() {""}, wobble:) │ ^^^^^^^^^ Expected type: fn() -> Int Found type: fn() -> String ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__function_call_incorrect_arity_with_labels_fault_tolerance.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\nfn wibble(wibble arg1: fn() -> Int, wobble arg2: Int) -> Int {\n arg1() + arg2\n}\n\npub fn main() {\n wibble(wobble: \"\")\n}\n" --- ----- SOURCE CODE fn wibble(wibble arg1: fn() -> Int, wobble arg2: Int) -> Int { arg1() + arg2 } pub fn main() { wibble(wobble: "") } ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:7:3 │ 7 │ wibble(wobble: "") │ ^^^^^^^^^^^^^^^^^^ Expected 2 arguments, got 1 This call accepts these additional labelled arguments: - wibble ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__function_call_incorrect_arity_with_labels_fault_tolerance2.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\nfn wibble(wibble arg1: fn() -> Int, wobble arg2: Int, wabble arg3: Int) -> Int {\n arg1() + arg2 + arg3\n}\n\npub fn main() {\n wibble(fn() {\"\"}, wobble: \"\")\n}\n" --- ----- SOURCE CODE fn wibble(wibble arg1: fn() -> Int, wobble arg2: Int, wabble arg3: Int) -> Int { arg1() + arg2 + arg3 } pub fn main() { wibble(fn() {""}, wobble: "") } ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:7:3 │ 7 │ wibble(fn() {""}, wobble: "") │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Expected 3 arguments, got 2 This call accepts these additional labelled arguments: - wabble error: Type mismatch ┌─ /src/one/two.gleam:7:10 │ 7 │ wibble(fn() {""}, wobble: "") │ ^^^^^^^^^ Expected type: fn() -> Int Found type: fn() -> String ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__invalid_javascript_external_do_not_stop_analysis.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\n@external(javascript, \"somemodule\", \"() => 123\")\npub fn one() -> Nil {\n Nil\n}\n\npub fn two() -> Nil {\n \"\"\n}\n" --- ----- SOURCE CODE @external(javascript, "somemodule", "() => 123") pub fn one() -> Nil { Nil } pub fn two() -> Nil { "" } ----- ERROR error: Invalid JavaScript function ┌─ /src/one/two.gleam:3:1 │ 3 │ pub fn one() -> Nil { │ ^^^^^^^^^^^^^^^^^^^ The function `one` has an external JavaScript implementation but the function name `() => 123` is not valid. error: Type mismatch ┌─ /src/one/two.gleam:8:3 │ 8 │ "" │ ^^ The type of this returned value doesn't match the return type annotation of this function. Expected type: Nil Found type: String ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__multiple_bad_statement_assignment_fault_tolerance.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\npub fn main() {\n let a = 1 + 2.0\n let b = 3 + 4.0\n let c = a + b\n}\n" --- ----- SOURCE CODE pub fn main() { let a = 1 + 2.0 let b = 3 + 4.0 let c = a + b } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:15 │ 3 │ let a = 1 + 2.0 │ ^^^ The + operator expects arguments of this type: Int But this argument has this type: Float Hint: the +. operator can be used with Floats error: Type mismatch ┌─ /src/one/two.gleam:4:15 │ 4 │ let b = 3 + 4.0 │ ^^^ The + operator expects arguments of this type: Int But this argument has this type: Float Hint: the +. operator can be used with Floats ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__multiple_bad_statement_assignment_with_annotation_fault_tolerance.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\npub fn main() {\n let a: Int = \"not an int\"\n let b: String = 1\n let c = a + 2\n}\n" --- ----- SOURCE CODE pub fn main() { let a: Int = "not an int" let b: String = 1 let c = a + 2 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:16 │ 3 │ let a: Int = "not an int" │ ^^^^^^^^^^^^ Expected type: Int Found type: String error: Type mismatch ┌─ /src/one/two.gleam:4:19 │ 4 │ let b: String = 1 │ ^ Expected type: String Found type: Int error: Type mismatch ┌─ /src/one/two.gleam:5:11 │ 5 │ let c = a + 2 │ ^ The + operator expects arguments of this type: Int But this argument has this type: String Hint: Strings can be joined using the `<>` operator. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__multiple_bad_statement_assignment_with_annotation_fault_tolerance2.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\npub fn main() {\n // Since the value is invalid the type is the annotation\n let a: Int = Junk\n let b: String = 1\n let c = a + 2\n}\n" --- ----- SOURCE CODE pub fn main() { // Since the value is invalid the type is the annotation let a: Int = Junk let b: String = 1 let c = a + 2 } ----- ERROR error: Unknown variable ┌─ /src/one/two.gleam:4:16 │ 4 │ let a: Int = Junk │ ^^^^ The custom type variant constructor `Junk` is not in scope here. error: Type mismatch ┌─ /src/one/two.gleam:5:19 │ 5 │ let b: String = 1 │ ^ Expected type: String Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__multiple_bad_statement_assignment_with_pattern_fault_tolerance2.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\npub fn main() {\n // Since the pattern is invalid no variable is created\n let Junk(a) = 7\n // Pattern is valid but does not type check\n let Ok(b) = 1\n let c = a + b\n}\n" --- ----- SOURCE CODE pub fn main() { // Since the pattern is invalid no variable is created let Junk(a) = 7 // Pattern is valid but does not type check let Ok(b) = 1 let c = a + b } ----- ERROR error: Unknown variable ┌─ /src/one/two.gleam:4:7 │ 4 │ let Junk(a) = 7 │ ^^^^^^^ The custom type variant constructor `Junk` is not in scope here. error: Type mismatch ┌─ /src/one/two.gleam:6:7 │ 6 │ let Ok(b) = 1 │ ^^^^^ Expected type: Int Found type: Result(Int, a) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__multiple_bad_statement_expression_fault_tolerance.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\npub fn main() {\n 1 + 2.0\n 3 + 4.0\n let c = 1 + 2\n}\n" --- ----- SOURCE CODE pub fn main() { 1 + 2.0 3 + 4.0 let c = 1 + 2 } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:7 │ 3 │ 1 + 2.0 │ ^^^ The + operator expects arguments of this type: Int But this argument has this type: Float Hint: the +. operator can be used with Floats error: Type mismatch ┌─ /src/one/two.gleam:4:7 │ 4 │ 3 + 4.0 │ ^^^ The + operator expects arguments of this type: Int But this argument has this type: Float Hint: the +. operator can be used with Floats ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__no_impl_function_fault_tolerance.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\npub fn no_impl() -> Nil\n\npub type X = UnknownType\n" --- ----- SOURCE CODE pub fn no_impl() -> Nil pub type X = UnknownType ----- ERROR error: Function without an implementation ┌─ /src/one/two.gleam:2:1 │ 2 │ pub fn no_impl() -> Nil │ ^^^^^^^^^^^^^^^^ We can't compile this function as it doesn't have an implementation. Add a body or an external implementation using the `@external` attribute. error: Unknown type ┌─ /src/one/two.gleam:4:14 │ 4 │ pub type X = UnknownType │ ^^^^^^^^^^^ The type `UnknownType` is not defined or imported in this module. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__provide_arg_type_to_fn_arg_infer_error.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\npub fn main() {\n fn(x) { x.2 }(z)\n}\n" --- ----- SOURCE CODE pub fn main() { fn(x) { x.2 }(z) } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:12 │ 3 │ fn(x) { x.2 }(z) │ ^ What type is this? To index into a tuple we need to know its size, but we don't know anything about this type yet. Please add some type annotations so we can continue. error: Unknown variable ┌─ /src/one/two.gleam:3:18 │ 3 │ fn(x) { x.2 }(z) │ ^ The name `z` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__provide_arg_type_to_fn_explicit_error.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\npub fn main() {\n let z = #(1,2)\n fn(x: #(Int, Int)) { x.2 }(z)\n}\n" --- ----- SOURCE CODE pub fn main() { let z = #(1,2) fn(x: #(Int, Int)) { x.2 }(z) } ----- ERROR error: Out of bounds tuple index ┌─ /src/one/two.gleam:4:26 │ 4 │ fn(x: #(Int, Int)) { x.2 }(z) │ ^^ This index is too large The index being accessed for this tuple is 2, but this tuple has 2 elements so the highest valid index is 1. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__provide_arg_type_to_fn_implicit_error.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\npub fn main() {\n let z = #(1,2)\n fn(x) { x.2 }(z)\n}\n" --- ----- SOURCE CODE pub fn main() { let z = #(1,2) fn(x) { x.2 }(z) } ----- ERROR error: Out of bounds tuple index ┌─ /src/one/two.gleam:4:13 │ 4 │ fn(x) { x.2 }(z) │ ^^ This index is too large The index being accessed for this tuple is 2, but this tuple has 2 elements so the highest valid index is 1. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__provide_arg_type_to_fn_not_a_tuple.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\npub fn main() {\n let z = \"not a tuple\"\n fn(x) { x.2 }(z)\n}\n" --- ----- SOURCE CODE pub fn main() { let z = "not a tuple" fn(x) { x.2 }(z) } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:4:12 │ 4 │ fn(x) { x.2 }(z) │ ^ This is not a tuple To index into this value it needs to be a tuple, however it has this type: String ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__provide_one_arg_type_to_two_args_fn.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\npub fn main() {\n let a = #(1,2)\n fn(x, y) { x.0 + y.1 }(a)\n}\n" --- ----- SOURCE CODE pub fn main() { let a = #(1,2) fn(x, y) { x.0 + y.1 }(a) } ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:4:4 │ 4 │ fn(x, y) { x.0 + y.1 }(a) │ ^^^^^^^^^^^^^^^^^^^^^^^^^ Expected 2 arguments, got 1 error: Type mismatch ┌─ /src/one/two.gleam:4:15 │ 4 │ fn(x, y) { x.0 + y.1 }(a) │ ^ What type is this? To index into a tuple we need to know its size, but we don't know anything about this type yet. Please add some type annotations so we can continue. error: Type mismatch ┌─ /src/one/two.gleam:4:21 │ 4 │ fn(x, y) { x.0 + y.1 }(a) │ ^ What type is this? To index into a tuple we need to know its size, but we don't know anything about this type yet. Please add some type annotations so we can continue. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__provide_two_args_type_to_fn_wrong_types.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\npub fn main() {\n let a = 1\n let b = \"not an int\"\n fn(x, y) { x + y }(a, b)\n}\n" --- ----- SOURCE CODE pub fn main() { let a = 1 let b = "not an int" fn(x, y) { x + y }(a, b) } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:5:19 │ 5 │ fn(x, y) { x + y }(a, b) │ ^ The + operator expects arguments of this type: Int But this argument has this type: String Hint: Strings can be joined using the `<>` operator. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__recursive_type.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\npub fn one(x) {\n two([x])\n}\n\npub fn two(x) {\n one(x)\n}\n" --- ----- SOURCE CODE pub fn one(x) { two([x]) } pub fn two(x) { one(x) } ----- ERROR error: Recursive type ┌─ /src/one/two.gleam:3:7 │ 3 │ two([x]) │ ^^^ I don't know how to work out what type this value has. It seems to be defined in terms of itself. Hint: Add some type annotations and try again. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__unlabelled_after_labelled.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "fn main(wibble wibber, wobber) {\n Nil\n}" --- ----- SOURCE CODE fn main(wibble wibber, wobber) { Nil } ----- ERROR error: Unlabelled argument after labelled argument ┌─ /src/one/two.gleam:1:24 │ 1 │ fn main(wibble wibber, wobber) { │ ^^^^^^ All unlabelled arguments must come before any labelled arguments. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__unlabelled_after_labelled_external.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "\n@external(erlang, \"\", \"\")\nfn main(wibble x: Int, y: Int) -> Int\n" --- ----- SOURCE CODE @external(erlang, "", "") fn main(wibble x: Int, y: Int) -> Int ----- ERROR error: Unlabelled argument after labelled argument ┌─ /src/one/two.gleam:3:24 │ 3 │ fn main(wibble x: Int, y: Int) -> Int │ ^^^^^^ All unlabelled arguments must come before any labelled arguments. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__functions__unlabelled_after_labelled_with_type.snap ================================================ --- source: compiler-core/src/type_/tests/functions.rs expression: "fn main(wibble wibber, wobber: Int) {\n Nil\n}" --- ----- SOURCE CODE fn main(wibble wibber, wobber: Int) { Nil } ----- ERROR error: Unlabelled argument after labelled argument ┌─ /src/one/two.gleam:1:24 │ 1 │ fn main(wibble wibber, wobber: Int) { │ ^^^^^^^^^^^ All unlabelled arguments must come before any labelled arguments. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__guards__string_variable_access.snap ================================================ --- source: compiler-core/src/type_/tests/guards.rs expression: "\npub fn a(a: String) {\n case a {\n _ if a.b -> 1\n _ -> 0\n }\n}\n" --- ----- SOURCE CODE pub fn a(a: String) { case a { _ if a.b -> 1 _ -> 0 } } ----- ERROR error: Unknown record field ┌─ /src/one/two.gleam:4:12 │ 4 │ _ if a.b -> 1 │ ^ This field does not exist The value being accessed has this type: String It does not have any fields. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__imports__import_errors_do_not_block_analysis.snap ================================================ --- source: compiler-core/src/type_/tests/imports.rs expression: "import unknown_module\n\npub fn main() {\n 1 + Nil\n}" --- ----- SOURCE CODE import unknown_module pub fn main() { 1 + Nil } ----- ERROR error: Unknown module ┌─ /src/one/two.gleam:1:1 │ 1 │ import unknown_module │ ^^^^^^^^^^^^^^^^^^^^^ No module has been found with the name `unknown_module`. error: Type mismatch ┌─ /src/one/two.gleam:4:7 │ 4 │ 1 + Nil │ ^^^ The + operator expects arguments of this type: Int But this argument has this type: Nil ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__imports__import_type_duplicate.snap ================================================ --- source: compiler-core/src/type_/tests/imports.rs expression: "import one.{One, type One}\n\npub fn main() -> One {\n todo\n}\n" --- ----- SOURCE CODE -- one.gleam pub type One = Int -- main.gleam import one.{One, type One} pub fn main() -> One { todo } ----- ERROR error: Unknown module value ┌─ /src/one/two.gleam:1:13 │ 1 │ import one.{One, type One} │ ^^^ Did you mean `type One`? `One` is only a type, it cannot be imported as a value. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__imports__import_type_duplicate_with_as.snap ================================================ --- source: compiler-core/src/type_/tests/imports.rs expression: "import one.{type One as MyOne, type One as MyOne}\n\npub type X = One\n" --- ----- SOURCE CODE -- one.gleam pub type One = Int -- main.gleam import one.{type One as MyOne, type One as MyOne} pub type X = One ----- ERROR error: Duplicate type definition ┌─ /src/one/two.gleam:1:32 │ 1 │ import one.{type One as MyOne, type One as MyOne} │ ----------------- ^^^^^^^^^^^^^^^^^ Redefined here │ │ │ First defined here The type `MyOne` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. error: Unknown type ┌─ /src/one/two.gleam:3:14 │ 3 │ pub type X = One │ ^^^ Did you mean `MyOne`? The type `One` is not defined or imported in this module. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__imports__import_type_duplicate_with_as_multiline.snap ================================================ --- source: compiler-core/src/type_/tests/imports.rs expression: "import one.{\n type One as MyOne,\n type One as MyOne\n }\n\npub type X = One\n" --- ----- SOURCE CODE -- one.gleam pub type One = Int -- main.gleam import one.{ type One as MyOne, type One as MyOne } pub type X = One ----- ERROR error: Duplicate type definition ┌─ /src/one/two.gleam:3:11 │ 2 │ type One as MyOne, │ ----------------- First defined here 3 │ type One as MyOne │ ^^^^^^^^^^^^^^^^^ Redefined here The type `MyOne` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. error: Unknown type ┌─ /src/one/two.gleam:6:14 │ 6 │ pub type X = One │ ^^^ Did you mean `MyOne`? The type `One` is not defined or imported in this module. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__imports__imported_constructor_instead_of_type.snap ================================================ --- source: compiler-core/src/type_/tests/imports.rs expression: "import module.{Wibble}\n\npub fn main(x: Wibble) {\n todo\n}" --- ----- SOURCE CODE -- module.gleam pub type Wibble { Wibble } -- main.gleam import module.{Wibble} pub fn main(x: Wibble) { todo } ----- ERROR error: Unknown type ┌─ /src/one/two.gleam:3:16 │ 3 │ pub fn main(x: Wibble) { │ ^^^^^^ The type `Wibble` is not defined or imported in this module. There is a value in scope with the name `Wibble`, but no type in scope with that name. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__imports__module_alias_used_as_a_name.snap ================================================ --- source: compiler-core/src/type_/tests/imports.rs expression: "\nimport one/two\n\npub fn main() {\n two\n}\n" --- ----- SOURCE CODE -- one/two.gleam -- main.gleam import one/two pub fn main() { two } ----- ERROR error: Module `two` used as a value ┌─ /src/one/two.gleam:5:3 │ 5 │ two │ ^^^ Modules are not values, so you cannot assign them to variables, pass them to functions, or anything else that you would do with a value. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__imports__unqualified_import_errors_do_not_block_later_unqualified.snap ================================================ --- source: compiler-core/src/type_/tests/imports.rs expression: "import gleam.{Unknown, type Int as Integer}\n\npub fn main() -> Integer {\n Nil\n}" --- ----- SOURCE CODE import gleam.{Unknown, type Int as Integer} pub fn main() -> Integer { Nil } ----- ERROR error: Unknown module value ┌─ /src/one/two.gleam:1:15 │ 1 │ import gleam.{Unknown, type Int as Integer} │ ^^^^^^^ The module `gleam` does not have a `Unknown` value. error: Type mismatch ┌─ /src/one/two.gleam:4:3 │ 4 │ Nil │ ^^^ The type of this returned value doesn't match the return type annotation of this function. Expected type: Integer Found type: Nil ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__imports__unqualified_using_opaque_constructor.snap ================================================ --- source: compiler-core/src/type_/tests/imports.rs expression: "import one.{Two}\n\npub fn main() {\n Two\n}" --- ----- SOURCE CODE -- one.gleam pub opaque type Two { Two } -- main.gleam import one.{Two} pub fn main() { Two } ----- ERROR error: Unknown module value ┌─ /src/one/two.gleam:1:13 │ 1 │ import one.{Two} │ ^^^ Did you mean `type Two`? `Two` is only a type, it cannot be imported as a value. error: Unknown variable ┌─ /src/one/two.gleam:4:3 │ 4 │ Two │ ^^^ The custom type variant constructor `Two` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__imports__unqualified_using_private_constructor.snap ================================================ --- source: compiler-core/src/type_/tests/imports.rs expression: "import one.{Two}\n\npub fn main() {\n Two\n}" --- ----- SOURCE CODE -- one.gleam type Two { Two } -- main.gleam import one.{Two} pub fn main() { Two } ----- ERROR error: Unknown module value ┌─ /src/one/two.gleam:1:13 │ 1 │ import one.{Two} │ ^^^ The module `one` does not have a `Two` value. error: Unknown variable ┌─ /src/one/two.gleam:4:3 │ 4 │ Two │ ^^^ The custom type variant constructor `Two` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__imports__unqualified_using_private_constructor_pattern.snap ================================================ --- source: compiler-core/src/type_/tests/imports.rs expression: "import one.{Two}\n\npub fn main(x) {\n let Two = x\n}" --- ----- SOURCE CODE -- one.gleam type Two { Two } -- main.gleam import one.{Two} pub fn main(x) { let Two = x } ----- ERROR error: Unknown module value ┌─ /src/one/two.gleam:1:13 │ 1 │ import one.{Two} │ ^^^ The module `one` does not have a `Two` value. error: Unknown variable ┌─ /src/one/two.gleam:4:7 │ 4 │ let Two = x │ ^^^ The custom type variant constructor `Two` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__imports__unqualified_using_private_function.snap ================================================ --- source: compiler-core/src/type_/tests/imports.rs expression: "import one.{two}\n\npub fn main() {\n two\n}" --- ----- SOURCE CODE -- one.gleam fn two() { 2 } -- main.gleam import one.{two} pub fn main() { two } ----- ERROR error: Unknown module value ┌─ /src/one/two.gleam:1:13 │ 1 │ import one.{two} │ ^^^ The module `one` does not have a `two` value. error: Unknown variable ┌─ /src/one/two.gleam:4:3 │ 4 │ two │ ^^^ The name `two` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__imports__using_opaque_constructor.snap ================================================ --- source: compiler-core/src/type_/tests/imports.rs expression: "import one\n\npub fn main() {\n one.Two\n}" --- ----- SOURCE CODE -- one.gleam pub opaque type Two { Two } -- main.gleam import one pub fn main() { one.Two } ----- ERROR error: Unknown module value ┌─ /src/one/two.gleam:4:7 │ 4 │ one.Two │ ^^^ one.Two is a type constructor, it cannot be used as a value ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__imports__using_private_constructor.snap ================================================ --- source: compiler-core/src/type_/tests/imports.rs expression: "import one\n\npub fn main() {\n one.Two\n}" --- ----- SOURCE CODE -- one.gleam type Two { Two } -- main.gleam import one pub fn main() { one.Two } ----- ERROR error: Unknown module value ┌─ /src/one/two.gleam:4:7 │ 4 │ one.Two │ ^^^ The module `one` does not have a `Two` value. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__imports__using_private_constructor_pattern.snap ================================================ --- source: compiler-core/src/type_/tests/imports.rs expression: "import one\n\npub fn main(x) {\n let one.Two = x\n}" --- ----- SOURCE CODE -- one.gleam type Two { Two } -- main.gleam import one pub fn main(x) { let one.Two = x } ----- ERROR error: Unknown module value ┌─ /src/one/two.gleam:4:7 │ 4 │ let one.Two = x │ ^^^^^^^ The module `one` does not have a `Two` value. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__imports__using_private_custom_type.snap ================================================ --- source: compiler-core/src/type_/tests/imports.rs expression: "import one\n\npub fn main() {\n one.X\n}" --- ----- SOURCE CODE -- one.gleam type X { Y } -- main.gleam import one pub fn main() { one.X } ----- ERROR error: Unknown module value ┌─ /src/one/two.gleam:4:7 │ 4 │ one.X │ ^ The module `one` does not have a `X` value. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__imports__using_private_external_type.snap ================================================ --- source: compiler-core/src/type_/tests/imports.rs expression: "import one\n\npub fn main() {\n one.X\n}" --- ----- SOURCE CODE -- one.gleam type X -- main.gleam import one pub fn main() { one.X } ----- ERROR error: Unknown module value ┌─ /src/one/two.gleam:4:7 │ 4 │ one.X │ ^ The module `one` does not have a `X` value. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__imports__using_private_function.snap ================================================ --- source: compiler-core/src/type_/tests/imports.rs expression: "import one\n\npub fn main() {\n one.two\n}" --- ----- SOURCE CODE -- one.gleam fn two() { 2 } -- main.gleam import one pub fn main() { one.two } ----- ERROR error: Unknown module value ┌─ /src/one/two.gleam:4:7 │ 4 │ one.two │ ^^^ The module `one` does not have a `two` value. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__imports__using_private_type_alias.snap ================================================ --- source: compiler-core/src/type_/tests/imports.rs expression: "import one\n\npub fn main() {\n one.X\n}" --- ----- SOURCE CODE -- one.gleam type X = Int -- main.gleam import one pub fn main() { one.X } ----- ERROR error: Unknown module value ┌─ /src/one/two.gleam:4:7 │ 4 │ one.X │ ^ The module `one` does not have a `X` value. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__imports__using_private_unqualified_custom_type.snap ================================================ --- source: compiler-core/src/type_/tests/imports.rs expression: "import one.{X}\n\npub fn main() {\n X\n}" --- ----- SOURCE CODE -- one.gleam type X { Y } -- main.gleam import one.{X} pub fn main() { X } ----- ERROR error: Unknown module value ┌─ /src/one/two.gleam:1:13 │ 1 │ import one.{X} │ ^ The module `one` does not have a `X` value. error: Unknown variable ┌─ /src/one/two.gleam:4:3 │ 4 │ X │ ^ The custom type variant constructor `X` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__imports__using_private_unqualified_external_type.snap ================================================ --- source: compiler-core/src/type_/tests/imports.rs expression: "import one.{X}\n\npub fn main() {\n X\n}" --- ----- SOURCE CODE -- one.gleam type X -- main.gleam import one.{X} pub fn main() { X } ----- ERROR error: Unknown module value ┌─ /src/one/two.gleam:1:13 │ 1 │ import one.{X} │ ^ The module `one` does not have a `X` value. error: Unknown variable ┌─ /src/one/two.gleam:4:3 │ 4 │ X │ ^ The custom type variant constructor `X` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__imports__using_private_unqualified_type_alias.snap ================================================ --- source: compiler-core/src/type_/tests/imports.rs expression: "import one.{X}\n\npub fn main() {\n X\n}" --- ----- SOURCE CODE -- one.gleam type X = Int -- main.gleam import one.{X} pub fn main() { X } ----- ERROR error: Unknown module value ┌─ /src/one/two.gleam:1:13 │ 1 │ import one.{X} │ ^ The module `one` does not have a `X` value. error: Unknown variable ┌─ /src/one/two.gleam:4:3 │ 4 │ X │ ^ The custom type variant constructor `X` is not in scope here. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__let_assert__non_string_message.snap ================================================ --- source: compiler-core/src/type_/tests/let_assert.rs expression: let assert 1 = 2 as 3 --- ----- SOURCE CODE let assert 1 = 2 as 3 ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:1:21 │ 1 │ let assert 1 = 2 as 3 │ ^ Expected type: String Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__pipes__pipe_callback_wrong_arity.snap ================================================ --- source: compiler-core/src/type_/tests/pipes.rs expression: "\nfn callback(a: Int) {\n fn() -> String {\n \"Called\"\n }\n}\n\npub fn main() {\n let x = 1 |> callback(2)\n}\n" --- ----- SOURCE CODE fn callback(a: Int) { fn() -> String { "Called" } } pub fn main() { let x = 1 |> callback(2) } ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:9:16 │ 9 │ let x = 1 |> callback(2) │ ^^^^^^^^^^^ Expected no arguments, got 1 ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__pretty__prelude_type_clash_custom_first.snap ================================================ --- source: compiler-core/src/type_/tests/pretty.rs expression: "print(tuple(vec![custom_bool(), bool()]))" --- #(Bool, gleam.Bool) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__pretty__prelude_type_clash_prelude_first.snap ================================================ --- source: compiler-core/src/type_/tests/pretty.rs expression: "print(tuple(vec![bool(), custom_bool()]))" --- #(Bool, one/two.Bool) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__pretty__repeated_prelude_type.snap ================================================ --- source: compiler-core/src/type_/tests/pretty.rs expression: "print(tuple(vec![int(), int(), int()]))" --- #(Int, Int, Int) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__target_implementations__function_with_no_valid_implementations.snap ================================================ --- source: compiler-core/src/type_/tests/target_implementations.rs expression: "\n@external(javascript, \"wibble\", \"wobble\")\nfn javascript_only() -> Int\n\n@external(erlang, \"wibble\", \"wobble\")\nfn erlang_only() -> Int\n\npub fn main() {\n javascript_only()\n erlang_only()\n}\n" --- ----- SOURCE CODE @external(javascript, "wibble", "wobble") fn javascript_only() -> Int @external(erlang, "wibble", "wobble") fn erlang_only() -> Int pub fn main() { javascript_only() erlang_only() } ----- ERROR error: Unsupported target ┌─ /src/one/two.gleam:9:5 │ 9 │ javascript_only() │ ^^^^^^^^^^^^^^^ This value is not available as it is defined using externals, and there is no implementation for the Erlang target. Hint: Did you mean to build for a different target? ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__type_alias__alias_cycle.snap ================================================ --- source: compiler-core/src/type_/tests/type_alias.rs expression: "\ntype A = B\ntype B = C\ntype C = D\ntype D = E\ntype E = A\n" --- ----- SOURCE CODE type A = B type B = C type C = D type D = E type E = A ----- ERROR error: Type cycle ┌─ /src/one/two.gleam:2:1 │ 2 │ type A = B │ ^^^^^^^^^^ This type alias is defined in terms of itself. ┌─────┐ │ E │ ↓ │ D │ ↓ │ C │ ↓ │ B │ ↓ │ A └─────┘ If we tried to compile this recursive type it would expand forever in a loop, and we'd never get the final type. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__type_alias__alias_direct_cycle.snap ================================================ --- source: compiler-core/src/type_/tests/type_alias.rs expression: "\ntype A = #(A, A)\n" --- ----- SOURCE CODE type A = #(A, A) ----- ERROR error: Type cycle ┌─ /src/one/two.gleam:2:1 │ 2 │ type A = #(A, A) │ ^^^^^^^^^^^^^^^^ This type alias is defined in terms of itself. ┌─────┐ │ A └─────┘ If we tried to compile this recursive type it would expand forever in a loop, and we'd never get the final type. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__type_alias__both_errors_are_shown.snap ================================================ --- source: compiler-core/src/type_/tests/type_alias.rs expression: "\ntype X =\n List(Intt)\n\nfn example(a: X) {\n todo\n}\n" --- ----- SOURCE CODE type X = List(Intt) fn example(a: X) { todo } ----- ERROR error: Unknown type ┌─ /src/one/two.gleam:3:8 │ 3 │ List(Intt) │ ^^^^ Did you mean `Int`? The type `Intt` is not defined or imported in this module. error: Unknown type ┌─ /src/one/two.gleam:5:15 │ 5 │ fn example(a: X) { │ ^ The type `X` is not defined or imported in this module. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__type_alias__conflict_with_import.snap ================================================ --- source: compiler-core/src/type_/tests/type_alias.rs expression: "import wibble.{type Wobble} type Wobble = Int" --- ----- SOURCE CODE -- wibble.gleam pub type Wobble = String -- main.gleam import wibble.{type Wobble} type Wobble = Int ----- ERROR error: Duplicate type definition ┌─ /src/one/two.gleam:1:29 │ 1 │ import wibble.{type Wobble} type Wobble = Int │ ----------- ^^^^^^^^^^^^^^^^^ Redefined here │ │ │ First defined here The type `Wobble` has been defined multiple times. Names in a Gleam module must be unique so one will need to be renamed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__type_alias__duplicate_parameter.snap ================================================ --- source: compiler-core/src/type_/tests/type_alias.rs expression: "\ntype A(a, a) =\n List(a)\n" --- ----- SOURCE CODE type A(a, a) = List(a) ----- ERROR error: Duplicate type parameter ┌─ /src/one/two.gleam:2:11 │ 2 │ type A(a, a) = │ ^ This definition has multiple type parameters named `a`. Rename or remove one of them. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__type_alias__duplicate_variable_error_does_not_stop_analysis.snap ================================================ --- source: compiler-core/src/type_/tests/type_alias.rs expression: "\ntype Two(a, a) =\n #(a, a)\n\ntype UnknownType =\n Dunno\n" --- ----- SOURCE CODE type Two(a, a) = #(a, a) type UnknownType = Dunno ----- ERROR error: Duplicate type parameter ┌─ /src/one/two.gleam:2:13 │ 2 │ type Two(a, a) = │ ^ This definition has multiple type parameters named `a`. Rename or remove one of them. error: Unknown type ┌─ /src/one/two.gleam:6:3 │ 6 │ Dunno │ ^^^^^ The type `Dunno` is not defined or imported in this module. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__type_alias__type_alias_error_does_not_stop_analysis.snap ================================================ --- source: compiler-core/src/type_/tests/type_alias.rs expression: "\ntype UnusedParameter(a) =\n Int\n\ntype UnknownType =\n Dunno\n" --- ----- SOURCE CODE type UnusedParameter(a) = Int type UnknownType = Dunno ----- ERROR error: Unused type parameter ┌─ /src/one/two.gleam:2:1 │ 2 │ ╭ type UnusedParameter(a) = 3 │ │ Int │ ╰─────^ The type variable `a` is unused. It can be safely removed. error: Unknown type ┌─ /src/one/two.gleam:6:3 │ 6 │ Dunno │ ^^^^^ The type `Dunno` is not defined or imported in this module. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__type_alias__unused_parameter.snap ================================================ --- source: compiler-core/src/type_/tests/type_alias.rs expression: "\ntype A(a) =\n Int\n" --- ----- SOURCE CODE type A(a) = Int ----- ERROR error: Unused type parameter ┌─ /src/one/two.gleam:2:1 │ 2 │ ╭ type A(a) = 3 │ │ Int │ ╰─────^ The type variable `a` is unused. It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__use___invalid_call_is_number.snap ================================================ --- source: compiler-core/src/type_/tests/use_.rs expression: "\nuse <- 123\n123\n" --- ----- SOURCE CODE use <- 123 123 ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:2:8 │ 2 │ use <- 123 │ ^^^ In a use expression, there should be a function on the right hand side of `<-`, but this value has type: Int See: https://tour.gleam.run/advanced-features/use/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__use___invalid_callback_type.snap ================================================ --- source: compiler-core/src/type_/tests/use_.rs expression: "\nlet x = fn(f) { f() + 1 }\nuse <- x()\nNil\n" --- ----- SOURCE CODE let x = fn(f) { f() + 1 } use <- x() Nil ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:4:1 │ 4 │ Nil │ ^^^ Expected type: Int Found type: Nil ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__use___invalid_callback_type_2.snap ================================================ --- source: compiler-core/src/type_/tests/use_.rs expression: "\nlet x = fn(f) { \"Hello, \" <> f() }\nuse <- x()\nlet n = 1\nn + 2\n" --- ----- SOURCE CODE let x = fn(f) { "Hello, " <> f() } use <- x() let n = 1 n + 2 ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:5:1 │ 5 │ n + 2 │ ^^^^^ Expected type: String Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__use___invalid_callback_type_3.snap ================================================ --- source: compiler-core/src/type_/tests/use_.rs expression: "\nlet x = fn(f) { \"Hello, \" <> f() }\nlet y = fn(f) { 1 + f() }\nuse <- x()\nuse <- y()\nlet n = 1\nn + 1\n" --- ----- SOURCE CODE let x = fn(f) { "Hello, " <> f() } let y = fn(f) { 1 + f() } use <- x() use <- y() let n = 1 n + 1 ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:5:1 │ 5 │ use <- y() │ ^^^^^^^^^^ Expected type: String Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__use___invalid_callback_type_4.snap ================================================ --- source: compiler-core/src/type_/tests/use_.rs expression: "\nlet x = fn(f) { \"Hello, \" <> f() }\nlet y = fn(f) { 1 + f() }\nlet z = fn(f) { 1.0 +. f() }\nuse <- x()\nuse <- y()\nlet n = 1\nuse <- z()\n1.0\n" --- ----- SOURCE CODE let x = fn(f) { "Hello, " <> f() } let y = fn(f) { 1 + f() } let z = fn(f) { 1.0 +. f() } use <- x() use <- y() let n = 1 use <- z() 1.0 ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:8:1 │ 8 │ use <- z() │ ^^^^^^^^^^ Expected type: Int Found type: Float error: Type mismatch ┌─ /src/one/two.gleam:6:1 │ 6 │ use <- y() │ ^^^^^^^^^^ Expected type: String Found type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__use___just_use_in_fn_body.snap ================================================ --- source: compiler-core/src/type_/tests/use_.rs expression: "\npub fn main() {\n use <- wibble()\n}\n\nfn wibble(f) {\n f()\n}\n" --- ----- SOURCE CODE pub fn main() { use <- wibble() } fn wibble(f) { f() } ----- WARNING warning: Incomplete use expression ┌─ /src/warning/wrn.gleam:3:3 │ 3 │ use <- wibble() │ ^^^^^^^^^^^^^^^ This code is incomplete This code will crash if it is run. Be sure to finish it before running your program. A use expression must always be followed by at least one expression. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__use___multiple_bad_statement_use_fault_tolerance.snap ================================================ --- source: compiler-core/src/type_/tests/use_.rs expression: "\nlet x = fn(f) { f() + 1 }\nuse <- x()\n\n1 + 2.0\n3.0 + 4\n5\n" --- ----- SOURCE CODE let x = fn(f) { f() + 1 } use <- x() 1 + 2.0 3.0 + 4 5 ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:5:5 │ 5 │ 1 + 2.0 │ ^^^ The + operator expects arguments of this type: Int But this argument has this type: Float Hint: the +. operator can be used with Floats error: Type mismatch ┌─ /src/one/two.gleam:6:1 │ 6 │ 3.0 + 4 │ ^^^ The + operator expects arguments of this type: Int But this argument has this type: Float Hint: the +. operator can be used with Floats ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__use___no_callback_body.snap ================================================ --- source: compiler-core/src/type_/tests/use_.rs expression: "\npub fn main() {\n let thingy = fn(f) { f() }\n use <- thingy()\n}\n" --- ----- SOURCE CODE pub fn main() { let thingy = fn(f) { f() } use <- thingy() } ----- WARNING warning: Incomplete use expression ┌─ /src/warning/wrn.gleam:4:3 │ 4 │ use <- thingy() │ ^^^^^^^^^^^^^^^ This code is incomplete This code will crash if it is run. Be sure to finish it before running your program. A use expression must always be followed by at least one expression. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__use___typed_pattern_wrong_type.snap ================================================ --- source: compiler-core/src/type_/tests/use_.rs expression: "\npub fn main() {\n use Box(x): Box(Bool), Box(y), Box(z) <- apply(Box(1))\n x + y + z\n}\n\ntype Box(a) {\n Box(a)\n}\n\nfn apply(arg, fun) {\n fun(arg, arg, arg)\n}\n" --- ----- SOURCE CODE pub fn main() { use Box(x): Box(Bool), Box(y), Box(z) <- apply(Box(1)) x + y + z } type Box(a) { Box(a) } fn apply(arg, fun) { fun(arg, arg, arg) } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:7 │ 3 │ use Box(x): Box(Bool), Box(y), Box(z) <- apply(Box(1)) │ ^^^^^^^^^^^^^^^^^ Expected type: Box(Bool) Found type: Box(Int) ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__use___use_with_function_that_doesnt_take_callback_as_last_arg_1.snap ================================================ --- source: compiler-core/src/type_/tests/use_.rs expression: "\nlet f = fn(a) { a + 1 }\nuse <- f\n123\n" --- ----- SOURCE CODE let f = fn(a) { a + 1 } use <- f 123 ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:8 │ 3 │ use <- f │ ^ The function on the right hand side of `<-` has to take a callback function as its last argument. But the last argument of this function has type: Int See: https://tour.gleam.run/advanced-features/use/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__use___use_with_function_that_doesnt_take_callback_as_last_arg_2.snap ================================================ --- source: compiler-core/src/type_/tests/use_.rs expression: "\nlet f = fn() { 1 }\nuse <- f\n123\n" --- ----- SOURCE CODE let f = fn() { 1 } use <- f 123 ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:3:8 │ 3 │ use <- f │ ^ Expected no arguments, got 1 The function on the right of `<-` here takes no arguments, but it has to take at least one argument, a callback function. See: https://tour.gleam.run/advanced-features/use/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__use___use_with_function_that_doesnt_take_callback_as_last_arg_3.snap ================================================ --- source: compiler-core/src/type_/tests/use_.rs expression: "\nlet f = fn(a, b) { a + b }\nuse <- f(1)\n123\n" --- ----- SOURCE CODE let f = fn(a, b) { a + b } use <- f(1) 123 ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:8 │ 3 │ use <- f(1) │ ^^^^ The function on the right hand side of `<-` has to take a callback function as its last argument. But the last argument of this function has type: Int See: https://tour.gleam.run/advanced-features/use/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__use___wrong_arity.snap ================================================ --- source: compiler-core/src/type_/tests/use_.rs expression: "\nlet f = fn(callback) { callback(1, 2) }\nuse <- f\n123\n" --- ----- SOURCE CODE let f = fn(callback) { callback(1, 2) } use <- f 123 ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:3:8 │ 3 │ use <- f │ --- ^ │ │ │ Expected 2 arguments, got 0 This function takes a callback that expects 2 arguments. But none were provided on the left hand side of `<-`. See: https://tour.gleam.run/advanced-features/use/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__use___wrong_arity_less_than_required.snap ================================================ --- source: compiler-core/src/type_/tests/use_.rs expression: "\nlet f = fn(a, b) { 1 }\nuse <- f\n123\n" --- ----- SOURCE CODE let f = fn(a, b) { 1 } use <- f 123 ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:3:8 │ 3 │ use <- f │ ^ Expected 2 arguments, got 1 The function on the right of `<-` here takes 2 arguments. The only argument that was supplied is the `use` callback function. See: https://tour.gleam.run/advanced-features/use/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__use___wrong_arity_less_than_required_2.snap ================================================ --- source: compiler-core/src/type_/tests/use_.rs expression: "\nlet f = fn(a, b, c) { 1 }\nuse <- f(1)\n123\n" --- ----- SOURCE CODE let f = fn(a, b, c) { 1 } use <- f(1) 123 ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:3:8 │ 3 │ use <- f(1) │ ^^^^ Expected 3 arguments, got 2 The function on the right of `<-` here takes 3 arguments. You supplied 1 argument and the final one is the `use` callback function. See: https://tour.gleam.run/advanced-features/use/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__use___wrong_arity_more_than_required.snap ================================================ --- source: compiler-core/src/type_/tests/use_.rs expression: "\nlet f = fn(a, b) { 1 }\nuse <- f(1, 2)\n123\n" --- ----- SOURCE CODE let f = fn(a, b) { 1 } use <- f(1, 2) 123 ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:3:8 │ 3 │ use <- f(1, 2) │ ^^^^^^^ Expected 2 arguments, got 3 The function on the right of `<-` here takes 2 arguments. All the arguments have already been supplied, so it cannot take the `use` callback function as a final argument. See: https://tour.gleam.run/advanced-features/use/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__use___wrong_arity_more_than_required_2.snap ================================================ --- source: compiler-core/src/type_/tests/use_.rs expression: "\nlet f = fn(a, b) { 1 }\nuse <- f(1, 2, 3)\n123\n" --- ----- SOURCE CODE let f = fn(a, b) { 1 } use <- f(1, 2, 3) 123 ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:3:8 │ 3 │ use <- f(1, 2, 3) │ ^^^^^^^^^^ Expected 2 arguments, got 4 The function on the right of `<-` here takes 2 arguments. All the arguments have already been supplied, so it cannot take the `use` callback function as a final argument. See: https://tour.gleam.run/advanced-features/use/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__use___wrong_callback_arg.snap ================================================ --- source: compiler-core/src/type_/tests/use_.rs expression: "\nlet x = fn(f) { \"Hello, \" <> f(1) }\nuse n <- x()\nn <> \"Giacomo!\"\n" --- ----- SOURCE CODE let x = fn(f) { "Hello, " <> f(1) } use n <- x() n <> "Giacomo!" ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:4:1 │ 4 │ n <> "Giacomo!" │ ^ The <> operator expects arguments of this type: String But this argument has this type: Int ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__use___wrong_callback_arg_2.snap ================================================ --- source: compiler-core/src/type_/tests/use_.rs expression: "\npub type Box {\n Box(Int)\n}\n\npub fn main() {\n let x = fn(f) { \"Hello, \" <> f(Box(1)) }\n use Box(\"hi\") <- x()\n \"Giacomo!\"\n}\n" --- ----- SOURCE CODE pub type Box { Box(Int) } pub fn main() { let x = fn(f) { "Hello, " <> f(Box(1)) } use Box("hi") <- x() "Giacomo!" } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:8:11 │ 8 │ use Box("hi") <- x() │ ^^^^ Expected type: Int Found type: String ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__use___wrong_callback_arg_3.snap ================================================ --- source: compiler-core/src/type_/tests/use_.rs expression: "\npub type Box {\n Box(Int)\n}\n\npub fn main() {\n let x = fn(f) { \"Hello, \" <> f(1) }\n use Box(1) <- x()\n \"Giacomo!\"\n}\n" --- ----- SOURCE CODE pub type Box { Box(Int) } pub fn main() { let x = fn(f) { "Hello, " <> f(1) } use Box(1) <- x() "Giacomo!" } ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:8:7 │ 8 │ use Box(1) <- x() │ ^^^^^^ Expected type: Int Found type: Box ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__use___wrong_callback_arg_with_wrong_annotation.snap ================================================ --- source: compiler-core/src/type_/tests/use_.rs expression: "\nlet x = fn(f) { \"Hello, \" <> f(1) }\nuse n: String <- x()\nn <> \"Giacomo!\"\n" --- ----- SOURCE CODE let x = fn(f) { "Hello, " <> f(1) } use n: String <- x() n <> "Giacomo!" ----- ERROR error: Type mismatch ┌─ /src/one/two.gleam:3:5 │ 3 │ use n: String <- x() │ ^^^^^^^^^ Expected type: Int Found type: String ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__use___wrong_callback_arity.snap ================================================ --- source: compiler-core/src/type_/tests/use_.rs expression: "\nlet x = fn(f) { \"Hello, \" <> f() }\nuse _ <- x()\n\"Giacomo!\"\n" --- ----- SOURCE CODE let x = fn(f) { "Hello, " <> f() } use _ <- x() "Giacomo!" ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:3:10 │ 3 │ use _ <- x() │ - ^^^ │ │ │ Expected no arguments, got 1 This function takes a callback that expects no arguments. But 1 was provided on the left hand side of `<-`. See: https://tour.gleam.run/advanced-features/use/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__use___wrong_callback_arity_2.snap ================================================ --- source: compiler-core/src/type_/tests/use_.rs expression: "\nlet x = fn(f) { \"Hello, \" <> f(1) }\nuse <- x()\n\"Giacomo!\"\n" --- ----- SOURCE CODE let x = fn(f) { "Hello, " <> f(1) } use <- x() "Giacomo!" ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:3:8 │ 3 │ use <- x() │ --- ^^^ │ │ │ Expected 1 argument, got 0 This function takes a callback that expects 1 argument. But none were provided on the left hand side of `<-`. See: https://tour.gleam.run/advanced-features/use/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__use___wrong_callback_arity_3.snap ================================================ --- source: compiler-core/src/type_/tests/use_.rs expression: "\nlet x = fn(f) { \"Hello, \" <> f(1) }\nuse _, _ <- x()\n\"Giacomo!\"\n" --- ----- SOURCE CODE let x = fn(f) { "Hello, " <> f(1) } use _, _ <- x() "Giacomo!" ----- ERROR error: Incorrect arity ┌─ /src/one/two.gleam:3:13 │ 3 │ use _, _ <- x() │ ---- ^^^ │ │ │ Expected 1 argument, got 2 This function takes a callback that expects 1 argument. But 2 were provided on the left hand side of `<-`. See: https://tour.gleam.run/advanced-features/use/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__aliased_module_used_by_unused_function_is_not_marked_as_unused.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "import wibble as woo\nfn wobble() {\n woo.a\n}\n" --- ----- SOURCE CODE -- wibble.gleam pub const a = 1 -- main.gleam import wibble as woo fn wobble() { woo.a } ----- WARNING warning: Unused private function ┌─ /src/warning/wrn.gleam:2:1 │ 2 │ fn wobble() { │ ^^^^^^^^^^^ This private function is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__assert_on_impossible_to_reach_integer_segment.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main(x) {\n let assert <<1, -1, 2, -3>> = x\n}" --- ----- SOURCE CODE pub fn main(x) { let assert <<1, -1, 2, -3>> = x } ----- WARNING warning: Assertion that will always fail ┌─ /src/warning/wrn.gleam:3:14 │ 3 │ let assert <<1, -1, 2, -3>> = x │ ^^^^^^^^^^^^^^^^ │ │ │ │ │ A 1 byte unsigned integer will never match this value │ A 1 byte unsigned integer will never match this value We can tell from the code above that the value will never match this pattern and that this code will always crash. Either change the pattern or use `panic` to unconditionally fail. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__assert_on_inferred_variant.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\ntype Wibble {\n Wibble(w: Int)\n Wobble(w: String)\n}\n\npub fn main() {\n let assert Wobble(w) = Wibble(10)\n w\n}\n" --- ----- SOURCE CODE type Wibble { Wibble(w: Int) Wobble(w: String) } pub fn main() { let assert Wobble(w) = Wibble(10) w } ----- WARNING warning: Assertion that will always fail ┌─ /src/warning/wrn.gleam:8:14 │ 8 │ let assert Wobble(w) = Wibble(10) │ ^^^^^^^^^ We can tell from the code above that the value will never match this pattern and that this code will always crash. Either change the pattern or use `panic` to unconditionally fail. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__bit_array_negative_truncated_segment.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n // -5 in 2's complement is 1111...111011\n // so if we truncate it to its first 3 bits we\n // get 011, which is positive 3!\n <<-5:size(3)>>\n}\n" --- ----- SOURCE CODE pub fn main() { // -5 in 2's complement is 1111...111011 // so if we truncate it to its first 3 bits we // get 011, which is positive 3! <<-5:size(3)>> } ----- WARNING warning: Truncated bit array segment ┌─ /src/warning/wrn.gleam:6:5 │ 6 │ <<-5:size(3)>> │ ^^ You can safely replace this with 3 This segment is 3 bits long, but -5 doesn't fit in that many bits. It would be truncated by taking its first 3 bits, resulting in the value 3. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__bit_array_negative_truncated_segment_2.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n <<-200:size(8)>>\n}\n" --- ----- SOURCE CODE pub fn main() { <<-200:size(8)>> } ----- WARNING warning: Truncated bit array segment ┌─ /src/warning/wrn.gleam:3:5 │ 3 │ <<-200:size(8)>> │ ^^^^ You can safely replace this with 56 This segment is 1 byte long, but -200 doesn't fit in that many bytes. It would be truncated by taking its first byte, resulting in the value 56. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__bit_array_truncated_segment.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n <<12:size(1)>>\n}\n" --- ----- SOURCE CODE pub fn main() { <<12:size(1)>> } ----- WARNING warning: Truncated bit array segment ┌─ /src/warning/wrn.gleam:3:5 │ 3 │ <<12:size(1)>> │ ^^ You can safely replace this with 0 This segment is 1 bit long, but 12 doesn't fit in that many bits. It would be truncated by taking its first bit, resulting in the value 0. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__bit_array_truncated_segment_in_bytes.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n <<258:size(8)>>\n}\n" --- ----- SOURCE CODE pub fn main() { <<258:size(8)>> } ----- WARNING warning: Truncated bit array segment ┌─ /src/warning/wrn.gleam:3:5 │ 3 │ <<258:size(8)>> │ ^^^ You can safely replace this with 2 This segment is 1 byte long, but 258 doesn't fit in that many bytes. It would be truncated by taking its first byte, resulting in the value 2. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__bit_array_truncated_segment_in_bytes_2.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n <<65_537:size(2)-unit(8)>>\n}\n" --- ----- SOURCE CODE pub fn main() { <<65_537:size(2)-unit(8)>> } ----- WARNING warning: Truncated bit array segment ┌─ /src/warning/wrn.gleam:3:5 │ 3 │ <<65_537:size(2)-unit(8)>> │ ^^^^^^ You can safely replace this with 1 This segment is 2 bytes long, but 65537 doesn't fit in that many bytes. It would be truncated by taking its first 2 bytes, resulting in the value 1. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__bool_assert_requires_v1_11.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn go(x) {\n assert x == 2\n}\n" --- ----- SOURCE CODE pub fn go(x) { assert x == 2 } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:3:3 │ 3 │ assert x == 2 │ ^^^^^^^^^^^^^ This requires a Gleam version >= 1.11.0 The bool `assert` statement was introduced in version v1.11.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.11.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__bool_literals_redundant_comparison.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { True == False }" --- ----- SOURCE CODE pub fn main() { True == False } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { True == False } │ ^^^^^^^^^^^^^ This is always `False` This comparison is redundant since it always fails. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__bool_literals_redundant_comparison_1.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { True != False }" --- ----- SOURCE CODE pub fn main() { True != False } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { True != False } │ ^^^^^^^^^^^^^ This is always `True` This comparison is redundant since it always succeeds. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__const_record_update_requires_v1_14_warning.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Wibble { Wibble(a: Int, b: Int) }\nconst base = Wibble(1, 2)\npub const wobble = Wibble(..base, b: 3)\n" --- ----- SOURCE CODE pub type Wibble { Wibble(a: Int, b: Int) } const base = Wibble(1, 2) pub const wobble = Wibble(..base, b: 3) ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:4:20 │ 4 │ pub const wobble = Wibble(..base, b: 3) │ ^^^^^^^^^^^^^^^^^^^^ This requires a Gleam version >= 1.14.0 The record update syntax for constants was introduced in version v1.14.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.14.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__constant_string_concatenation_requires_v1_4.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub const string = \"wibble\" <> \"wobble\"" --- ----- SOURCE CODE pub const string = "wibble" <> "wobble" ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:1:20 │ 1 │ pub const string = "wibble" <> "wobble" │ ^^^^^^^^^^^^^^^^^^^^ This requires a Gleam version >= 1.4.0 Constant strings concatenation was introduced in version v1.4.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.4.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__constructing_anonymous_function_is_pure.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\nfn make_panic(message) {\n fn() { panic as message }\n}\n\npub fn main() {\n make_panic(\"This is a crash\")\n Nil\n}\n" --- ----- SOURCE CODE fn make_panic(message) { fn() { panic as message } } pub fn main() { make_panic("This is a crash") Nil } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:7:3 │ 7 │ make_panic("This is a crash") │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__deprecated_constant.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n@deprecated(\"Don't use this!\")\npub const a = Nil\n\npub fn b() {\n a\n}\n" --- ----- SOURCE CODE @deprecated("Don't use this!") pub const a = Nil pub fn b() { a } ----- WARNING warning: Deprecated value used ┌─ /src/warning/wrn.gleam:6:3 │ 6 │ a │ ^ This value has been deprecated It was deprecated with this message: Don't use this! ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__deprecated_function.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n@deprecated(\"Don't use this!\")\npub fn a() {\n Nil\n}\n\npub fn b() {\n a\n}\n " --- ----- SOURCE CODE @deprecated("Don't use this!") pub fn a() { Nil } pub fn b() { a } ----- WARNING warning: Deprecated value used ┌─ /src/warning/wrn.gleam:8:3 │ 8 │ a │ ^ This value has been deprecated It was deprecated with this message: Don't use this! ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__deprecated_imported_call_function.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\nimport module\n\npub fn a() {\n module.a()\n}\n" --- ----- SOURCE CODE -- module.gleam @deprecated("Don't use this!") pub fn a() { Nil } -- main.gleam import module pub fn a() { module.a() } ----- WARNING warning: Deprecated value used ┌─ /src/warning/wrn.gleam:5:10 │ 5 │ module.a() │ ^ This value has been deprecated It was deprecated with this message: Don't use this! ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__deprecated_imported_constant.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\nimport module\n\npub fn a() {\n module.a\n}\n" --- ----- SOURCE CODE -- module.gleam @deprecated("Don't use this!") pub const a = Nil -- main.gleam import module pub fn a() { module.a } ----- WARNING warning: Deprecated value used ┌─ /src/warning/wrn.gleam:5:10 │ 5 │ module.a │ ^ This value has been deprecated It was deprecated with this message: Don't use this! ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__deprecated_imported_function.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\nimport module\n\npub fn a() {\n module.a\n}\n" --- ----- SOURCE CODE -- module.gleam @deprecated("Don't use this!") pub fn a() { Nil } -- main.gleam import module pub fn a() { module.a } ----- WARNING warning: Deprecated value used ┌─ /src/warning/wrn.gleam:5:10 │ 5 │ module.a │ ^ This value has been deprecated It was deprecated with this message: Don't use this! ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__deprecated_imported_unqualified_constant.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\nimport module.{a}\n\npub fn b() {\n a\n}\n" --- ----- SOURCE CODE -- module.gleam @deprecated("Don't use this!") pub const a = Nil -- main.gleam import module.{a} pub fn b() { a } ----- WARNING warning: Deprecated value used ┌─ /src/warning/wrn.gleam:5:3 │ 5 │ a │ ^ This value has been deprecated It was deprecated with this message: Don't use this! ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__deprecated_imported_unqualified_function.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\nimport module.{a}\n\npub fn b() {\n a\n}\n" --- ----- SOURCE CODE -- module.gleam @deprecated("Don't use this!") pub fn a() { Nil } -- main.gleam import module.{a} pub fn b() { a } ----- WARNING warning: Deprecated value used ┌─ /src/warning/wrn.gleam:5:3 │ 5 │ a │ ^ This value has been deprecated It was deprecated with this message: Don't use this! ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__deprecated_list_append_syntax.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn main() {\n let letters = [\"b\", \"c\"]\n [\"a\"..letters]\n }\n " --- ----- SOURCE CODE pub fn main() { let letters = ["b", "c"] ["a"..letters] } ----- WARNING warning: Deprecated prepend syntax ┌─ test/path:4:11 │ 4 │ ["a"..letters] │ ^^ This spread should be preceded by a comma This syntax for prepending to a list is deprecated. When prepending an item to a list it should be preceded by a comma, like this: `[item, ..list]`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__deprecated_list_pattern_syntax.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn main() {\n let letters = [\"b\", \"c\"]\n case letters {\n [\"a\"..rest] -> rest\n _ -> []\n }\n }\n " --- ----- SOURCE CODE pub fn main() { let letters = ["b", "c"] case letters { ["a"..rest] -> rest _ -> [] } } ----- WARNING warning: Deprecated list pattern matching syntax ┌─ test/path:5:13 │ 5 │ ["a"..rest] -> rest │ ^^ This spread should be preceded by a comma This syntax for pattern matching on a list is deprecated. When matching on the rest of a list it should always be preceded by a comma, like this: `[item, ..list]`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__deprecated_list_pattern_syntax_1.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn main() {\n let letters = [\"b\", \"c\"]\n case letters {\n [] -> []\n [..] -> []\n }\n }\n " --- ----- SOURCE CODE pub fn main() { let letters = ["b", "c"] case letters { [] -> [] [..] -> [] } } ----- WARNING warning: Deprecated list pattern matching syntax ┌─ test/path:6:9 │ 6 │ [..] -> [] │ ^^^^ This can be replaced with `_` This syntax for pattern matching on lists is deprecated. To match on all possible lists, use the `_` catch-all pattern instead. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__deprecated_record_pattern_syntax.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Wibble {\n Wibble(one: Int, two: Int)\n}\n\npub fn main() {\n let wibble = Wibble(one: 1, two: 2)\n case wibble {\n Wibble(one: one ..) -> one\n }\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(one: Int, two: Int) } pub fn main() { let wibble = Wibble(one: 1, two: 2) case wibble { Wibble(one: one ..) -> one } } ----- WARNING warning: Deprecated record pattern matching syntax ┌─ test/path:9:21 │ 9 │ Wibble(one: one ..) -> one │ ^^ This should be preceded by a comma This syntax for pattern matching on a record is deprecated. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__deprecated_record_pattern_syntax_with_label_shorthand.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Wibble {\n Wibble(one: Int, two: Int)\n}\n\npub fn main() {\n let wibble = Wibble(one: 1, two: 2)\n case wibble {\n Wibble(one: ..) -> one\n }\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(one: Int, two: Int) } pub fn main() { let wibble = Wibble(one: 1, two: 2) case wibble { Wibble(one: ..) -> one } } ----- WARNING warning: Deprecated record pattern matching syntax ┌─ test/path:9:17 │ 9 │ Wibble(one: ..) -> one │ ^^ This should be preceded by a comma This syntax for pattern matching on a record is deprecated. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__deprecated_record_pattern_syntax_with_no_labels.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Wibble {\n Wibble(one: Int, two: Int)\n}\n\npub fn main() {\n let wibble = Wibble(one: 1, two: 2)\n case wibble {\n Wibble(one ..) -> one\n }\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(one: Int, two: Int) } pub fn main() { let wibble = Wibble(one: 1, two: 2) case wibble { Wibble(one ..) -> one } } ----- WARNING warning: Deprecated record pattern matching syntax ┌─ test/path:9:16 │ 9 │ Wibble(one ..) -> one │ ^^ This should be preceded by a comma This syntax for pattern matching on a record is deprecated. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__deprecated_target_shorthand_erlang.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n@target(erl)\npub fn wibble() { panic }\n" --- ----- SOURCE CODE @target(erl) pub fn wibble() { panic } ----- WARNING warning: Deprecated target shorthand syntax ┌─ test/path:2:9 │ 2 │ @target(erl) │ ^^^ This should be replaced with `erlang` This shorthand target name is deprecated. Use the full name: `erlang` instead. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__deprecated_target_shorthand_javascript.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n@target(js)\npub fn wibble() { panic }\n" --- ----- SOURCE CODE @target(js) pub fn wibble() { panic } ----- WARNING warning: Deprecated target shorthand syntax ┌─ test/path:2:9 │ 2 │ @target(js) │ ^^ This should be replaced with `javascript` This shorthand target name is deprecated. Use the full name: `javascript` instead. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__deprecated_type_used_as_arg.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n@deprecated(\"Don't use this!\")\npub type Cat {\n Cat(name: String)\n}\n\npub fn cat_name(cat: Cat) {\n cat.name\n}\n " --- ----- SOURCE CODE @deprecated("Don't use this!") pub type Cat { Cat(name: String) } pub fn cat_name(cat: Cat) { cat.name } ----- WARNING warning: Deprecated type used ┌─ /src/warning/wrn.gleam:7:22 │ 7 │ pub fn cat_name(cat: Cat) { │ ^^^ This type has been deprecated It was deprecated with this message: Don't use this! ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__deprecated_type_used_as_case_clause.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n@deprecated(\"The type Animal has been deprecated.\")\npub type Animal {\n Cat\n Dog\n}\n\npub fn sound(animal) -> String {\n case animal {\n Dog -> \"Woof\"\n Cat -> \"Meow\"\n }\n}\n\npub fn main(){\n let cat = Cat\n sound(cat)\n}\n " --- ----- SOURCE CODE @deprecated("The type Animal has been deprecated.") pub type Animal { Cat Dog } pub fn sound(animal) -> String { case animal { Dog -> "Woof" Cat -> "Meow" } } pub fn main(){ let cat = Cat sound(cat) } ----- WARNING warning: Deprecated value used ┌─ /src/warning/wrn.gleam:10:5 │ 10 │ Dog -> "Woof" │ ^^^ This value has been deprecated It was deprecated with this message: The type Animal has been deprecated. warning: Deprecated value used ┌─ /src/warning/wrn.gleam:11:5 │ 11 │ Cat -> "Meow" │ ^^^ This value has been deprecated It was deprecated with this message: The type Animal has been deprecated. warning: Deprecated value used ┌─ /src/warning/wrn.gleam:16:15 │ 16 │ let cat = Cat │ ^^^ This value has been deprecated It was deprecated with this message: The type Animal has been deprecated. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__deprecated_type_used_in_alias.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n@deprecated(\"Don't use this!\")\npub type Cat {\n Cat(name: String)\n}\n\npub type Dog = Cat\n " --- ----- SOURCE CODE @deprecated("Don't use this!") pub type Cat { Cat(name: String) } pub type Dog = Cat ----- WARNING warning: Deprecated type used ┌─ /src/warning/wrn.gleam:7:16 │ 7 │ pub type Dog = Cat │ ^^^ This type has been deprecated It was deprecated with this message: Don't use this! ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__detached_doc_comment.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n/// This comment is detached\n//\n\n/// This is actual documentation\npub const pi = 3.14\n" --- ----- SOURCE CODE /// This comment is detached // /// This is actual documentation pub const pi = 3.14 ----- WARNING warning: Detached doc comment ┌─ test/path:2:4 │ 2 │ /// This comment is detached │ ^^^^^^^^^^^^^^^^^^^^^^^^^ This is not attached to a definition This doc comment is followed by a regular comment so it is not attached to any definition. Hint: Move the comment above the doc comment ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__different_records_0_redundant_comparison.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Either {\n Left\n Right\n}\n\npub fn main() -> Bool {\n Left == Right\n}\n" --- ----- SOURCE CODE pub type Either { Left Right } pub fn main() -> Bool { Left == Right } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:8:3 │ 8 │ Left == Right │ ^^^^^^^^^^^^^ This is always `False` This comparison is redundant since it always fails. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__different_records_1_redundant_comparison.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Either {\n Left(Int)\n Right\n}\n\npub fn main() -> Bool {\n Left(1) == Right\n}\n" --- ----- SOURCE CODE pub type Either { Left(Int) Right } pub fn main() -> Bool { Left(1) == Right } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:8:3 │ 8 │ Left(1) == Right │ ^^^^^^^^^^^^^^^^ This is always `False` This comparison is redundant since it always fails. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__different_records_2_redundant_comparison.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Either {\n Left(Int)\n Right(Int)\n}\n\npub fn main() -> Bool {\n Left(1) == Right(1)\n}\n" --- ----- SOURCE CODE pub type Either { Left(Int) Right(Int) } pub fn main() -> Bool { Left(1) == Right(1) } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:8:3 │ 8 │ Left(1) == Right(1) │ ^^^^^^^^^^^^^^^^^^^ This is always `False` This comparison is redundant since it always fails. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__different_records_3_redundant_comparison.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Either {\n Left\n Right(Int)\n}\n\npub fn main() -> Bool {\n Left == Right(1)\n}\n" --- ----- SOURCE CODE pub type Either { Left Right(Int) } pub fn main() -> Bool { Left == Right(1) } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:8:3 │ 8 │ Left == Right(1) │ ^^^^^^^^^^^^^^^^ This is always `False` This comparison is redundant since it always fails. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__doesnt_warn_twice_for_unreachable_code_if_has_already_warned_in_a_block_1.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn wibble(_) { 1 }\n pub fn main() {\n panic\n let _ = \"unreachable\" // warning here\n panic\n \"no warning here!\"\n }\n " --- ----- SOURCE CODE pub fn wibble(_) { 1 } pub fn main() { panic let _ = "unreachable" // warning here panic "no warning here!" } ----- WARNING warning: Unreachable code ┌─ /src/warning/wrn.gleam:5:21 │ 5 │ let _ = "unreachable" // warning here │ ^^^^^^^^^^^^^ This code is unreachable because it comes after a `panic`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__doesnt_warn_twice_for_unreachable_code_if_has_already_warned_in_a_block_2.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn main() {\n let _ = {\n panic\n 1 // warning here\n }\n \"no warning here!\"\n }\n " --- ----- SOURCE CODE pub fn main() { let _ = { panic 1 // warning here } "no warning here!" } ----- WARNING warning: Unreachable code ┌─ /src/warning/wrn.gleam:5:15 │ 5 │ 1 // warning here │ ^ This code is unreachable because it comes after a `panic`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__double_unary_bool_literal.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { let _ = !!True }" --- ----- SOURCE CODE pub fn main() { let _ = !!True } ----- WARNING warning: Unnecessary double negation (!!) on bool ┌─ /src/warning/wrn.gleam:1:25 │ 1 │ pub fn main() { let _ = !!True } │ ^^ You can safely remove this. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__double_unary_bool_variable.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn main() {\n let x = True\n let _ = !!x\n }\n " --- ----- SOURCE CODE pub fn main() { let x = True let _ = !!x } ----- WARNING warning: Unnecessary double negation (!!) on bool ┌─ /src/warning/wrn.gleam:4:21 │ 4 │ let _ = !!x │ ^^ You can safely remove this. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__double_unary_integer_literal.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { let _ = --7 }" --- ----- SOURCE CODE pub fn main() { let _ = --7 } ----- WARNING warning: Unnecessary double negation (--) on integer ┌─ /src/warning/wrn.gleam:1:25 │ 1 │ pub fn main() { let _ = --7 } │ ^^ You can safely remove this. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__double_unary_integer_variable.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn main() {\n let x = 7\n let _ = --x\n }\n " --- ----- SOURCE CODE pub fn main() { let x = 7 let _ = --x } ----- WARNING warning: Unnecessary double negation (--) on integer ┌─ /src/warning/wrn.gleam:4:21 │ 4 │ let _ = --x │ ^^ You can safely remove this. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__echo_followed_by_panic.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n echo panic\n}\n" --- ----- SOURCE CODE pub fn main() { echo panic } ----- WARNING warning: Unreachable code ┌─ /src/warning/wrn.gleam:3:3 │ 3 │ echo panic │ ^^^^^^^^^^ This `echo` won't print anything because the expression it should be printing always panics. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__echo_followed_by_panicking_expression.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main(a) {\n echo case a {\n 1 -> panic\n _ -> [1, panic]\n }\n}\n" --- ----- SOURCE CODE pub fn main(a) { echo case a { 1 -> panic _ -> [1, panic] } } ----- WARNING warning: Unreachable code ┌─ /src/warning/wrn.gleam:3:3 │ 3 │ ╭ echo case a { 4 │ │ 1 -> panic 5 │ │ _ -> [1, panic] 6 │ │ } │ ╰───^ This `echo` won't print anything because the expression it should be printing always panics. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__empty_func_warning_test.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { wibble() }\npub fn wibble() { }\n" --- ----- SOURCE CODE pub fn main() { wibble() } pub fn wibble() { } ----- WARNING warning: Unimplemented function ┌─ /src/warning/wrn.gleam:2:1 │ 2 │ pub fn wibble() { } │ ^^^^^^^^^^^^^^^ This code is incomplete This code will crash if it is run. Be sure to finish it before running your program. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__empty_guard_clause.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n let wibble = 10\n case wibble {\n 10 if -> True\n _ -> False\n }\n\n}" --- ----- SOURCE CODE pub fn main() { let wibble = 10 case wibble { 10 if -> True _ -> False } } ----- WARNING warning: Deprecated empty guard syntax ┌─ test/path:5:12 │ 5 │ 10 if -> True │ ^^ This can be removed. This syntax for an empty guard is deprecated. To have a clause without a guard, remove this. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__even_number_of_multiple_bool_negations_raise_a_single_warning.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { let _ = !!!!True }" --- ----- SOURCE CODE pub fn main() { let _ = !!!!True } ----- WARNING warning: Unnecessary double negation (!!) on bool ┌─ /src/warning/wrn.gleam:1:25 │ 1 │ pub fn main() { let _ = !!!!True } │ ^^^^ You can safely remove this. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__even_number_of_multiple_integer_negations_raise_a_single_warning.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { let _ = ----7 }" --- ----- SOURCE CODE pub fn main() { let _ = ----7 } ----- WARNING warning: Unnecessary double negation (--) on integer ┌─ /src/warning/wrn.gleam:1:25 │ 1 │ pub fn main() { let _ = ----7 } │ ^^^^ You can safely remove this. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__expression_in_expression_segment_size_requires_v1_12_warning.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n <<1:size(3 * 8)>>\n}\n" --- ----- SOURCE CODE pub fn main() { <<1:size(3 * 8)>> } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:3:12 │ 3 │ <<1:size(3 * 8)>> │ ^^^^^ This requires a Gleam version >= 1.12.0 Expressions in segment sizes were introduced in version v1.12.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.12.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__expression_in_pattern_segment_size_requires_v1_12_warning.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main(x) {\n case x {\n <<_:size(3*8)>> -> 1\n _ -> 2\n }\n}" --- ----- SOURCE CODE pub fn main(x) { case x { <<_:size(3*8)>> -> 1 _ -> 2 } } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:4:14 │ 4 │ <<_:size(3*8)>> -> 1 │ ^^^ This requires a Gleam version >= 1.12.0 Expressions in segment sizes were introduced in version v1.12.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.12.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__external_annotation_on_custom_type_requires_v1_14.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n@external(erlang, \"wibble\", \"wobble\")\npub type Wobble\n" --- ----- SOURCE CODE @external(erlang, "wibble", "wobble") pub type Wobble ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:3:1 │ 3 │ pub type Wobble │ ^^^^^^^^^^^^^^^ This requires a Gleam version >= 1.14.0 The `@external` annotation on custom types was introduced in version v1.14.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.14.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__float_divide_in_guards_requires_v1_3.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n case Nil {\n _ if 1.0 /. 1.0 == 0.0 -> Nil\n _ -> Nil\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case Nil { _ if 1.0 /. 1.0 == 0.0 -> Nil _ -> Nil } } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:4:8 │ 4 │ _ if 1.0 /. 1.0 == 0.0 -> Nil │ ^^^^^^^^^^ This requires a Gleam version >= 1.3.0 Arithmetic operations in guards were introduced in version v1.3.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.3.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__float_literals_redundant_comparison.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { 1.0 == 1.0 }" --- ----- SOURCE CODE pub fn main() { 1.0 == 1.0 } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { 1.0 == 1.0 } │ ^^^^^^^^^^ This is always `True` This comparison is redundant since it always succeeds. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__float_literals_redundant_comparison_2.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { 1.0 == 2.0 }" --- ----- SOURCE CODE pub fn main() { 1.0 == 2.0 } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { 1.0 == 2.0 } │ ^^^^^^^^^^ This is always `False` This comparison is redundant since it always fails. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__float_literals_redundant_comparison_3.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { 1.0 != 1.0 }" --- ----- SOURCE CODE pub fn main() { 1.0 != 1.0 } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { 1.0 != 1.0 } │ ^^^^^^^^^^ This is always `False` This comparison is redundant since it always fails. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__float_literals_redundant_comparison_4.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { 1.0 != 2.0 }" --- ----- SOURCE CODE pub fn main() { 1.0 != 2.0 } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { 1.0 != 2.0 } │ ^^^^^^^^^^ This is always `True` This comparison is redundant since it always succeeds. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__float_literals_redundant_comparison_5.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { 1.0 >. 2.0 }" --- ----- SOURCE CODE pub fn main() { 1.0 >. 2.0 } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { 1.0 >. 2.0 } │ ^^^^^^^^^^ This is always `False` This comparison is redundant since it always fails. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__float_literals_redundant_comparison_6.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { 1.0 <=. 2.0 }" --- ----- SOURCE CODE pub fn main() { 1.0 <=. 2.0 } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { 1.0 <=. 2.0 } │ ^^^^^^^^^^^ This is always `True` This comparison is redundant since it always succeeds. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__float_literals_redundant_comparison_7.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { 1.0 <. 2.0 }" --- ----- SOURCE CODE pub fn main() { 1.0 <. 2.0 } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { 1.0 <. 2.0 } │ ^^^^^^^^^^ This is always `True` This comparison is redundant since it always succeeds. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__float_literals_redundant_comparison_8.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { 1.0 >=. 2.0 }" --- ----- SOURCE CODE pub fn main() { 1.0 >=. 2.0 } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { 1.0 >=. 2.0 } │ ^^^^^^^^^^^ This is always `False` This comparison is redundant since it always fails. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__float_literals_redundant_comparison_different_repr.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { 1_0.0 == 10.0 }" --- ----- SOURCE CODE pub fn main() { 1_0.0 == 10.0 } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { 1_0.0 == 10.0 } │ ^^^^^^^^^^^^^ This is always `True` This comparison is redundant since it always succeeds. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__float_literals_redundant_comparison_different_repr_2.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { 10.0 == 1.0e1 }" --- ----- SOURCE CODE pub fn main() { 10.0 == 1.0e1 } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { 10.0 == 1.0e1 } │ ^^^^^^^^^^^^^ This is always `True` This comparison is redundant since it always succeeds. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__float_literals_redundant_comparison_infinity.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { 1.0e500 == 1.0e600 }" --- ----- SOURCE CODE pub fn main() { 1.0e500 == 1.0e600 } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { 1.0e500 == 1.0e600 } │ ^^^^^^^^^^^^^^^^^^ This is always `True` This comparison is redundant since it always succeeds. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__float_literals_redundant_comparison_omitted_zero.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { 10. == 10.0 }" --- ----- SOURCE CODE pub fn main() { 10. == 10.0 } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { 10. == 10.0 } │ ^^^^^^^^^^^ This is always `True` This comparison is redundant since it always succeeds. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__float_literals_redundant_comparison_precision_loss.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { 1.0e-500 == 1.0e-600 }" --- ----- SOURCE CODE pub fn main() { 1.0e-500 == 1.0e-600 } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { 1.0e-500 == 1.0e-600 } │ ^^^^^^^^^^^^^^^^^^^^ This is always `True` This comparison is redundant since it always succeeds. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__float_literals_redundant_comparison_signed_zero.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { 0.0 == -0.0 }" --- ----- SOURCE CODE pub fn main() { 0.0 == -0.0 } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { 0.0 == -0.0 } │ ^^^^^^^^^^^ This is always `True` This comparison is redundant since it always succeeds. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__float_minus_in_guards_requires_v1_3.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n case Nil {\n _ if 1.0 -. 1.0 == 0.0 -> Nil\n _ -> Nil\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case Nil { _ if 1.0 -. 1.0 == 0.0 -> Nil _ -> Nil } } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:4:10 │ 4 │ _ if 1.0 -. 1.0 == 0.0 -> Nil │ ^^^^^^^^^^ This requires a Gleam version >= 1.3.0 Arithmetic operations in guards were introduced in version v1.3.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.3.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__float_multiplication_in_guards_requires_v1_3.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n case Nil {\n _ if 1.0 *. 1.0 == 0.0 -> Nil\n _ -> Nil\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case Nil { _ if 1.0 *. 1.0 == 0.0 -> Nil _ -> Nil } } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:4:8 │ 4 │ _ if 1.0 *. 1.0 == 0.0 -> Nil │ ^^^^^^^^^^ This requires a Gleam version >= 1.3.0 Arithmetic operations in guards were introduced in version v1.3.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.3.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__float_plus_in_guards_requires_v1_3.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n case Nil {\n _ if 1.0 +. 1.0 == 2.0 -> Nil\n _ -> Nil\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case Nil { _ if 1.0 +. 1.0 == 2.0 -> Nil _ -> Nil } } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:4:10 │ 4 │ _ if 1.0 +. 1.0 == 2.0 -> Nil │ ^^^^^^^^^^ This requires a Gleam version >= 1.3.0 Arithmetic operations in guards were introduced in version v1.3.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.3.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__function_is_impure_if_uses_todo.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\nfn add(a: Int, b: Int) -> Int {\n case a + b {\n 0 -> todo as \"Handle zero\"\n x -> x\n }\n}\n\npub fn main() {\n add(1, 2)\n Nil\n}\n" --- ----- SOURCE CODE fn add(a: Int, b: Int) -> Int { case a + b { 0 -> todo as "Handle zero" x -> x } } pub fn main() { add(1, 2) Nil } ----- WARNING warning: Todo found ┌─ /src/warning/wrn.gleam:4:10 │ 4 │ 0 -> todo as "Handle zero" │ ^^^^^^^^^^^^^^^^^^^^^ This code is incomplete This code will crash if it is run. Be sure to finish it before running your program. Hint: I think its type is `Int`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__function_is_pure_on_erlang_if_external_on_js.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n@external(javascript, \"./maths.mjs\", \"add\")\nfn add(a: Int, b: Int) -> Int { a + b }\n\npub fn main() {\n add(1, 2)\n Nil\n}\n" --- ----- SOURCE CODE @external(javascript, "./maths.mjs", "add") fn add(a: Int, b: Int) -> Int { a + b } pub fn main() { add(1, 2) Nil } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:6:3 │ 6 │ add(1, 2) │ ^^^^^^^^^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__function_is_pure_on_js_if_external_on_erlang.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n@external(erlang, \"maths\", \"add\")\nfn add(a: Int, b: Int) -> Int { a + b }\n\npub fn main() {\n add(1, 2)\n Nil\n}\n" --- ----- SOURCE CODE @external(erlang, "maths", "add") fn add(a: Int, b: Int) -> Int { a + b } pub fn main() { add(1, 2) Nil } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:6:3 │ 6 │ add(1, 2) │ ^^^^^^^^^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__import_module_twice.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "import gleam/wibble as a\nimport gleam/wibble as b\n\npub fn main() {\n a.wobble() + b.wobble()\n}\n" --- ----- SOURCE CODE -- gleam/wibble.gleam pub fn wobble() { 1 } -- main.gleam import gleam/wibble as a import gleam/wibble as b pub fn main() { a.wobble() + b.wobble() } ----- WARNING warning: Duplicate import ┌─ /src/warning/wrn.gleam:2:1 │ 1 │ import gleam/wibble as a │ ------------------------ First imported here 2 │ import gleam/wibble as b │ ^^^^^^^^^^^^^^^^^^^^^^^^ Reimported here The gleam/wibble module has been imported twice. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__importing_non_direct_dep_package.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\nimport some_module\npub const x = some_module.x\n " --- ----- SOURCE CODE -- some_module.gleam pub const x = 1 -- main.gleam import some_module pub const x = some_module.x ----- WARNING warning: Transitive dependency imported ┌─ /src/warning/wrn.gleam:2:1 │ 2 │ import some_module │ ^^^^^^^^^^^^^^^^^^ The module `some_module` is being imported, but `non-dependency-package`, the package it belongs to, is not a direct dependency of your package. In a future version of Gleam this may become a compile error. Run this command to add it to your dependencies: gleam add non-dependency-package ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__impossible_to_reach_integer_segment.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main(x) {\n case x {\n <<9:size(2)>> -> True\n _ -> False\n }\n}" --- ----- SOURCE CODE pub fn main(x) { case x { <<9:size(2)>> -> True _ -> False } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:4:5 │ 4 │ <<9:size(2)>> -> True │ ^^^^^^^^^^^^^ │ │ │ A 2 bits unsigned integer will never match this value This pattern cannot be reached as it contains segments that will never match. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__impossible_to_reach_integer_segment_2.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main(x) {\n case x {\n <<-9:unsigned>> -> True\n _ -> False\n }\n}" --- ----- SOURCE CODE pub fn main(x) { case x { <<-9:unsigned>> -> True _ -> False } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:4:5 │ 4 │ <<-9:unsigned>> -> True │ ^^^^^^^^^^^^^^^ │ │ │ A 1 byte unsigned integer will never match this value This pattern cannot be reached as it contains segments that will never match. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__impossible_to_reach_integer_segment_3.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main(x) {\n case x {\n <<312>> -> True\n _ -> False\n }\n}" --- ----- SOURCE CODE pub fn main(x) { case x { <<312>> -> True _ -> False } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:4:5 │ 4 │ <<312>> -> True │ ^^^^^^^ │ │ │ A 1 byte unsigned integer will never match this value This pattern cannot be reached as it contains segments that will never match. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__impossible_to_reach_integer_segment_4.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main(x) {\n case x {\n <<-1>> -> True\n _ -> False\n }\n}" --- ----- SOURCE CODE pub fn main(x) { case x { <<-1>> -> True _ -> False } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:4:5 │ 4 │ <<-1>> -> True │ ^^^^^^ │ │ │ A 1 byte unsigned integer will never match this value This pattern cannot be reached as it contains segments that will never match. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__incomplete_code_block_raises_warning.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n {}\n}\n" --- ----- SOURCE CODE pub fn main() { {} } ----- WARNING warning: Incomplete block ┌─ /src/warning/wrn.gleam:3:5 │ 3 │ {} │ ^^ This code is incomplete This code will crash if it is run. Be sure to finish it before running your program. A block must always contain at least one expression. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__int_divide_in_guards_requires_v1_3.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n case Nil {\n _ if 1 / 1 == 0 -> Nil\n _ -> Nil\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case Nil { _ if 1 / 1 == 0 -> Nil _ -> Nil } } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:4:8 │ 4 │ _ if 1 / 1 == 0 -> Nil │ ^^^^^ This requires a Gleam version >= 1.3.0 Arithmetic operations in guards were introduced in version v1.3.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.3.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__int_literals_redundant_comparison.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { 1 == 1 }" --- ----- SOURCE CODE pub fn main() { 1 == 1 } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { 1 == 1 } │ ^^^^^^ This is always `True` This comparison is redundant since it always succeeds. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__int_literals_redundant_comparison_2.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { 1 == 2 }" --- ----- SOURCE CODE pub fn main() { 1 == 2 } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { 1 == 2 } │ ^^^^^^ This is always `False` This comparison is redundant since it always fails. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__int_literals_redundant_comparison_3.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { 1 != 1 }" --- ----- SOURCE CODE pub fn main() { 1 != 1 } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { 1 != 1 } │ ^^^^^^ This is always `False` This comparison is redundant since it always fails. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__int_literals_redundant_comparison_4.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { 1 != 2 }" --- ----- SOURCE CODE pub fn main() { 1 != 2 } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { 1 != 2 } │ ^^^^^^ This is always `True` This comparison is redundant since it always succeeds. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__int_literals_redundant_comparison_5.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { 1 > 2 }" --- ----- SOURCE CODE pub fn main() { 1 > 2 } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { 1 > 2 } │ ^^^^^ This is always `False` This comparison is redundant since it always fails. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__int_literals_redundant_comparison_6.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { 1 <= 2 }" --- ----- SOURCE CODE pub fn main() { 1 <= 2 } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { 1 <= 2 } │ ^^^^^^ This is always `True` This comparison is redundant since it always succeeds. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__int_literals_redundant_comparison_7.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { 1 < 2 }" --- ----- SOURCE CODE pub fn main() { 1 < 2 } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { 1 < 2 } │ ^^^^^ This is always `True` This comparison is redundant since it always succeeds. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__int_literals_redundant_comparison_8.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { 1 >= 2 }" --- ----- SOURCE CODE pub fn main() { 1 >= 2 } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { 1 >= 2 } │ ^^^^^^ This is always `False` This comparison is redundant since it always fails. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__int_minus_in_guards_requires_v1_3.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n case Nil {\n _ if 1 - 1 == 0 -> Nil\n _ -> Nil\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case Nil { _ if 1 - 1 == 0 -> Nil _ -> Nil } } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:4:10 │ 4 │ _ if 1 - 1 == 0 -> Nil │ ^^^^^ This requires a Gleam version >= 1.3.0 Arithmetic operations in guards were introduced in version v1.3.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.3.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__int_multiplication_in_guards_requires_v1_3.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n case Nil {\n _ if 1 * 1 == 0 -> Nil\n _ -> Nil\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case Nil { _ if 1 * 1 == 0 -> Nil _ -> Nil } } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:4:8 │ 4 │ _ if 1 * 1 == 0 -> Nil │ ^^^^^ This requires a Gleam version >= 1.3.0 Arithmetic operations in guards were introduced in version v1.3.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.3.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__int_plus_in_guards_requires_v1_3.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n case Nil {\n _ if 1 + 1 == 2 -> Nil\n _ -> Nil\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case Nil { _ if 1 + 1 == 2 -> Nil _ -> Nil } } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:4:10 │ 4 │ _ if 1 + 1 == 2 -> Nil │ ^^^^^ This requires a Gleam version >= 1.3.0 Arithmetic operations in guards were introduced in version v1.3.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.3.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__int_remainder_in_guards_requires_v1_3.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n case Nil {\n _ if 1 % 1 == 0 -> Nil\n _ -> Nil\n }\n}\n" --- ----- SOURCE CODE pub fn main() { case Nil { _ if 1 % 1 == 0 -> Nil _ -> Nil } } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:4:8 │ 4 │ _ if 1 % 1 == 0 -> Nil │ ^^^^^ This requires a Gleam version >= 1.3.0 Arithmetic operations in guards were introduced in version v1.3.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.3.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__internal_annotation_on_constant_requires_v1_1.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n@internal\npub const wibble = 1\n" --- ----- SOURCE CODE @internal pub const wibble = 1 ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:2:1 │ 2 │ @internal │ ^^^^^^^^^ This requires a Gleam version >= 1.1.0 The `@internal` annotation was introduced in version v1.1.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.1.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__internal_annotation_on_function_requires_v1_1.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n@internal\npub fn wibble() { Nil }\n" --- ----- SOURCE CODE @internal pub fn wibble() { Nil } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:2:1 │ 2 │ @internal │ ^^^^^^^^^ This requires a Gleam version >= 1.1.0 The `@internal` annotation was introduced in version v1.1.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.1.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__internal_annotation_on_type_requires_v1_1.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n@internal\npub type Wibble\n" --- ----- SOURCE CODE @internal pub type Wibble ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:2:1 │ 2 │ @internal │ ^^^^^^^^^ This requires a Gleam version >= 1.1.0 The `@internal` annotation was introduced in version v1.1.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.1.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__javascript_external_module_with_at_requires_v1_2.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n@external(javascript, \"module@module\", \"func\")\npub fn main() { Nil }\n" --- ----- SOURCE CODE @external(javascript, "module@module", "func") pub fn main() { Nil } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:2:1 │ 2 │ @external(javascript, "module@module", "func") │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This requires a Gleam version >= 1.2.0 The ability to have `@` in a Javascript module's name was introduced in version v1.2.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.2.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__javascript_unsafe_int_binary.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn go() {\n [\n 0b11111111111111111111111111111111111111111111111111110,\n 0b11111111111111111111111111111111111111111111111111111,\n 0b100000000000000000000000000000000000000000000000000000,\n ]\n}\n" --- ----- SOURCE CODE pub fn go() { [ 0b11111111111111111111111111111111111111111111111111110, 0b11111111111111111111111111111111111111111111111111111, 0b100000000000000000000000000000000000000000000000000000, ] } ----- WARNING warning: Int is outside JavaScript's safe integer range ┌─ /src/warning/wrn.gleam:6:5 │ 6 │ 0b100000000000000000000000000000000000000000000000000000, │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This is not a safe integer value on JavaScript This integer value is too large to be represented accurately by JavaScript's number type. To avoid this warning integer values must be in the range -(2^53 - 1) - (2^53 - 1). See JavaScript's Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER properties for more information. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__javascript_unsafe_int_decimal.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn go() {\n [\n 9_007_199_254_740_990,\n 9_007_199_254_740_991,\n 9_007_199_254_740_992,\n -9_007_199_254_740_990,\n -9_007_199_254_740_991,\n -9_007_199_254_740_992,\n ]\n}\n" --- ----- SOURCE CODE pub fn go() { [ 9_007_199_254_740_990, 9_007_199_254_740_991, 9_007_199_254_740_992, -9_007_199_254_740_990, -9_007_199_254_740_991, -9_007_199_254_740_992, ] } ----- WARNING warning: Int is outside JavaScript's safe integer range ┌─ /src/warning/wrn.gleam:6:5 │ 6 │ 9_007_199_254_740_992, │ ^^^^^^^^^^^^^^^^^^^^^ This is not a safe integer value on JavaScript This integer value is too large to be represented accurately by JavaScript's number type. To avoid this warning integer values must be in the range -(2^53 - 1) - (2^53 - 1). See JavaScript's Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER properties for more information. warning: Int is outside JavaScript's safe integer range ┌─ /src/warning/wrn.gleam:9:5 │ 9 │ -9_007_199_254_740_992, │ ^^^^^^^^^^^^^^^^^^^^^^ This is not a safe integer value on JavaScript This integer value is too large to be represented accurately by JavaScript's number type. To avoid this warning integer values must be in the range -(2^53 - 1) - (2^53 - 1). See JavaScript's Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER properties for more information. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__javascript_unsafe_int_hex.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn go() {\n [\n 0x1FFFFFFFFFFFFE,\n 0x1FFFFFFFFFFFFF,\n 0x20000000000000,\n ]\n}\n" --- ----- SOURCE CODE pub fn go() { [ 0x1FFFFFFFFFFFFE, 0x1FFFFFFFFFFFFF, 0x20000000000000, ] } ----- WARNING warning: Int is outside JavaScript's safe integer range ┌─ /src/warning/wrn.gleam:6:5 │ 6 │ 0x20000000000000, │ ^^^^^^^^^^^^^^^^ This is not a safe integer value on JavaScript This integer value is too large to be represented accurately by JavaScript's number type. To avoid this warning integer values must be in the range -(2^53 - 1) - (2^53 - 1). See JavaScript's Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER properties for more information. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__javascript_unsafe_int_in_const.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: pub const i = 9_007_199_254_740_992 --- ----- SOURCE CODE pub const i = 9_007_199_254_740_992 ----- WARNING warning: Int is outside JavaScript's safe integer range ┌─ /src/warning/wrn.gleam:1:15 │ 1 │ pub const i = 9_007_199_254_740_992 │ ^^^^^^^^^^^^^^^^^^^^^ This is not a safe integer value on JavaScript This integer value is too large to be represented accurately by JavaScript's number type. To avoid this warning integer values must be in the range -(2^53 - 1) - (2^53 - 1). See JavaScript's Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER properties for more information. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__javascript_unsafe_int_in_const_tuple.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub const i = #(9_007_199_254_740_992)" --- ----- SOURCE CODE pub const i = #(9_007_199_254_740_992) ----- WARNING warning: Int is outside JavaScript's safe integer range ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub const i = #(9_007_199_254_740_992) │ ^^^^^^^^^^^^^^^^^^^^^ This is not a safe integer value on JavaScript This integer value is too large to be represented accurately by JavaScript's number type. To avoid this warning integer values must be in the range -(2^53 - 1) - (2^53 - 1). See JavaScript's Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER properties for more information. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__javascript_unsafe_int_in_pattern.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn go() {\n let assert <<9_007_199_254_740_992:64>> = <<>>\n}\n" --- ----- SOURCE CODE pub fn go() { let assert <<9_007_199_254_740_992:64>> = <<>> } ----- WARNING warning: Int is outside JavaScript's safe integer range ┌─ /src/warning/wrn.gleam:3:16 │ 3 │ let assert <<9_007_199_254_740_992:64>> = <<>> │ ^^^^^^^^^^^^^^^^^^^^^ This is not a safe integer value on JavaScript This integer value is too large to be represented accurately by JavaScript's number type. To avoid this warning integer values must be in the range -(2^53 - 1) - (2^53 - 1). See JavaScript's Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER properties for more information. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__javascript_unsafe_int_in_tuple.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn go() {\n #(9_007_199_254_740_992)\n}\n" --- ----- SOURCE CODE pub fn go() { #(9_007_199_254_740_992) } ----- WARNING warning: Int is outside JavaScript's safe integer range ┌─ /src/warning/wrn.gleam:3:5 │ 3 │ #(9_007_199_254_740_992) │ ^^^^^^^^^^^^^^^^^^^^^ This is not a safe integer value on JavaScript This integer value is too large to be represented accurately by JavaScript's number type. To avoid this warning integer values must be in the range -(2^53 - 1) - (2^53 - 1). See JavaScript's Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER properties for more information. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__javascript_unsafe_int_octal.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn go() {\n [\n 0o377777777777777776,\n 0o377777777777777777,\n 0o400000000000000000,\n ]\n}\n" --- ----- SOURCE CODE pub fn go() { [ 0o377777777777777776, 0o377777777777777777, 0o400000000000000000, ] } ----- WARNING warning: Int is outside JavaScript's safe integer range ┌─ /src/warning/wrn.gleam:6:5 │ 6 │ 0o400000000000000000, │ ^^^^^^^^^^^^^^^^^^^^ This is not a safe integer value on JavaScript This integer value is too large to be represented accurately by JavaScript's number type. To avoid this warning integer values must be in the range -(2^53 - 1) - (2^53 - 1). See JavaScript's Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER properties for more information. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__javascript_unsafe_int_segment_in_bit_array.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn go() {\n <<9_007_199_254_740_992:64>>\n}\n" --- ----- SOURCE CODE pub fn go() { <<9_007_199_254_740_992:64>> } ----- WARNING warning: Int is outside JavaScript's safe integer range ┌─ /src/warning/wrn.gleam:3:5 │ 3 │ <<9_007_199_254_740_992:64>> │ ^^^^^^^^^^^^^^^^^^^^^ This is not a safe integer value on JavaScript This integer value is too large to be represented accurately by JavaScript's number type. To avoid this warning integer values must be in the range -(2^53 - 1) - (2^53 - 1). See JavaScript's Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER properties for more information. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__javascript_unsafe_int_segment_in_const_bit_array.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub const i = <<9_007_199_254_740_992:64>>\n" --- ----- SOURCE CODE pub const i = <<9_007_199_254_740_992:64>> ----- WARNING warning: Int is outside JavaScript's safe integer range ┌─ /src/warning/wrn.gleam:2:17 │ 2 │ pub const i = <<9_007_199_254_740_992:64>> │ ^^^^^^^^^^^^^^^^^^^^^ This is not a safe integer value on JavaScript This integer value is too large to be represented accurately by JavaScript's number type. To avoid this warning integer values must be in the range -(2^53 - 1) - (2^53 - 1). See JavaScript's Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER properties for more information. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__javascript_unsafe_int_segment_size_in_bit_array.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn go() {\n [\n <<0:9_007_199_254_740_992>>,\n <<0:size(9_007_199_254_740_992)>>,\n ]\n}\n" --- ----- SOURCE CODE pub fn go() { [ <<0:9_007_199_254_740_992>>, <<0:size(9_007_199_254_740_992)>>, ] } ----- WARNING warning: Int is outside JavaScript's safe integer range ┌─ /src/warning/wrn.gleam:4:9 │ 4 │ <<0:9_007_199_254_740_992>>, │ ^^^^^^^^^^^^^^^^^^^^^ This is not a safe integer value on JavaScript This integer value is too large to be represented accurately by JavaScript's number type. To avoid this warning integer values must be in the range -(2^53 - 1) - (2^53 - 1). See JavaScript's Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER properties for more information. warning: Int is outside JavaScript's safe integer range ┌─ /src/warning/wrn.gleam:5:14 │ 5 │ <<0:size(9_007_199_254_740_992)>>, │ ^^^^^^^^^^^^^^^^^^^^^ This is not a safe integer value on JavaScript This integer value is too large to be represented accurately by JavaScript's number type. To avoid this warning integer values must be in the range -(2^53 - 1) - (2^53 - 1). See JavaScript's Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER properties for more information. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__javascript_unsafe_int_segment_size_in_const_bit_array.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub const ints = [\n <<0:9_007_199_254_740_992>>,\n <<0:size(9_007_199_254_740_992)>>,\n]\n" --- ----- SOURCE CODE pub const ints = [ <<0:9_007_199_254_740_992>>, <<0:size(9_007_199_254_740_992)>>, ] ----- WARNING warning: Int is outside JavaScript's safe integer range ┌─ /src/warning/wrn.gleam:3:7 │ 3 │ <<0:9_007_199_254_740_992>>, │ ^^^^^^^^^^^^^^^^^^^^^ This is not a safe integer value on JavaScript This integer value is too large to be represented accurately by JavaScript's number type. To avoid this warning integer values must be in the range -(2^53 - 1) - (2^53 - 1). See JavaScript's Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER properties for more information. warning: Int is outside JavaScript's safe integer range ┌─ /src/warning/wrn.gleam:4:12 │ 4 │ <<0:size(9_007_199_254_740_992)>>, │ ^^^^^^^^^^^^^^^^^^^^^ This is not a safe integer value on JavaScript This integer value is too large to be represented accurately by JavaScript's number type. To avoid this warning integer values must be in the range -(2^53 - 1) - (2^53 - 1). See JavaScript's Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER properties for more information. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__javascript_unsafe_int_segment_size_in_pattern.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn go() {\n let assert <<0:9_007_199_254_740_992>> = <<>>\n}\n" --- ----- SOURCE CODE pub fn go() { let assert <<0:9_007_199_254_740_992>> = <<>> } ----- WARNING warning: Int is outside JavaScript's safe integer range ┌─ /src/warning/wrn.gleam:3:18 │ 3 │ let assert <<0:9_007_199_254_740_992>> = <<>> │ ^^^^^^^^^^^^^^^^^^^^^ This is not a safe integer value on JavaScript This integer value is too large to be represented accurately by JavaScript's number type. To avoid this warning integer values must be in the range -(2^53 - 1) - (2^53 - 1). See JavaScript's Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER properties for more information. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__javascript_unsafe_int_with_external_function_call.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n helper() + 9_007_199_254_740_992\n}\n\n@external(javascript, \"a\", \"b\")\nfn helper() -> Int\n" --- ----- SOURCE CODE pub fn main() { helper() + 9_007_199_254_740_992 } @external(javascript, "a", "b") fn helper() -> Int ----- WARNING warning: Int is outside JavaScript's safe integer range ┌─ /src/warning/wrn.gleam:3:14 │ 3 │ helper() + 9_007_199_254_740_992 │ ^^^^^^^^^^^^^^^^^^^^^ This is not a safe integer value on JavaScript This integer value is too large to be represented accurately by JavaScript's number type. To avoid this warning integer values must be in the range -(2^53 - 1) - (2^53 - 1). See JavaScript's Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER properties for more information. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__label_shorthand_in_call_requires_v1_4.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Wibble { Wibble(wibble: Int) }\n\npub fn main() {\n let wibble = 1\n Wibble(wibble:)\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(wibble: Int) } pub fn main() { let wibble = 1 Wibble(wibble:) } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:6:10 │ 6 │ Wibble(wibble:) │ ^^^^^^^ This requires a Gleam version >= 1.4.0 The label shorthand syntax was introduced in version v1.4.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.4.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__label_shorthand_in_constand_requires_v1_4.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Wibble { Wibble(wibble: Int) }\n\npub const wibble = 1\npub const wobble = Wibble(wibble:)\n" --- ----- SOURCE CODE pub type Wibble { Wibble(wibble: Int) } pub const wibble = 1 pub const wobble = Wibble(wibble:) ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:5:27 │ 5 │ pub const wobble = Wibble(wibble:) │ ^^^^^^^ This requires a Gleam version >= 1.4.0 The label shorthand syntax was introduced in version v1.4.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.4.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__label_shorthand_in_pattern_requires_v1_4.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Wibble { Wibble(wibble: Int) }\n\npub fn main(wibble) {\n case wibble {\n Wibble(wibble:) -> wibble\n }\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(wibble: Int) } pub fn main(wibble) { case wibble { Wibble(wibble:) -> wibble } } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:6:12 │ 6 │ Wibble(wibble:) -> wibble │ ^^^^^^^ This requires a Gleam version >= 1.4.0 The label shorthand syntax was introduced in version v1.4.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.4.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__let_assert_with_message_requires_v1_7.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n let assert Ok(10) = Ok(20) as \"This will crash...\"\n}\n" --- ----- SOURCE CODE pub fn main() { let assert Ok(10) = Ok(20) as "This will crash..." } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:3:33 │ 3 │ let assert Ok(10) = Ok(20) as "This will crash..." │ ^^^^^^^^^^^^^^^^^^^^ This requires a Gleam version >= 1.7.0 Specifying a custom panic message when using let assert was introduced in version v1.7.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.7.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__list_literals_redundant_comparison.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main(a, b) { [1] == [a, b(1)] }" --- ----- SOURCE CODE pub fn main(a, b) { [1] == [a, b(1)] } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:21 │ 1 │ pub fn main(a, b) { [1] == [a, b(1)] } │ ^^^^^^^^^^^^^^^^ This is always `False` This comparison is redundant since it always fails. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__list_literals_redundant_comparison_2.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main(a, b) { [1] != [a, b(1)] }" --- ----- SOURCE CODE pub fn main(a, b) { [1] != [a, b(1)] } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:21 │ 1 │ pub fn main(a, b) { [1] != [a, b(1)] } │ ^^^^^^^^^^^^^^^^ This is always `True` This comparison is redundant since it always succeeds. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__list_literals_redundant_comparison_3.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { [1] != [1] }" --- ----- SOURCE CODE pub fn main() { [1] != [1] } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { [1] != [1] } │ ^^^^^^^^^^ This is always `False` This comparison is redundant since it always fails. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__list_literals_redundant_comparison_4.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main(a) { [1, ..[1, a]] == [1, ..[1, a]] }" --- ----- SOURCE CODE pub fn main(a) { [1, ..[1, a]] == [1, ..[1, a]] } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:18 │ 1 │ pub fn main(a) { [1, ..[1, a]] == [1, ..[1, a]] } │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This is always `True` This comparison is redundant since it always succeeds. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__list_literals_redundant_comparison_5.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main(a) { [1, ..a] == [1, ..a] }" --- ----- SOURCE CODE pub fn main(a) { [1, ..a] == [1, ..a] } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:18 │ 1 │ pub fn main(a) { [1, ..a] == [1, ..a] } │ ^^^^^^^^^^^^^^^^^^^^ This is always `True` This comparison is redundant since it always succeeds. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__list_literals_redundant_comparison_7.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main(a) { [a(1), 2] == [a(1), 3] }" --- ----- SOURCE CODE pub fn main(a) { [a(1), 2] == [a(1), 3] } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:18 │ 1 │ pub fn main(a) { [a(1), 2] == [a(1), 3] } │ ^^^^^^^^^^^^^^^^^^^^^^ This is always `False` This comparison is redundant since it always fails. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__missing_float_option_in_bit_array_constant_segment_requires_v1_10.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: pub const bits = <<1.2>> --- ----- SOURCE CODE pub const bits = <<1.2>> ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:1:20 │ 1 │ pub const bits = <<1.2>> │ ^^^ This requires a Gleam version >= 1.10.0 The ability to omit the `float` annotation for float segments was introduced in version v1.10.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.10.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__missing_float_option_in_bit_array_pattern_segment_requires_v1_10.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main(a) {\n case a {\n <<1.2>> -> Nil\n _ -> Nil\n }\n}\n" --- ----- SOURCE CODE pub fn main(a) { case a { <<1.2>> -> Nil _ -> Nil } } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:4:7 │ 4 │ <<1.2>> -> Nil │ ^^^ This requires a Gleam version >= 1.10.0 The ability to omit the `float` annotation for float segments was introduced in version v1.10.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.10.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__missing_float_option_in_bit_array_segment_requires_v1_10.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n <<1.2>>\n}\n" --- ----- SOURCE CODE pub fn main() { <<1.2>> } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:3:5 │ 3 │ <<1.2>> │ ^^^ This requires a Gleam version >= 1.10.0 The ability to omit the `float` annotation for float segments was introduced in version v1.10.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.10.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__missing_utf_8_option_in_bit_array_constant_segment_requires_v1_5.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub const bits = <<\"hello\">>" --- ----- SOURCE CODE pub const bits = <<"hello">> ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:1:20 │ 1 │ pub const bits = <<"hello">> │ ^^^^^^^ This requires a Gleam version >= 1.5.0 The ability to omit the `utf8` annotation for string segments was introduced in version v1.5.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.5.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__missing_utf_8_option_in_bit_array_pattern_segment_requires_v1_5.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main(a) {\n case a {\n <<\"hello\">> -> Nil\n _ -> Nil\n }\n}\n" --- ----- SOURCE CODE pub fn main(a) { case a { <<"hello">> -> Nil _ -> Nil } } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:4:7 │ 4 │ <<"hello">> -> Nil │ ^^^^^^^ This requires a Gleam version >= 1.5.0 The ability to omit the `utf8` annotation for string segments was introduced in version v1.5.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.5.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__missing_utf_8_option_in_bit_array_segment_requires_v1_5.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n <<\"hello\">>\n}\n" --- ----- SOURCE CODE pub fn main() { <<"hello">> } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:3:5 │ 3 │ <<"hello">> │ ^^^^^^^ This requires a Gleam version >= 1.5.0 The ability to omit the `utf8` annotation for string segments was introduced in version v1.5.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.5.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__module_used_by_unused_function_is_not_marked_as_unused.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "import wibble\nfn wobble() {\n wibble.a\n}\n" --- ----- SOURCE CODE -- wibble.gleam pub const a = 1 -- main.gleam import wibble fn wobble() { wibble.a } ----- WARNING warning: Unused private function ┌─ /src/warning/wrn.gleam:2:1 │ 2 │ fn wobble() { │ ^^^^^^^^^^^ This private function is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__multiple_impossible_to_reach_integer_segments.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main(x) {\n case x {\n <<1, -1, 2, -3>> -> True\n _ -> False\n }\n}" --- ----- SOURCE CODE pub fn main(x) { case x { <<1, -1, 2, -3>> -> True _ -> False } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:4:5 │ 4 │ <<1, -1, 2, -3>> -> True │ ^^^^^^^^^^^^^^^^ │ │ │ │ │ A 1 byte unsigned integer will never match this value │ A 1 byte unsigned integer will never match this value This pattern cannot be reached as it contains segments that will never match. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__nested_tuple_access_requires_v1_1.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n let tuple = #(1, #(1, 1))\n tuple.1.0\n}\n" --- ----- SOURCE CODE pub fn main() { let tuple = #(1, #(1, 1)) tuple.1.0 } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:4:3 │ 4 │ tuple.1.0 │ ^^^^^^^^^ This requires a Gleam version >= 1.1.0 The ability to access nested tuple fields was introduced in version v1.1.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.1.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__odd_number_of_multiple_bool_negations_raise_a_single_warning_that_highlights_the_unnecessary_ones.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { let _ = !!!!!False }" --- ----- SOURCE CODE pub fn main() { let _ = !!!!!False } ----- WARNING warning: Unnecessary double negation (!!) on bool ┌─ /src/warning/wrn.gleam:1:25 │ 1 │ pub fn main() { let _ = !!!!!False } │ ^^^^ You can safely remove this. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__odd_number_of_multiple_integer_negations_raise_a_single_warning_that_highlights_the_unnecessary_ones.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { let _ = -----7 }" --- ----- SOURCE CODE pub fn main() { let _ = -----7 } ----- WARNING warning: Unnecessary double negation (--) on integer ┌─ /src/warning/wrn.gleam:1:25 │ 1 │ pub fn main() { let _ = -----7 } │ ^^^^ You can safely remove this. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__opaque_external_type_raises_a_warning.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: pub opaque type External --- ----- SOURCE CODE pub opaque type External ----- WARNING warning: Opaque external type ┌─ /src/warning/wrn.gleam:1:1 │ 1 │ pub opaque type External │ ^^^^^^^^^^^^^^^^^^^^^^^^ This type has no constructors so making it opaque is redundant. Hint: Remove the `opaque` qualifier from the type definition. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__panic_used_as_function.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() {\n panic()\n }" --- ----- SOURCE CODE pub fn main() { panic() } ----- WARNING warning: Panic used as a function ┌─ /src/warning/wrn.gleam:2:11 │ 2 │ panic() │ ^^^^^^^ `panic` is not a function, you can just write `panic` instead of `panic()`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__panic_used_as_function_2.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() {\n panic(1)\n }" --- ----- SOURCE CODE pub fn main() { panic(1) } ----- WARNING warning: Panic used as a function ┌─ /src/warning/wrn.gleam:2:17 │ 2 │ panic(1) │ ^ `panic` is not a function and will crash before it can do anything with this argument. Hint: if you want to display an error message you should write `panic as "my error message"` See: https://tour.gleam.run/advanced-features/panic/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__panic_used_as_function_3.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() {\n panic(1, Nil)\n }" --- ----- SOURCE CODE pub fn main() { panic(1, Nil) } ----- WARNING warning: Panic used as a function ┌─ /src/warning/wrn.gleam:2:17 │ 2 │ panic(1, Nil) │ ^^^^^^ `panic` is not a function and will crash before it can do anything with these arguments. Hint: if you want to display an error message you should write `panic as "my error message"` See: https://tour.gleam.run/advanced-features/panic/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__panic_used_as_function_inside_pipeline.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn wibble(_) { 1 }\n pub fn main() {\n 1 |> panic |> wibble\n }\n " --- ----- SOURCE CODE pub fn wibble(_) { 1 } pub fn main() { 1 |> panic |> wibble } ----- WARNING warning: Unreachable code ┌─ /src/warning/wrn.gleam:4:27 │ 4 │ 1 |> panic |> wibble │ ^^^^^^ This code is unreachable because it comes after a `panic`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__pattern_matching_on_64_float_float_is_unreachable.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn go(x) {\n case x {\n <<_:64-float>> -> \"Float\"\n <<_:64-float>> -> \"unreachable\"\n _ -> \"Other\"\n }\n}\n" --- ----- SOURCE CODE pub fn go(x) { case x { <<_:64-float>> -> "Float" <<_:64-float>> -> "unreachable" _ -> "Other" } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:5:5 │ 5 │ <<_:64-float>> -> "unreachable" │ ^^^^^^^^^^^^^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__pattern_matching_on_literal_empty_bit_array.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() {\n case <<>> {\n _ -> Nil\n }\n }" --- ----- SOURCE CODE pub fn main() { case <<>> { _ -> Nil } } ----- WARNING warning: Match on a literal value ┌─ /src/warning/wrn.gleam:2:14 │ 2 │ case <<>> { │ ^^^^ There's no need to pattern match on this value Matching on a literal value is redundant since you can already tell which branch is going to match with this value. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__pattern_matching_on_literal_empty_list.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() {\n case [] {\n _ -> Nil\n }\n }" --- ----- SOURCE CODE pub fn main() { case [] { _ -> Nil } } ----- WARNING warning: Match on a literal value ┌─ /src/warning/wrn.gleam:2:14 │ 2 │ case [] { │ ^^ There's no need to pattern match on this value Matching on a literal value is redundant since you can already tell which branch is going to match with this value. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__pattern_matching_on_literal_empty_tuple.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() {\n case #() {\n _ -> Nil\n }\n }" --- ----- SOURCE CODE pub fn main() { case #() { _ -> Nil } } ----- WARNING warning: Match on a literal value ┌─ /src/warning/wrn.gleam:2:14 │ 2 │ case #() { │ ^^^ There's no need to pattern match on this value Matching on a literal value is redundant since you can already tell which branch is going to match with this value. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__pattern_matching_on_literal_float.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Wibble { Wibble }\npub fn main() {\n case 1.0 {\n _ -> Nil\n }\n}" --- ----- SOURCE CODE pub type Wibble { Wibble } pub fn main() { case 1.0 { _ -> Nil } } ----- WARNING warning: Match on a literal value ┌─ /src/warning/wrn.gleam:4:8 │ 4 │ case 1.0 { │ ^^^ There's no need to pattern match on this value Matching on a literal value is redundant since you can already tell which branch is going to match with this value. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__pattern_matching_on_literal_int.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Wibble { Wibble }\npub fn main() {\n case 1 {\n _ -> Nil\n }\n}" --- ----- SOURCE CODE pub type Wibble { Wibble } pub fn main() { case 1 { _ -> Nil } } ----- WARNING warning: Match on a literal value ┌─ /src/warning/wrn.gleam:4:8 │ 4 │ case 1 { │ ^ There's no need to pattern match on this value Matching on a literal value is redundant since you can already tell which branch is going to match with this value. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__pattern_matching_on_literal_list.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() {\n case [1, 2] {\n _ -> Nil\n }\n }" --- ----- SOURCE CODE pub fn main() { case [1, 2] { _ -> Nil } } ----- WARNING warning: Redundant list ┌─ /src/warning/wrn.gleam:2:14 │ 2 │ case [1, 2] { │ ^^^^^^ You can remove this list wrapper Instead of building a list and matching on it, you can match on its contents directly. A case expression can take multiple subjects separated by commas like this: case one_subject, another_subject { _, _ -> todo } See: https://tour.gleam.run/flow-control/multiple-subjects/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__pattern_matching_on_literal_list_with_tail.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() {\n case [1, 2, ..[]] {\n _ -> Nil\n }\n }" --- ----- SOURCE CODE pub fn main() { case [1, 2, ..[]] { _ -> Nil } } ----- WARNING warning: Redundant list ┌─ /src/warning/wrn.gleam:2:14 │ 2 │ case [1, 2, ..[]] { │ ^^^^^^^^^^^^ You can remove this list wrapper Instead of building a list and matching on it, you can match on its contents directly. A case expression can take multiple subjects separated by commas like this: case one_subject, another_subject { _, _ -> todo } See: https://tour.gleam.run/flow-control/multiple-subjects/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__pattern_matching_on_literal_record.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Wibble { Wibble(Int) }\npub fn main() {\n let n = 1\n case Wibble(n) {\n _ -> Nil\n }\n}" --- ----- SOURCE CODE pub type Wibble { Wibble(Int) } pub fn main() { let n = 1 case Wibble(n) { _ -> Nil } } ----- WARNING warning: Redundant record ┌─ /src/warning/wrn.gleam:5:8 │ 5 │ case Wibble(n) { │ ^^^^^^^^^ You can remove this record wrapper Instead of building a record and matching on it, you can match on its contents directly. A case expression can take multiple subjects separated by commas like this: case one_subject, another_subject { _, _ -> todo } See: https://tour.gleam.run/flow-control/multiple-subjects/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__pattern_matching_on_literal_record_with_no_args.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Wibble { Wibble }\npub fn main() {\n case Wibble {\n _ -> Nil\n }\n}" --- ----- SOURCE CODE pub type Wibble { Wibble } pub fn main() { case Wibble { _ -> Nil } } ----- WARNING warning: Match on a literal value ┌─ /src/warning/wrn.gleam:4:8 │ 4 │ case Wibble { │ ^^^^^^ There's no need to pattern match on this value Matching on a literal value is redundant since you can already tell which branch is going to match with this value. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__pattern_matching_on_literal_string.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Wibble { Wibble }\npub fn main() {\n case \"hello\" {\n _ -> Nil\n }\n}" --- ----- SOURCE CODE pub type Wibble { Wibble } pub fn main() { case "hello" { _ -> Nil } } ----- WARNING warning: Match on a literal value ┌─ /src/warning/wrn.gleam:4:8 │ 4 │ case "hello" { │ ^^^^^^^ There's no need to pattern match on this value Matching on a literal value is redundant since you can already tell which branch is going to match with this value. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__pattern_matching_on_literal_tuple.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() {\n case #(1, 2) {\n _ -> Nil\n }\n }" --- ----- SOURCE CODE pub fn main() { case #(1, 2) { _ -> Nil } } ----- WARNING warning: Redundant tuple ┌─ /src/warning/wrn.gleam:2:14 │ 2 │ case #(1, 2) { │ ^^^^^^^ You can remove this tuple wrapper Instead of building a tuple and matching on it, you can match on its contents directly. A case expression can take multiple subjects separated by commas like this: case one_subject, another_subject { _, _ -> todo } See: https://tour.gleam.run/flow-control/multiple-subjects/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__pattern_matching_on_multiple_literal_tuples.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() {\n let wibble = 1\n case #(1, 2), #(wibble, wibble) {\n _, _ -> Nil\n }\n }" --- ----- SOURCE CODE pub fn main() { let wibble = 1 case #(1, 2), #(wibble, wibble) { _, _ -> Nil } } ----- WARNING warning: Redundant tuple ┌─ /src/warning/wrn.gleam:3:14 │ 3 │ case #(1, 2), #(wibble, wibble) { │ ^^^^^^^ You can remove this tuple wrapper Instead of building a tuple and matching on it, you can match on its contents directly. A case expression can take multiple subjects separated by commas like this: case one_subject, another_subject { _, _ -> todo } See: https://tour.gleam.run/flow-control/multiple-subjects/ warning: Redundant tuple ┌─ /src/warning/wrn.gleam:3:23 │ 3 │ case #(1, 2), #(wibble, wibble) { │ ^^^^^^^^^^^^^^^^^ You can remove this tuple wrapper Instead of building a tuple and matching on it, you can match on its contents directly. A case expression can take multiple subjects separated by commas like this: case one_subject, another_subject { _, _ -> todo } See: https://tour.gleam.run/flow-control/multiple-subjects/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__prefer_list_is_empty_over_0_eq_list_length.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n import gleam/list\n\n pub fn main() {\n let a_list = []\n let _ = 0 == list.length(a_list)\n }\n " --- ----- SOURCE CODE -- gleam/list.gleam pub fn length(_list: List(a)) -> Int { 0 } -- main.gleam import gleam/list pub fn main() { let a_list = [] let _ = 0 == list.length(a_list) } ----- WARNING warning: Inefficient use of `list.length` ┌─ /src/warning/wrn.gleam:6:21 │ 6 │ let _ = 0 == list.length(a_list) │ ^^^^^^^^^^^^^^^^^^^^^^^^ The `list.length` function has to iterate across the whole list to calculate the length, which is wasteful if you only need to know if the list is empty or not. Hint: You can use `the_list == []` instead. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__prefer_list_is_empty_over_0_lt_list_length.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n import gleam/list\n\n pub fn main() {\n let a_list = []\n let _ = 0 < list.length(a_list)\n }\n " --- ----- SOURCE CODE -- gleam/list.gleam pub fn length(_list: List(a)) -> Int { 0 } -- main.gleam import gleam/list pub fn main() { let a_list = [] let _ = 0 < list.length(a_list) } ----- WARNING warning: Inefficient use of `list.length` ┌─ /src/warning/wrn.gleam:6:21 │ 6 │ let _ = 0 < list.length(a_list) │ ^^^^^^^^^^^^^^^^^^^^^^^ The `list.length` function has to iterate across the whole list to calculate the length, which is wasteful if you only need to know if the list is empty or not. Hint: You can use `the_list != []` instead. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__prefer_list_is_empty_over_0_not_eq_list_length.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n import gleam/list\n\n pub fn main() {\n let a_list = []\n let _ = 0 != list.length(a_list)\n }\n " --- ----- SOURCE CODE -- gleam/list.gleam pub fn length(_list: List(a)) -> Int { 0 } -- main.gleam import gleam/list pub fn main() { let a_list = [] let _ = 0 != list.length(a_list) } ----- WARNING warning: Inefficient use of `list.length` ┌─ /src/warning/wrn.gleam:6:21 │ 6 │ let _ = 0 != list.length(a_list) │ ^^^^^^^^^^^^^^^^^^^^^^^^ The `list.length` function has to iterate across the whole list to calculate the length, which is wasteful if you only need to know if the list is empty or not. Hint: You can use `the_list != []` instead. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__prefer_list_is_empty_over_list_length_eq_0.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n import gleam/list\n\n pub fn main() {\n let a_list = []\n let _ = list.length(a_list) == 0\n }\n " --- ----- SOURCE CODE -- gleam/list.gleam pub fn length(_list: List(a)) -> Int { 0 } -- main.gleam import gleam/list pub fn main() { let a_list = [] let _ = list.length(a_list) == 0 } ----- WARNING warning: Inefficient use of `list.length` ┌─ /src/warning/wrn.gleam:6:21 │ 6 │ let _ = list.length(a_list) == 0 │ ^^^^^^^^^^^^^^^^^^^^^^^^ The `list.length` function has to iterate across the whole list to calculate the length, which is wasteful if you only need to know if the list is empty or not. Hint: You can use `the_list == []` instead. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__prefer_list_is_empty_over_list_length_eq_negative_0.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n import gleam/list\n\n pub fn main() {\n let a_list = []\n let _ = list.length(a_list) == -0\n }\n " --- ----- SOURCE CODE -- gleam/list.gleam pub fn length(_list: List(a)) -> Int { 0 } -- main.gleam import gleam/list pub fn main() { let a_list = [] let _ = list.length(a_list) == -0 } ----- WARNING warning: Inefficient use of `list.length` ┌─ /src/warning/wrn.gleam:6:21 │ 6 │ let _ = list.length(a_list) == -0 │ ^^^^^^^^^^^^^^^^^^^^^^^^^ The `list.length` function has to iterate across the whole list to calculate the length, which is wasteful if you only need to know if the list is empty or not. Hint: You can use `the_list == []` instead. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__prefer_list_is_empty_over_list_length_gt_0.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n import gleam/list\n\n pub fn main() {\n let a_list = []\n let _ = list.length(a_list) > 0\n }\n " --- ----- SOURCE CODE -- gleam/list.gleam pub fn length(_list: List(a)) -> Int { 0 } -- main.gleam import gleam/list pub fn main() { let a_list = [] let _ = list.length(a_list) > 0 } ----- WARNING warning: Inefficient use of `list.length` ┌─ /src/warning/wrn.gleam:6:21 │ 6 │ let _ = list.length(a_list) > 0 │ ^^^^^^^^^^^^^^^^^^^^^^^ The `list.length` function has to iterate across the whole list to calculate the length, which is wasteful if you only need to know if the list is empty or not. Hint: You can use `the_list != []` instead. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__prefer_list_is_empty_over_list_length_gt_negative_0.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n import gleam/list\n\n pub fn main() {\n let a_list = []\n let _ = list.length(a_list) > 0\n }\n " --- ----- SOURCE CODE -- gleam/list.gleam pub fn length(_list: List(a)) -> Int { 0 } -- main.gleam import gleam/list pub fn main() { let a_list = [] let _ = list.length(a_list) > 0 } ----- WARNING warning: Inefficient use of `list.length` ┌─ /src/warning/wrn.gleam:6:21 │ 6 │ let _ = list.length(a_list) > 0 │ ^^^^^^^^^^^^^^^^^^^^^^^ The `list.length` function has to iterate across the whole list to calculate the length, which is wasteful if you only need to know if the list is empty or not. Hint: You can use `the_list != []` instead. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__prefer_list_is_empty_over_list_length_lt_1.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n import gleam/list\n\n pub fn main() {\n let a_list = []\n let _ = list.length(a_list) < 1\n }\n " --- ----- SOURCE CODE -- gleam/list.gleam pub fn length(_list: List(a)) -> Int { 0 } -- main.gleam import gleam/list pub fn main() { let a_list = [] let _ = list.length(a_list) < 1 } ----- WARNING warning: Inefficient use of `list.length` ┌─ /src/warning/wrn.gleam:6:21 │ 6 │ let _ = list.length(a_list) < 1 │ ^^^^^^^^^^^^^^^^^^^^^^^ The `list.length` function has to iterate across the whole list to calculate the length, which is wasteful if you only need to know if the list is empty or not. Hint: You can use `the_list == []` instead. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__prefer_list_is_empty_over_list_length_lt_eq_0.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n import gleam/list\n\n pub fn main() {\n let a_list = []\n let _ = list.length(a_list) <= 0\n }\n " --- ----- SOURCE CODE -- gleam/list.gleam pub fn length(_list: List(a)) -> Int { 0 } -- main.gleam import gleam/list pub fn main() { let a_list = [] let _ = list.length(a_list) <= 0 } ----- WARNING warning: Inefficient use of `list.length` ┌─ /src/warning/wrn.gleam:6:21 │ 6 │ let _ = list.length(a_list) <= 0 │ ^^^^^^^^^^^^^^^^^^^^^^^^ The `list.length` function has to iterate across the whole list to calculate the length, which is wasteful if you only need to know if the list is empty or not. Hint: You can use `the_list == []` instead. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__prefer_list_is_empty_over_list_length_not_eq_0.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n import gleam/list\n\n pub fn main() {\n let a_list = []\n let _ = list.length(a_list) != 0\n }\n " --- ----- SOURCE CODE -- gleam/list.gleam pub fn length(_list: List(a)) -> Int { 0 } -- main.gleam import gleam/list pub fn main() { let a_list = [] let _ = list.length(a_list) != 0 } ----- WARNING warning: Inefficient use of `list.length` ┌─ /src/warning/wrn.gleam:6:21 │ 6 │ let _ = list.length(a_list) != 0 │ ^^^^^^^^^^^^^^^^^^^^^^^^ The `list.length` function has to iterate across the whole list to calculate the length, which is wasteful if you only need to know if the list is empty or not. Hint: You can use `the_list != []` instead. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__prefer_list_is_empty_over_negative_0_eq_list_length.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n import gleam/list\n\n pub fn main() {\n let a_list = []\n let _ = -0 == list.length(a_list)\n }\n " --- ----- SOURCE CODE -- gleam/list.gleam pub fn length(_list: List(a)) -> Int { 0 } -- main.gleam import gleam/list pub fn main() { let a_list = [] let _ = -0 == list.length(a_list) } ----- WARNING warning: Inefficient use of `list.length` ┌─ /src/warning/wrn.gleam:6:21 │ 6 │ let _ = -0 == list.length(a_list) │ ^^^^^^^^^^^^^^^^^^^^^^^^^ The `list.length` function has to iterate across the whole list to calculate the length, which is wasteful if you only need to know if the list is empty or not. Hint: You can use `the_list == []` instead. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__prefer_list_is_empty_over_negative_0_lt_list_length.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n import gleam/list\n\n pub fn main() {\n let a_list = []\n let _ = 0 < list.length(a_list)\n }\n " --- ----- SOURCE CODE -- gleam/list.gleam pub fn length(_list: List(a)) -> Int { 0 } -- main.gleam import gleam/list pub fn main() { let a_list = [] let _ = 0 < list.length(a_list) } ----- WARNING warning: Inefficient use of `list.length` ┌─ /src/warning/wrn.gleam:6:21 │ 6 │ let _ = 0 < list.length(a_list) │ ^^^^^^^^^^^^^^^^^^^^^^^ The `list.length` function has to iterate across the whole list to calculate the length, which is wasteful if you only need to know if the list is empty or not. Hint: You can use `the_list != []` instead. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__pure_pipeline_raises_warning.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\nfn add(a, b) { a + b }\n\npub fn main() {\n 1 |> add(2)\n Nil\n}\n" --- ----- SOURCE CODE fn add(a, b) { a + b } pub fn main() { 1 |> add(2) Nil } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:5:3 │ 5 │ 1 |> add(2) │ ^^^^^^^^^^^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__pure_pipeline_with_many_steps_raises_warning.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\nfn add(a, b) { a + b }\n\npub fn main() {\n 1 |> add(2) |> add(3) |> add(4)\n Nil\n}\n" --- ----- SOURCE CODE fn add(a, b) { a + b } pub fn main() { 1 |> add(2) |> add(3) |> add(4) Nil } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:5:3 │ 5 │ 1 |> add(2) |> add(3) |> add(4) │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__pure_standard_library_function.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\nimport gleam/dict\n\npub fn main() {\n dict.insert(dict.new(), 1, 2)\n Nil\n}\n" --- ----- SOURCE CODE -- gleam/dict.gleam pub type Dict(key, value) @external(erlang, "map", "new") pub fn new() -> Dict(a, b) @external(erlang, "map", "insert") pub fn insert(dict: Dict(key, value), key: key, value: value) -> Dict(key, value) -- main.gleam import gleam/dict pub fn main() { dict.insert(dict.new(), 1, 2) Nil } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:5:3 │ 5 │ dict.insert(dict.new(), 1, 2) │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__reachable_pattern_after_unreachable_equal_pattern.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn wibble(bits) {\n case bits {\n <<97:3>> -> 1\n <<\"a\">> -> 2\n _ -> 3\n }\n}\n" --- ----- SOURCE CODE pub fn wibble(bits) { case bits { <<97:3>> -> 1 <<"a">> -> 2 _ -> 3 } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:4:5 │ 4 │ <<97:3>> -> 1 │ ^^^^^^^^ │ │ │ A 3 bits unsigned integer will never match this value This pattern cannot be reached as it contains segments that will never match. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__record_access_variant_inference_requires_v1_6.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Wibble {\n Wibble(a: Int, b: Int)\n Wobble(a: Int, c: Int)\n}\n\npub fn main(wibble) {\n case wibble {\n Wibble(..) -> wibble.b\n Wobble(..) -> wibble.c\n }\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(a: Int, b: Int) Wobble(a: Int, c: Int) } pub fn main(wibble) { case wibble { Wibble(..) -> wibble.b Wobble(..) -> wibble.c } } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:9:26 │ 9 │ Wibble(..) -> wibble.b │ ^ This requires a Gleam version >= 1.6.0 Field access on custom types when the variant is known was introduced in version v1.6.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.6.0" warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:10:26 │ 10 │ Wobble(..) -> wibble.c │ ^ This requires a Gleam version >= 1.6.0 Field access on custom types when the variant is known was introduced in version v1.6.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.6.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__record_select_redundant_comparison.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Wibble {\n Wibble(field: Int)\n}\n\npub fn main(wibble: Wibble) { wibble.field == wibble.field }\n" --- ----- SOURCE CODE pub type Wibble { Wibble(field: Int) } pub fn main(wibble: Wibble) { wibble.field == wibble.field } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:6:31 │ 6 │ pub fn main(wibble: Wibble) { wibble.field == wibble.field } │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This is always `True` This comparison is redundant since it always succeeds. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__record_select_redundant_comparison_1.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Wibble {\n Wibble(field: Int)\n}\n\npub fn main(wibble: Wibble) { wibble.field != wibble.field }\n" --- ----- SOURCE CODE pub type Wibble { Wibble(field: Int) } pub fn main(wibble: Wibble) { wibble.field != wibble.field } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:6:31 │ 6 │ pub fn main(wibble: Wibble) { wibble.field != wibble.field } │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This is always `False` This comparison is redundant since it always fails. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__record_update_variant_inference_requires_v1_6.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Wibble {\n Wibble(a: Int, b: Int)\n Wobble(a: Int, c: Int)\n}\n\npub fn main(wibble) {\n case wibble {\n Wibble(..) -> Wibble(..wibble, b: 10)\n Wobble(..) -> panic\n }\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(a: Int, b: Int) Wobble(a: Int, c: Int) } pub fn main(wibble) { case wibble { Wibble(..) -> Wibble(..wibble, b: 10) Wobble(..) -> panic } } ----- WARNING warning: Incompatible gleam version range ┌─ /src/warning/wrn.gleam:9:28 │ 9 │ Wibble(..) -> Wibble(..wibble, b: 10) │ ^^^^^^ This requires a Gleam version >= 1.6.0 Record updates for custom types when the variant is known was introduced in version v1.6.0. But the Gleam version range specified in your `gleam.toml` would allow this code to run on an earlier version like v1.0.0, resulting in compilation errors! Hint: Remove the version constraint from your `gleam.toml` or update it to be: gleam = ">= 1.6.0" ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__record_update_warnings_test2.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub type Person {\n Person(name: String, age: Int)\n }\n pub fn update_person() {\n let past = Person(\"Quinn\", 27)\n let present = Person(..past)\n present\n }" --- ----- SOURCE CODE pub type Person { Person(name: String, age: Int) } pub fn update_person() { let past = Person("Quinn", 27) let present = Person(..past) present } ----- WARNING warning: Fieldless record update ┌─ /src/warning/wrn.gleam:7:27 │ 7 │ let present = Person(..past) │ ^^^^^^^^^^^^^^ This record update doesn't change any fields Hint: Add some fields to change or replace it with the record itself. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__record_update_warnings_test3.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub type Person {\n Person(name: String, age: Int)\n }\n pub fn update_person() {\n let past = Person(\"Quinn\", 27)\n let present = Person(..past, name: \"Quinn\", age: 28)\n present\n }" --- ----- SOURCE CODE pub type Person { Person(name: String, age: Int) } pub fn update_person() { let past = Person("Quinn", 27) let present = Person(..past, name: "Quinn", age: 28) present } ----- WARNING warning: Redundant record update ┌─ /src/warning/wrn.gleam:7:27 │ 7 │ let present = Person(..past, name: "Quinn", age: 28) │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This record update specifies all fields Hint: It is better style to use the record creation syntax. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__record_update_with_wrong_types_but_all_fields_produces_warning.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Wibble {\n Wibble(a: Int, b: Bool)\n}\n\npub fn main() {\n let original = Wibble(a: 1, b: True)\n Wibble(..original, a: True, b: 1)\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(a: Int, b: Bool) } pub fn main() { let original = Wibble(a: 1, b: True) Wibble(..original, a: True, b: 1) } ----- WARNING warning: Redundant record update ┌─ /src/warning/wrn.gleam:8:3 │ 8 │ Wibble(..original, a: True, b: 1) │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This record update specifies all fields Hint: It is better style to use the record creation syntax. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__redundant_function_capture_in_pipe_1.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn wibble(_, _) { 1 }\n\n pub fn main() {\n 1 |> wibble(_, 2) |> wibble(2)\n }\n" --- ----- SOURCE CODE pub fn wibble(_, _) { 1 } pub fn main() { 1 |> wibble(_, 2) |> wibble(2) } ----- WARNING warning: Redundant function capture ┌─ /src/warning/wrn.gleam:5:17 │ 5 │ 1 |> wibble(_, 2) |> wibble(2) │ ^ You can safely remove this This function capture is redundant since the value is already piped as the first argument of this call. See: https://tour.gleam.run/functions/pipelines/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__redundant_function_capture_in_pipe_2.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn wobble(_) { 1 }\n\n pub fn main() {\n 1 |> wobble(_) |> wobble\n }\n" --- ----- SOURCE CODE pub fn wobble(_) { 1 } pub fn main() { 1 |> wobble(_) |> wobble } ----- WARNING warning: Redundant function capture ┌─ /src/warning/wrn.gleam:5:17 │ 5 │ 1 |> wobble(_) |> wobble │ ^ You can safely remove this This function capture is redundant since the value is already piped as the first argument of this call. See: https://tour.gleam.run/functions/pipelines/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__redundant_function_capture_in_pipe_3.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn wobble(_) { 1 }\n\n pub fn main() {\n 1 |> wobble |> wobble(_)\n }\n" --- ----- SOURCE CODE pub fn wobble(_) { 1 } pub fn main() { 1 |> wobble |> wobble(_) } ----- WARNING warning: Redundant function capture ┌─ /src/warning/wrn.gleam:5:27 │ 5 │ 1 |> wobble |> wobble(_) │ ^ You can safely remove this This function capture is redundant since the value is already piped as the first argument of this call. See: https://tour.gleam.run/functions/pipelines/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__redundant_function_capture_in_pipe_4.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn wibble(_, _) { 1 }\n\n pub fn main() {\n 1 |> wibble(2) |> wibble(_, 2)\n }\n" --- ----- SOURCE CODE pub fn wibble(_, _) { 1 } pub fn main() { 1 |> wibble(2) |> wibble(_, 2) } ----- WARNING warning: Redundant function capture ┌─ /src/warning/wrn.gleam:5:30 │ 5 │ 1 |> wibble(2) |> wibble(_, 2) │ ^ You can safely remove this This function capture is redundant since the value is already piped as the first argument of this call. See: https://tour.gleam.run/functions/pipelines/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__redundant_let_assert.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n let assert wibble = [1, 2, 3]\n wibble\n}\n" --- ----- SOURCE CODE pub fn main() { let assert wibble = [1, 2, 3] wibble } ----- WARNING warning: Redundant assertion ┌─ /src/warning/wrn.gleam:3:7 │ 3 │ let assert wibble = [1, 2, 3] │ ^^^^^^ You can remove this This assertion is redundant since the pattern covers all possibilities. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__redundant_let_assert_on_custom_type.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Wibble {\n Wibble(Int, Bool)\n}\n\npub fn main() {\n let assert Wibble(_, bool) = Wibble(1, True)\n bool\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(Int, Bool) } pub fn main() { let assert Wibble(_, bool) = Wibble(1, True) bool } ----- WARNING warning: Redundant assertion ┌─ /src/warning/wrn.gleam:7:7 │ 7 │ let assert Wibble(_, bool) = Wibble(1, True) │ ^^^^^^ You can remove this This assertion is redundant since the pattern covers all possibilities. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__result_discard_warning_test.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn wibble() { Ok(5) }\npub fn main() {\n wibble()\n 5\n}" --- ----- SOURCE CODE pub fn wibble() { Ok(5) } pub fn main() { wibble() 5 } ----- WARNING warning: Unused result value ┌─ /src/warning/wrn.gleam:4:3 │ 4 │ wibble() │ ^^^^^^^^ The Result value created here is unused Hint: If you are sure you don't need it you can assign it to `_`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__result_in_case_discarded.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main(x) {\n case x {\n _ -> Error(Nil)\n }\n Nil\n}" --- ----- SOURCE CODE pub fn main(x) { case x { _ -> Error(Nil) } Nil } ----- WARNING warning: Unused result value ┌─ /src/warning/wrn.gleam:3:3 │ 3 │ ╭ case x { 4 │ │ _ -> Error(Nil) 5 │ │ } │ ╰───^ The Result value created here is unused Hint: If you are sure you don't need it you can assign it to `_`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__shadow_imported_constant.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\nimport module.{value}\n\npub const value = 1\n" --- ----- SOURCE CODE -- module.gleam pub const value = 1 -- main.gleam import module.{value} pub const value = 1 ----- WARNING warning: Unused imported module ┌─ /src/warning/wrn.gleam:2:1 │ 2 │ import module.{value} │ ^^^^^^^^^^^^^^^^^^^^^ This imported module is never used Hint: You can safely remove it. warning: Shadowed Import ┌─ /src/warning/wrn.gleam:4:1 │ 4 │ pub const value = 1 │ ^^^^^^^^^^^^^^^ `value` is defined here Definition of value shadows an imported value. The imported value could not be used in this module anyway. Hint: Either rename the definition or remove the import. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__shadow_imported_function.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\nimport module.{wibble}\n\npub fn wibble() { Nil }\n" --- ----- SOURCE CODE -- module.gleam pub fn wibble() { Nil } -- main.gleam import module.{wibble} pub fn wibble() { Nil } ----- WARNING warning: Unused imported module ┌─ /src/warning/wrn.gleam:2:1 │ 2 │ import module.{wibble} │ ^^^^^^^^^^^^^^^^^^^^^^ This imported module is never used Hint: You can safely remove it. warning: Shadowed Import ┌─ /src/warning/wrn.gleam:4:1 │ 4 │ pub fn wibble() { Nil } │ ^^^^^^^^^^^^^^^ `wibble` is defined here Definition of wibble shadows an imported value. The imported value could not be used in this module anyway. Hint: Either rename the definition or remove the import. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__string_literals_redundant_comparison.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { \"wibble\" == \"wobble\" }" --- ----- SOURCE CODE pub fn main() { "wibble" == "wobble" } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { "wibble" == "wobble" } │ ^^^^^^^^^^^^^^^^^^^^ This is always `False` This comparison is redundant since it always fails. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__string_literals_redundant_comparison_1.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { \"wibble\" != \"wobble\" }" --- ----- SOURCE CODE pub fn main() { "wibble" != "wobble" } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { "wibble" != "wobble" } │ ^^^^^^^^^^^^^^^^^^^^ This is always `True` This comparison is redundant since it always succeeds. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__todo_used_as_function.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() {\n todo()\n }" --- ----- SOURCE CODE pub fn main() { todo() } ----- WARNING warning: Todo found ┌─ /src/warning/wrn.gleam:2:11 │ 2 │ todo() │ ^^^^ This code is incomplete This code will crash if it is run. Be sure to finish it before running your program. Hint: I think its type is `fn() -> a`. warning: Todo used as a function ┌─ /src/warning/wrn.gleam:2:11 │ 2 │ todo() │ ^^^^^^ `todo` is not a function, you can just write `todo` instead of `todo()`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__todo_used_as_function_2.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() {\n todo(1)\n }" --- ----- SOURCE CODE pub fn main() { todo(1) } ----- WARNING warning: Todo found ┌─ /src/warning/wrn.gleam:2:11 │ 2 │ todo(1) │ ^^^^ This code is incomplete This code will crash if it is run. Be sure to finish it before running your program. Hint: I think its type is `fn(Int) -> a`. warning: Todo used as a function ┌─ /src/warning/wrn.gleam:2:16 │ 2 │ todo(1) │ ^ `todo` is not a function and will crash before it can do anything with this argument. Hint: if you want to display an error message you should write `todo as "my error message"` See: https://tour.gleam.run/advanced-features/todo/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__todo_used_as_function_3.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() {\n todo(1, Nil)\n }" --- ----- SOURCE CODE pub fn main() { todo(1, Nil) } ----- WARNING warning: Todo found ┌─ /src/warning/wrn.gleam:2:11 │ 2 │ todo(1, Nil) │ ^^^^ This code is incomplete This code will crash if it is run. Be sure to finish it before running your program. Hint: I think its type is `fn(Int, Nil) -> a`. warning: Todo used as a function ┌─ /src/warning/wrn.gleam:2:16 │ 2 │ todo(1, Nil) │ ^^^^^^ `todo` is not a function and will crash before it can do anything with these arguments. Hint: if you want to display an error message you should write `todo as "my error message"` See: https://tour.gleam.run/advanced-features/todo/ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__todo_warning_correct_location.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() {\n todo\n }" --- ----- SOURCE CODE pub fn main() { todo } ----- WARNING warning: Todo found ┌─ /src/warning/wrn.gleam:2:9 │ 2 │ todo │ ^^^^ This code is incomplete This code will crash if it is run. Be sure to finish it before running your program. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__todo_warning_test.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { 1 == todo }" --- ----- SOURCE CODE pub fn main() { 1 == todo } ----- WARNING warning: Todo found ┌─ /src/warning/wrn.gleam:1:22 │ 1 │ pub fn main() { 1 == todo } │ ^^^^ This code is incomplete This code will crash if it is run. Be sure to finish it before running your program. Hint: I think its type is `Int`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__todo_with_known_type.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() -> String {\n todo\n}" --- ----- SOURCE CODE pub fn main() -> String { todo } ----- WARNING warning: Todo found ┌─ /src/warning/wrn.gleam:2:3 │ 2 │ todo │ ^^^^ This code is incomplete This code will crash if it is run. Be sure to finish it before running your program. Hint: I think its type is `String`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_code_after_case_subject_panics_1.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn main(a, b) {\n case a, panic, b {\n _, _, _ -> \"no warning here!\"\n }\n }\n " --- ----- SOURCE CODE pub fn main(a, b) { case a, panic, b { _, _, _ -> "no warning here!" } } ----- WARNING warning: Unreachable code ┌─ /src/warning/wrn.gleam:3:28 │ 3 │ case a, panic, b { │ ^ This code is unreachable because it comes after a `panic`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_code_after_case_subject_panics_2.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn main(a, b) {\n case a, b, panic {\n _, _, _ -> \"no warning here!\"\n }\n \"warning here!\"\n }\n " --- ----- SOURCE CODE pub fn main(a, b) { case a, b, panic { _, _, _ -> "no warning here!" } "warning here!" } ----- WARNING warning: Unreachable code ┌─ /src/warning/wrn.gleam:6:13 │ 6 │ "warning here!" │ ^^^^^^^^^^^^^^^ This code is unreachable because it comes after a `panic`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_code_analysis_treats_anonymous_functions_independently_2.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn main() {\n let _ = fn() {\n panic\n \"warning here!\"\n }\n panic\n \"warning here!\"\n }\n " --- ----- SOURCE CODE pub fn main() { let _ = fn() { panic "warning here!" } panic "warning here!" } ----- WARNING warning: Unreachable code ┌─ /src/warning/wrn.gleam:5:15 │ 5 │ "warning here!" │ ^^^^^^^^^^^^^^^ This code is unreachable because it comes after a `panic`. warning: Unreachable code ┌─ /src/warning/wrn.gleam:8:13 │ 8 │ "warning here!" │ ^^^^^^^^^^^^^^^ This code is unreachable because it comes after a `panic`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_code_analysis_treats_anonymous_functions_independently_3.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn main() {\n panic\n let _ = \"warning here!\"\n let _ = fn() {\n panic\n \"warning here!\"\n }\n }\n " --- ----- SOURCE CODE pub fn main() { panic let _ = "warning here!" let _ = fn() { panic "warning here!" } } ----- WARNING warning: Unreachable code ┌─ /src/warning/wrn.gleam:4:21 │ 4 │ let _ = "warning here!" │ ^^^^^^^^^^^^^^^ This code is unreachable because it comes after a `panic`. warning: Unreachable code ┌─ /src/warning/wrn.gleam:7:15 │ 7 │ "warning here!" │ ^^^^^^^^^^^^^^^ This code is unreachable because it comes after a `panic`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_code_for_panic_as_first_pipeline_item.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn wibble(_) { 1 }\n pub fn main() {\n panic |> wibble\n }\n " --- ----- SOURCE CODE pub fn wibble(_) { 1 } pub fn main() { panic |> wibble } ----- WARNING warning: Unreachable code ┌─ /src/warning/wrn.gleam:4:22 │ 4 │ panic |> wibble │ ^^^^^^ This code is unreachable because it comes after a `panic`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_function_argument_if_panic_is_argument.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn wibble(_, _) { 1 }\n pub fn main() {\n wibble(panic, 1)\n }" --- ----- SOURCE CODE pub fn wibble(_, _) { 1 } pub fn main() { wibble(panic, 1) } ----- WARNING warning: Unreachable code ┌─ /src/warning/wrn.gleam:4:25 │ 4 │ wibble(panic, 1) │ ^ This argument is unreachable because the previous one always panics. Your code will crash before reaching this point. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_function_call_if_panic_is_last_argument_1.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn wibble(_, _) { 1 }\n pub fn main() {\n wibble(1, panic)\n 1\n }" --- ----- SOURCE CODE pub fn wibble(_, _) { 1 } pub fn main() { wibble(1, panic) 1 } ----- WARNING warning: Unreachable code ┌─ /src/warning/wrn.gleam:4:11 │ 4 │ wibble(1, panic) │ ^^^^^^ This function call is unreachable because its last argument always panics. Your code will crash before reaching this point. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_function_call_if_panic_is_last_argument_2.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn wibble(_, _) { 1 }\n pub fn main() {\n wibble(1, panic)\n }" --- ----- SOURCE CODE pub fn wibble(_, _) { 1 } pub fn main() { wibble(1, panic) } ----- WARNING warning: Unreachable code ┌─ /src/warning/wrn.gleam:4:11 │ 4 │ wibble(1, panic) │ ^^^^^^ This function call is unreachable because its last argument always panics. Your code will crash before reaching this point. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_int_pattern_with_prefix_int.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn wibble(bits) {\n case bits {\n <<0b1:1, _:1>> -> 1\n <<0b11:2>> -> 2\n _ -> 3\n }\n}" --- ----- SOURCE CODE pub fn wibble(bits) { case bits { <<0b1:1, _:1>> -> 1 <<0b11:2>> -> 2 _ -> 3 } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:5:5 │ 5 │ <<0b11:2>> -> 2 │ ^^^^^^^^^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_int_pattern_with_string_of_same_value.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn wibble(bits) {\n case bits {\n <<\"a\">> -> 1\n <<97>> -> 2\n _ -> 3\n }\n}" --- ----- SOURCE CODE pub fn wibble(bits) { case bits { <<"a">> -> 1 <<97>> -> 2 _ -> 3 } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:5:5 │ 5 │ <<97>> -> 2 │ ^^^^^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_string_pattern_with_different_encodings.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn wibble(bits) {\n case bits {\n <<\"\\u{0000}a\\u{0000}b\":utf8>> -> 1\n // This is the same as the one above, it shouldn't be reachable\n <<\"ab\":utf16>> -> 2\n _ -> 3\n }\n}" --- ----- SOURCE CODE pub fn wibble(bits) { case bits { <<"\u{0000}a\u{0000}b":utf8>> -> 1 // This is the same as the one above, it shouldn't be reachable <<"ab":utf16>> -> 2 _ -> 3 } } ----- WARNING warning: Unreachable pattern ┌─ /src/warning/wrn.gleam:6:5 │ 6 │ <<"ab":utf16>> -> 2 │ ^^^^^^^^^^^^^^ This pattern cannot be reached as a previous pattern matches the same values. Hint: It can be safely removed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_use_after_panic.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn wibble(_) { 1 }\n pub fn main() {\n panic\n use <- wibble\n 1\n }\n " --- ----- SOURCE CODE pub fn wibble(_) { 1 } pub fn main() { panic use <- wibble 1 } ----- WARNING warning: Unreachable code ┌─ /src/warning/wrn.gleam:5:20 │ 5 │ use <- wibble │ ^^^^^^ This code is unreachable because it comes after a `panic`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_warning_1.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() {\n panic\n 1\n }" --- ----- SOURCE CODE pub fn main() { panic 1 } ----- WARNING warning: Unreachable code ┌─ /src/warning/wrn.gleam:3:11 │ 3 │ 1 │ ^ This code is unreachable because it comes after a `panic`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_warning_2.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() {\n let _ = panic\n 1\n }" --- ----- SOURCE CODE pub fn main() { let _ = panic 1 } ----- WARNING warning: Unreachable code ┌─ /src/warning/wrn.gleam:3:11 │ 3 │ 1 │ ^ This code is unreachable because it comes after a `panic`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_warning_doesnt_escape_out_of_a_block_if_panic_is_not_last.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() {\n let n = {\n panic\n 1\n }\n n\n }" --- ----- SOURCE CODE pub fn main() { let n = { panic 1 } n } ----- WARNING warning: Unreachable code ┌─ /src/warning/wrn.gleam:4:13 │ 4 │ 1 │ ^ This code is unreachable because it comes after a `panic`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_warning_for_panic_as_last_item_of_pipe_on_next_expression.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn wibble(_) { 1 }\n pub fn main() {\n 1 |> wibble |> panic\n \"unreachable\"\n }\n " --- ----- SOURCE CODE pub fn wibble(_) { 1 } pub fn main() { 1 |> wibble |> panic "unreachable" } ----- WARNING warning: Unreachable code ┌─ /src/warning/wrn.gleam:5:13 │ 5 │ "unreachable" │ ^^^^^^^^^^^^^ This code is unreachable because it comes after a `panic`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_warning_if_all_branches_panic.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() {\n let n = 1\n case n {\n 0 -> panic\n _ -> panic\n }\n 1\n }" --- ----- SOURCE CODE pub fn main() { let n = 1 case n { 0 -> panic _ -> panic } 1 } ----- WARNING warning: Unreachable code ┌─ /src/warning/wrn.gleam:7:11 │ 7 │ 1 │ ^ This code is unreachable because it comes after a `panic`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_warning_if_all_branches_panic_2.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() {\n let n = 1\n case n {\n 0 -> {\n panic\n 2\n }\n _ -> panic\n }\n 1\n }" --- ----- SOURCE CODE pub fn main() { let n = 1 case n { 0 -> { panic 2 } _ -> panic } 1 } ----- WARNING warning: Unreachable code ┌─ /src/warning/wrn.gleam:6:15 │ 6 │ 2 │ ^ This code is unreachable because it comes after a `panic`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unreachable_warning_on_following_expression_if_panic_is_last_in_a_block.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() {\n let _ = {\n panic\n }\n 1\n }" --- ----- SOURCE CODE pub fn main() { let _ = { panic } 1 } ----- WARNING warning: Unreachable code ┌─ /src/warning/wrn.gleam:5:11 │ 5 │ 1 │ ^ This code is unreachable because it comes after a `panic`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_alias_for_duplicate_module_no_warning_for_alias_test.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n import a/wibble\n import b/wibble as wobble\n pub const one = wibble.one\n " --- ----- SOURCE CODE -- a/wibble.gleam pub const one = 1 -- b/wibble.gleam pub const two = 2 -- main.gleam import a/wibble import b/wibble as wobble pub const one = wibble.one ----- WARNING warning: Unused imported module ┌─ /src/warning/wrn.gleam:3:13 │ 3 │ import b/wibble as wobble │ ^^^^^^^^^^^^^^^^^^^^^^^^^ This imported module is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_alias_warning_test.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n import gleam/wibble.{one} as wobble\n pub const one = one\n " --- ----- SOURCE CODE -- gleam/wibble.gleam pub const one = 1 -- main.gleam import gleam/wibble.{one} as wobble pub const one = one ----- WARNING warning: Unused imported module alias ┌─ /src/warning/wrn.gleam:2:39 │ 2 │ import gleam/wibble.{one} as wobble │ ^^^^^^^^^ This alias is never used Hint: You can safely remove it. import gleam/wibble as _ warning: Shadowed Import ┌─ /src/warning/wrn.gleam:3:13 │ 3 │ pub const one = one │ ^^^^^^^^^^^^^ `one` is defined here Definition of one shadows an imported value. The imported value could not be used in this module anyway. Hint: Either rename the definition or remove the import. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_binary_operation_raises_a_warning.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n let string = \"a\" <> \"b\" \"c\" <> \"d\"\n string\n}\n" --- ----- SOURCE CODE pub fn main() { let string = "a" <> "b" "c" <> "d" string } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:3:27 │ 3 │ let string = "a" <> "b" "c" <> "d" │ ^^^^^^^^^^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_bit_array.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn main() {\n <<3>>\n\t\t\t\t2\n }" --- ----- SOURCE CODE pub fn main() { <<3>> 2 } ----- WARNING warning: Unused literal ┌─ /src/warning/wrn.gleam:3:9 │ 3 │ <<3>> │ ^^^^^ This value is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_block_wrapping_pure_expression.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n { 1 }\n Nil\n}\n" --- ----- SOURCE CODE pub fn main() { { 1 } Nil } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:3:3 │ 3 │ { 1 } │ ^^^^^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_block_wrapping_pure_expressions.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n {\n True\n 1\n }\n Nil\n}\n" --- ----- SOURCE CODE pub fn main() { { True 1 } Nil } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:3:3 │ 3 │ ╭ { 4 │ │ True 5 │ │ 1 6 │ │ } │ ╰───^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. warning: Unused literal ┌─ /src/warning/wrn.gleam:4:5 │ 4 │ True │ ^^^^ This value is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_bool_negation_raises_a_warning.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n !True\n 1\n}\n" --- ----- SOURCE CODE pub fn main() { !True 1 } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:3:3 │ 3 │ !True │ ^^^^^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_case_expression.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n let a = 1\n case a {\n 1 -> a\n _ -> 12\n }\n Nil\n}\n" --- ----- SOURCE CODE pub fn main() { let a = 1 case a { 1 -> a _ -> 12 } Nil } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:4:5 │ 4 │ ╭ case a { 5 │ │ 1 -> a 6 │ │ _ -> 12 7 │ │ } │ ╰─────^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_destructure.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn a(b) { case b { #(c, _) -> 5 } }" --- ----- SOURCE CODE pub fn a(b) { case b { #(c, _) -> 5 } } ----- WARNING warning: Unused variable ┌─ /src/warning/wrn.gleam:1:26 │ 1 │ pub fn a(b) { case b { #(c, _) -> 5 } } │ ^ This variable is never used Hint: You can ignore it with an underscore: `_c`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_discard_pattern.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() {\n let a = 10\n let _ = case a {\n _ as b -> b\n }\n}\n" --- ----- SOURCE CODE pub fn main() { let a = 10 let _ = case a { _ as b -> b } } ----- WARNING warning: Unused discard pattern ┌─ /src/warning/wrn.gleam:4:5 │ 4 │ _ as b -> b │ ^^^^^^ `_ as b` can be written more concisely as `b` ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_float.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { 1.0 2 }" --- ----- SOURCE CODE pub fn main() { 1.0 2 } ----- WARNING warning: Unused literal ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { 1.0 2 } │ ^^^ This value is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_fn_function_call.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n fn(a) { a + 1 }(1)\n Nil\n}\n" --- ----- SOURCE CODE pub fn main() { fn(a) { a + 1 }(1) Nil } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:3:5 │ 3 │ fn(a) { a + 1 }(1) │ ^^^^^^^^^^^^^^^^^^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_function_literal_raises_a_warning.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n fn(n) { n + 1 }\n 1\n}\n" --- ----- SOURCE CODE pub fn main() { fn(n) { n + 1 } 1 } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:3:3 │ 3 │ fn(n) { n + 1 } │ ^^^^^^^^^^^^^^^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_imported_module_warnings_test.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: import gleam/wibble --- ----- SOURCE CODE -- gleam/wibble.gleam pub fn wobble() { 1 } -- main.gleam import gleam/wibble ----- WARNING warning: Unused imported module ┌─ /src/warning/wrn.gleam:1:1 │ 1 │ import gleam/wibble │ ^^^^^^^^^^^^^^^^^^^ This imported module is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_imported_module_with_alias_and_unqualified_name_no_warnings_test.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "import gleam/one.{two} as three\npub fn wibble() { two() }" --- ----- SOURCE CODE -- gleam/one.gleam pub fn two() { 1 } -- main.gleam import gleam/one.{two} as three pub fn wibble() { two() } ----- WARNING warning: Unused imported module alias ┌─ /src/warning/wrn.gleam:1:24 │ 1 │ import gleam/one.{two} as three │ ^^^^^^^^ This alias is never used Hint: You can safely remove it. import gleam/one as _ ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_imported_module_with_alias_and_unqualified_name_warnings_test.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "import gleam/one.{two} as three" --- ----- SOURCE CODE -- gleam/one.gleam pub fn two() { 1 } -- main.gleam import gleam/one.{two} as three ----- WARNING warning: Unused imported module ┌─ /src/warning/wrn.gleam:1:1 │ 1 │ import gleam/one.{two} as three │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This imported module is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_imported_module_with_alias_warnings_test.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: import gleam/wibble as wobble --- ----- SOURCE CODE -- gleam/wibble.gleam pub fn wobble() { 1 } -- main.gleam import gleam/wibble as wobble ----- WARNING warning: Unused imported module ┌─ /src/warning/wrn.gleam:1:1 │ 1 │ import gleam/wibble as wobble │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This imported module is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_int.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main() { 1 2 }" --- ----- SOURCE CODE pub fn main() { 1 2 } ----- WARNING warning: Unused literal ┌─ /src/warning/wrn.gleam:1:17 │ 1 │ pub fn main() { 1 2 } │ ^ This value is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_int_negation_raises_a_warning.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n -1\n 1\n}\n" --- ----- SOURCE CODE pub fn main() { -1 1 } ----- WARNING warning: Unused literal ┌─ /src/warning/wrn.gleam:3:3 │ 3 │ -1 │ ^^ This value is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_label_shorthand_pattern_arg.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Wibble { Wibble(arg1: Int, arg2: Bool ) }\n\npub fn main() {\n let Wibble(arg1:, arg2:) = Wibble(1, True)\n arg1\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(arg1: Int, arg2: Bool ) } pub fn main() { let Wibble(arg1:, arg2:) = Wibble(1, True) arg1 } ----- WARNING warning: Unused variable ┌─ /src/warning/wrn.gleam:5:21 │ 5 │ let Wibble(arg1:, arg2:) = Wibble(1, True) │ ^^^^^ This variable is never used Hint: You can ignore it with an underscore: `arg2: _`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_label_shorthand_pattern_arg_shadowing.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Wibble { Wibble(arg1: Int, arg2: Bool ) }\n\npub fn main() {\n let Wibble(arg1:, arg2:) = Wibble(1, True)\n let arg1 = False\n arg1\n}\n" --- ----- SOURCE CODE pub type Wibble { Wibble(arg1: Int, arg2: Bool ) } pub fn main() { let Wibble(arg1:, arg2:) = Wibble(1, True) let arg1 = False arg1 } ----- WARNING warning: Unused variable ┌─ /src/warning/wrn.gleam:5:14 │ 5 │ let Wibble(arg1:, arg2:) = Wibble(1, True) │ ^^^^^ This variable is never used Hint: You can ignore it with an underscore: `arg1: _`. warning: Unused variable ┌─ /src/warning/wrn.gleam:5:21 │ 5 │ let Wibble(arg1:, arg2:) = Wibble(1, True) │ ^^^^^ This variable is never used Hint: You can ignore it with an underscore: `arg2: _`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_list.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn main() {\n [1, 2, 3]\n\t\t\t\t2\n }" --- ----- SOURCE CODE pub fn main() { [1, 2, 3] 2 } ----- WARNING warning: Unused literal ┌─ /src/warning/wrn.gleam:3:9 │ 3 │ [1, 2, 3] │ ^^^^^^^^^ This value is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_module_select_const.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\nimport wibble\n\npub fn main() {\n wibble.a\n 1\n}\n" --- ----- SOURCE CODE -- wibble.gleam pub const a = 1 -- main.gleam import wibble pub fn main() { wibble.a 1 } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:5:3 │ 5 │ wibble.a │ ^^^^^^^^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_module_select_constructor.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\nimport wibble\n\npub fn main() {\n wibble.Wibble\n 1\n}\n" --- ----- SOURCE CODE -- wibble.gleam pub type Wibble { Wibble(Int) } -- main.gleam import wibble pub fn main() { wibble.Wibble 1 } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:5:3 │ 5 │ wibble.Wibble │ ^^^^^^^^^^^^^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_module_select_constructor_call.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\nimport wibble\n\npub fn main() {\n wibble.Wibble(1)\n 1\n}\n" --- ----- SOURCE CODE -- wibble.gleam pub type Wibble { Wibble(Int) } -- main.gleam import wibble pub fn main() { wibble.Wibble(1) 1 } ----- WARNING warning: Unused literal ┌─ /src/warning/wrn.gleam:5:3 │ 5 │ wibble.Wibble(1) │ ^^^^^^^^^^^^^^^^ This value is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_module_select_function.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\nimport wibble\n\npub fn main() {\n wibble.println\n 1\n}\n" --- ----- SOURCE CODE -- wibble.gleam pub fn println(a) { Nil } -- main.gleam import wibble pub fn main() { wibble.println 1 } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:5:3 │ 5 │ wibble.println │ ^^^^^^^^^^^^^^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_module_wuth_alias_warning_test.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: import gleam/wibble as wobble --- ----- SOURCE CODE -- gleam/wibble.gleam pub const one = 1 -- main.gleam import gleam/wibble as wobble ----- WARNING warning: Unused imported module ┌─ /src/warning/wrn.gleam:1:1 │ 1 │ import gleam/wibble as wobble │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This imported module is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_pipeline_ending_with_pure_fn.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n 1\n |> fn(n) { n + 1 }\n\n Nil\n}\n" --- ----- SOURCE CODE pub fn main() { 1 |> fn(n) { n + 1 } Nil } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:3:5 │ 3 │ ╭ 1 4 │ │ |> fn(n) { n + 1 } │ ╰──────────────────────^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_pipeline_ending_with_variant_raises_a_warning.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Wibble(a) { Wibble(a) }\npub fn wibble(a) { a }\n\npub fn main() {\n 1 |> wibble |> Wibble\n 1\n}\n" --- ----- SOURCE CODE pub type Wibble(a) { Wibble(a) } pub fn wibble(a) { a } pub fn main() { 1 |> wibble |> Wibble 1 } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:6:3 │ 6 │ 1 |> wibble |> Wibble │ ^^^^^^^^^^^^^^^^^^^^^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_pipeline_ending_with_variant_raises_a_warning_2.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\nimport wibble\n\npub fn wobble(a) { a }\n\npub fn main() {\n 1 |> wobble |> wibble.Wibble\n 1\n}\n" --- ----- SOURCE CODE -- wibble.gleam pub type Wibble { Wibble(Int) } -- main.gleam import wibble pub fn wobble(a) { a } pub fn main() { 1 |> wobble |> wibble.Wibble 1 } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:7:3 │ 7 │ 1 |> wobble |> wibble.Wibble │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_private_const_warnings_test.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: const a = 1 --- ----- SOURCE CODE const a = 1 ----- WARNING warning: Unused private constant ┌─ /src/warning/wrn.gleam:1:1 │ 1 │ const a = 1 │ ^^^^^^^ This private constant is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_private_fn_warnings_test.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "fn a() { 1 }" --- ----- SOURCE CODE fn a() { 1 } ----- WARNING warning: Unused private function ┌─ /src/warning/wrn.gleam:1:1 │ 1 │ fn a() { 1 } │ ^^^^^^ This private function is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_private_type_warnings_test.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: type X --- ----- SOURCE CODE type X ----- WARNING warning: Unused private type ┌─ /src/warning/wrn.gleam:1:1 │ 1 │ type X │ ^^^^^^ This private type is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_private_type_warnings_test3.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: type X = Int --- ----- SOURCE CODE type X = Int ----- WARNING warning: Unused private type ┌─ /src/warning/wrn.gleam:1:1 │ 1 │ type X = Int │ ^^^^^^^^^^^^ This private type is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_private_type_warnings_test6.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "type X { X }" --- ----- SOURCE CODE type X { X } ----- WARNING warning: Unused private type ┌─ /src/warning/wrn.gleam:1:1 │ 1 │ type X { X } │ ^^^^^^ This private type is never used Hint: You can safely remove it. warning: Unused private constructor ┌─ /src/warning/wrn.gleam:1:10 │ 1 │ type X { X } │ ^ This private constructor is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_pure_function.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\nfn add(a, b) { a + b }\n\npub fn main() {\n add(1, 2)\n Nil\n}\n" --- ----- SOURCE CODE fn add(a, b) { a + b } pub fn main() { add(1, 2) Nil } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:5:3 │ 5 │ add(1, 2) │ ^^^^^^^^^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_pure_function_that_calls_other_pure_function.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\nfn sub(a, b) { add(a, -b) }\n\nfn add(a, b) { a + b }\n\npub fn main() {\n sub(1, 2)\n Nil\n}\n" --- ----- SOURCE CODE fn sub(a, b) { add(a, -b) } fn add(a, b) { a + b } pub fn main() { sub(1, 2) Nil } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:7:3 │ 7 │ sub(1, 2) │ ^^^^^^^^^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_record_access_raises_a_warning.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Thing {\n Thing(value: Int)\n}\n\npub fn main() {\n let thing = Thing(1)\n thing.value\n 1\n}\n" --- ----- SOURCE CODE pub type Thing { Thing(value: Int) } pub fn main() { let thing = Thing(1) thing.value 1 } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:8:3 │ 8 │ thing.value │ ^^^^^^^^^^^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_record_constructor_raises_a_warning.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Thing {\n Thing(value: Int)\n}\n\npub fn main() {\n Thing(1)\n 1\n}\n" --- ----- SOURCE CODE pub type Thing { Thing(value: Int) } pub fn main() { Thing(1) 1 } ----- WARNING warning: Unused literal ┌─ /src/warning/wrn.gleam:7:3 │ 7 │ Thing(1) │ ^^^^^^^^ This value is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_record_update_raises_a_warning.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub type Thing {\n Thing(value: Int, other: Int)\n}\n\npub fn main() {\n let thing = Thing(1, 2)\n Thing(..thing, value: 1)\n 1\n}\n" --- ----- SOURCE CODE pub type Thing { Thing(value: Int, other: Int) } pub fn main() { let thing = Thing(1, 2) Thing(..thing, value: 1) 1 } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:8:3 │ 8 │ Thing(..thing, value: 1) │ ^^^^^^^^^^^^^^^^^^^^^^^^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_recursive_function_argument.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main(x: Int) {\n main(x)\n}\n" --- ----- SOURCE CODE pub fn main(x: Int) { main(x) } ----- WARNING warning: Unused function argument ┌─ /src/warning/wrn.gleam:2:13 │ 2 │ pub fn main(x: Int) { │ ^ This argument is never used This argument is passed to the function when recursing, but it's never used for anything. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_recursive_function_argument_2.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main(x, times) {\n case times {\n 0 -> main(x, 0)\n _ -> main(x, times - 1)\n }\n}\n" --- ----- SOURCE CODE pub fn main(x, times) { case times { 0 -> main(x, 0) _ -> main(x, times - 1) } } ----- WARNING warning: Unused function argument ┌─ /src/warning/wrn.gleam:2:13 │ 2 │ pub fn main(x, times) { │ ^ This argument is never used This argument is passed to the function when recursing, but it's never used for anything. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_recursive_function_inside_anonymous_function.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main(x: Int) {\n let _useless = fn(_) { main(x) }\n Nil\n}\n" --- ----- SOURCE CODE pub fn main(x: Int) { let _useless = fn(_) { main(x) } Nil } ----- WARNING warning: Unused function argument ┌─ /src/warning/wrn.gleam:2:13 │ 2 │ pub fn main(x: Int) { │ ^ This argument is never used This argument is passed to the function when recursing, but it's never used for anything. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_recursive_function_with_shadowing.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main(x: Int) {\n let _useless = fn(x) { main(x) }\n main(x)\n}\n" --- ----- SOURCE CODE pub fn main(x: Int) { let _useless = fn(x) { main(x) } main(x) } ----- WARNING warning: Unused function argument ┌─ /src/warning/wrn.gleam:2:13 │ 2 │ pub fn main(x: Int) { │ ^ This argument is never used This argument is passed to the function when recursing, but it's never used for anything. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_string.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn main() {\n \"1\"\n 2\n }" --- ----- SOURCE CODE pub fn main() { "1" 2 } ----- WARNING warning: Unused literal ┌─ /src/warning/wrn.gleam:3:9 │ 3 │ "1" │ ^^^ This value is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_tuple.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn main() {\n #(1.0, \"Hello world\")\n\t\t\t\t2\n }" --- ----- SOURCE CODE pub fn main() { #(1.0, "Hello world") 2 } ----- WARNING warning: Unused literal ┌─ /src/warning/wrn.gleam:3:9 │ 3 │ #(1.0, "Hello world") │ ^^^^^^^^^^^^^^^^^^^^^ This value is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_tuple_index_raises_a_warning.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n #(1, 2).0\n 1\n}\n" --- ----- SOURCE CODE pub fn main() { #(1, 2).0 1 } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:3:3 │ 3 │ #(1, 2).0 │ ^^^^^^^^^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_variable_assignment_pattern.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\ntype Wibble {\n Wibble(a: Int, b: Int)\n}\n\npub fn main() {\n let Wibble(a:, ..) as w = Wibble(1, 2)\n a\n}\n" --- ----- SOURCE CODE type Wibble { Wibble(a: Int, b: Int) } pub fn main() { let Wibble(a:, ..) as w = Wibble(1, 2) a } ----- WARNING warning: Unused variable ┌─ /src/warning/wrn.gleam:7:25 │ 7 │ let Wibble(a:, ..) as w = Wibble(1, 2) │ ^ This variable is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_variable_raises_a_warning.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n let number = 1\n number\n 1\n}\n" --- ----- SOURCE CODE pub fn main() { let number = 1 number 1 } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:4:3 │ 4 │ number │ ^^^^^^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_variable_shadowing_test.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn a() { let b = 1 let b = 2 b }" --- ----- SOURCE CODE pub fn a() { let b = 1 let b = 2 b } ----- WARNING warning: Unused variable ┌─ /src/warning/wrn.gleam:1:18 │ 1 │ pub fn a() { let b = 1 let b = 2 b } │ ^ This variable is never used Hint: You can ignore it with an underscore: `_b`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_variable_string_prefix_pattern.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n let assert \"hello\" as hello <> rest = \"hello, world\"\n rest\n}\n" --- ----- SOURCE CODE pub fn main() { let assert "hello" as hello <> rest = "hello, world" rest } ----- WARNING warning: Unused variable ┌─ /src/warning/wrn.gleam:3:25 │ 3 │ let assert "hello" as hello <> rest = "hello, world" │ ^^^^^ This variable is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_variable_string_prefix_pattern2.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n let assert \"hello\" as hello <> rest = \"hello, world\"\n hello\n}\n" --- ----- SOURCE CODE pub fn main() { let assert "hello" as hello <> rest = "hello, world" hello } ----- WARNING warning: Unused variable ┌─ /src/warning/wrn.gleam:3:34 │ 3 │ let assert "hello" as hello <> rest = "hello, world" │ ^^^^ This variable is never used Hint: You can ignore it with an underscore: `_rest`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_variable_warnings_test.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn a(b) { 1 }" --- ----- SOURCE CODE pub fn a(b) { 1 } ----- WARNING warning: Unused function argument ┌─ /src/warning/wrn.gleam:1:10 │ 1 │ pub fn a(b) { 1 } │ ^ This argument is never used Hint: You can ignore it with an underscore: `_b`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__unused_variable_warnings_test2.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn a() { let b = 1 5 }" --- ----- SOURCE CODE pub fn a() { let b = 1 5 } ----- WARNING warning: Unused variable ┌─ /src/warning/wrn.gleam:1:18 │ 1 │ pub fn a() { let b = 1 5 } │ ^ This variable is never used Hint: You can ignore it with an underscore: `_b`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__use_with_pure_fn_expression_is_marked_as_unused.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn main() {\n {\n use _ <- fn(a) { a }\n 1\n }\n\n Nil\n}\n" --- ----- SOURCE CODE pub fn main() { { use _ <- fn(a) { a } 1 } Nil } ----- WARNING warning: Unused value ┌─ /src/warning/wrn.gleam:3:5 │ 3 │ ╭ { 4 │ │ use _ <- fn(a) { a } 5 │ │ 1 6 │ │ } │ ╰─────^ This value is never used This expression computes a value without any side effects, but then the value isn't used at all. You might want to assign it to a variable, or delete the expression entirely if it's not needed. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__variables_redundant_comparison.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "pub fn main(a) { a == a }" --- ----- SOURCE CODE pub fn main(a) { a == a } ----- WARNING warning: Redundant comparison ┌─ /src/warning/wrn.gleam:1:18 │ 1 │ pub fn main(a) { a == a } │ ^^^^^^ This is always `True` This comparison is redundant since it always succeeds. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__warning_many_at_same_time.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\nfn main() { let five = 5 }" --- ----- SOURCE CODE fn main() { let five = 5 } ----- WARNING warning: Unused private function ┌─ /src/warning/wrn.gleam:2:1 │ 2 │ fn main() { let five = 5 } │ ^^^^^^^^^ This private function is never used Hint: You can safely remove it. warning: Unused variable ┌─ /src/warning/wrn.gleam:2:17 │ 2 │ fn main() { let five = 5 } │ ^^^^ This variable is never used Hint: You can ignore it with an underscore: `_five`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__warning_private_function_never_used.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "fn main() { 5 }" --- ----- SOURCE CODE fn main() { 5 } ----- WARNING warning: Unused private function ┌─ /src/warning/wrn.gleam:1:1 │ 1 │ fn main() { 5 } │ ^^^^^^^^^ This private function is never used Hint: You can safely remove it. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__warning_variable_never_used_test.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\npub fn wibble() { Ok(5) }\npub fn main() { let five = wibble() }" --- ----- SOURCE CODE pub fn wibble() { Ok(5) } pub fn main() { let five = wibble() } ----- WARNING warning: Unused variable ┌─ /src/warning/wrn.gleam:3:21 │ 3 │ pub fn main() { let five = wibble() } │ ^^^^ This variable is never used Hint: You can ignore it with an underscore: `_five`. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__warnings_for_matches_on_literal_values_that_are_not_like_an_if_1.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn main() {\n case True {\n _ -> 1\n }\n }\n " --- ----- SOURCE CODE pub fn main() { case True { _ -> 1 } } ----- WARNING warning: Match on a literal value ┌─ /src/warning/wrn.gleam:3:14 │ 3 │ case True { │ ^^^^ There's no need to pattern match on this value Matching on a literal value is redundant since you can already tell which branch is going to match with this value. ================================================ FILE: compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__warnings_for_matches_on_literal_values_that_are_not_like_an_if_2.snap ================================================ --- source: compiler-core/src/type_/tests/warnings.rs expression: "\n pub fn main() {\n case True {\n True -> 1\n }\n }\n " --- ----- SOURCE CODE pub fn main() { case True { True -> 1 } } ----- WARNING warning: Match on a literal value ┌─ /src/warning/wrn.gleam:3:14 │ 3 │ case True { │ ^^^^ There's no need to pattern match on this value Matching on a literal value is redundant since you can already tell which branch is going to match with this value. ================================================ FILE: compiler-core/src/type_/tests/target_implementations.rs ================================================ use ecow::EcoString; use itertools::Itertools; use crate::{ analyse::TargetSupport, assert_module_error, build::Target, type_::expression::Implementations, }; use super::compile_module_with_opts; macro_rules! assert_targets { ($src:expr, $implementations:expr $(,)?) => { let result = $crate::type_::tests::target_implementations::implementations($src); let expected = $implementations .iter() .map(|(name, expected_impl)| ((*name).into(), *expected_impl)) .collect_vec(); assert_eq!(expected, result); }; } pub fn implementations(src: &str) -> Vec<(EcoString, Implementations)> { compile_module_with_opts( "test_module", src, None, vec![], Target::Erlang, TargetSupport::NotEnforced, None, ) .expect("compile src") .type_info .values .into_iter() .map(|(name, value)| (name, value.variant.implementations())) .sorted() .collect_vec() } #[test] pub fn pure_gleam_function() { assert_targets!( r#" pub fn pure_gleam_1() { 1 + 1 } pub fn pure_gleam_2() { pure_gleam_1() * 2 } "#, [ ( "pure_gleam_1", Implementations { gleam: true, uses_erlang_externals: false, uses_javascript_externals: false, can_run_on_erlang: true, can_run_on_javascript: true, } ), ( "pure_gleam_2", Implementations { gleam: true, uses_erlang_externals: false, uses_javascript_externals: false, can_run_on_erlang: true, can_run_on_javascript: true, } ) ], ); } #[test] pub fn erlang_only_function() { assert_targets!( r#" @external(erlang, "wibble", "wobble") pub fn erlang_only_1() -> Int pub fn erlang_only_2() { erlang_only_1() * 2 } "#, [ ( "erlang_only_1", Implementations { gleam: false, uses_erlang_externals: true, uses_javascript_externals: false, can_run_on_erlang: true, can_run_on_javascript: false, } ), ( "erlang_only_2", Implementations { gleam: false, uses_erlang_externals: true, uses_javascript_externals: false, can_run_on_erlang: true, can_run_on_javascript: false, } ) ], ); } #[test] pub fn externals_only_function() { assert_targets!( r#" @external(erlang, "wibble", "wobble") @external(javascript, "wibble", "wobble") pub fn all_externals_1() -> Int pub fn all_externals_2() { all_externals_1() * 2 } "#, [ ( "all_externals_1", Implementations { gleam: false, uses_erlang_externals: true, uses_javascript_externals: true, can_run_on_erlang: true, can_run_on_javascript: true, } ), ( "all_externals_2", Implementations { gleam: false, uses_erlang_externals: true, uses_javascript_externals: true, can_run_on_erlang: true, can_run_on_javascript: true, } ) ], ); } #[test] pub fn externals_with_pure_gleam_body() { assert_targets!( r#" @external(javascript, "wibble", "wobble") pub fn javascript_external_and_pure_body() -> Int { 1 + 1 } @external(erlang, "wibble", "wobble") pub fn erlang_external_and_pure_body() -> Int { 1 + 1 } pub fn pure_gleam() { javascript_external_and_pure_body() + erlang_external_and_pure_body() } "#, [ ( "erlang_external_and_pure_body", Implementations { gleam: true, uses_erlang_externals: true, uses_javascript_externals: false, can_run_on_erlang: true, can_run_on_javascript: true, } ), ( "javascript_external_and_pure_body", Implementations { gleam: true, uses_erlang_externals: false, uses_javascript_externals: true, can_run_on_erlang: true, can_run_on_javascript: true, } ), ( "pure_gleam", Implementations { gleam: true, uses_erlang_externals: true, uses_javascript_externals: true, can_run_on_erlang: true, can_run_on_javascript: true, } ) ], ); } #[test] pub fn erlang_external_with_javascript_body() { assert_targets!( r#" @external(javascript, "wibble", "wobble") fn javascript_only() -> Int @external(erlang, "wibble", "wobble") pub fn erlang_external_and_javascript_body() -> Int { javascript_only() } pub fn all_externals() -> Int { erlang_external_and_javascript_body() } "#, [ ( "all_externals", Implementations { gleam: false, uses_erlang_externals: true, uses_javascript_externals: true, can_run_on_erlang: true, can_run_on_javascript: true, } ), ( "erlang_external_and_javascript_body", Implementations { gleam: false, uses_erlang_externals: true, uses_javascript_externals: true, can_run_on_erlang: true, can_run_on_javascript: true, } ), ( "javascript_only", Implementations { gleam: false, uses_erlang_externals: false, uses_javascript_externals: true, can_run_on_erlang: false, can_run_on_javascript: true, } ) ], ); } #[test] pub fn javascript_external_with_erlang_body() { assert_targets!( r#" @external(erlang, "wibble", "wobble") pub fn erlang_only() -> Int @external(javascript, "wibble", "wobble") pub fn javascript_external_and_erlang_body() -> Int { erlang_only() } pub fn all_externals() -> Int { javascript_external_and_erlang_body() } "#, [ ( "all_externals", Implementations { gleam: false, uses_erlang_externals: true, uses_javascript_externals: true, can_run_on_erlang: true, can_run_on_javascript: true, } ), ( "erlang_only", Implementations { gleam: false, uses_erlang_externals: true, uses_javascript_externals: false, can_run_on_erlang: true, can_run_on_javascript: false, } ), ( "javascript_external_and_erlang_body", Implementations { gleam: false, uses_erlang_externals: true, uses_javascript_externals: true, can_run_on_erlang: true, can_run_on_javascript: true, } ) ], ); } #[test] pub fn function_with_no_valid_implementations() { assert_module_error!( r#" @external(javascript, "wibble", "wobble") fn javascript_only() -> Int @external(erlang, "wibble", "wobble") fn erlang_only() -> Int pub fn main() { javascript_only() erlang_only() } "# ); } #[test] pub fn invalid_both_and_one_called_from_erlang() { let src = r#" @external(erlang, "wibble", "wobble") @external(javascript, "wibble", "wobble") fn both_external() -> Int @external(javascript, "wibble", "wobble") fn javascript_only() -> Int pub fn no_valid_erlang_impl() { both_external() javascript_only() } "#; let out = compile_module_with_opts( "test_module", src, None, vec![], Target::Erlang, TargetSupport::Enforced, None, ); assert!(out.into_result().is_err()); } #[test] pub fn invalid_both_and_one_called_from_javascript() { let src = r#" @external(erlang, "wibble", "wobble") @external(javascript, "wibble", "wobble") fn both_external() -> Int @external(erlang, "wibble", "wobble") fn erlang_only() -> Int pub fn no_valid_javascript_impl() { both_external() erlang_only() } "#; let out = compile_module_with_opts( "test_module", src, None, vec![], Target::JavaScript, TargetSupport::Enforced, None, ); assert!(out.into_result().is_err()); } #[test] pub fn invalid_both_and_one_called_from_erlang_flipped() { let src = r#" @external(erlang, "wibble", "wobble") @external(javascript, "wibble", "wobble") fn both_external() -> Int @external(javascript, "wibble", "wobble") fn javascript_only() -> Int pub fn no_valid_erlang_impl() { javascript_only() both_external() } "#; let out = compile_module_with_opts( "test_module", src, None, vec![], Target::Erlang, TargetSupport::Enforced, None, ); assert!(out.into_result().is_err()); } #[test] pub fn invalid_both_and_one_called_from_javascript_flipped() { let src = r#" @external(erlang, "wibble", "wobble") @external(javascript, "wibble", "wobble") fn both_external() -> Int @external(erlang, "wibble", "wobble") fn erlang_only() -> Int pub fn no_valid_javascript_impl() { erlang_only() both_external() } "#; let out = compile_module_with_opts( "test_module", src, None, vec![], Target::JavaScript, TargetSupport::Enforced, None, ); assert!(out.into_result().is_err()); } #[test] pub fn invalid_erlang_with_external() { let src = r#" @external(javascript, "wibble", "wobble") fn javascript_only() -> Int @external(javascript, "one", "two") pub fn no_valid_erlang_impl() { javascript_only() } "#; let out = compile_module_with_opts( "test_module", src, None, vec![], Target::Erlang, TargetSupport::Enforced, None, ); assert!(out.into_result().is_err()); } #[test] pub fn invalid_javascript_with_external() { let src = r#" @external(erlang, "wibble", "wobble") fn erlang_only() -> Int @external(erlang, "one", "two") pub fn no_valid_javascript_impl() { erlang_only() } "#; let out = compile_module_with_opts( "test_module", src, None, vec![], Target::JavaScript, TargetSupport::Enforced, None, ); assert!(out.into_result().is_err()); } ================================================ FILE: compiler-core/src/type_/tests/type_alias.rs ================================================ use crate::{assert_module_error, assert_module_infer}; #[test] fn alias_dep() { assert_module_infer!( r#" type E = #(F, C) type F = fn(CustomA) -> CustomB(B) type A = Int type B = C type C = CustomA type D = CustomB(C) type CustomA { CustomA() } type CustomB(a) { CustomB(a) } "#, vec![], ) } #[test] fn custom_type_dep() { assert_module_infer!( r#" type A { A(Blah) } type Blah { B(Int) } "#, vec![], ) } #[test] fn alias_cycle() { assert_module_error!( r#" type A = B type B = C type C = D type D = E type E = A "# ); } #[test] fn alias_direct_cycle() { assert_module_error!( r#" type A = #(A, A) "# ); } #[test] fn alias_different_module() { assert_module_infer!( ("other", "pub type Blah = Bool"), r#" import other type Blah = #(other.Blah, other.Blah) "#, vec![], ); } #[test] fn duplicate_parameter() { assert_module_error!( r#" type A(a, a) = List(a) "# ); } #[test] fn unused_parameter() { assert_module_error!( r#" type A(a) = Int "# ); } #[test] fn type_alias_error_does_not_stop_analysis() { // Both these aliases have errors! We do not stop on the first one. assert_module_error!( r#" type UnusedParameter(a) = Int type UnknownType = Dunno "# ); } #[test] fn duplicate_variable_error_does_not_stop_analysis() { // Both these aliases have errors! We do not stop on the first one. assert_module_error!( r#" type Two(a, a) = #(a, a) type UnknownType = Dunno "# ); } // https://github.com/gleam-lang/gleam/issues/3191 #[test] fn both_errors_are_shown() { // The alias has an error, and it causes the function to have an error as it // refers to the type that does not exist. assert_module_error!( r#" type X = List(Intt) fn example(a: X) { todo } "# ); } #[test] fn conflict_with_import() { // We cannot declare a type with the same name as an imported type assert_module_error!( ("wibble", "pub type Wobble = String"), "import wibble.{type Wobble} type Wobble = Int", ); } ================================================ FILE: compiler-core/src/type_/tests/use_.rs ================================================ use crate::{assert_error, assert_infer, assert_module_error, assert_module_infer, assert_warning}; #[test] fn arity_1() { assert_module_infer!( r#" pub fn main() { use <- pair() 123 } fn pair(f) { let x = f() #(x, x) } "#, vec![("main", "fn() -> #(Int, Int)")], ) } #[test] fn arity_2() { assert_module_infer!( r#" pub fn main() { use <- pair(1.0) 123 } fn pair(x, f) { let y = f() #(x, y) } "#, vec![("main", "fn() -> #(Float, Int)")], ) } #[test] fn arity_3() { assert_module_infer!( r#" pub fn main() { use <- trip(1.0, "") 123 } fn trip(x, y, f) { let z = f() #(x, y, z) } "#, vec![("main", "fn() -> #(Float, String, Int)")], ) } #[test] fn call_is_variable() { assert_infer!( r#" let call = fn(f) { f() } use <- call 123 "#, "Int" ); } #[test] fn call_is_literal() { assert_infer!( r#" use <- fn(f) { f() } 123.0 "#, "Float" ); } #[test] fn call_is_capture() { assert_infer!( r#" let f = fn(a, b) { a() + b } use <- f(_, 123) 123 "#, "Int" ); } #[test] fn invalid_call_is_number() { assert_error!( r#" use <- 123 123 "# ); } #[test] fn wrong_arity() { assert_error!( r#" let f = fn(callback) { callback(1, 2) } use <- f 123 "# ); } #[test] fn use_with_function_that_doesnt_take_callback_as_last_arg_1() { assert_error!( r#" let f = fn(a) { a + 1 } use <- f 123 "# ); } #[test] fn use_with_function_that_doesnt_take_callback_as_last_arg_2() { assert_error!( r#" let f = fn() { 1 } use <- f 123 "# ); } #[test] fn use_with_function_that_doesnt_take_callback_as_last_arg_3() { assert_error!( r#" let f = fn(a, b) { a + b } use <- f(1) 123 "# ); } #[test] fn wrong_arity_less_than_required() { assert_error!( r#" let f = fn(a, b) { 1 } use <- f 123 "# ); } #[test] fn wrong_arity_less_than_required_2() { assert_error!( r#" let f = fn(a, b, c) { 1 } use <- f(1) 123 "# ); } #[test] fn wrong_arity_more_than_required() { assert_error!( r#" let f = fn(a, b) { 1 } use <- f(1, 2) 123 "# ); } #[test] fn wrong_arity_more_than_required_2() { assert_error!( r#" let f = fn(a, b) { 1 } use <- f(1, 2, 3) 123 "# ); } #[test] fn no_callback_body() { assert_warning!( r#" pub fn main() { let thingy = fn(f) { f() } use <- thingy() } "# ); } #[test] fn invalid_callback_type() { assert_error!( r#" let x = fn(f) { f() + 1 } use <- x() Nil "# ); } #[test] fn invalid_callback_type_2() { assert_error!( r#" let x = fn(f) { "Hello, " <> f() } use <- x() let n = 1 n + 2 "# ); } #[test] fn invalid_callback_type_3() { assert_error!( r#" let x = fn(f) { "Hello, " <> f() } let y = fn(f) { 1 + f() } use <- x() use <- y() let n = 1 n + 1 "# ); } #[test] fn invalid_callback_type_4() { assert_error!( r#" let x = fn(f) { "Hello, " <> f() } let y = fn(f) { 1 + f() } let z = fn(f) { 1.0 +. f() } use <- x() use <- y() let n = 1 use <- z() 1.0 "# ); } #[test] fn wrong_callback_arity() { assert_error!( r#" let x = fn(f) { "Hello, " <> f() } use _ <- x() "Giacomo!" "# ); } #[test] fn wrong_callback_arity_2() { assert_error!( r#" let x = fn(f) { "Hello, " <> f(1) } use <- x() "Giacomo!" "# ); } #[test] fn wrong_callback_arity_3() { assert_error!( r#" let x = fn(f) { "Hello, " <> f(1) } use _, _ <- x() "Giacomo!" "# ); } #[test] fn wrong_callback_arg() { assert_error!( r#" let x = fn(f) { "Hello, " <> f(1) } use n <- x() n <> "Giacomo!" "# ); } #[test] fn wrong_callback_arg_with_wrong_annotation() { assert_error!( r#" let x = fn(f) { "Hello, " <> f(1) } use n: String <- x() n <> "Giacomo!" "# ); } #[test] fn wrong_callback_arg_2() { assert_module_error!( r#" pub type Box { Box(Int) } pub fn main() { let x = fn(f) { "Hello, " <> f(Box(1)) } use Box("hi") <- x() "Giacomo!" } "# ); } #[test] fn wrong_callback_arg_3() { assert_module_error!( r#" pub type Box { Box(Int) } pub fn main() { let x = fn(f) { "Hello, " <> f(1) } use Box(1) <- x() "Giacomo!" } "# ); } #[test] fn discard() { assert_infer!( r#" let x = fn(f) { f(123) } use _ <- x() Nil "#, "Nil", ); } #[test] fn discard_named() { assert_infer!( r#" let x = fn(f) { f(123) } use _wibble <- x() Nil "#, "Nil", ); } #[test] fn just_use_in_fn_body() { assert_warning!( r#" pub fn main() { use <- wibble() } fn wibble(f) { f() } "# ); } #[test] fn labels() { assert_module_infer!( r#" pub fn main() { use x <- apply(arg: 1) x } fn apply(fun fun, arg arg) { fun(arg) } "#, vec![("main", "fn() -> Int")], ); } #[test] fn patterns() { assert_module_infer!( r#" pub fn main() { use Box(x) <- apply(Box(1)) x } type Box(a) { Box(a) } fn apply(arg, fun) { fun(arg) } "#, vec![("main", "fn() -> Int")], ); } #[test] fn multiple_patterns() { assert_module_infer!( r#" pub fn main() { use Box(x), Box(y), Box(z) <- apply(Box(1)) x + y + z } type Box(a) { Box(a) } fn apply(arg, fun) { fun(arg, arg, arg) } "#, vec![("main", "fn() -> Int")], ); } #[test] fn typed_pattern() { assert_module_infer!( r#" pub fn main() { use Box(x): Box(Int), Box(y), Box(z) <- apply(Box(1)) x + y + z } type Box(a) { Box(a) } fn apply(arg, fun) { fun(arg, arg, arg) } "#, vec![("main", "fn() -> Int")], ); } #[test] fn typed_pattern_wrong_type() { assert_module_error!( r#" pub fn main() { use Box(x): Box(Bool), Box(y), Box(z) <- apply(Box(1)) x + y + z } type Box(a) { Box(a) } fn apply(arg, fun) { fun(arg, arg, arg) } "# ); } #[test] fn multiple_bad_statement_use_fault_tolerance() { assert_error!( r#" let x = fn(f) { f() + 1 } use <- x() 1 + 2.0 3.0 + 4 5 "# ); } ================================================ FILE: compiler-core/src/type_/tests/version_inference.rs ================================================ use hexpm::version::Version; use super::compile_module; fn infer_version(module: &str) -> Version { compile_module("test_module", module, None, vec![]) .expect("module to compile") .type_info .minimum_required_version } #[test] fn internal_annotation_on_constant_requires_v1_1() { let version = infer_version( " @internal pub const wibble = 1 ", ); assert_eq!(version, Version::new(1, 1, 0)); } #[test] fn internal_annotation_on_type_requires_v1_1() { let version = infer_version( " @internal pub type Wibble ", ); assert_eq!(version, Version::new(1, 1, 0)); } #[test] fn internal_annotation_on_function_requires_v1_1() { let version = infer_version( " @internal pub fn wibble() {} ", ); assert_eq!(version, Version::new(1, 1, 0)); } #[test] fn nested_tuple_access_requires_v1_1() { let version = infer_version( " pub fn main() { let tuple = #(1, #(1, 1)) tuple.1.0 } ", ); assert_eq!(version, Version::new(1, 1, 0)); } #[test] fn javascript_external_module_with_at_requires_v1_2() { let version = infer_version( " @external(javascript, \"module@module\", \"func\") pub fn main() {} ", ); assert_eq!(version, Version::new(1, 2, 0)); } #[test] fn int_plus_in_guards_requires_v1_3() { let version = infer_version( " pub fn main() { case todo { _ if 1 + 1 == 2 -> todo _ -> todo } } ", ); assert_eq!(version, Version::new(1, 3, 0)); } #[test] fn float_plus_in_guards_requires_v1_3() { let version = infer_version( " pub fn main() { case todo { _ if 1.0 +. 1.0 == 2.0 -> todo _ -> todo } } ", ); assert_eq!(version, Version::new(1, 3, 0)); } #[test] fn int_minus_in_guards_requires_v1_3() { let version = infer_version( " pub fn main() { case todo { _ if 1 - 1 == 0 -> todo _ -> todo } } ", ); assert_eq!(version, Version::new(1, 3, 0)); } #[test] fn float_minus_in_guards_requires_v1_3() { let version = infer_version( " pub fn main() { case todo { _ if 1.0 -. 1.0 == 0.0 -> todo _ -> todo } } ", ); assert_eq!(version, Version::new(1, 3, 0)); } #[test] fn int_multiplication_in_guards_requires_v1_3() { let version = infer_version( " pub fn main() { case todo { _ if 1 * 1 == 0 -> todo _ -> todo } } ", ); assert_eq!(version, Version::new(1, 3, 0)); } #[test] fn float_multiplication_in_guards_requires_v1_3() { let version = infer_version( " pub fn main() { case todo { _ if 1.0 *. 1.0 == 0.0 -> todo _ -> todo } } ", ); assert_eq!(version, Version::new(1, 3, 0)); } #[test] fn int_divide_in_guards_requires_v1_3() { let version = infer_version( " pub fn main() { case todo { _ if 1 / 1 == 0 -> todo _ -> todo } } ", ); assert_eq!(version, Version::new(1, 3, 0)); } #[test] fn float_divide_in_guards_requires_v1_3() { let version = infer_version( " pub fn main() { case todo { _ if 1.0 /. 1.0 == 0.0 -> todo _ -> todo } } ", ); assert_eq!(version, Version::new(1, 3, 0)); } #[test] fn int_remainder_in_guards_requires_v1_3() { let version = infer_version( " pub fn main() { case todo { _ if 1 % 1 == 0 -> todo _ -> todo } } ", ); assert_eq!(version, Version::new(1, 3, 0)); } #[test] fn label_shorthand_in_constand_requires_v1_4() { let version = infer_version( " pub type Wibble { Wibble(wibble: Int) } pub const wibble = 1 pub const wobble = Wibble(wibble:) ", ); assert_eq!(version, Version::new(1, 4, 0)); } #[test] fn label_shorthand_in_call_requires_v1_4() { let version = infer_version( " pub type Wibble { Wibble(wibble: Int) } pub fn main() { let wibble = 1 Wibble(wibble:) } ", ); assert_eq!(version, Version::new(1, 4, 0)); } #[test] fn label_shorthand_in_pattern_requires_v1_4() { let version = infer_version( " pub type Wibble { Wibble(wibble: Int) } pub fn main() { case Wibble(1) { Wibble(wibble:) -> todo } } ", ); assert_eq!(version, Version::new(1, 4, 0)); } #[test] fn label_shorthand_in_record_update_requires_v1_4() { let version = infer_version( " pub type Vec2 { Vec2(x: Int, y: Int) } pub fn main() { let x = 1 Vec2(..Vec2(0, 0), x:) } ", ); assert_eq!(version, Version::new(1, 4, 0)); } #[test] fn constant_string_concatenation_requires_v1_4() { let version = infer_version("pub const string = \"wibble\" <> \"wobble\""); assert_eq!(version, Version::new(1, 4, 0)); } #[test] fn missing_utf_8_option_in_bit_array_segment_requires_v1_5() { let version = infer_version( " pub fn main() { <<\"hello\", \" world!\">> } ", ); assert_eq!(version, Version::new(1, 5, 0)); } #[test] fn missing_utf_8_option_in_bit_array_constant_segment_requires_v1_5() { let version = infer_version("const bits = <<\"hello\", \" world!\">>"); assert_eq!(version, Version::new(1, 5, 0)); } #[test] fn missing_utf_8_option_in_bit_array_pattern_segment_requires_v1_5() { let version = infer_version( " pub fn main() { case todo { <<\"hello\", \" world!\">> -> todo _ -> todo } } ", ); assert_eq!(version, Version::new(1, 5, 0)); } #[test] fn missing_float_option_in_bit_array_segment_requires_v1_10() { let version = infer_version( " pub fn main() { <<1.2>> } ", ); assert_eq!(version, Version::new(1, 10, 0)); } #[test] fn missing_float_option_in_bit_array_constant_segment_requires_v1_10() { let version = infer_version("const bits = <<1.2>>"); assert_eq!(version, Version::new(1, 10, 0)); } #[test] fn missing_float_option_in_bit_array_pattern_segment_requires_v1_10() { let version = infer_version( " pub fn main() { case todo { <<1.11>> -> todo _ -> todo } } ", ); assert_eq!(version, Version::new(1, 10, 0)); } #[test] fn const_record_update_requires_v1_14() { let version = infer_version( " pub type Wibble { Wibble(a: Int, b: Int) } const base = Wibble(1, 2) const wobble = Wibble(..base, a: 3) ", ); assert_eq!(version, Version::new(1, 14, 0)); } #[test] fn inference_picks_the_bigger_of_two_versions() { let version = infer_version( " pub fn main() { case todo { <<\"hello\", \" world!\">> -> todo _ if 1 + 1 == 2-> todo _ -> todo } } ", ); assert_eq!(version, Version::new(1, 5, 0)); } #[test] fn inference_picks_the_bigger_of_two_versions_2() { let version = infer_version( " @external(javascript, \"module@module\", \"func\") pub fn main() { let tuple = #(1, #(1, 1)) tuple.1.0 } ", ); assert_eq!(version, Version::new(1, 2, 0)); } #[test] fn bool_assert_requires_v1_11() { let version = infer_version( " pub fn main() { assert 1 != 2 } ", ); assert_eq!(version, Version::new(1, 11, 0)); } #[test] fn expression_in_expression_segment_size_requires_v1_12() { let version = infer_version( " pub fn main() { <<1:size(3 * 8)>> } ", ); assert_eq!(version, Version::new(1, 12, 0)); } #[test] fn expression_in_pattern_segment_size_requires_v1_12() { let version = infer_version( " pub fn main(x) { case x { <<_:size(3*8)>> -> 1 _ -> 2 } }", ); assert_eq!(version, Version::new(1, 12, 0)); } ================================================ FILE: compiler-core/src/type_/tests/warnings.rs ================================================ use super::*; use crate::{ assert_js_no_warnings, assert_js_warning, assert_no_warnings, assert_warning, assert_warnings_with_gleam_version, }; #[test] fn unknown_label() { // https://github.com/gleam-lang/gleam/issues/1098 // calling function with unused labelled argument should not emit warnings assert_no_warnings!( r#"fn greet(name name: String, title _title: String) { name } pub fn main() { greet(name: "Sam", title: "Mr") }"#, ); } #[test] fn todo_warning_test() { assert_warning!("pub fn main() { 1 == todo }"); } // https://github.com/gleam-lang/gleam/issues/1669 #[test] fn todo_warning_correct_location() { assert_warning!( "pub fn main() { todo }" ); } #[test] fn todo_with_known_type() { assert_warning!( "pub fn main() -> String { todo }" ); } #[test] fn empty_func_warning_test() { assert_warning!( "pub fn main() { wibble() } pub fn wibble() { } " ); } #[test] fn warning_variable_never_used_test() { assert_warning!( " pub fn wibble() { Ok(5) } pub fn main() { let five = wibble() }" ); } #[test] fn warning_private_function_never_used() { assert_warning!("fn main() { 5 }"); } #[test] fn warning_many_at_same_time() { assert_warning!( " fn main() { let five = 5 }" ); } #[test] fn result_discard_warning_test() { // Implicitly discarded Results emit warnings assert_warning!( " pub fn wibble() { Ok(5) } pub fn main() { wibble() 5 }" ); } #[test] fn result_discard_warning_test2() { // Explicitly discarded Results do not emit warnings assert_no_warnings!( " pub fn wibble() { Ok(5) } pub fn main() { let _ = wibble() 5 }", ); } #[test] fn unused_int() { assert_warning!("pub fn main() { 1 2 }"); } #[test] fn unused_float() { assert_warning!("pub fn main() { 1.0 2 }"); } #[test] fn unused_string() { assert_warning!( " pub fn main() { \"1\" 2 }" ); } #[test] fn unused_bit_array() { assert_warning!( " pub fn main() { <<3>> 2 }" ); } #[test] fn unused_tuple() { assert_warning!( " pub fn main() { #(1.0, \"Hello world\") 2 }" ); } #[test] fn unused_list() { assert_warning!( " pub fn main() { [1, 2, 3] 2 }" ); } #[test] fn record_update_warnings_test() { // Some fields are given in a record update do not emit warnings assert_no_warnings!( " pub type Person { Person(name: String, age: Int) } pub fn update_person() { let past = Person(\"Quinn\", 27) let present = Person(..past, name: \"Santi\") present }", ); } #[test] fn record_update_warnings_test2() { // No fields are given in a record update emit warnings assert_warning!( " pub type Person { Person(name: String, age: Int) } pub fn update_person() { let past = Person(\"Quinn\", 27) let present = Person(..past) present }" ); } #[test] fn record_update_warnings_test3() { // All fields given in a record update emits warnings assert_warning!( " pub type Person { Person(name: String, age: Int) } pub fn update_person() { let past = Person(\"Quinn\", 27) let present = Person(..past, name: \"Quinn\", age: 28) present }" ); } #[test] fn unused_private_type_warnings_test() { // External type assert_warning!("type X"); } #[test] fn unused_private_type_warnings_test2() { assert_no_warnings!("pub type Y"); } #[test] fn unused_private_type_warnings_test3() { // Type alias assert_warning!("type X = Int"); } #[test] fn unused_private_type_warnings_test4() { assert_no_warnings!("pub type Y = Int"); } #[test] fn unused_private_type_warnings_test5() { assert_no_warnings!("type Y = Int pub fn run(x: Y) { x }"); } #[test] fn unused_private_type_warnings_test6() { // Custom type assert_warning!("type X { X }"); } #[test] fn unused_private_type_warnings_test7() { assert_no_warnings!("pub type X { X }"); } #[test] fn unused_private_type_warnings_test8() { assert_no_warnings!( " type X { X } pub fn a() { let b = X case b { X -> 1 } }" ); } #[test] fn unused_private_fn_warnings_test() { assert_warning!("fn a() { 1 }"); } #[test] fn used_private_fn_warnings_test() { assert_no_warnings!("pub fn a() { 1 }"); } #[test] fn used_private_fn_warnings_test2() { assert_no_warnings!("fn a() { 1 } pub fn b() { a }"); } #[test] fn unused_private_const_warnings_test() { assert_warning!("const a = 1"); } #[test] fn used_private_const_warnings_test() { assert_no_warnings!("pub const a = 1"); } #[test] fn used_private_const_warnings_test2() { assert_no_warnings!("const a = 1 pub fn b() { a }"); } #[test] fn unused_variable_warnings_test() { // function argument assert_warning!("pub fn a(b) { 1 }"); } #[test] fn used_variable_warnings_test() { assert_no_warnings!("pub fn a(b) { b }"); } #[test] fn unused_variable_warnings_test2() { // Simple let assert_warning!("pub fn a() { let b = 1 5 }"); } #[test] fn used_variable_warnings_test2() { assert_no_warnings!("pub fn a() { let b = 1 b }"); } #[test] fn unused_variable_shadowing_test() { assert_warning!("pub fn a() { let b = 1 let b = 2 b }"); } #[test] fn used_variable_shadowing_test() { assert_no_warnings!("pub fn a() { let b = 1 let b = b + 1 b }"); } #[test] fn unused_destructure() { // Destructure assert_warning!("pub fn a(b) { case b { #(c, _) -> 5 } }"); } #[test] fn used_destructure() { assert_no_warnings!("pub fn a(b) { case b { #(c, _) -> c } }"); } #[test] fn unused_imported_module_warnings_test() { assert_warning!( ("gleam/wibble", "pub fn wobble() { 1 }"), "import gleam/wibble" ); } #[test] fn unused_imported_module_with_alias_warnings_test() { assert_warning!( ("gleam/wibble", "pub fn wobble() { 1 }"), "import gleam/wibble as wobble" ); } // https://github.com/gleam-lang/gleam/issues/2326 #[test] fn unused_imported_module_with_alias_and_unqualified_name_warnings_test() { assert_warning!( ("thepackage", "gleam/one", "pub fn two() { 1 }"), "import gleam/one.{two} as three" ); } #[test] fn unused_imported_module_with_alias_and_unqualified_name_no_warnings_test() { assert_warning!( ("package", "gleam/one", "pub fn two() { 1 }"), "import gleam/one.{two} as three\npub fn wibble() { two() }" ); } #[test] fn unused_imported_module_no_warning_on_used_function_test() { assert_no_warnings!( ("thepackage", "gleam/wibble", "pub fn wobble() { 1 }"), "import gleam/wibble pub fn wibble() { wibble.wobble() }", ); } #[test] fn unused_imported_module_no_warning_on_used_type_test() { assert_no_warnings!( ("thepackage", "gleam/wibble", "pub type Wibble = Int"), "import gleam/wibble pub fn wibble(a: wibble.Wibble) { a }", ); } #[test] fn unused_imported_module_no_warning_on_used_unqualified_function_test() { assert_no_warnings!( ("thepackage", "gleam/wibble", "pub fn wobble() { 1 }"), "import gleam/wibble.{wobble} pub fn wibble() { wobble() }", ); } #[test] fn unused_imported_module_no_warning_on_used_unqualified_type_test() { assert_no_warnings!( ("thepackage", "gleam/wibble", "pub type Wibble = Int"), "import gleam/wibble.{type Wibble} pub fn wibble(a: Wibble) { a }", ); } // https://github.com/gleam-lang/gleam/issues/3313 #[test] fn imported_module_with_alias_no_warning_when_only_used_in_case_test() { assert_no_warnings!( ( "thepackage", "gleam/wibble", "pub type Wibble { Wibble(Int) }" ), "import gleam/wibble as f\npub fn wibble(a) { case a { f.Wibble(int) -> { int } } }", ); } #[test] fn module_access_registers_import_usage() { assert_no_warnings!( ("thepackage", "gleam/bibble", "pub const bobble = 1"), "import gleam/bibble pub fn main() { bibble.bobble }", ); } // https://github.com/gleam-lang/gleam/issues/978 #[test] fn bit_pattern_var_use() { assert_no_warnings!( " pub fn main(x) { let assert <> = x name }", ); } // https://github.com/gleam-lang/gleam/issues/989 #[test] fn alternative_case_clause_pattern_variable_usage() { assert_no_warnings!( " pub fn main(s) { case s { [a] | [a, _] -> a _ -> 0 } }" ); } // https://github.com/gleam-lang/gleam/issues/1742 #[test] fn imported_function_referenced_in_constant() { assert_no_warnings!( ("thepackage", "one", "pub fn two() { 2 }"), " import one pub const make_two = one.two " ); } // https://github.com/gleam-lang/gleam/issues/1742 #[test] fn imported_constructor_referenced_in_constant() { assert_no_warnings!( ("thepackage", "one", "pub type Two { Two(Int) }"), " import one pub const make_two = one.Two " ); } // https://github.com/gleam-lang/gleam/issues/2050 #[test] fn double_unary_integer_literal() { assert_warning!("pub fn main() { let _ = --7 }"); } #[test] fn even_number_of_multiple_integer_negations_raise_a_single_warning() { assert_warning!("pub fn main() { let _ = ----7 }"); } #[test] fn odd_number_of_multiple_integer_negations_raise_a_single_warning_that_highlights_the_unnecessary_ones() { assert_warning!("pub fn main() { let _ = -----7 }"); } #[test] fn even_number_of_multiple_bool_negations_raise_a_single_warning() { assert_warning!("pub fn main() { let _ = !!!!True }"); } #[test] fn odd_number_of_multiple_bool_negations_raise_a_single_warning_that_highlights_the_unnecessary_ones() { assert_warning!("pub fn main() { let _ = !!!!!False }"); } // https://github.com/gleam-lang/gleam/issues/2050 #[test] fn double_unary_integer_variable() { assert_warning!( r#" pub fn main() { let x = 7 let _ = --x } "# ); } // https://github.com/gleam-lang/gleam/issues/2050 #[test] fn double_unary_bool_literal() { assert_warning!("pub fn main() { let _ = !!True }"); } // https://github.com/gleam-lang/gleam/issues/2050 #[test] fn double_unary_bool_variable() { assert_warning!( r#" pub fn main() { let x = True let _ = !!x } "# ); } /// https://github.com/gleam-lang/gleam/issues/2067 #[test] fn prefer_list_is_empty_over_list_length_eq_0() { assert_warning!( ( "gleam_stdlib", "gleam/list", "pub fn length(_list: List(a)) -> Int { 0 }" ), r#" import gleam/list pub fn main() { let a_list = [] let _ = list.length(a_list) == 0 } "# ); } /// https://github.com/gleam-lang/gleam/issues/2067 #[test] fn prefer_list_is_empty_over_list_length_eq_negative_0() { assert_warning!( ( "gleam_stdlib", "gleam/list", "pub fn length(_list: List(a)) -> Int { 0 }" ), r#" import gleam/list pub fn main() { let a_list = [] let _ = list.length(a_list) == -0 } "# ); } /// https://github.com/gleam-lang/gleam/issues/2067 #[test] fn prefer_list_is_empty_over_0_eq_list_length() { assert_warning!( ( "gleam_stdlib", "gleam/list", "pub fn length(_list: List(a)) -> Int { 0 }" ), r#" import gleam/list pub fn main() { let a_list = [] let _ = 0 == list.length(a_list) } "# ); } /// https://github.com/gleam-lang/gleam/issues/2067 #[test] fn prefer_list_is_empty_over_negative_0_eq_list_length() { assert_warning!( ( "gleam_stdlib", "gleam/list", "pub fn length(_list: List(a)) -> Int { 0 }" ), r#" import gleam/list pub fn main() { let a_list = [] let _ = -0 == list.length(a_list) } "# ); } /// https://github.com/gleam-lang/gleam/issues/2067 #[test] fn prefer_list_is_empty_over_list_length_not_eq_0() { assert_warning!( ( "gleam_stdlib", "gleam/list", "pub fn length(_list: List(a)) -> Int { 0 }" ), r#" import gleam/list pub fn main() { let a_list = [] let _ = list.length(a_list) != 0 } "# ); } /// https://github.com/gleam-lang/gleam/issues/2067 #[test] fn prefer_list_is_empty_over_0_not_eq_list_length() { assert_warning!( ( "gleam_stdlib", "gleam/list", "pub fn length(_list: List(a)) -> Int { 0 }" ), r#" import gleam/list pub fn main() { let a_list = [] let _ = 0 != list.length(a_list) } "# ); } /// https://github.com/gleam-lang/gleam/issues/2067 #[test] fn prefer_list_is_empty_over_list_length_lt_eq_0() { assert_warning!( ( "gleam_stdlib", "gleam/list", "pub fn length(_list: List(a)) -> Int { 0 }" ), r#" import gleam/list pub fn main() { let a_list = [] let _ = list.length(a_list) <= 0 } "# ); } /// https://github.com/gleam-lang/gleam/issues/2067 #[test] fn prefer_list_is_empty_over_list_length_lt_1() { assert_warning!( ( "gleam_stdlib", "gleam/list", "pub fn length(_list: List(a)) -> Int { 0 }" ), r#" import gleam/list pub fn main() { let a_list = [] let _ = list.length(a_list) < 1 } "# ); } /// https://github.com/gleam-lang/gleam/issues/4861 #[test] fn prefer_list_is_empty_over_list_length_gt_negative_0() { assert_warning!( ( "gleam_stdlib", "gleam/list", "pub fn length(_list: List(a)) -> Int { 0 }" ), r#" import gleam/list pub fn main() { let a_list = [] let _ = list.length(a_list) > 0 } "# ); } /// https://github.com/gleam-lang/gleam/issues/4861 #[test] fn prefer_list_is_empty_over_negative_0_lt_list_length() { assert_warning!( ( "gleam_stdlib", "gleam/list", "pub fn length(_list: List(a)) -> Int { 0 }" ), r#" import gleam/list pub fn main() { let a_list = [] let _ = 0 < list.length(a_list) } "# ); } /// https://github.com/gleam-lang/gleam/issues/4861 #[test] fn prefer_list_is_empty_over_list_length_gt_0() { assert_warning!( ( "gleam_stdlib", "gleam/list", "pub fn length(_list: List(a)) -> Int { 0 }" ), r#" import gleam/list pub fn main() { let a_list = [] let _ = list.length(a_list) > 0 } "# ); } /// https://github.com/gleam-lang/gleam/issues/4861 #[test] fn prefer_list_is_empty_over_0_lt_list_length() { assert_warning!( ( "gleam_stdlib", "gleam/list", "pub fn length(_list: List(a)) -> Int { 0 }" ), r#" import gleam/list pub fn main() { let a_list = [] let _ = 0 < list.length(a_list) } "# ); } /// https://github.com/gleam-lang/gleam/issues/2067 #[test] fn allow_list_length_eq_1() { assert_no_warnings!( ( "gleam_stdlib", "gleam/list", "pub fn length(_list: List(a)) -> Int { 0 }" ), r#" import gleam/list pub fn main() { let a_list = [] let _ = list.length(a_list) == 1 } "# ); } /// https://github.com/gleam-lang/gleam/issues/2067 #[test] fn allow_1_eq_list_length() { assert_no_warnings!( ( "gleam_stdlib", "gleam/list", "pub fn length(_list: List(a)) -> Int { 0 }" ), r#" import gleam/list pub fn main() { let a_list = [] let _ = 1 == list.length(a_list) } "# ); } /// https://github.com/gleam-lang/gleam/issues/2067 #[test] fn allow_list_length_eq_3() { assert_no_warnings!( ( "gleam_stdlib", "gleam/list", "pub fn length(_list: List(a)) -> Int { 0 }" ), r#" import gleam/list pub fn main() { let a_list = [] let _ = list.length(a_list) == 3 } "# ); } /// https://github.com/gleam-lang/gleam/issues/2067 #[test] fn allow_1_lt_list_length() { assert_no_warnings!( ( "gleam_stdlib", "gleam/list", "pub fn length(_list: List(a)) -> Int { 0 }" ), r#" import gleam/list pub fn main() { let a_list = [] let _ = 1 < list.length(a_list) } "# ); } /// https://github.com/gleam-lang/gleam/issues/2067 #[test] fn allow_list_length_gt_1() { assert_no_warnings!( ( "gleam_stdlib", "gleam/list", "pub fn length(_list: List(a)) -> Int { 0 }" ), r#" import gleam/list pub fn main() { let a_list = [] let _ = list.length(a_list) > 1 } "# ); } #[test] fn unused_external_function_arguments() { // https://github.com/gleam-lang/gleam/issues/2259 assert_no_warnings!( r#" @external(erlang, "go", "go") pub fn go(a: item_a) -> Nil "#, ); } #[test] fn importing_non_direct_dep_package() { // Warn if an imported module is from a package that is not a direct dependency assert_warning!( // Magic string package name that the test setup will detect to not // register this package as a dep. ("non-dependency-package", "some_module", "pub const x = 1"), r#" import some_module pub const x = some_module.x "# ); } #[test] fn deprecated_constant() { assert_warning!( r#" @deprecated("Don't use this!") pub const a = Nil pub fn b() { a } "# ); } #[test] fn deprecated_imported_constant() { assert_warning!( ( "package", "module", r#"@deprecated("Don't use this!") pub const a = Nil"# ), r#" import module pub fn a() { module.a } "# ); } #[test] fn deprecated_imported_unqualified_constant() { assert_warning!( ( "package", "module", r#"@deprecated("Don't use this!") pub const a = Nil"# ), r#" import module.{a} pub fn b() { a } "# ); } #[test] fn deprecated_function() { assert_warning!( r#" @deprecated("Don't use this!") pub fn a() { Nil } pub fn b() { a } "# ); } #[test] fn deprecated_imported_function() { assert_warning!( ( "package", "module", r#"@deprecated("Don't use this!") pub fn a() { Nil }"# ), r#" import module pub fn a() { module.a } "# ); } #[test] fn deprecated_imported_call_function() { assert_warning!( ( "package", "module", r#"@deprecated("Don't use this!") pub fn a() { Nil }"# ), r#" import module pub fn a() { module.a() } "# ); } #[test] fn deprecated_imported_unqualified_function() { assert_warning!( ( "package", "module", r#"@deprecated("Don't use this!") pub fn a() { Nil }"# ), r#" import module.{a} pub fn b() { a } "# ); } #[test] fn deprecated_type_used_in_alias() { assert_warning!( r#" @deprecated("Don't use this!") pub type Cat { Cat(name: String) } pub type Dog = Cat "# ); } #[test] fn deprecated_type_used_as_arg() { assert_warning!( r#" @deprecated("Don't use this!") pub type Cat { Cat(name: String) } pub fn cat_name(cat: Cat) { cat.name } "# ); } #[test] fn deprecated_type_used_as_case_clause() { assert_warning!( r#" @deprecated("The type Animal has been deprecated.") pub type Animal { Cat Dog } pub fn sound(animal) -> String { case animal { Dog -> "Woof" Cat -> "Meow" } } pub fn main(){ let cat = Cat sound(cat) } "# ); } #[test] fn const_bytes_option() { assert_no_warnings!("pub const x = <<<<>>:bits>>"); } #[test] fn unused_module_wuth_alias_warning_test() { assert_warning!( ("gleam/wibble", "pub const one = 1"), "import gleam/wibble as wobble" ); } #[test] fn unused_alias_warning_test() { assert_warning!( ("gleam/wibble", "pub const one = 1"), r#" import gleam/wibble.{one} as wobble pub const one = one "#, ); } #[test] fn used_type_with_import_alias_no_warning_test() { assert_no_warnings!( ("gleam", "gleam/wibble", "pub const one = 1"), "import gleam/wibble as _wobble" ); } #[test] fn discarded_module_no_warnings_test() { assert_no_warnings!( ("gleam", "wibble", "pub const one = 1"), "import wibble as _wobble" ); } #[test] fn unused_alias_for_duplicate_module_no_warning_for_alias_test() { assert_warning!( ("a/wibble", "pub const one = 1"), ("b/wibble", "pub const two = 2"), r#" import a/wibble import b/wibble as wobble pub const one = wibble.one "#, ); } #[test] fn result_in_case_discarded() { assert_warning!( " pub fn main(x) { case x { _ -> Error(Nil) } Nil }" ); } #[test] fn pattern_matching_on_literal_tuple() { assert_warning!( "pub fn main() { case #(1, 2) { _ -> Nil } }" ); } #[test] fn pattern_matching_on_multiple_literal_tuples() { assert_warning!( "pub fn main() { let wibble = 1 case #(1, 2), #(wibble, wibble) { _, _ -> Nil } }" ); } #[test] fn pattern_matching_on_tuples_doesnt_raise_a_warning() { assert_no_warnings!( "pub fn main() { let wibble = #(1, 2) // This doesn't raise a warning since `wibble` is not a literal tuple. case wibble { _ -> Nil } }" ); } #[test] fn pattern_matching_on_literal_empty_tuple() { assert_warning!( "pub fn main() { case #() { _ -> Nil } }" ); } #[test] fn pattern_matching_on_literal_list() { assert_warning!( "pub fn main() { case [1, 2] { _ -> Nil } }" ); } #[test] fn pattern_matching_on_literal_list_with_tail() { assert_warning!( "pub fn main() { case [1, 2, ..[]] { _ -> Nil } }" ); } #[test] fn pattern_matching_on_literal_empty_list() { assert_warning!( "pub fn main() { case [] { _ -> Nil } }" ); } #[test] fn pattern_matching_on_literal_empty_bit_array() { assert_warning!( "pub fn main() { case <<>> { _ -> Nil } }" ); } #[test] fn pattern_matching_on_literal_record() { assert_warning!( " pub type Wibble { Wibble(Int) } pub fn main() { let n = 1 case Wibble(n) { _ -> Nil } }" ); } #[test] fn pattern_matching_on_literal_record_with_no_args() { assert_warning!( " pub type Wibble { Wibble } pub fn main() { case Wibble { _ -> Nil } }" ); } #[test] fn pattern_matching_on_literal_int() { assert_warning!( " pub type Wibble { Wibble } pub fn main() { case 1 { _ -> Nil } }" ); } #[test] fn pattern_matching_on_literal_float() { assert_warning!( " pub type Wibble { Wibble } pub fn main() { case 1.0 { _ -> Nil } }" ); } #[test] fn pattern_matching_on_literal_string() { assert_warning!( " pub type Wibble { Wibble } pub fn main() { case \"hello\" { _ -> Nil } }" ); } #[test] fn opaque_external_type_raises_a_warning() { assert_warning!("pub opaque type External"); } #[test] fn unused_binary_operation_raises_a_warning() { assert_warning!( r#" pub fn main() { let string = "a" <> "b" "c" <> "d" string } "# ); } #[test] fn unused_record_access_raises_a_warning() { assert_warning!( r#" pub type Thing { Thing(value: Int) } pub fn main() { let thing = Thing(1) thing.value 1 } "# ); } #[test] fn unused_record_constructor_raises_a_warning() { assert_warning!( r#" pub type Thing { Thing(value: Int) } pub fn main() { Thing(1) 1 } "# ); } #[test] fn unused_record_update_raises_a_warning() { assert_warning!( r#" pub type Thing { Thing(value: Int, other: Int) } pub fn main() { let thing = Thing(1, 2) Thing(..thing, value: 1) 1 } "# ); } #[test] fn unused_variable_raises_a_warning() { assert_warning!( r#" pub fn main() { let number = 1 number 1 } "# ); } #[test] fn unused_function_literal_raises_a_warning() { assert_warning!( r#" pub fn main() { fn(n) { n + 1 } 1 } "# ); } #[test] fn unused_tuple_index_raises_a_warning() { assert_warning!( r#" pub fn main() { #(1, 2).0 1 } "# ); } #[test] fn unused_bool_negation_raises_a_warning() { assert_warning!( r#" pub fn main() { !True 1 } "# ); } #[test] fn unused_int_negation_raises_a_warning() { assert_warning!( r#" pub fn main() { -1 1 } "# ); } #[test] fn unused_pipeline_ending_with_variant_raises_a_warning() { assert_warning!( r#" pub type Wibble(a) { Wibble(a) } pub fn wibble(a) { a } pub fn main() { 1 |> wibble |> Wibble 1 } "# ); } #[test] fn unused_pipeline_ending_with_variant_raises_a_warning_2() { assert_warning!( ("wibble", "pub type Wibble { Wibble(Int) }"), r#" import wibble pub fn wobble(a) { a } pub fn main() { 1 |> wobble |> wibble.Wibble 1 } "# ); } #[test] fn unused_pipeline_not_ending_with_variant_raises_no_warnings() { assert_no_warnings!( r#" pub type Wibble(a) { Wibble(a) } pub fn wibble(a) { echo a } pub fn main() { 1 |> wibble |> wibble 1 } "# ); } #[test] fn unused_module_select_constructor() { assert_warning!( ("wibble", "pub type Wibble { Wibble(Int) }"), r#" import wibble pub fn main() { wibble.Wibble 1 } "# ); } #[test] fn unused_module_select_constructor_call() { assert_warning!( ("wibble", "pub type Wibble { Wibble(Int) }"), r#" import wibble pub fn main() { wibble.Wibble(1) 1 } "# ); } #[test] fn unused_module_select_function() { assert_warning!( ("wibble", "pub fn println(a) { Nil }"), r#" import wibble pub fn main() { wibble.println 1 } "# ); } #[test] fn unused_module_select_const() { assert_warning!( ("wibble", "pub const a = 1"), r#" import wibble pub fn main() { wibble.a 1 } "# ); } #[test] fn module_used_by_unused_function_is_not_marked_as_unused() { assert_warning!( ("wibble", "pub const a = 1"), "import wibble fn wobble() { wibble.a } " ); } #[test] fn aliased_module_used_by_unused_function_is_not_marked_as_unused() { assert_warning!( ("wibble", "pub const a = 1"), "import wibble as woo fn wobble() { woo.a } " ); } #[test] fn calling_function_from_other_module_is_not_marked_unused() { assert_no_warnings!( ("wibble", "wibble", "pub fn println(a) { panic }"), r#" import wibble pub fn main() { wibble.println("hello!") 1 } "# ); } /* TODO: These tests are commented out until we figure out a better way to deal with reexports of internal types and reintroduce the warning. As things stand it would break both Lustre and Mist. You can see the thread starting around here for more context: https://discord.com/channels/768594524158427167/768594524158427170/1227250677734969386 #[test] fn internal_type_in_public_function_return() { assert_warning!( " @internal pub type Wibble { Wibble } pub fn wibble() -> Wibble { Wibble } " ); } #[test] fn type_from_internal_module_in_public_function_return() { assert_warning!( ("thepackage/internal", "pub type Wibble { Wibble }"), " import thepackage/internal.{type Wibble, Wibble} pub fn wibble() -> Wibble { Wibble }" ); } #[test] fn internal_type_in_public_function_argument() { assert_warning!( " @internal pub type Wibble { Wibble } pub fn wibble(_wibble: Wibble) -> Int { 1 } " ); } #[test] fn type_from_internal_module_in_public_function_argument() { assert_warning!( ("thepackage/internal", "pub type Wibble { Wibble }"), " import thepackage/internal.{type Wibble} pub fn wibble(_wibble: Wibble) -> Int { 1 } " ); } #[test] fn internal_type_in_public_constructor() { assert_warning!( " @internal pub type Wibble { Wibble } pub type Wobble { Wobble(Wibble) } " ); } #[test] fn type_from_internal_module_in_public_constructor() { assert_warning!( ("thepackage/internal", "pub type Wibble { Wibble }"), " import thepackage/internal.{type Wibble} pub type Wobble { Wobble(Wibble) }" ); } #[test] fn type_from_internal_module_dependency_in_public_constructor() { assert_warning!( ("dep", "dep/internal", "pub type Wibble { Wibble }"), " import dep/internal.{type Wibble} pub type Wobble { Wobble(Wibble) }" ); } */ #[test] fn redundant_let_assert() { assert_warning!( " pub fn main() { let assert wibble = [1, 2, 3] wibble } " ); } #[test] fn redundant_let_assert_on_custom_type() { assert_warning!( " pub type Wibble { Wibble(Int, Bool) } pub fn main() { let assert Wibble(_, bool) = Wibble(1, True) bool } " ); } #[test] fn panic_used_as_function() { assert_warning!( "pub fn main() { panic() }" ); } #[test] fn panic_used_as_function_2() { assert_warning!( "pub fn main() { panic(1) }" ); } #[test] fn panic_used_as_function_3() { assert_warning!( "pub fn main() { panic(1, Nil) }" ); } #[test] fn todo_used_as_function() { assert_warning!( "pub fn main() { todo() }" ); } #[test] fn todo_used_as_function_2() { assert_warning!( "pub fn main() { todo(1) }" ); } #[test] fn todo_used_as_function_3() { assert_warning!( "pub fn main() { todo(1, Nil) }" ); } #[test] fn unreachable_warning_1() { assert_warning!( "pub fn main() { panic 1 }" ); } #[test] fn unreachable_warning_2() { assert_warning!( "pub fn main() { let _ = panic 1 }" ); } #[test] fn unreachable_warning_if_all_branches_panic() { assert_warning!( "pub fn main() { let n = 1 case n { 0 -> panic _ -> panic } 1 }" ); } #[test] fn unreachable_warning_if_all_branches_panic_2() { assert_warning!( "pub fn main() { let n = 1 case n { 0 -> { panic 2 } _ -> panic } 1 }" ); } #[test] fn no_unreachable_warning_if_at_least_a_branch_is_reachable() { assert_no_warnings!( "pub fn main() { let n = 1 case n { 0 -> panic _ -> 1 } 1 }" ); } #[test] fn unreachable_warning_doesnt_escape_out_of_a_block_if_panic_is_not_last() { assert_warning!( "pub fn main() { let n = { panic 1 } n }" ); } #[test] fn unreachable_warning_on_following_expression_if_panic_is_last_in_a_block() { assert_warning!( "pub fn main() { let _ = { panic } 1 }" ); } #[test] fn unreachable_function_argument_if_panic_is_argument() { assert_warning!( " pub fn wibble(_, _) { 1 } pub fn main() { wibble(panic, 1) }" ); } #[test] fn unreachable_function_call_if_panic_is_last_argument_1() { assert_warning!( " pub fn wibble(_, _) { 1 } pub fn main() { wibble(1, panic) 1 }" ); } #[test] fn unreachable_function_call_if_panic_is_last_argument_2() { assert_warning!( " pub fn wibble(_, _) { 1 } pub fn main() { wibble(1, panic) }" ); } #[test] fn no_unreachable_warning_if_panic_comes_last_in_function_body() { assert_no_warnings!( " pub fn wibble() { panic } pub fn main() { panic }" ); } #[test] fn unreachable_code_for_panic_as_first_pipeline_item() { assert_warning!( " pub fn wibble(_) { 1 } pub fn main() { panic |> wibble } " ); } #[test] fn panic_used_as_function_inside_pipeline() { assert_warning!( " pub fn wibble(_) { 1 } pub fn main() { 1 |> panic |> wibble } " ); } #[test] fn unreachable_warning_for_panic_as_last_item_of_pipe_on_next_expression() { assert_warning!( r#" pub fn wibble(_) { 1 } pub fn main() { 1 |> wibble |> panic "unreachable" } "# ); } #[test] fn doesnt_warn_twice_for_unreachable_code_if_has_already_warned_in_a_block_1() { assert_warning!( r#" pub fn wibble(_) { 1 } pub fn main() { panic let _ = "unreachable" // warning here panic "no warning here!" } "# ); } #[test] fn doesnt_warn_twice_for_unreachable_code_if_has_already_warned_in_a_block_2() { assert_warning!( r#" pub fn main() { let _ = { panic 1 // warning here } "no warning here!" } "# ); } #[test] fn unreachable_use_after_panic() { assert_warning!( r#" pub fn wibble(_) { 1 } pub fn main() { panic use <- wibble 1 } "# ); } #[test] fn unreachable_code_after_case_subject_panics_1() { assert_warning!( r#" pub fn main(a, b) { case a, panic, b { _, _, _ -> "no warning here!" } } "# ); } #[test] fn unreachable_code_after_case_subject_panics_2() { assert_warning!( r#" pub fn main(a, b) { case a, b, panic { _, _, _ -> "no warning here!" } "warning here!" } "# ); } #[test] fn unreachable_code_analysis_treats_anonymous_functions_independently_1() { assert_no_warnings!( r#" pub fn main() { let _ = fn() { panic } "no warning here!" } "# ); } #[test] fn unreachable_code_analysis_treats_anonymous_functions_independently_2() { assert_warning!( r#" pub fn main() { let _ = fn() { panic "warning here!" } panic "warning here!" } "# ); } #[test] fn unreachable_code_analysis_treats_anonymous_functions_independently_3() { assert_warning!( r#" pub fn main() { panic let _ = "warning here!" let _ = fn() { panic "warning here!" } } "# ); } #[test] fn no_warnings_for_matches_used_like_ifs() { assert_no_warnings!( r#" pub fn main() { case True { _ if True -> 1 _ -> 2 } } "# ); } #[test] fn no_warnings_for_matches_used_like_ifs_2() { assert_no_warnings!( r#" pub fn main() { case 1 { _ if True -> 1 _ -> 2 } } "# ); } #[test] fn warnings_for_matches_on_literal_values_that_are_not_like_an_if_1() { assert_warning!( r#" pub fn main() { case True { _ -> 1 } } "# ); } #[test] fn warnings_for_matches_on_literal_values_that_are_not_like_an_if_2() { assert_warning!( r#" pub fn main() { case True { True -> 1 } } "# ); } #[test] fn redundant_function_capture_in_pipe_1() { assert_warning!( " pub fn wibble(_, _) { 1 } pub fn main() { 1 |> wibble(_, 2) |> wibble(2) } " ); } #[test] fn redundant_function_capture_in_pipe_2() { assert_warning!( " pub fn wobble(_) { 1 } pub fn main() { 1 |> wobble(_) |> wobble } " ); } #[test] fn redundant_function_capture_in_pipe_3() { assert_warning!( " pub fn wobble(_) { 1 } pub fn main() { 1 |> wobble |> wobble(_) } " ); } #[test] fn redundant_function_capture_in_pipe_4() { assert_warning!( " pub fn wibble(_, _) { 1 } pub fn main() { 1 |> wibble(2) |> wibble(_, 2) } " ); } #[test] fn redundant_function_capture_in_pipe_5() { assert_no_warnings!( " pub fn wibble(_, _) { 1 } pub fn main() { 1 |> wibble(2, _) } " ); } #[test] fn deprecated_list_append_syntax() { assert_warning!( r#" pub fn main() { let letters = ["b", "c"] ["a"..letters] } "# ); } #[test] fn deprecated_list_pattern_syntax() { assert_warning!( r#" pub fn main() { let letters = ["b", "c"] case letters { ["a"..rest] -> rest _ -> [] } } "# ); } // https://github.com/gleam-lang/gleam/issues/3383 #[test] fn deprecated_list_pattern_syntax_1() { assert_warning!( r#" pub fn main() { let letters = ["b", "c"] case letters { [] -> [] [..] -> [] } } "# ); } // https://github.com/gleam-lang/gleam/issues/3473 #[test] fn deprecated_record_pattern_syntax() { assert_warning!( r#" pub type Wibble { Wibble(one: Int, two: Int) } pub fn main() { let wibble = Wibble(one: 1, two: 2) case wibble { Wibble(one: one ..) -> one } } "# ); } #[test] fn deprecated_record_pattern_syntax_with_no_labels() { assert_warning!( r#" pub type Wibble { Wibble(one: Int, two: Int) } pub fn main() { let wibble = Wibble(one: 1, two: 2) case wibble { Wibble(one ..) -> one } } "# ); } #[test] fn deprecated_record_pattern_syntax_with_label_shorthand() { assert_warning!( r#" pub type Wibble { Wibble(one: Int, two: Int) } pub fn main() { let wibble = Wibble(one: 1, two: 2) case wibble { Wibble(one: ..) -> one } } "# ); } #[test] fn deprecated_record_pattern_syntax_has_no_warning_if_everything_is_discarded() { assert_no_warnings!( r#" pub type Wibble { Wibble(one: Int, two: Int) } pub fn main() { let wibble = Wibble(one: 1, two: 2) case wibble { Wibble(..) -> 1 } } "# ); } #[test] fn deprecated_record_pattern_syntax_has_no_warning_if_there_is_a_comma_before_spread() { assert_no_warnings!( r#" pub type Wibble { Wibble(one: Int, two: Int) } pub fn main() { let wibble = Wibble(one: 1, two: 2) case wibble { Wibble(one: one, ..) -> one } } "# ); } #[test] fn unused_label_shorthand_pattern_arg() { assert_warning!( r#" pub type Wibble { Wibble(arg1: Int, arg2: Bool ) } pub fn main() { let Wibble(arg1:, arg2:) = Wibble(1, True) arg1 } "# ); } #[test] fn unused_label_shorthand_pattern_arg_shadowing() { assert_warning!( r#" pub type Wibble { Wibble(arg1: Int, arg2: Bool ) } pub fn main() { let Wibble(arg1:, arg2:) = Wibble(1, True) let arg1 = False arg1 } "# ); } #[test] fn internal_annotation_on_constant_requires_v1_1() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " @internal pub const wibble = 1 ", ); } #[test] fn internal_annotation_on_type_requires_v1_1() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " @internal pub type Wibble ", ); } #[test] fn internal_annotation_on_function_requires_v1_1() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " @internal pub fn wibble() { Nil } ", ); } #[test] fn nested_tuple_access_requires_v1_1() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " pub fn main() { let tuple = #(1, #(1, 1)) tuple.1.0 } ", ); } #[test] fn javascript_external_module_with_at_requires_v1_2() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " @external(javascript, \"module@module\", \"func\") pub fn main() { Nil } ", ); } #[test] fn int_plus_in_guards_requires_v1_3() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " pub fn main() { case Nil { _ if 1 + 1 == 2 -> Nil _ -> Nil } } ", ); } #[test] fn float_plus_in_guards_requires_v1_3() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " pub fn main() { case Nil { _ if 1.0 +. 1.0 == 2.0 -> Nil _ -> Nil } } ", ); } #[test] fn int_minus_in_guards_requires_v1_3() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " pub fn main() { case Nil { _ if 1 - 1 == 0 -> Nil _ -> Nil } } ", ); } #[test] fn float_minus_in_guards_requires_v1_3() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " pub fn main() { case Nil { _ if 1.0 -. 1.0 == 0.0 -> Nil _ -> Nil } } ", ); } #[test] fn int_multiplication_in_guards_requires_v1_3() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " pub fn main() { case Nil { _ if 1 * 1 == 0 -> Nil _ -> Nil } } ", ); } #[test] fn float_multiplication_in_guards_requires_v1_3() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " pub fn main() { case Nil { _ if 1.0 *. 1.0 == 0.0 -> Nil _ -> Nil } } ", ); } #[test] fn int_divide_in_guards_requires_v1_3() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " pub fn main() { case Nil { _ if 1 / 1 == 0 -> Nil _ -> Nil } } ", ); } #[test] fn float_divide_in_guards_requires_v1_3() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " pub fn main() { case Nil { _ if 1.0 /. 1.0 == 0.0 -> Nil _ -> Nil } } ", ); } #[test] fn int_remainder_in_guards_requires_v1_3() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " pub fn main() { case Nil { _ if 1 % 1 == 0 -> Nil _ -> Nil } } ", ); } #[test] fn label_shorthand_in_constand_requires_v1_4() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " pub type Wibble { Wibble(wibble: Int) } pub const wibble = 1 pub const wobble = Wibble(wibble:) ", ); } #[test] fn label_shorthand_in_call_requires_v1_4() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " pub type Wibble { Wibble(wibble: Int) } pub fn main() { let wibble = 1 Wibble(wibble:) } ", ); } #[test] fn label_shorthand_in_pattern_requires_v1_4() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " pub type Wibble { Wibble(wibble: Int) } pub fn main(wibble) { case wibble { Wibble(wibble:) -> wibble } } ", ); } #[test] fn constant_string_concatenation_requires_v1_4() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), "pub const string = \"wibble\" <> \"wobble\"" ); } #[test] fn missing_utf_8_option_in_bit_array_segment_requires_v1_5() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " pub fn main() { <<\"hello\">> } ", ); } #[test] fn missing_utf_8_option_in_bit_array_constant_segment_requires_v1_5() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), "pub const bits = <<\"hello\">>" ); } #[test] fn missing_utf_8_option_in_bit_array_pattern_segment_requires_v1_5() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " pub fn main(a) { case a { <<\"hello\">> -> Nil _ -> Nil } } ", ); } #[test] fn missing_float_option_in_bit_array_segment_requires_v1_10() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " pub fn main() { <<1.2>> } ", ); } #[test] fn missing_float_option_in_bit_array_constant_segment_requires_v1_10() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), "pub const bits = <<1.2>>" ); } #[test] fn missing_float_option_in_bit_array_pattern_segment_requires_v1_10() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " pub fn main(a) { case a { <<1.2>> -> Nil _ -> Nil } } ", ); } #[test] fn record_update_variant_inference_requires_v1_6() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " pub type Wibble { Wibble(a: Int, b: Int) Wobble(a: Int, c: Int) } pub fn main(wibble) { case wibble { Wibble(..) -> Wibble(..wibble, b: 10) Wobble(..) -> panic } } ", ); } #[test] fn record_access_variant_inference_requires_v1_6() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " pub type Wibble { Wibble(a: Int, b: Int) Wobble(a: Int, c: Int) } pub fn main(wibble) { case wibble { Wibble(..) -> wibble.b Wobble(..) -> wibble.c } } ", ); } #[test] fn let_assert_with_message_requires_v1_7() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), r#" pub fn main() { let assert Ok(10) = Ok(20) as "This will crash..." } "#, ); } #[test] fn bool_assert_requires_v1_11() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " pub fn go(x) { assert x == 2 } ", ); } #[test] fn javascript_unsafe_int_decimal() { assert_js_warning!( r#" pub fn go() { [ 9_007_199_254_740_990, 9_007_199_254_740_991, 9_007_199_254_740_992, -9_007_199_254_740_990, -9_007_199_254_740_991, -9_007_199_254_740_992, ] } "# ); } #[test] fn javascript_unsafe_int_binary() { assert_js_warning!( r#" pub fn go() { [ 0b11111111111111111111111111111111111111111111111111110, 0b11111111111111111111111111111111111111111111111111111, 0b100000000000000000000000000000000000000000000000000000, ] } "# ); } #[test] fn javascript_unsafe_int_octal() { assert_js_warning!( r#" pub fn go() { [ 0o377777777777777776, 0o377777777777777777, 0o400000000000000000, ] } "# ); } #[test] fn javascript_unsafe_int_hex() { assert_js_warning!( r#" pub fn go() { [ 0x1FFFFFFFFFFFFE, 0x1FFFFFFFFFFFFF, 0x20000000000000, ] } "# ); } #[test] fn javascript_unsafe_int_in_tuple() { assert_js_warning!( r#" pub fn go() { #(9_007_199_254_740_992) } "# ); } #[test] fn javascript_unsafe_int_segment_in_bit_array() { assert_js_warning!( r#" pub fn go() { <<9_007_199_254_740_992:64>> } "# ); } #[test] fn javascript_unsafe_int_segment_size_in_bit_array() { assert_js_warning!( r#" pub fn go() { [ <<0:9_007_199_254_740_992>>, <<0:size(9_007_199_254_740_992)>>, ] } "# ); } #[test] fn javascript_unsafe_int_in_const() { assert_js_warning!(r#"pub const i = 9_007_199_254_740_992"#); } #[test] fn javascript_unsafe_int_in_const_tuple() { assert_js_warning!(r#"pub const i = #(9_007_199_254_740_992)"#); } #[test] fn javascript_unsafe_int_segment_in_const_bit_array() { assert_js_warning!( r#" pub const i = <<9_007_199_254_740_992:64>> "# ); } #[test] fn javascript_unsafe_int_segment_size_in_const_bit_array() { assert_js_warning!( r#" pub const ints = [ <<0:9_007_199_254_740_992>>, <<0:size(9_007_199_254_740_992)>>, ] "# ); } #[test] fn javascript_unsafe_int_in_pattern() { assert_js_warning!( r#" pub fn go() { let assert <<9_007_199_254_740_992:64>> = <<>> } "# ); } #[test] fn javascript_unsafe_int_segment_size_in_pattern() { assert_js_warning!( r#" pub fn go() { let assert <<0:9_007_199_254_740_992>> = <<>> } "# ); } #[test] fn javascript_unsafe_int_with_external_implementation() { assert_js_no_warnings!( r#" @external(javascript, "./test.mjs", "go") pub fn go() -> Int { 9_007_199_254_740_992 } "# ); } #[test] fn javascript_unsafe_int_segment_in_pattern_with_external_implementation() { assert_js_no_warnings!( r#" @external(javascript, "./test.mjs", "go") pub fn go(b: BitArray) -> BitArray { let assert <<0xFFF0000000000000:64>> = b } "# ); } #[test] fn javascript_unsafe_int_with_external_function_call() { assert_js_warning!( r#" pub fn main() { helper() + 9_007_199_254_740_992 } @external(javascript, "a", "b") fn helper() -> Int "# ); } #[test] fn incomplete_code_block_raises_warning() { assert_warning!( r#" pub fn main() { {} } "# ); } #[test] fn deprecated_target_shorthand_erlang() { assert_warning!( " @target(erl) pub fn wibble() { panic } " ); } #[test] fn deprecated_target_shorthand_javascript() { assert_warning!( " @target(js) pub fn wibble() { panic } " ); } #[test] fn unused_block_wrapping_pure_expressions() { assert_warning!( r#" pub fn main() { { True 1 } Nil } "# ); } #[test] fn unused_block_wrapping_pure_expression() { assert_warning!( r#" pub fn main() { { 1 } Nil } "# ); } #[test] fn unused_block_wrapping_impure_expressions_is_not_reported_as_pure() { assert_no_warnings!( r#" pub fn main() { { wibble() 1 } Nil } fn wibble() { panic } "# ); } #[test] fn unused_case_expression() { assert_warning!( r#" pub fn main() { let a = 1 case a { 1 -> a _ -> 12 } Nil } "# ); } #[test] fn impure_case_expression_is_not_marked_as_unused() { assert_no_warnings!( r#" pub fn main() { let a = 1 case a { 1 -> wibble() _ -> 12 } Nil } fn wibble() { panic } "# ); } #[test] fn impure_case_expression_is_not_marked_as_unused_2() { assert_no_warnings!( r#" pub fn main() { let a = 1 case wibble() { 1 -> a _ -> 12 } Nil } fn wibble() { panic } "# ); } #[test] fn unused_fn_function_call() { assert_warning!( r#" pub fn main() { fn(a) { a + 1 }(1) Nil } "# ); } #[test] fn impure_fn_function_call_not_mark_as_unused() { assert_no_warnings!( r#" pub fn main() { fn(_) { panic }(1) Nil } "# ); } #[test] fn unused_pipeline_ending_with_pure_fn() { assert_warning!( r#" pub fn main() { 1 |> fn(n) { n + 1 } Nil } "# ); } #[test] fn unused_pipeline_ending_with_impure_fn() { assert_no_warnings!( r#" pub fn main() { 1 |> fn(_) { panic } Nil } "# ); } #[test] fn pipeline_with_regular_function_call_is_never_marked_unused() { assert_no_warnings!( r#" pub fn main() { 1 |> wibble Nil } fn wibble(n) { echo n } "# ); } #[test] fn use_with_pure_fn_expression_is_marked_as_unused() { assert_warning!( r#" pub fn main() { { use _ <- fn(a) { a } 1 } Nil } "# ); } #[test] fn use_statement_calling_regular_function_is_never_marked_unused() { assert_no_warnings!( r#" pub fn main() { { use _ <- each([1, 2, 3]) 1 } Nil } fn each(list, _fun) { echo list } "# ); } #[test] // https://github.com/gleam-lang/gleam/issues/3425 fn unused_variable_assignment_pattern() { assert_warning!( " type Wibble { Wibble(a: Int, b: Int) } pub fn main() { let Wibble(a:, ..) as w = Wibble(1, 2) a } " ); } #[test] fn unused_variable_string_prefix_pattern() { assert_warning!( r#" pub fn main() { let assert "hello" as hello <> rest = "hello, world" rest } "# ); } #[test] fn unused_variable_string_prefix_pattern2() { assert_warning!( r#" pub fn main() { let assert "hello" as hello <> rest = "hello, world" hello } "# ); } #[test] fn echo_followed_by_panic() { assert_warning!( " pub fn main() { echo panic } " ); } #[test] fn echo_followed_by_panicking_expression() { assert_warning!( " pub fn main(a) { echo case a { 1 -> panic _ -> [1, panic] } } " ); } #[test] fn assert_on_inferred_variant() { assert_warning!( " type Wibble { Wibble(w: Int) Wobble(w: String) } pub fn main() { let assert Wobble(w) = Wibble(10) w } " ); } #[test] fn bit_array_truncated_segment() { assert_warning!( " pub fn main() { <<12:size(1)>> } " ); } #[test] fn unused_pure_function() { assert_warning!( " fn add(a, b) { a + b } pub fn main() { add(1, 2) Nil } " ); } #[test] fn bit_array_truncated_segment_in_bytes() { assert_warning!( " pub fn main() { <<258:size(8)>> } " ); } #[test] fn unused_pure_function_that_calls_other_pure_function() { assert_warning!( " fn sub(a, b) { add(a, -b) } fn add(a, b) { a + b } pub fn main() { sub(1, 2) Nil } " ); } #[test] fn bit_array_truncated_segment_in_bytes_2() { assert_warning!( " pub fn main() { <<65_537:size(2)-unit(8)>> } " ); } #[test] fn bit_array_truncated_segment_in_range() { assert_no_warnings!( " pub fn main() { <<255>> } " ); } #[test] fn function_is_impure_if_external() { assert_no_warnings!( r#" @external(erlang, "maths", "add") fn add(a: Int, b: Int) -> Int pub fn main() { add(1, 2) Nil } "# ); } #[test] fn bit_array_truncated_segment_in_range_2() { assert_no_warnings!( " pub fn main() { <<0>> } " ); } #[test] fn function_is_impure_if_uses_echo() { assert_no_warnings!( r#" fn add(a: Int, b: Int) -> Int { echo a + b } pub fn main() { add(1, 2) Nil } "# ); } #[test] fn bit_array_negative_truncated_segment() { assert_warning!( " pub fn main() { // -5 in 2's complement is 1111...111011 // so if we truncate it to its first 3 bits we // get 011, which is positive 3! <<-5:size(3)>> } " ); } #[test] fn bit_array_negative_truncated_segment_2() { assert_warning!( " pub fn main() { <<-200:size(8)>> } " ); } #[test] fn bit_array_negative_truncated_segment_in_range() { assert_no_warnings!( " pub fn main() { <<-128>> } " ); } #[test] fn function_is_impure_if_uses_panic() { assert_no_warnings!( r#" fn add(a: Int, b: Int) -> Int { case a + b { 0 -> panic as "Cannot add to zero" x -> x } } pub fn main() { add(1, 2) Nil } "# ); } #[test] fn function_is_impure_if_uses_todo() { // We have to use `assert_warning` here instead of `assert_no_warnings`, because // `todo` will always emit a warning. However, that should be the only warning; // there should not be an unused function warning. assert_warning!( r#" fn add(a: Int, b: Int) -> Int { case a + b { 0 -> todo as "Handle zero" x -> x } } pub fn main() { add(1, 2) Nil } "# ); } #[test] fn function_is_impure_if_uses_let_assert() { assert_no_warnings!( r#" fn assert_ok(x: Result(a, b)) -> a { let assert Ok(x) = x x } pub fn main() { assert_ok(Ok(10)) Nil } "# ); } #[test] fn function_is_impure_if_uses_assert() { assert_no_warnings!( r#" fn assert_equal(a, b) { assert a == b } pub fn main() { assert_equal(1, 2) Nil } "# ); } #[test] fn function_is_impure_if_call_impure_function() { assert_no_warnings!( r#" @external(erlang, "erlang", "something") fn impure() -> Nil fn add(a: Int, b: Int) -> Int { impure() a + b } pub fn main() { add(1, 2) Nil } "# ); } #[test] fn pure_pipeline_raises_warning() { assert_warning!( " fn add(a, b) { a + b } pub fn main() { 1 |> add(2) Nil } " ); } #[test] fn pure_pipeline_with_many_steps_raises_warning() { assert_warning!( " fn add(a, b) { a + b } pub fn main() { 1 |> add(2) |> add(3) |> add(4) Nil } " ); } #[test] fn pipeline_with_echo_is_impure() { assert_no_warnings!( " fn add(a, b) { a + b } pub fn main() { 1 |> add(2) |> echo |> add(3) Nil } " ); } #[test] fn pipeline_with_impure_function_raises_no_warnings() { assert_no_warnings!( " fn add(a, b) { echo a + b } pub fn main() { 1 |> add(2) Nil } " ); } #[test] fn function_is_pure_on_js_if_external_on_erlang() { assert_js_warning!( r#" @external(erlang, "maths", "add") fn add(a: Int, b: Int) -> Int { a + b } pub fn main() { add(1, 2) Nil } "# ); } #[test] fn function_is_pure_on_erlang_if_external_on_js() { assert_warning!( r#" @external(javascript, "./maths.mjs", "add") fn add(a: Int, b: Int) -> Int { a + b } pub fn main() { add(1, 2) Nil } "# ); } #[test] fn pure_standard_library_function() { assert_warning!( ( "gleam_stdlib", "gleam/dict", r#" pub type Dict(key, value) @external(erlang, "map", "new") pub fn new() -> Dict(a, b) @external(erlang, "map", "insert") pub fn insert(dict: Dict(key, value), key: key, value: value) -> Dict(key, value) "# ), " import gleam/dict pub fn main() { dict.insert(dict.new(), 1, 2) Nil } " ); } #[test] fn impure_standard_library_function() { assert_no_warnings!( ( "gleam_stdlib", "gleam/io", r#" @external(erlang, "io", "print") pub fn println(message: String) -> Nil "# ), r#" import gleam/io pub fn main() { io.println("Hello, world!") Nil } "# ); } #[test] fn trusted_pure_standard_library_function_that_panics_is_impure() { assert_no_warnings!( ( "gleam_stdlib", "gleam/int", r#" pub fn add(_a, _b) { panic } "# ), " import gleam/int pub fn main() { int.add(1, 2) Nil } " ); } #[test] fn higher_order_function_is_not_marked_as_pure() { assert_no_warnings!( ( "gleam/list", r#" pub fn each(list, f) { case list { [] -> Nil [first, ..rest] -> { f(first) each(rest, f) } } } "# ), " import gleam/list pub fn main() { list.each([1, 2, 3, 4], fn(x) { echo x }) Nil } " ); } #[test] fn stdlib_list_each_is_not_marked_as_pure() { assert_no_warnings!( ( "gleam", "gleam/list", r#" pub fn each(list, f) { case list { [] -> Nil [first, ..rest] -> { f(first) each(rest, f) } } } "# ), " import gleam/list pub fn main() { list.each([1, 2, 3, 4], fn(x) { echo x }) Nil } " ); } #[test] fn dict_each_function_is_not_marked_as_pure() { assert_no_warnings!( ( "gleam_stdlib", "gleam/dict", r#" pub type Dict(key, value) @external(erlang, "maps", "new") @external(javascript, "../dict.mjs", "make") pub fn new() -> Dict(k, v) pub fn each(dict: Dict(k, v), fun: fn(k, v) -> a) -> Nil { fold(dict, Nil, fn(nil, k, v) { fun(k, v) nil }) } @external(javascript, "../dict.mjs", "fold") pub fn fold( over dict: Dict(k, v), from initial: acc, with fun: fn(acc, k, v) -> acc, ) -> acc { let fun = fn(key, value, acc) { fun(acc, key, value) } do_fold(fun, initial, dict) } @external(erlang, "maps", "fold") fn do_fold(fun: fn(k, v, acc) -> acc, initial: acc, dict: Dict(k, v)) -> acc "# ), " import gleam/dict pub fn main() { dict.each(dict.new(), fn(_, _) { echo 1 }) Nil } " ); } #[test] fn dict_fold_function_is_not_marked_as_pure() { assert_no_warnings!( ( "gleam_stdlib", "gleam/dict", r#" pub type Dict(key, value) @external(erlang, "maps", "new") @external(javascript, "../dict.mjs", "make") pub fn new() -> Dict(k, v) @external(javascript, "../dict.mjs", "fold") pub fn fold( over dict: Dict(k, v), from initial: acc, with fun: fn(acc, k, v) -> acc, ) -> acc { let fun = fn(key, value, acc) { fun(acc, key, value) } do_fold(fun, initial, dict) } @external(erlang, "maps", "fold") fn do_fold(fun: fn(k, v, acc) -> acc, initial: acc, dict: Dict(k, v)) -> acc "# ), " import gleam/dict pub fn main() { dict.fold(dict.new(), Nil, fn(_, _, _) { Nil }) Nil } " ); } #[test] fn calling_local_variable_not_marked_as_pure() { assert_no_warnings!( " pub fn main() { let side_effects = fn() { panic } side_effects() Nil } " ); } #[test] fn constructing_anonymous_function_is_pure() { assert_warning!( r#" fn make_panic(message) { fn() { panic as message } } pub fn main() { make_panic("This is a crash") Nil } "# ); } // https://github.com/gleam-lang/gleam/issues/4504 #[test] fn impure_stdlib_test_function() { assert_no_warnings!( ( "gleam_stdlib", "gleam/should", r#" @external(erlang, "gleam_test_ffi", "should_equal") pub fn equal(a: t, b: t) -> Nil "# ), " import gleam/should pub fn main() { 1 |> should.equal(1) } " ); } // https://github.com/gleam-lang/gleam/issues/4505 #[test] fn impure_function_using_a_pipe() { assert_no_warnings!( " fn impure(_x) { panic } fn also_impure(x) { x |> impure } pub fn main() { also_impure(10) Nil } " ); } // https://github.com/gleam-lang/gleam/issues/4505 #[test] fn impure_function_using_a_pipe_with_a_call() { assert_no_warnings!( " fn impure(_x, _y) { panic } fn also_impure(x) { x |> impure(1) } pub fn main() { also_impure(10) Nil } " ); } // https://github.com/gleam-lang/gleam/issues/4505 #[test] fn impure_function_using_a_pipe_into_anonymous_function() { assert_no_warnings!( " fn impure(x) { x |> fn(_x) { panic } } pub fn main() { impure(10) Nil } " ); } // https://github.com/gleam-lang/gleam/issues/4505 #[test] fn impure_function_using_a_pipe_into_echo() { assert_no_warnings!( " fn impure(x) { x |> echo } pub fn main() { impure(10) Nil } " ); } // https://github.com/gleam-lang/gleam/issues/4505 #[test] fn impure_function_using_a_pipe_into_result_of_call() { assert_no_warnings!( " fn make_panic() { fn(_x) { panic } } fn impure(x) { x |> make_panic() } pub fn main() { impure(10) Nil } " ); } #[test] fn no_assert_warning_for_bit_array_with_variable() { assert_no_warnings!( r#" @external(erlang, "gleam@function", "identity") fn codepoint(value: Int) -> UtfCodepoint pub fn main() { let codepoint = codepoint(32) assert <> == <<" ">> } "# ); } // https://github.com/gleam-lang/gleam/issues/4637 #[test] fn pattern_matching_on_32_float_plus_infinity_still_reachable() { assert_no_warnings!( r#" pub fn go(x) { case x { <<_:32-float>> -> "Float" <<0x7f800000:32>> -> "+Infinity" _ -> "Other" } } "# ); } #[test] fn no_assert_warning_for_tuple_with_variable() { assert_no_warnings!( r#" pub fn main() { let x = 3 assert #(1, 2, x) == #(1, 2, 3) } "# ); } #[test] fn pattern_matching_on_32_float_plus_infinity_still_reachable_2() { assert_no_warnings!( r#" pub fn go(x) { case x { <<_:32-float>> -> "Float" <<0x7f80:16, 0x0000:16>> -> "+Infinity" _ -> "Other" } } "# ); } #[test] fn no_assert_warning_for_list_with_variable() { assert_no_warnings!( r#" pub fn main() { let x = 3 assert [1, 2, x] == [1, 2, 3] } "# ); } #[test] fn pattern_matching_on_32_float_minus_infinity_still_reachable() { assert_no_warnings!( r#" pub fn go(x) { case x { <<_:32-float>> -> "Float" <<0xff800000:32>> -> "-Infinity" _ -> "Other" } } "# ); } #[test] fn no_assert_warning_for_constructor_with_variable() { assert_no_warnings!( r#" type Box(t) { Box(t) } pub fn main() { let x = 42 assert Box(x) == Box(42) } "# ); } #[test] fn pattern_matching_on_32_float_minus_infinity_still_reachable_2() { assert_no_warnings!( r#" pub fn go(x) { case x { <<_:32-float>> -> "Float" <<0xff80:16, 0x0000:16>> -> "-Infinity" _ -> "Other" } } "# ); } #[test] fn pattern_matching_on_32_float_nan_still_reachable() { assert_no_warnings!( r#" pub fn go(x) { case x { <<_:32-float>> -> "Float" <<0x7fc00000:32>> -> "NaN" _ -> "Other" } } "# ); } #[test] fn pattern_matching_on_32_float_nan_still_reachable_2() { assert_no_warnings!( r#" pub fn go(x) { case x { <<_:32-float>> -> "Float" <<0x7fc0:16, 0x0000:16>> -> "NaN" _ -> "Other" } } "# ); } #[test] fn pattern_matching_on_64_float_plus_infinity_still_reachable() { assert_no_warnings!( r#" pub fn go(x) { case x { <<_:64-float>> -> "Float" <<0x7ff0000000000000:64>> -> "+Infinity" _ -> "Other" } } "# ); } #[test] fn pattern_matching_on_64_float_plus_infinity_still_reachable_2() { assert_no_warnings!( r#" pub fn go(x) { case x { <<_:64-float>> -> "Float" <<0x7ff00000:32, 0x00000000:32>> -> "+Infinity" _ -> "Other" } } "# ); } #[test] fn pattern_matching_on_64_float_minus_infinity_still_reachable() { assert_no_warnings!( r#" pub fn go(x) { case x { <<_:64-float>> -> "Float" <<0xfff0000000000000:64>> -> "-Infinity" _ -> "Other" } } "# ); } #[test] fn pattern_matching_on_64_float_minus_infinity_still_reachable_2() { assert_no_warnings!( r#" pub fn go(x) { case x { <<_:64-float>> -> "Float" <<0xfff00000:32, 0x00000000:32>> -> "-Infinity" _ -> "Other" } } "# ); } #[test] fn pattern_matching_on_64_float_nan_still_reachable() { assert_no_warnings!( r#" pub fn go(x) { case x { <<_:64-float>> -> "Float" <<0x7ff8000000000000:64>> -> "NaN" _ -> "Other" } } "# ); } #[test] fn pattern_matching_on_64_float_nan_still_reachable_2() { assert_no_warnings!( r#" pub fn go(x) { case x { <<_:64-float>> -> "Float" <<0x7ff80000:32, 0x00000000:32>> -> "NaN" _ -> "Other" } } "# ); } #[test] fn pattern_matching_on_64_float_int_is_still_reachable() { assert_no_warnings!( r#" pub fn go(x) { case x { <<_:64-float>> -> "Float" <<_:64-int>> -> "Int" _ -> "Other" } } "# ); } #[test] fn pattern_matching_on_64_float_float_is_unreachable() { assert_warning!( r#" pub fn go(x) { case x { <<_:64-float>> -> "Float" <<_:64-float>> -> "unreachable" _ -> "Other" } } "# ); } #[test] fn import_module_twice() { assert_warning!( ("gleam/wibble", "pub fn wobble() { 1 }"), "import gleam/wibble as a import gleam/wibble as b pub fn main() { a.wobble() + b.wobble() } " ); } //https://github.com/gleam-lang/gleam/issues/4666 #[test] fn shadow_imported_function() { assert_warning!( ( "thepackage", "module", r#" pub fn wibble() { Nil } "# ), r#" import module.{wibble} pub fn wibble() { Nil } "# ); } //https://github.com/gleam-lang/gleam/issues/4666 #[test] fn shadow_imported_constant() { assert_warning!( ( "thepackage", "module", r#" pub const value = 1 "# ), r#" import module.{value} pub const value = 1 "# ); } #[test] fn int_literals_redundant_comparison() { assert_warning!("pub fn main() { 1 == 1 }"); } #[test] fn int_literals_redundant_comparison_2() { assert_warning!("pub fn main() { 1 == 2 }"); } #[test] fn int_literals_redundant_comparison_3() { assert_warning!("pub fn main() { 1 != 1 }"); } #[test] fn int_literals_redundant_comparison_4() { assert_warning!("pub fn main() { 1 != 2 }"); } #[test] fn int_literals_redundant_comparison_5() { assert_warning!("pub fn main() { 1 > 2 }"); } #[test] fn int_literals_redundant_comparison_6() { assert_warning!("pub fn main() { 1 <= 2 }"); } #[test] fn int_literals_redundant_comparison_7() { assert_warning!("pub fn main() { 1 < 2 }"); } #[test] fn int_literals_redundant_comparison_8() { assert_warning!("pub fn main() { 1 >= 2 }"); } #[test] fn float_literals_redundant_comparison() { assert_warning!("pub fn main() { 1.0 == 1.0 }"); } #[test] fn float_literals_redundant_comparison_2() { assert_warning!("pub fn main() { 1.0 == 2.0 }"); } #[test] fn float_literals_redundant_comparison_3() { assert_warning!("pub fn main() { 1.0 != 1.0 }"); } #[test] fn float_literals_redundant_comparison_4() { assert_warning!("pub fn main() { 1.0 != 2.0 }"); } #[test] fn float_literals_redundant_comparison_5() { assert_warning!("pub fn main() { 1.0 >. 2.0 }"); } #[test] fn float_literals_redundant_comparison_6() { assert_warning!("pub fn main() { 1.0 <=. 2.0 }"); } #[test] fn float_literals_redundant_comparison_7() { assert_warning!("pub fn main() { 1.0 <. 2.0 }"); } #[test] fn float_literals_redundant_comparison_8() { assert_warning!("pub fn main() { 1.0 >=. 2.0 }"); } #[test] fn float_literals_redundant_comparison_different_repr() { assert_warning!("pub fn main() { 1_0.0 == 10.0 }"); } #[test] fn float_literals_redundant_comparison_different_repr_2() { assert_warning!("pub fn main() { 10.0 == 1.0e1 }"); } #[test] fn float_literals_redundant_comparison_precision_loss() { assert_warning!("pub fn main() { 1.0e-500 == 1.0e-600 }"); } #[test] fn float_literals_redundant_comparison_infinity() { assert_warning!("pub fn main() { 1.0e500 == 1.0e600 }"); } #[test] fn float_literals_redundant_comparison_signed_zero() { assert_warning!("pub fn main() { 0.0 == -0.0 }"); } #[test] fn float_literals_redundant_comparison_omitted_zero() { assert_warning!("pub fn main() { 10. == 10.0 }"); } #[test] fn bool_literals_redundant_comparison() { assert_warning!("pub fn main() { True == False }"); } #[test] fn bool_literals_redundant_comparison_1() { assert_warning!("pub fn main() { True != False }"); } #[test] fn list_literals_redundant_comparison() { assert_warning!("pub fn main(a, b) { [1] == [a, b(1)] }"); } #[test] fn list_literals_redundant_comparison_2() { assert_warning!("pub fn main(a, b) { [1] != [a, b(1)] }"); } #[test] fn list_literals_redundant_comparison_3() { assert_warning!("pub fn main() { [1] != [1] }"); } #[test] fn list_literals_redundant_comparison_4() { assert_warning!("pub fn main(a) { [1, ..[1, a]] == [1, ..[1, a]] }"); } #[test] fn list_literals_redundant_comparison_5() { assert_warning!("pub fn main(a) { [1, ..a] == [1, ..a] }"); } #[test] fn list_literals_redundant_comparison_6() { assert_no_warnings!("pub fn main(a) { [a(1)] == [a(1)] }"); } #[test] fn list_literals_redundant_comparison_7() { assert_warning!("pub fn main(a) { [a(1), 2] == [a(1), 3] }"); } #[test] fn string_literals_redundant_comparison() { assert_warning!("pub fn main() { \"wibble\" == \"wobble\" }"); } #[test] fn string_literals_redundant_comparison_1() { assert_warning!("pub fn main() { \"wibble\" != \"wobble\" }"); } #[test] fn variables_redundant_comparison() { assert_warning!("pub fn main(a) { a == a }"); } #[test] fn variables_not_redundant_comparison() { assert_no_warnings!("pub fn main(a, b) { a != b }"); } #[test] fn constructor_functions_not_redundant_comparison() { assert_no_warnings!( " type Comparison { Wobble(String) } pub fn main() { Wobble == Wobble } " ); } #[test] fn record_select_redundant_comparison() { assert_warning!( " pub type Wibble { Wibble(field: Int) } pub fn main(wibble: Wibble) { wibble.field == wibble.field } " ); } #[test] fn record_select_redundant_comparison_1() { assert_warning!( " pub type Wibble { Wibble(field: Int) } pub fn main(wibble: Wibble) { wibble.field != wibble.field } " ); } #[test] fn record_select_not_redundant_comparison() { assert_no_warnings!( " pub type Wibble { Wibble(field: Int) } pub fn main(wibble: Wibble, wobble: Wibble) { wibble.field == wobble.field } " ); } #[test] fn record_select_not_redundant_comparison_2() { assert_no_warnings!( " pub type Wibble { Wibble(field: Int) } fn new() -> Wibble { Wibble(1) } pub fn main() { new().field == new().field // ^^ functions might have side effects so we can't // tell if this is redundant or not! } " ); } #[test] fn different_records_0_redundant_comparison() { assert_warning!( " pub type Either { Left Right } pub fn main() -> Bool { Left == Right } " ); } #[test] fn different_records_1_redundant_comparison() { assert_warning!( " pub type Either { Left(Int) Right } pub fn main() -> Bool { Left(1) == Right } " ); } #[test] fn different_records_2_redundant_comparison() { assert_warning!( " pub type Either { Left(Int) Right(Int) } pub fn main() -> Bool { Left(1) == Right(1) } " ); } #[test] fn different_records_3_redundant_comparison() { assert_warning!( " pub type Either { Left Right(Int) } pub fn main() -> Bool { Left == Right(1) } " ); } #[test] fn unused_discard_pattern() { assert_warning!( "pub fn main() { let a = 10 let _ = case a { _ as b -> b } } " ); } #[test] fn discarded_argument_suggestion_is_not_given_for_different_functions() { assert_no_warnings!( " pub fn main(_x) { 1 } pub fn wibble() { x } " ); } #[test] fn empty_guard_clause() { assert_warning!( " pub fn main() { let wibble = 10 case wibble { 10 if -> True _ -> False } }" ); } #[test] fn impossible_to_reach_integer_segment() { assert_warning!( " pub fn main(x) { case x { <<9:size(2)>> -> True _ -> False } }" ); } #[test] fn impossible_to_reach_integer_segment_2() { assert_warning!( " pub fn main(x) { case x { <<-9:unsigned>> -> True _ -> False } }" ); } #[test] fn impossible_to_reach_integer_segment_3() { assert_warning!( " pub fn main(x) { case x { <<312>> -> True _ -> False } }" ); } #[test] fn impossible_to_reach_integer_segment_4() { assert_warning!( " pub fn main(x) { case x { <<-1>> -> True _ -> False } }" ); } #[test] fn multiple_impossible_to_reach_integer_segments() { assert_warning!( " pub fn main(x) { case x { <<1, -1, 2, -3>> -> True _ -> False } }" ); } #[test] fn assert_on_impossible_to_reach_integer_segment() { assert_warning!( " pub fn main(x) { let assert <<1, -1, 2, -3>> = x }" ); } // https://github.com/gleam-lang/gleam/issues/4958 #[test] fn unreachable_string_pattern_with_different_encodings() { assert_warning!( r#" pub fn wibble(bits) { case bits { <<"\u{0000}a\u{0000}b":utf8>> -> 1 // This is the same as the one above, it shouldn't be reachable <<"ab":utf16>> -> 2 _ -> 3 } }"# ); } #[test] fn unreachable_int_pattern_with_string_of_same_value() { assert_warning!( r#" pub fn wibble(bits) { case bits { <<"a">> -> 1 <<97>> -> 2 _ -> 3 } }"# ); } #[test] fn unreachable_int_pattern_with_prefix_int() { assert_warning!( r#" pub fn wibble(bits) { case bits { <<0b1:1, _:1>> -> 1 <<0b11:2>> -> 2 _ -> 3 } }"# ); } #[test] fn reachable_pattern_after_unreachable_equal_pattern() { assert_warning!( r#" pub fn wibble(bits) { case bits { <<97:3>> -> 1 <<"a">> -> 2 _ -> 3 } } "# ); } #[test] fn unused_recursive_function_argument() { assert_warning!( " pub fn main(x: Int) { main(x) } " ); } #[test] fn unused_recursive_function_argument_2() { assert_warning!( " pub fn main(x, times) { case times { 0 -> main(x, 0) _ -> main(x, times - 1) } } " ); } #[test] fn unused_recursive_function_with_shadowing() { assert_warning!( " pub fn main(x: Int) { let _useless = fn(x) { main(x) } main(x) } " ); } #[test] fn unused_recursive_function_inside_anonymous_function() { assert_warning!( " pub fn main(x: Int) { let _useless = fn(_) { main(x) } Nil } " ); } #[test] fn unused_recursive_function_with_variable_shadowing() { assert_no_warnings!( " pub fn main(x: Int) { let x = x * 2 main(x) } " ); } #[test] fn unused_recursive_function_passing_argument_in_a_different_position_counts_as_using_it() { assert_no_warnings!( " pub fn main(x: Int, y) { case y { 0 -> main(1, x) _ -> main(0, 0) } } " ); } #[test] fn unused_recursive_function_with_shadowed_function() { assert_no_warnings!( " pub fn main(x: Int) { let main = fn(x) { x } main(x) } " ); } #[test] fn external_annotation_on_custom_type_requires_v1_14() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), r#" @external(erlang, "wibble", "wobble") pub type Wobble "#, ); } #[test] fn detached_doc_comment() { assert_warning!( " /// This comment is detached // /// This is actual documentation pub const pi = 3.14 " ); } #[test] fn const_record_update_requires_v1_14_warning() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " pub type Wibble { Wibble(a: Int, b: Int) } const base = Wibble(1, 2) pub const wobble = Wibble(..base, b: 3) ", ); } #[test] fn expression_in_expression_segment_size_requires_v1_12_warning() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " pub fn main() { <<1:size(3 * 8)>> } ", ); } #[test] fn expression_in_pattern_segment_size_requires_v1_12_warning() { assert_warnings_with_gleam_version!( Range::higher_than(Version::new(1, 0, 0)), " pub fn main(x) { case x { <<_:size(3*8)>> -> 1 _ -> 2 } }", ); } #[test] fn record_update_with_all_wrong_fields_produces_no_warnings_1() { assert_no_warnings!( " pub type Wibble { Wibble(a: Int, b: Bool) } pub fn main() { let original = Wibble(a: 1, b: True) Wibble(..original, c: 2) } ", ); } #[test] fn record_update_with_all_wrong_fields_produces_no_warnings_2() { assert_no_warnings!( " pub type Wibble { Wibble(a: Int, b: Bool) } pub fn main() { let original = Wibble(a: 1, b: True) Wibble(..original, a: True) } ", ); } #[test] fn record_update_with_wrong_types_but_all_fields_produces_warning() { assert_warning!( " pub type Wibble { Wibble(a: Int, b: Bool) } pub fn main() { let original = Wibble(a: 1, b: True) Wibble(..original, a: True, b: 1) } " ); } ================================================ FILE: compiler-core/src/type_/tests.rs ================================================ use super::*; use crate::{ analyse::TargetSupport, ast::{TypedModule, TypedStatement, UntypedExpr, UntypedModule}, build::{Origin, Outcome, Target}, config::{GleamVersion, PackageConfig}, error::Error, type_::{build_prelude, expression::FunctionDefinition, pretty::Printer}, uid::UniqueIdGenerator, warning::{TypeWarningEmitter, VectorWarningEmitterIO, WarningEmitter, WarningEmitterIO}, }; use ecow::EcoString; use itertools::Itertools; use pubgrub::Range; use std::rc::Rc; use vec1::Vec1; use camino::Utf8PathBuf; mod accessors; mod assert; mod assignments; mod conditional_compilation; mod custom_types; mod dead_code_detection; mod echo; mod errors; mod exhaustiveness; mod externals; mod functions; mod guards; mod imports; mod let_assert; mod pipes; mod pretty; mod target_implementations; mod type_alias; mod use_; mod version_inference; mod warnings; #[macro_export] macro_rules! assert_infer { ($src:expr, $type_:expr $(,)?) => { let t = $crate::type_::tests::infer($src); assert_eq!(($src, t), ($src, $type_.to_string()),); }; } #[macro_export] macro_rules! assert_module_infer { ($(($name:expr, $module_src:literal)),+, $src:literal, $module:expr $(,)?) => { let constructors = $crate::type_::tests::infer_module($src, vec![$(("thepackage", $name, $module_src)),*]); let expected = $crate::type_::tests::stringify_tuple_strs($module); assert_eq!(($src, constructors), ($src, expected)); }; ($src:expr, $module:expr $(,)?) => {{ let constructors = $crate::type_::tests::infer_module($src, vec![]); let expected = $crate::type_::tests::stringify_tuple_strs($module); assert_eq!(($src, constructors), ($src, expected)); }}; } #[macro_export] macro_rules! assert_js_module_infer { ($src:expr, $module:expr $(,)?) => {{ let constructors = $crate::type_::tests::infer_module_with_target( "test_module", $src, vec![], $crate::build::Target::JavaScript, ); let expected = $crate::type_::tests::stringify_tuple_strs($module); assert_eq!(($src, constructors), ($src, expected)); }}; } #[macro_export] macro_rules! assert_module_error { ($src:expr) => { let error = $crate::type_::tests::module_error($src, vec![]); let output = format!("----- SOURCE CODE\n{}\n\n----- ERROR\n{}", $src, error); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }; ($(($name:expr, $module_src:literal)),+, $src:literal $(,)?) => { let error = $crate::type_::tests::module_error( $src, vec![ $(("thepackage", $name, $module_src)),* ], ); let mut output = String::from("----- SOURCE CODE\n"); for (name, src) in [$(($name, $module_src)),*] { output.push_str(&format!("-- {name}.gleam\n{src}\n\n")); } output.push_str(&format!("-- main.gleam\n{}\n\n----- ERROR\n{error}", $src)); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }; ($(($package:literal, $name:expr, $module_src:literal)),+, $src:literal $(,)?) => { let error = $crate::type_::tests::module_error( $src, vec![ $(($package, $name, $module_src)),* ], ); let mut output = String::from("----- SOURCE CODE\n"); for (name, src) in [$(($name, $module_src)),*] { output.push_str(&format!("-- {name}.gleam\n{src}\n\n")); } output.push_str(&format!("-- main.gleam\n{}\n\n----- ERROR\n{error}", $src)); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }; } #[macro_export] macro_rules! assert_internal_module_error { ($src:expr) => { let error = $crate::type_::tests::internal_module_error($src, vec![]); let output = format!("----- SOURCE CODE\n{}\n\n----- ERROR\n{}", $src, error); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }; } #[macro_export] macro_rules! assert_js_module_error { ($src:expr) => { let error = $crate::type_::tests::module_error_with_target( $src, vec![], $crate::build::Target::JavaScript, ); let output = format!("----- SOURCE CODE\n{}\n\n----- ERROR\n{}", $src, error); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }; } #[macro_export] macro_rules! assert_module_syntax_error { ($src:expr) => { let error = $crate::type_::tests::syntax_error($src); let output = format!("----- SOURCE CODE\n{}\n\n----- ERROR\n{}", $src, error); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }; } #[macro_export] macro_rules! assert_error { ($src:expr, $error:expr $(,)?) => { let result = $crate::type_::tests::compile_statement_sequence($src) .expect_err("should infer an error"); assert_eq!(($src, sort_options($error)), ($src, sort_options(result)),); }; ($src:expr) => { let (error, names) = $crate::type_::tests::compile_statement_sequence($src) .expect_err("should infer an error"); let error = $crate::error::Error::Type { names: Box::new(names), src: $src.into(), path: camino::Utf8PathBuf::from("/src/one/two.gleam"), errors: error, }; let error_string = error.pretty_string(); let output = format!( "----- SOURCE CODE\n{}\n\n----- ERROR\n{}", $src, error_string ); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }; } fn get_warnings( src: &str, deps: Vec>, target: Target, gleam_version: Option>, ) -> Vec { let warnings = VectorWarningEmitterIO::default(); _ = compile_module_with_opts( "test_module", src, Some(Rc::new(warnings.clone())), deps, target, TargetSupport::NotEnforced, gleam_version, ); warnings.take().into_iter().collect_vec() } pub(crate) fn get_printed_warnings( src: &str, deps: Vec>, target: Target, gleam_version: Option>, ) -> String { print_warnings(get_warnings(src, deps, target, gleam_version)) } fn print_warnings(warnings: Vec) -> String { let mut nocolor = termcolor::Buffer::no_color(); for warning in warnings { warning.pretty(&mut nocolor); } String::from_utf8(nocolor.into_inner()).expect("Error printing produced invalid utf8") } #[macro_export] macro_rules! assert_warning { ($src:expr) => { let warning = $crate::type_::tests::get_printed_warnings($src, vec![], crate::build::Target::Erlang, None); assert!(!warning.is_empty()); let output = format!("----- SOURCE CODE\n{}\n\n----- WARNING\n{}", $src, warning); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }; ($(($name:expr, $module_src:literal)),+, $src:literal $(,)?) => { let warning = $crate::type_::tests::get_printed_warnings( $src, vec![ $(("thepackage", $name, $module_src)),* ], crate::build::Target::Erlang, None ); assert!(!warning.is_empty()); let mut output = String::from("----- SOURCE CODE\n"); for (name, src) in [$(($name, $module_src)),*] { output.push_str(&format!("-- {name}.gleam\n{src}\n\n")); } output.push_str(&format!("-- main.gleam\n{}\n\n----- WARNING\n{warning}", $src)); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }; ($(($package:expr, $name:expr, $module_src:literal)),+, $src:expr) => { let warning = $crate::type_::tests::get_printed_warnings( $src, vec![$(($package, $name, $module_src)),*], crate::build::Target::Erlang, None ); assert!(!warning.is_empty()); let mut output = String::from("----- SOURCE CODE\n"); for (name, src) in [$(($name, $module_src)),*] { output.push_str(&format!("-- {name}.gleam\n{src}\n\n")); } output.push_str(&format!("-- main.gleam\n{}\n\n----- WARNING\n{warning}", $src)); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }; } #[macro_export] macro_rules! assert_js_warning { ($src:expr) => { let warning = $crate::type_::tests::get_printed_warnings( $src, vec![], crate::build::Target::JavaScript, None, ); assert!(!warning.is_empty()); let output = format!("----- SOURCE CODE\n{}\n\n----- WARNING\n{}", $src, warning); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }; } #[macro_export] macro_rules! assert_js_no_warnings { ($src:expr) => { let warning = $crate::type_::tests::get_printed_warnings( $src, vec![], crate::build::Target::JavaScript, None, ); assert!(warning.is_empty()); }; } #[macro_export] macro_rules! assert_warnings_with_gleam_version { ($gleam_version:expr, $src:expr$(,)?) => { let warning = $crate::type_::tests::get_printed_warnings( $src, vec![], crate::build::Target::Erlang, Some($gleam_version), ); assert!(!warning.is_empty()); let output = format!("----- SOURCE CODE\n{}\n\n----- WARNING\n{}", $src, warning); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }; } #[macro_export] macro_rules! assert_js_warnings_with_gleam_version { ($gleam_version:expr, $src:expr$(,)?) => { let warning = $crate::type_::tests::get_printed_warnings( $src, vec![], crate::build::Target::JavaScript, Some($gleam_version), ); assert!(!warning.is_empty()); let output = format!("----- SOURCE CODE\n{}\n\n----- WARNING\n{}", $src, warning); insta::assert_snapshot!(insta::internals::AutoName, output, $src); }; } #[macro_export] macro_rules! assert_js_no_warnings_with_gleam_version { ($gleam_version:expr, $src:expr$(,)?) => { let warning = $crate::type_::tests::get_printed_warnings( $src, vec![], crate::build::Target::JavaScript, Some($gleam_version), ); assert!(warning.is_empty()); }; } #[macro_export] macro_rules! assert_no_warnings { ($src:expr $(,)?) => { let warnings = $crate::type_::tests::get_warnings($src, vec![], crate::build::Target::Erlang, None); assert_eq!(warnings, vec![]); }; ($(($name:expr, $module_src:literal)),+, $src:expr $(,)?) => { let warnings = $crate::type_::tests::get_warnings( $src, vec![$(("thepackage", $name, $module_src)),*], crate::build::Target::Erlang, None, ); assert_eq!(warnings, vec![]); }; ($(($package:expr, $name:expr, $module_src:literal)),+, $src:expr $(,)?) => { let warnings = $crate::type_::tests::get_warnings( $src, vec![$(($package, $name, $module_src)),*], crate::build::Target::Erlang, None, ); assert_eq!(warnings, vec![]); }; } fn compile_statement_sequence( src: &str, ) -> Result, (Vec1, Names)> { let ast = crate::parse::parse_statement_sequence(src).expect("syntax error"); let mut modules = im::HashMap::new(); let ids = UniqueIdGenerator::new(); // DUPE: preludeinsertion // TODO: Currently we do this here and also in the tests. It would be better // to have one place where we create all this required state for use in each // place. let _ = modules.insert(PRELUDE_MODULE_NAME.into(), build_prelude(&ids)); let mut problems = Problems::new(); let dev_dependencies = HashSet::new(); let mut environment = EnvironmentArguments { ids, current_package: "thepackage".into(), gleam_version: None, current_module: "themodule".into(), target: Target::Erlang, importable_modules: &modules, target_support: TargetSupport::Enforced, current_origin: Origin::Src, dev_dependencies: &dev_dependencies, } .build(); let res = ExprTyper::new( &mut environment, FunctionDefinition { has_body: true, has_erlang_external: false, has_javascript_external: false, }, &mut problems, ) .infer_statements(ast); match Vec1::try_from_vec(problems.take_errors()) { Err(_) => Ok(res), Ok(errors) => Err((errors, environment.names)), } } fn infer(src: &str) -> String { let mut printer = Printer::new(); let result = compile_statement_sequence(src).expect("should successfully infer"); printer.pretty_print(result.last().type_().as_ref(), 0) } pub fn stringify_tuple_strs(module: Vec<(&str, &str)>) -> Vec<(EcoString, String)> { module .into_iter() .map(|(k, v)| (k.into(), v.into())) .collect() } /// A module loaded as a dependency in the tests. /// /// In order the tuple elements indicate: /// 1. The package name. /// 2. The module name. /// 3. The module source. type DependencyModule<'a> = (&'a str, &'a str, &'a str); pub fn infer_module(src: &str, dep: Vec>) -> Vec<(EcoString, String)> { infer_module_with_target("test_module", src, dep, Target::Erlang) } pub fn infer_module_with_target( module_name: &str, src: &str, dep: Vec>, target: Target, ) -> Vec<(EcoString, String)> { let ast = compile_module_with_opts( module_name, src, None, dep, target, TargetSupport::NotEnforced, None, ) .expect("should successfully infer"); ast.type_info .values .iter() .filter(|(_, v)| v.publicity.is_importable()) .map(|(k, v)| { let mut printer = Printer::new(); (k.clone(), printer.pretty_print(&v.type_, 0)) }) .sorted() .collect() } pub fn compile_module( module_name: &str, src: &str, warnings: Option>, dep: Vec>, ) -> Outcome> { compile_module_with_opts( module_name, src, warnings, dep, Target::Erlang, TargetSupport::NotEnforced, None, ) } pub fn compile_module_with_opts( module_name: &str, src: &str, warnings: Option>, dep: Vec>, target: Target, target_support: TargetSupport, gleam_version: Option>, ) -> Outcome> { let ids = UniqueIdGenerator::new(); let mut modules = im::HashMap::new(); let emitter = WarningEmitter::new(warnings.unwrap_or_else(|| Rc::new(VectorWarningEmitterIO::default()))); // DUPE: preludeinsertion // TODO: Currently we do this here and also in the tests. It would be better // to have one place where we create all this required state for use in each // place. let _ = modules.insert(PRELUDE_MODULE_NAME.into(), build_prelude(&ids)); let mut direct_dependencies = HashMap::from_iter(vec![]); for (package, name, module_src) in dep { let parsed = crate::parse::parse_module(Utf8PathBuf::from("test/path"), module_src, &emitter) .expect("syntax error"); let mut ast = parsed.module; ast.name = name.into(); let line_numbers = LineNumbers::new(module_src); let mut config = PackageConfig::default(); config.name = package.into(); let module = crate::analyse::ModuleAnalyzerConstructor::<()> { target, ids: &ids, origin: Origin::Src, importable_modules: &modules, warnings: &TypeWarningEmitter::null(), direct_dependencies: &HashMap::new(), dev_dependencies: &HashSet::new(), target_support, package_config: &config, } .infer_module(ast, line_numbers, "".into()) .expect("should successfully infer"); let _ = modules.insert(name.into(), module.type_info); if package != "non-dependency-package" { let _ = direct_dependencies.insert(package.into(), ()); } } let parsed = crate::parse::parse_module(Utf8PathBuf::from("test/path"), src, &emitter) .expect("syntax error"); let mut ast = parsed.module; ast.name = module_name.into(); let mut config = PackageConfig::default(); config.name = "thepackage".into(); config.gleam_version = gleam_version.map(GleamVersion::from_pubgrub); let warnings = TypeWarningEmitter::new("/src/warning/wrn.gleam".into(), src.into(), emitter); crate::analyse::ModuleAnalyzerConstructor::<()> { target, ids: &ids, origin: Origin::Src, importable_modules: &modules, warnings: &warnings, direct_dependencies: &direct_dependencies, dev_dependencies: &HashSet::from_iter(["dev_dependency".into()]), target_support: TargetSupport::Enforced, package_config: &config, } .infer_module(ast, LineNumbers::new(src), "".into()) } pub fn module_error(src: &str, deps: Vec>) -> String { module_error_with_target(src, deps, Target::Erlang) } pub fn module_error_with_target( src: &str, deps: Vec>, target: Target, ) -> String { let outcome = compile_module_with_opts( "themodule", src, None, deps, target, TargetSupport::NotEnforced, None, ); let (error, names) = match outcome { Outcome::Ok(_) => panic!("should infer an error"), Outcome::PartialFailure(ast, errors) => (errors.into(), ast.names), Outcome::TotalFailure(errors) => (errors.into(), Default::default()), }; let error = Error::Type { names: Box::new(names), src: src.into(), path: Utf8PathBuf::from("/src/one/two.gleam"), errors: Vec1::try_from_vec(error).expect("should have at least one error"), }; error.pretty_string() } pub fn internal_module_error(src: &str, deps: Vec>) -> String { internal_module_error_with_target(src, deps, Target::Erlang) } pub fn internal_module_error_with_target( src: &str, deps: Vec>, target: Target, ) -> String { let outcome = compile_module_with_opts( "thepackage/internal/themodule", src, None, deps, target, TargetSupport::NotEnforced, None, ); let (error, names) = match outcome { Outcome::Ok(_) => panic!("should infer an error"), Outcome::PartialFailure(ast, errors) => (errors.into(), ast.names), Outcome::TotalFailure(errors) => (errors.into(), Default::default()), }; let error = Error::Type { names: Box::new(names), src: src.into(), path: Utf8PathBuf::from("/src/one/two.gleam"), errors: Vec1::try_from_vec(error).expect("should have at least one error"), }; error.pretty_string() } pub fn syntax_error(src: &str) -> String { let error = crate::parse::parse_module(Utf8PathBuf::from("test/path"), src, &WarningEmitter::null()) .expect_err("should trigger an error when parsing"); let error = Error::Parse { src: src.into(), path: Utf8PathBuf::from("/src/one/two.gleam"), error: Box::new(error), }; error.pretty_string() } #[test] fn field_map_reorder_test() { let int = |value: &str| UntypedExpr::Int { value: value.into(), int_value: crate::parse::parse_int_value(value).unwrap(), location: SrcSpan { start: 0, end: 0 }, }; struct Case { arity: u32, fields: HashMap, arguments: Vec>, expected_result: Result<(), crate::type_::Error>, expected_arguments: Vec>, } impl Case { fn test(self) { let mut arguments = self.arguments; let fm = FieldMap { arity: self.arity, fields: self.fields, }; let location = SrcSpan { start: 0, end: 0 }; assert_eq!( self.expected_result, fm.reorder(&mut arguments, location, IncorrectArityContext::Function) ); assert_eq!(self.expected_arguments, arguments); } } Case { arity: 0, fields: HashMap::new(), arguments: vec![], expected_result: Ok(()), expected_arguments: vec![], } .test(); Case { arity: 3, fields: HashMap::new(), arguments: vec![ CallArg { implicit: None, location: Default::default(), label: None, value: int("1"), }, CallArg { implicit: None, location: Default::default(), label: None, value: int("2"), }, CallArg { implicit: None, location: Default::default(), label: None, value: int("3"), }, ], expected_result: Ok(()), expected_arguments: vec![ CallArg { implicit: None, location: Default::default(), label: None, value: int("1"), }, CallArg { implicit: None, location: Default::default(), label: None, value: int("2"), }, CallArg { implicit: None, location: Default::default(), label: None, value: int("3"), }, ], } .test(); Case { arity: 3, fields: [("last".into(), 2)].into(), arguments: vec![ CallArg { implicit: None, location: Default::default(), label: None, value: int("1"), }, CallArg { implicit: None, location: Default::default(), label: None, value: int("2"), }, CallArg { implicit: None, location: Default::default(), label: Some("last".into()), value: int("3"), }, ], expected_result: Ok(()), expected_arguments: vec![ CallArg { implicit: None, location: Default::default(), label: None, value: int("1"), }, CallArg { implicit: None, location: Default::default(), label: None, value: int("2"), }, CallArg { implicit: None, location: Default::default(), label: Some("last".into()), value: int("3"), }, ], } .test(); } #[test] fn infer_module_type_retention_test() { let module: UntypedModule = crate::ast::Module { documentation: vec![], name: "ok".into(), definitions: vec![], type_info: (), names: Default::default(), unused_definition_positions: Default::default(), }; let direct_dependencies = HashMap::from_iter(vec![]); let ids = UniqueIdGenerator::new(); let mut modules = im::HashMap::new(); // DUPE: preludeinsertion // TODO: Currently we do this here and also in the tests. It would be better // to have one place where we create all this required state for use in each // place. let _ = modules.insert(PRELUDE_MODULE_NAME.into(), build_prelude(&ids)); let mut config = PackageConfig::default(); config.name = "thepackage".into(); let module = crate::analyse::ModuleAnalyzerConstructor::<()> { target: Target::Erlang, ids: &ids, origin: Origin::Src, importable_modules: &modules, warnings: &TypeWarningEmitter::null(), direct_dependencies: &direct_dependencies, dev_dependencies: &HashSet::new(), target_support: TargetSupport::Enforced, package_config: &config, } .infer_module(module, LineNumbers::new(""), "".into()) .expect("Should infer OK"); assert_eq!( module.type_info, ModuleInterface { warnings: vec![], origin: Origin::Src, package: "thepackage".into(), name: "ok".into(), is_internal: false, types: HashMap::new(), types_value_constructors: HashMap::from([ ( "Bool".into(), TypeVariantConstructors { type_parameters_ids: vec![], variants: vec![ TypeValueConstructor { name: "True".into(), parameters: vec![], documentation: None, }, TypeValueConstructor { name: "False".into(), parameters: vec![], documentation: None, } ], opaque: Opaque::NotOpaque, } ), ( "UtfCodepoint".into(), TypeVariantConstructors { type_parameters_ids: vec![], variants: vec![], opaque: Opaque::NotOpaque, } ), ( "Result".into(), TypeVariantConstructors { type_parameters_ids: vec![1, 2], variants: vec![ TypeValueConstructor { name: "Ok".into(), parameters: vec![TypeValueConstructorField { type_: generic_var(1), label: None, documentation: None, }], documentation: None, }, TypeValueConstructor { name: "Error".into(), parameters: vec![TypeValueConstructorField { type_: generic_var(2), label: None, documentation: None, }], documentation: None, } ], opaque: Opaque::NotOpaque, } ), ( "Nil".into(), TypeVariantConstructors { type_parameters_ids: vec![], variants: vec![TypeValueConstructor { name: "Nil".into(), parameters: vec![], documentation: None, }], opaque: Opaque::NotOpaque, } ) ]), values: HashMap::new(), accessors: HashMap::new(), line_numbers: LineNumbers::new(""), src_path: "".into(), minimum_required_version: Version::new(0, 1, 0), type_aliases: HashMap::new(), documentation: Vec::new(), contains_echo: false, references: References::default(), inline_functions: HashMap::new(), } ); } #[test] fn simple_exprs() { assert_infer!("True", "Bool"); assert_infer!("False", "Bool"); assert_infer!("1", "Int"); assert_infer!("-2", "Int"); assert_infer!("1.0", "Float"); assert_infer!("-8.0", "Float"); assert_infer!("\"ok\"", "String"); assert_infer!("\"ok\"", "String"); assert_infer!("[]", "List(a)"); assert_infer!("4 % 1", "Int"); assert_infer!("4 > 1", "Bool"); assert_infer!("4 >= 1", "Bool"); assert_infer!("4 <= 1", "Bool"); assert_infer!("4 < 1", "Bool"); // Numbers with _'s assert_infer!("1000_000", "Int"); assert_infer!("1_000", "Int"); assert_infer!("1_000.", "Float"); assert_infer!("10_000.001", "Float"); assert_infer!("100_000.", "Float"); // Nil assert_infer!("Nil", "Nil"); // todo assert_infer!("todo", "a"); assert_infer!("1 == todo", "Bool"); assert_infer!("todo != 1", "Bool"); assert_infer!("todo + 1", "Int"); assert_infer!("todo(\"test\") + 1", "Int"); // hex, octal, and binary literals assert_infer!("0xF", "Int"); assert_infer!("0o11", "Int"); assert_infer!("0b1010", "Int"); // scientific notation assert_infer!("6.02e23", "Float"); assert_infer!("6.02e-23", "Float"); } #[test] fn assert() { assert_infer!("let assert [] = [] 1", "Int"); assert_infer!("let assert [a] = [1] a", "Int"); assert_infer!("let assert [a, 2] = [1] a", "Int"); assert_infer!("let assert [a, .._] = [1] a", "Int"); assert_infer!("let assert [a, .._,] = [1] a", "Int"); assert_infer!("fn(x) { let assert [a] = x a }", "fn(List(a)) -> a"); assert_infer!("fn(x) { let assert [a] = x a + 1 }", "fn(List(Int)) -> Int"); assert_infer!("let assert _x = 1 2.0", "Float"); assert_infer!("let assert _ = 1 2.0", "Float"); assert_infer!("let assert #(tag, x) = #(1.0, 1) x", "Int"); assert_infer!("fn(x) { let assert #(a, b) = x a }", "fn(#(a, b)) -> a"); assert_infer!("let assert 5: Int = 5 5", "Int"); } #[test] fn lists() { assert_infer!("[]", "List(a)"); assert_infer!("[1]", "List(Int)"); assert_infer!("[1, 2, 3]", "List(Int)"); assert_infer!("[[]]", "List(List(a))"); assert_infer!("[[1.0, 2.0]]", "List(List(Float))"); assert_infer!("[fn(x) { x }]", "List(fn(a) -> a)"); assert_infer!("[fn(x) { x + 1 }]", "List(fn(Int) -> Int)"); assert_infer!("[fn(x) { x }, fn(x) { x + 1 }]", "List(fn(Int) -> Int)"); assert_infer!("[fn(x) { x + 1 }, fn(x) { x }]", "List(fn(Int) -> Int)"); assert_infer!("[[], []]", "List(List(a))"); assert_infer!("[[], [1]]", "List(List(Int))"); assert_infer!("[1, ..[2, ..[]]]", "List(Int)"); assert_infer!("[fn(x) { x }, ..[]]", "List(fn(a) -> a)"); assert_infer!("let x = [1, ..[]] [2, ..x]", "List(Int)"); } #[test] fn trailing_comma_lists() { assert_infer!("[1, ..[2, ..[],]]", "List(Int)"); assert_infer!("[fn(x) { x },..[]]", "List(fn(a) -> a)"); assert_infer!("let f = fn(x) { x } [f, f]", "List(fn(a) -> a)"); assert_infer!("[#([], [])]", "List(#(List(a), List(b)))"); } #[test] fn tuples() { assert_infer!("#(1)", "#(Int)"); assert_infer!("#(1, 2.0)", "#(Int, Float)"); assert_infer!("#(1, 2.0, 3)", "#(Int, Float, Int)"); assert_infer!("#(1, 2.0, #(1, 1))", "#(Int, Float, #(Int, Int))",); } #[test] fn expr_fn() { assert_infer!("fn(x) { x }", "fn(a) -> a"); assert_infer!("fn(x) { x }", "fn(a) -> a"); assert_infer!("fn(x, y) { x }", "fn(a, b) -> a"); assert_infer!("fn(x, y) { [] }", "fn(a, b) -> List(c)"); assert_infer!("let x = 1.0 1", "Int"); assert_infer!("let id = fn(x) { x } id(1)", "Int"); assert_infer!("let x = fn() { 1.0 } x()", "Float"); assert_infer!("fn(x) { x }(1)", "Int"); assert_infer!("fn() { 1 }", "fn() -> Int"); assert_infer!("fn() { 1.1 }", "fn() -> Float"); assert_infer!("fn(x) { 1.1 }", "fn(a) -> Float"); assert_infer!("fn(x) { x }", "fn(a) -> a"); assert_infer!("let x = fn(x) { 1.1 } x", "fn(a) -> Float"); assert_infer!("fn(x, y, z) { 1 }", "fn(a, b, c) -> Int"); assert_infer!("fn(x) { let y = x y }", "fn(a) -> a"); assert_infer!("let id = fn(x) { x } id(1)", "Int"); assert_infer!( "let constant = fn(x) { fn(y) { x } } let one = constant(1) one(2.0)", "Int", ); assert_infer!("fn(f) { f(1) }", "fn(fn(Int) -> a) -> a"); assert_infer!("fn(f, x) { f(x) }", "fn(fn(a) -> b, a) -> b"); assert_infer!("fn(f) { fn(x) { f(x) } }", "fn(fn(a) -> b) -> fn(a) -> b"); assert_infer!( "fn(f) { fn(x) { fn(y) { f(x, y) } } }", "fn(fn(a, b) -> c) -> fn(a) -> fn(b) -> c", ); assert_infer!( "fn(f) { fn(x, y) { f(x)(y) } }", "fn(fn(a) -> fn(b) -> c) -> fn(a, b) -> c", ); assert_infer!( "fn(f) { fn(x) { let ff = f ff(x) } }", "fn(fn(a) -> b) -> fn(a) -> b", ); assert_infer!( "fn(f) { fn(x, y) { let ff = f(x) ff(y) } }", "fn(fn(a) -> fn(b) -> c) -> fn(a, b) -> c", ); assert_infer!("fn(x) { fn(y) { x } }", "fn(a) -> fn(b) -> a"); assert_infer!("fn(f) { f() }", "fn(fn() -> a) -> a"); assert_infer!("fn(f, x) { f(f(x)) }", "fn(fn(a) -> a, a) -> a"); assert_infer!( "let id = fn(a) { a } fn(x) { x(id) }", "fn(fn(fn(a) -> a) -> b) -> b", ); assert_infer!("let add = fn(x, y) { x + y } add(_, 2)", "fn(Int) -> Int"); assert_infer!("fn(x) { #(1, x) }", "fn(a) -> #(Int, a)"); assert_infer!("fn(x, y) { #(x, y) }", "fn(a, b) -> #(a, b)"); assert_infer!("fn(x) { #(x, x) }", "fn(a) -> #(a, a)"); assert_infer!("fn(x) -> Int { x }", "fn(Int) -> Int"); assert_infer!("fn(x) -> a { x }", "fn(a) -> a"); assert_infer!("fn() -> Int { 2 }", "fn() -> Int"); } #[test] fn case() { assert_infer!("case 1 { a -> 1 }", "Int"); assert_infer!("case 1 { a -> 1.0 b -> 2.0 c -> 3.0 }", "Float"); assert_infer!("case 1 { a -> a }", "Int"); assert_infer!("case 1 { 1 -> 10 2 -> 20 x -> x * 10 }", "Int"); assert_infer!("case 2.0 { 2.0 -> 1 x -> 0 }", "Int"); assert_infer!(r#"case "ok" { "ko" -> 1 x -> 0 }"#, "Int"); } #[test] fn multiple_subject_case() { assert_infer!("case 1, 2.0 { a, b -> a }", "Int"); assert_infer!("case 1, 2.0 { a, b -> b }", "Float"); assert_infer!("case 1, 2.0, 3 { a, b, c -> a + c }", "Int"); } #[test] fn tuple_index() { assert_infer!("#(1, 2.0).0", "Int"); assert_infer!("#(1, 2.0).1", "Float"); } #[test] fn pipe() { assert_infer!("1 |> fn(x) { x }", "Int"); assert_infer!("1.0 |> fn(x) { x }", "Float"); assert_infer!("let id = fn(x) { x } 1 |> id", "Int"); assert_infer!("let id = fn(x) { x } 1.0 |> id", "Float"); assert_infer!("let add = fn(x, y) { x + y } 1 |> add(_, 2)", "Int"); assert_infer!("let add = fn(x, y) { x + y } 1 |> add(2, _)", "Int"); assert_infer!("let add = fn(x, y) { x + y } 1 |> add(2)", "Int"); assert_infer!("let id = fn(x) { x } 1 |> id()", "Int"); assert_infer!("let add = fn(x) { fn(y) { y + x } } 1 |> add(1)", "Int"); assert_infer!( "let add = fn(x, _, _) { fn(y) { y + x } } 1 |> add(1, 2, 3)", "Int" ); } #[test] fn bit_array() { assert_infer!("let assert <> = <<1>> x", "Int"); } #[test] fn bit_array2() { assert_infer!("let assert <> = <<1>> x", "Int"); } #[test] fn bit_array3() { assert_infer!("let assert <> = <<1>> x", "Float"); } #[test] fn bit_array4() { assert_infer!("let assert <> = <<1>> x", "BitArray"); } #[test] fn bit_array5() { assert_infer!("let assert <> = <<1>> x", "BitArray"); } #[test] fn bit_array6() { assert_infer!("let assert <> = <<1>> x", "BitArray"); } #[test] fn bit_array7() { assert_infer!("let assert <> = <<1>> x", "BitArray"); } #[test] fn bit_array8() { assert_infer!( "let assert <> = <<128013:32>> x", "UtfCodepoint" ); } #[test] fn bit_array9() { assert_infer!( "let assert <> = <<128013:32>> x", "UtfCodepoint" ); } #[test] fn bit_array10() { assert_infer!( "let assert <> = <<128013:32>> x", "UtfCodepoint" ); } #[test] fn bit_array11() { assert_infer!( "let a = <<1>> let assert <> = <<1, a:2-bits>> x", "BitArray" ); } #[test] fn bit_array12() { assert_infer!("let x = <<<<1>>:bits, <<2>>:bits>> x", "BitArray"); } #[test] fn infer_module_test() { assert_module_infer!( "pub fn repeat(i, x) { case i { 0 -> [] i -> [x .. repeat(i - 1, x)] } }", vec![("repeat", "fn(Int, a) -> List(a)")], ); } #[test] fn infer_module_test1() { assert_module_infer!( "pub fn length(list) { case list { [] -> 0 [x .. xs] -> length(xs) + 1 } }", vec![("length", "fn(List(a)) -> Int")], ); } #[test] fn infer_module_test2() { assert_module_infer!( "fn private() { 1 } pub fn public() { 1 }", vec![("public", "fn() -> Int")], ); } #[test] fn infer_module_test3() { assert_module_infer!( "pub type Is { Yes No } pub fn yes() { Yes } pub fn no() { No }", vec![ ("No", "Is"), ("Yes", "Is"), ("no", "fn() -> Is"), ("yes", "fn() -> Is"), ], ); } #[test] fn infer_module_test4() { assert_module_infer!( "pub type Num { I(Int) } pub fn one() { I(1) }", vec![("I", "fn(Int) -> Num"), ("one", "fn() -> Num")], ); } #[test] fn infer_module_test5() { assert_module_infer!( "pub type Box(a) { Box(a) } pub fn int() { Box(1) } pub fn float() { Box(1.0) }", vec![ ("Box", "fn(a) -> Box(a)"), ("float", "fn() -> Box(Float)"), ("int", "fn() -> Box(Int)"), ], ); } #[test] fn infer_module_test6() { assert_module_infer!( "pub type Singleton { Singleton } pub fn go(x) { let Singleton = x 1 }", vec![("Singleton", "Singleton"), ("go", "fn(Singleton) -> Int")], ); } #[test] fn infer_module_test7() { assert_module_infer!( "pub type Box(a) { Box(a) } pub fn unbox(x) { let Box(a) = x a }", vec![("Box", "fn(a) -> Box(a)"), ("unbox", "fn(Box(a)) -> a")], ); } #[test] fn infer_module_test8() { assert_module_infer!( "pub type I { I(Int) } pub fn open(x) { case x { I(i) -> i } }", vec![("I", "fn(Int) -> I"), ("open", "fn(I) -> Int")], ); } #[test] fn infer_module_test9() { assert_module_infer!( "pub fn status() { 1 } pub fn list_of(x) { [x] }", vec![("list_of", "fn(a) -> List(a)"), ("status", "fn() -> Int")], ); } #[test] fn infer_module_test10() { assert_module_infer!( " @external(erlang, \"\", \"\") pub fn go(x: String) -> String ", vec![("go", "fn(String) -> String")], ); } #[test] fn infer_module_test11() { assert_module_infer!( " @external(erlang, \"\", \"\") pub fn go(x: Int) -> Float ", vec![("go", "fn(Int) -> Float")], ); } #[test] fn infer_module_test12() { assert_module_infer!( " @external(erlang, \"\", \"\") pub fn go(x: Int) -> Int ", vec![("go", "fn(Int) -> Int")], ); } #[test] fn infer_module_test13() { assert_module_infer!( " @external(erlang, \"\", \"\") pub fn ok() -> fn(Int) -> Int ", vec![("ok", "fn() -> fn(Int) -> Int")], ); } #[test] fn infer_module_test14() { assert_module_infer!( " @external(erlang, \"\", \"\") pub fn go(x: Int) -> b ", vec![("go", "fn(Int) -> a")], ); } #[test] fn infer_module_test15() { assert_module_infer!( " @external(erlang, \"\", \"\") pub fn go(x: Bool) -> b ", vec![("go", "fn(Bool) -> a")], ); } #[test] fn infer_module_test16() { assert_module_infer!( " @external(erlang, \"\", \"\") pub fn go(x: List(a)) -> a ", vec![("go", "fn(List(a)) -> a")], ); } #[test] fn infer_module_test17() { assert_module_infer!( " @external(erlang, \"\", \"\") fn go(x: Int) -> b pub fn x() { go(1) }", vec![("x", "fn() -> a")], ); } #[test] fn infer_module_test18() { assert_module_infer!( "@external(erlang, \"\", \"\") fn id(a: a) -> a pub fn i(x) { id(x) } pub fn a() { id(1) } pub fn b() { id(1.0) }", vec![ ("a", "fn() -> Int"), ("b", "fn() -> Float"), ("i", "fn(a) -> a"), ], ); } #[test] fn infer_module_test19() { assert_module_infer!( " @external(erlang, \"\", \"\") pub fn len(a: List(a)) -> Int ", vec![("len", "fn(List(a)) -> Int")], ); } #[test] fn infer_module_test20() { assert_module_infer!( "pub type Connection\n @external(erlang, \"\", \"\") pub fn is_open(x: Connection) -> Bool ", vec![("is_open", "fn(Connection) -> Bool")], ); } #[test] fn infer_module_test21() { assert_module_infer!( "pub type Pair(a, b)\n @external(erlang, \"\", \"\") pub fn pair(x: a) -> Pair(a, a) ", vec![("pair", "fn(a) -> Pair(a, a)")], ); } #[test] fn infer_module_test22() { assert_module_infer!( "pub fn one() { 1 } pub fn zero() { one() - 1 } pub fn two() { one() + zero() }", vec![ ("one", "fn() -> Int"), ("two", "fn() -> Int"), ("zero", "fn() -> Int"), ], ); } #[test] fn infer_module_test23() { assert_module_infer!( "pub fn one() { 1 } pub fn zero() { one() - 1 } pub fn two() { one() + zero() }", vec![ ("one", "fn() -> Int"), ("two", "fn() -> Int"), ("zero", "fn() -> Int"), ], ); } #[test] fn infer_module_test24() { // Structs assert_module_infer!( "pub type Box { Box(boxed: Int) }", vec![("Box", "fn(Int) -> Box")] ); } #[test] fn infer_module_test25() { assert_module_infer!( "pub type Tup(a, b) { Tup(first: a, second: b) }", vec![("Tup", "fn(a, b) -> Tup(a, b)")] ); } #[test] fn infer_module_test26() { assert_module_infer!( "pub type Tup(a, b, c) { Tup(first: a, second: b, third: c) } pub fn third(t) { let Tup(_ , _, third: a) = t a }", vec![ ("Tup", "fn(a, b, c) -> Tup(a, b, c)"), ("third", "fn(Tup(a, b, c)) -> c"), ], ); } #[test] fn infer_label_shorthand_pattern() { assert_module_infer!( "pub type Tup(a, b, c) { Tup(first: a, second: b, third: c) } pub fn third(t) { let Tup(_, _, third:) = t third }", vec![ ("Tup", "fn(a, b, c) -> Tup(a, b, c)"), ("third", "fn(Tup(a, b, c)) -> c"), ], ); } #[test] fn infer_module_test27() { // Anon structs assert_module_infer!( "pub fn ok(x) { #(1, x) }", vec![("ok", "fn(a) -> #(Int, a)")], ); } #[test] fn infer_module_test28() { assert_module_infer!( " @external(erlang, \"\", \"\") pub fn ok(a: Int) -> #(Int, Int) ", vec![("ok", "fn(Int) -> #(Int, Int)")], ); } #[test] fn infer_module_test29() { assert_module_infer!( " @external(erlang, \"\", \"\") pub fn go(a: #(a, c)) -> c ", vec![("go", "fn(#(a, b)) -> b")], ); } #[test] fn infer_module_test30() { assert_module_infer!( "pub fn always(ignore _a, return b) { b }", vec![("always", "fn(a, b) -> b")], ); } #[test] fn infer_module_test31() { // Using types before they are defined assert_module_infer!( "pub type I { I(Num) } pub type Num { Num }", vec![("I", "fn(Num) -> I"), ("Num", "Num")] ); } #[test] fn infer_module_test32() { assert_module_infer!( "pub type I { I(Num) } pub type Num", vec![("I", "fn(Num) -> I")] ); } #[test] fn type_alias() { assert_module_infer!( "type Html = String pub fn go() { 1 }", vec![("go", "fn() -> Int")], ); assert_module_infer!( "type IntString = Result(Int, String) pub fn ok_one() -> IntString { Ok(1) }", vec![("ok_one", "fn() -> Result(Int, String)")] ); } #[test] fn build_in_type_alias_shadow() { // We can create an alias with the same name as a built in type assert_module_infer!( "type Int = Float pub fn ok_one() -> Int { 1.0 }", vec![("ok_one", "fn() -> Float")] ); } #[test] fn accessor() { // We can access fields on custom types with only one variant assert_module_infer!( " pub type Person { Person(name: String, age: Int) } pub fn get_age(person: Person) { person.age } pub fn get_name(person: Person) { person.name }", vec![ ("Person", "fn(String, Int) -> Person"), ("get_age", "fn(Person) -> Int"), ("get_name", "fn(Person) -> String"), ] ); // We can access fields on custom types with only one variant assert_module_infer!( " pub type One { One(name: String) } pub type Two { Two(one: One) } pub fn get(x: Two) { x.one.name }", vec![ ("One", "fn(String) -> One"), ("Two", "fn(One) -> Two"), ("get", "fn(Two) -> String"), ] ); } #[test] fn generic_accessor() { // Field access correctly handles type parameters assert_module_infer!( " pub type Box(a) { Box(inner: a) } pub fn get_box(x: Box(Box(a))) { x.inner } pub fn get_generic(x: Box(a)) { x.inner } pub fn get_get_box(x: Box(Box(a))) { x.inner.inner } pub fn get_int(x: Box(Int)) { x.inner } pub fn get_string(x: Box(String)) { x.inner } ", vec![ ("Box", "fn(a) -> Box(a)"), ("get_box", "fn(Box(Box(a))) -> Box(a)"), ("get_generic", "fn(Box(a)) -> a"), ("get_get_box", "fn(Box(Box(a))) -> a"), ("get_int", "fn(Box(Int)) -> Int"), ("get_string", "fn(Box(String)) -> String"), ] ); } #[test] fn generic_accessor_later_defined() { // Field access works before type is defined assert_module_infer!( " pub fn name(cat: Cat) { cat.name } pub opaque type Cat { Cat(name: String) }", vec![("name", "fn(Cat) -> String"),] ); } #[test] fn custom_type_annotation() { // We can annotate let with custom types assert_module_infer!( " pub type Person { Person(name: String, age: Int) } pub fn create_person(name: String) { let x: Person = Person(name: name, age: 1) x }", vec![ ("Person", "fn(String, Int) -> Person"), ("create_person", "fn(String) -> Person"), ] ); assert_module_infer!( " pub type Box(inner) { Box(inner) } pub fn create_int_box(value: Int) { let x: Box(Int) = Box(value) x } pub fn create_float_box(value: Float) { let x: Box(Float) = Box(value) x }", vec![ ("Box", "fn(a) -> Box(a)"), ("create_float_box", "fn(Float) -> Box(Float)"), ("create_int_box", "fn(Int) -> Box(Int)"), ] ); } #[test] fn opaque_accessors() { // Opaque type constructors are available in the module where they are defined // but are not exported assert_module_infer!( " pub opaque type One { One(name: String) } pub fn get(x: One) { x.name }", vec![("get", "fn(One) -> String"),] ); } #[test] fn fn_annotation_reused() { // Type variables are shared between function annotations and function // annotations within their body assert_module_infer!( " pub type Box(a) { Box(value: a) } pub fn go(box1: Box(a)) { fn(box2: Box(a)) { box1.value == box2.value } }", vec![ ("Box", "fn(a) -> Box(a)"), ("go", "fn(Box(a)) -> fn(Box(a)) -> Bool") ] ); // Type variables are shared between function annotations and let // annotations within their body assert_module_infer!( " pub type Box(a) { Box(value: a) } pub fn go(box1: Box(a)) { let x: Box(a) = box1 fn(box2: Box(a)) { x.value == box2.value } }", vec![ ("Box", "fn(a) -> Box(a)"), ("go", "fn(Box(a)) -> fn(Box(a)) -> Bool") ] ); } #[test] fn accessor_multiple_variants() { // We can access fields on custom types with multiple variants assert_module_infer!( " pub type Person { Teacher(name: String, title: String) Student(name: String, age: Int) } pub fn get_name(person: Person) { person.name }", vec![ ("Student", "fn(String, Int) -> Person"), ("Teacher", "fn(String, String) -> Person"), ("get_name", "fn(Person) -> String"), ] ); } #[test] fn record_accessor_multiple_variants_parameterised_types() { // We can access fields on custom types with multiple variants // In positions other than the 1st field assert_module_infer!( " pub type Person { Teacher(name: String, age: List(Int), title: String) Student(name: String, age: List(Int)) } pub fn get_name(person: Person) { person.name } pub fn get_age(person: Person) { person.age }", vec![ ("Student", "fn(String, List(Int)) -> Person"), ("Teacher", "fn(String, List(Int), String) -> Person"), ("get_age", "fn(Person) -> List(Int)"), ("get_name", "fn(Person) -> String"), ] ); } #[test] fn accessor_multiple_variants_positions_other_than_first() { // We can access fields on custom types with multiple variants // In positions other than the 1st field assert_module_infer!( " pub type Person { Teacher(name: String, age: Int, title: String) Student(name: String, age: Int) } pub fn get_name(person: Person) { person.name } pub fn get_age(person: Person) { person.age }", vec![ ("Student", "fn(String, Int) -> Person"), ("Teacher", "fn(String, Int, String) -> Person"), ("get_age", "fn(Person) -> Int"), ("get_name", "fn(Person) -> String"), ] ); } #[test] fn box_record() { assert_module_infer!( " pub type Box { Box(a: Nil, b: Int, c: Int, d: Int) } pub fn main() { Box(b: 1, c: 1, d: 1, a: Nil) }", vec![ ("Box", "fn(Nil, Int, Int, Int) -> Box"), ("main", "fn() -> Box"), ], ); } #[test] fn record_update_no_fields() { // No arguments given to a record update assert_module_infer!( " pub type Person { Person(name: String, age: Int) } pub fn identity(person: Person) { Person(..person) }", vec![ ("Person", "fn(String, Int) -> Person"), ("identity", "fn(Person) -> Person") ] ); } #[test] fn record_update() { // Some arguments given to a record update assert_module_infer!( " pub type Person { Person(name: String, age: Int) } pub fn update_name(person: Person, name: String) { Person(..person, name: name) }", vec![ ("Person", "fn(String, Int) -> Person"), ("update_name", "fn(Person, String) -> Person") ] ); } #[test] fn record_update_all_fields() { // All arguments given in order to a record update assert_module_infer!( " pub type Person { Person(name: String, age: Int) } pub fn update_person(person: Person, name: String, age: Int) { Person(..person, name: name, age: age, ) }", vec![ ("Person", "fn(String, Int) -> Person"), ("update_person", "fn(Person, String, Int) -> Person") ] ); } #[test] fn record_update_out_of_order() { // All arguments given out of order to a record update assert_module_infer!( " pub type Person { Person(name: String, age: Int) } pub fn update_person(person: Person, name: String, age: Int) { Person(..person, age: age, name: name) }", vec![ ("Person", "fn(String, Int) -> Person"), ("update_person", "fn(Person, String, Int) -> Person") ] ); } #[test] fn record_update_generic() { // A record update with polymorphic types assert_module_infer!( " pub type Box(a, b) { Box(left: a, right: b) } pub fn combine_boxes(a: Box(Int, Bool), b: Box(Bool, Int)) { Box(..a, left: a.left + b.right, right: b.left) }", vec![ ("Box", "fn(a, b) -> Box(a, b)"), ( "combine_boxes", "fn(Box(Int, Bool), Box(Bool, Int)) -> Box(Int, Bool)" ) ] ); } #[test] fn record_update_generic_unannotated() { // A record update with unannotated polymorphic types assert_module_infer!( " pub type Box(a, b) { Box(left: a, right: b) } pub fn combine_boxes(a: Box(t1, t2), b: Box(t2, t1)) { Box(..a, left: b.right, right: b.left) }", vec![ ("Box", "fn(a, b) -> Box(a, b)"), ("combine_boxes", "fn(Box(a, b), Box(b, a)) -> Box(a, b)") ] ); } #[test] fn record_update_variant_inference() { assert_module_infer!( " pub type Shape { Circle(cx: Int, cy: Int, radius: Int) Square(x: Int, y: Int, width: Int, height: Int) } pub fn grow(shape) { case shape { Circle(radius:, ..) as circle -> Circle(..circle, radius: radius + 1) Square(width:, height:, ..) as square -> Square(..square, width: width + 1, height: height + 1) } } ", vec![ ("Circle", "fn(Int, Int, Int) -> Shape"), ("Square", "fn(Int, Int, Int, Int) -> Shape"), ("grow", "fn(Shape) -> Shape") ] ); } #[test] fn record_access_variant_inference() { assert_module_infer!( " pub type Wibble { Wibble(a: Int, b: Int) Wobble(a: Int, c: Int) } pub fn get(wibble) { case wibble { Wibble(..) as w -> w.b Wobble(..) as w -> w.c } } ", vec![ ("Wibble", "fn(Int, Int) -> Wibble"), ("Wobble", "fn(Int, Int) -> Wibble"), ("get", "fn(Wibble) -> Int") ] ); } #[test] fn local_variable_variant_inference() { assert_module_infer!( " pub type Wibble { Wibble(a: Int, b: Int) Wobble(a: Int, c: Int) } pub fn main() { let always_wibble = Wibble(1, 2) always_wibble.b } ", vec![ ("Wibble", "fn(Int, Int) -> Wibble"), ("Wobble", "fn(Int, Int) -> Wibble"), ("main", "fn() -> Int") ] ); } #[test] fn record_update_variant_inference_for_original_variable() { assert_module_infer!( r#" pub type Wibble { Wibble(a: Int, b: Int) Wobble(a: Int, c: String) } pub fn update(wibble: Wibble) -> Wibble { case wibble { Wibble(..) -> Wibble(..wibble, a: 1) Wobble(..) -> Wobble(..wibble, c: "hello") } } "#, vec![ ("Wibble", "fn(Int, Int) -> Wibble"), ("Wobble", "fn(Int, String) -> Wibble"), ("update", "fn(Wibble) -> Wibble") ] ); } #[test] fn record_access_variant_inference_for_original_variable() { assert_module_infer!( " pub type Wibble { Wibble(a: Int, b: Int) Wobble(a: Int, c: Int) } pub fn get(wibble) { case wibble { Wibble(..) -> wibble.b Wobble(..) -> wibble.c } } ", vec![ ("Wibble", "fn(Int, Int) -> Wibble"), ("Wobble", "fn(Int, Int) -> Wibble"), ("get", "fn(Wibble) -> Int") ] ); } #[test] fn variant_inference_for_imported_type() { assert_module_infer!( ( "wibble", " pub type Wibble { Wibble(a: Int, b: Int) Wobble(a: Int, c: Int) } " ), " import wibble.{Wibble, Wobble} pub fn main(wibble) { case wibble { Wibble(..) -> Wibble(a: 1, b: wibble.b + 1) Wobble(..) -> Wobble(..wibble, c: wibble.c - 4) } } ", vec![("main", "fn(Wibble) -> Wibble")] ); } #[test] fn local_variable_variant_inference_for_imported_type() { assert_module_infer!( ( "wibble", " pub type Wibble { Wibble(a: Int, b: Int) Wobble(a: Int, c: Int) } " ), " import wibble.{Wibble} pub fn main() { let wibble = Wibble(4, 9) Wibble(..wibble, b: wibble.b) } ", vec![("main", "fn() -> Wibble")] ); } #[test] fn record_update_variant_inference_fails_for_several_possible_variants() { assert_module_error!( " pub type Vector { Vector2(x: Float, y: Float) Vector3(x: Float, y: Float, z: Float) } pub fn increase_y(vector, by increase) { case vector { Vector2(y:, ..) as vector | Vector3(y:, ..) as vector -> Vector2(..vector, y: y +. increase) } } " ); } #[test] fn record_update_variant_inference_fails_for_several_possible_variants_on_subject_variable() { assert_module_error!( r#" pub type Wibble { Wibble(a: Int, b: Int) Wobble(a: Int, c: String) } pub fn update(wibble: Wibble) -> Wibble { case wibble { Wibble(..) | Wobble(..) -> Wibble(..wibble, a: 1) } } "# ); } #[test] fn type_unification_does_not_cause_false_positives_for_variant_matching() { assert_module_error!( r#" pub type Wibble { Wibble(a: Int, b: Int) Wobble(a: Int, c: String) } pub fn wibbler() { todo } pub fn main() { let c = wibbler() case todo { Wibble(..) -> Wibble(..c, b: 1) _ -> todo } } "# ); } #[test] fn type_unification_does_not_allow_different_variants_to_be_treated_as_safe() { assert_module_error!( r#" pub type Wibble { Wibble(a: Int, b: Int) Wobble(a: Int, c: String) } pub fn main() { let a = case todo { Wibble(..) as b -> Wibble(..b, b: 1) Wobble(..) as b -> Wobble(..b, c: "a") } a.b } "# ); } // https://github.com/gleam-lang/gleam/issues/1358 #[test] fn type_unification_does_not_allow_lowercase_bools_in_match_clause() { assert_module_error!( r#" pub fn main() { case 42 > 42 { true -> 1 false -> 2 } } "# ); } #[test] fn record_update_variant_inference_in_alternate_pattern_with_all_same_variants() { assert_module_infer!( r#" pub type Vector { Vector2(x: Float, y: Float) Vector3(x: Float, y: Float, z: Float) } pub fn increase_y(vector, by increase) { case vector { Vector2(y:, ..) as vector -> Vector2(..vector, y: y +. increase) Vector3(y:, z: 12.3, ..) as vector | Vector3(y:, z: 15.0, ..) as vector -> Vector3(..vector, y: y +. increase, z: 0.0) _ -> panic as "Could not increase Y" } } "#, vec![ ("Vector2", "fn(Float, Float) -> Vector"), ("Vector3", "fn(Float, Float, Float) -> Vector"), ("increase_y", "fn(Vector, Float) -> Vector") ] ); } #[test] fn variant_inference_does_not_escape_clause_scope() { assert_module_error!( " pub type Thingy { A(a: Int) B(x: Int, b: Int) } pub fn fun(x) { case x { A(..) -> x.a B(..) -> x.b } x.b } " ); } #[test] // https://github.com/gleam-lang/gleam/issues/3797 fn type_unification_removes_inferred_variant_in_tuples() { assert_module_error!( r#" pub type Either(a, b) { Left(value: a) Right(value: b) } fn a_or_b(_first: value, second: value) -> value { second } pub fn main() { let #(right) = a_or_b(#(Left(5)), #(Right("hello"))) Left(..right, value: 10) } "# ); } #[test] // https://github.com/gleam-lang/gleam/issues/3797 fn type_unification_removes_inferred_variant_in_functions() { assert_module_error!( r#" pub type Either(a, b) { Left(value: a) Right(value: b) } fn a_or_b(_first: value, second: value) -> value { second } pub fn main() { let func = a_or_b(fn() { Left(1) }, fn() { Right("hello") }) Left(..func(), value: 10) } "# ); } #[test] // https://github.com/gleam-lang/gleam/issues/3797 fn type_unification_removes_inferred_variant_in_nested_type() { assert_module_error!( r#" pub type Box(a) { Box(inner: a) } pub type Either(a, b) { Left(value: a) Right(value: b) } fn a_or_b(_first: value, second: value) -> value { second } pub fn main() { let Box(inner) = a_or_b(Box(Left(1)), Box(Right("hello"))) Left(..inner, value: 10) } "# ); } #[test] fn module_constants() { assert_module_infer!( " pub const test_int1 = 123 pub const test_int2: Int = 321 pub const test_int3 = 0xE pub const test_int4 = 0o10 pub const test_int5 = 0o10011 pub const test_float: Float = 4.2 pub const test_string = \"hey!\" pub const test_list = [1,2,3] pub const test_tuple = #(\"yes!\", 42) pub const test_var1 = test_int1 pub const test_var2: Int = test_int1", vec![ ("test_float", "Float"), ("test_int1", "Int"), ("test_int2", "Int"), ("test_int3", "Int"), ("test_int4", "Int"), ("test_int5", "Int"), ("test_list", "List(Int)"), ("test_string", "String"), ("test_tuple", "#(String, Int)"), ("test_var1", "Int"), ("test_var2", "Int") ], ); } #[test] fn custom_type_module_constants() { assert_module_infer!( "pub type Test { A } pub const some_test = A", vec![("A", "Test"), ("some_test", "Test")], ); } #[test] fn const_record_update() { assert_module_infer!( "pub type Person { Person(name: String, age: Int) } pub const alice = Person(\"Alice\", 30) pub const bob = Person(..alice, name: \"Bob\")", vec![ ("Person", "fn(String, Int) -> Person"), ("alice", "Person"), ("bob", "Person") ], ); } #[test] fn const_record_update_all_fields() { assert_warning!( "pub type Person { Person(name: String, age: Int, city: String) } pub const base = Person(\"Alice\", 30, \"London\") pub const updated = Person(..base, name: \"Bob\", age: 25, city: \"Paris\")" ); } #[test] fn const_record_update_chain() { assert_module_infer!( "pub type Person { Person(name: String, age: Int, city: String) } pub const alice = Person(\"Alice\", 30, \"London\") pub const bob = Person(..alice, name: \"Bob\") pub const charlie = Person(..bob, age: 25)", vec![ ("Person", "fn(String, Int, String) -> Person"), ("alice", "Person"), ("bob", "Person"), ("charlie", "Person") ], ); } #[test] fn const_record_update_with_labeled_args() { assert_module_infer!( "pub type Person { Person(name: String, age: Int, city: String) } pub const alice = Person(name: \"Alice\", age: 30, city: \"London\") pub const bob = Person(..alice, name: \"Bob\")", vec![ ("Person", "fn(String, Int, String) -> Person"), ("alice", "Person"), ("bob", "Person") ], ); } #[test] fn const_record_update_type_mismatch_error() { assert_module_error!( "pub type Person { Person(name: String, age: Int) } pub type Animal { Animal(species: String) } pub const alice = Person(\"Alice\", 30) pub const dog = Animal(..alice)" ); } #[test] fn const_record_update_variant_mismatch_error() { assert_module_error!( "pub type Subject { Person(name: String, age: Int) Animal(species: String) } pub const alice = Person(\"Alice\", 30) pub const dog = Animal(..alice)" ); } #[test] fn const_record_update_field_type_mismatch_error() { assert_module_error!( "pub type Person { Person(name: String, age: Int) } pub const alice = Person(\"Alice\", 30) pub const bob = Person(..alice, age: \"not a number\")" ); } #[test] fn const_record_update_nonexistent_field() { assert_module_error!( "pub type Person { Person(name: String, age: Int) } pub const alice = Person(\"Alice\", 30) pub const bob = Person(..alice, nonexistent: \"value\")" ); } #[test] fn const_record_update_non_record() { assert_module_error!( "pub type Person { Person(name: String, age: Int) } pub const number = 42 pub const bob = Person(..number, name: \"Bob\")" ); } #[test] fn const_record_update_fieldless_warning() { assert_warning!( "pub type Animal { Animal(species: String) } pub const alice = Animal(\"Cat\") pub const dog = Animal(..alice)" ); } #[test] fn const_record_update_multi_variant() { assert_module_infer!( "pub type Pet { Dog(name: String, age: Int) Cat(name: String, breed: String) } pub const my_dog = Dog(\"Rex\", 5) pub const another_dog = Dog(..my_dog, name: \"Max\")", vec![ ("Cat", "fn(String, String) -> Pet"), ("Dog", "fn(String, Int) -> Pet"), ("another_dog", "Pet"), ("my_dog", "Pet"), ] ); } #[test] fn const_record_update_variant_without_args() { assert_module_error!( "pub type Status { Active Inactive } pub const status1 = Active pub const status2 = Active(..status1)" ); } #[test] fn const_record_update_unlabelled_fields() { assert_module_error!( "pub type Point { Point(Int, Int) } pub const origin = Point(0, 0) pub const point = Point(..origin)" ); } #[test] fn const_record_update_generic_respecialization() { assert_module_infer!( "pub type Box(a) { Box(name: String, value: a) } pub const base = Box(\"score\", 50) pub const updated = Box(..base, value: \"Hello\")", vec![ ("Box", "fn(String, a) -> Box(a)"), ("base", "Box(Int)"), ("updated", "Box(String)") ], ); } #[test] fn generic_unlabelled_field_in_updated_const_record_wrong_type() { assert_module_error!( "pub type Wibble(a) { Wibble(a, b: Int, c: a) } const w = Wibble(1, 2, 3) const w2 = Wibble(..w, c: False)" ); } #[test] fn module_constant_functions() { assert_module_infer!( "pub fn int_identity(i: Int) -> Int { i } pub const int_identity_alias1 = int_identity pub const int_identity_alias2 = int_identity_alias1 pub const int_identity_alias3: fn(Int) -> Int = int_identity_alias2", vec![ ("int_identity", "fn(Int) -> Int"), ("int_identity_alias1", "fn(Int) -> Int"), ("int_identity_alias2", "fn(Int) -> Int"), ("int_identity_alias3", "fn(Int) -> Int"), ] ); } #[test] fn functions_used_before_definition() { assert_module_infer!( "pub fn a() { b() } pub fn b() { 1 }", vec![("a", "fn() -> Int"), ("b", "fn() -> Int")], ); } #[test] fn functions_used_before_definition1() { assert_module_infer!( "pub fn a() { b() + c() } fn b() { 1 } fn c() { 1 }", vec![("a", "fn() -> Int")], ); } #[test] fn functions_used_before_definition2() { assert_module_infer!( "fn b() { 1 } pub fn a() { b() + c() } fn c() { 1 }", vec![("a", "fn() -> Int")], ); } #[test] fn functions_used_before_definition3() { assert_module_infer!( "pub fn a() { Thing } pub type Thing { Thing }", vec![("Thing", "Thing"), ("a", "fn() -> Thing"),], ); } #[test] fn types_used_before_definition() { assert_module_infer!( "pub type Y { Y(X) } pub type X", vec![("Y", "fn(X) -> Y")], ); } #[test] fn types_used_before_definition1() { assert_module_infer!( "pub type Y { Y(x: X) } pub type X", vec![("Y", "fn(X) -> Y")], ); } #[test] fn consts_used_before_definition() { assert_module_infer!( "pub fn a() { b } const b = 1", vec![("a", "fn() -> Int")], ); } #[test] fn mutual_recursion() { assert_module_infer!( "pub fn a() { b() } fn b() { a() }", vec![("a", "fn() -> a")], ); assert_module_infer!( "pub fn a() { b() } fn b() { a() + 1 }", vec![("a", "fn() -> Int")], ); } #[test] fn type_annotations() { assert_module_infer!( "pub type Box(x) { Box(label: String, contents: x) } pub fn id(x: Box(y)) { x }", vec![ ("Box", "fn(String, a) -> Box(a)"), ("id", "fn(Box(a)) -> Box(a)"), ], ); assert_module_infer!("pub fn go(x: Int) { x }", vec![("go", "fn(Int) -> Int")],); assert_module_infer!("pub fn go(x: b) -> b { x }", vec![("go", "fn(a) -> a")],); assert_module_infer!("pub fn go(x) -> b { x }", vec![("go", "fn(a) -> a")],); assert_module_infer!("pub fn go(x: b) { x }", vec![("go", "fn(a) -> a")],); assert_module_infer!( "pub fn go(x: List(b)) -> List(b) { x }", vec![("go", "fn(List(a)) -> List(a)")], ); assert_module_infer!( "pub fn go(x: List(b)) { x }", vec![("go", "fn(List(a)) -> List(a)")], ); assert_module_infer!( "pub fn go(x: List(String)) { x }", vec![("go", "fn(List(String)) -> List(String)")], ); assert_module_infer!("pub fn go(x: b, y: c) { x }", vec![("go", "fn(a, b) -> a")],); assert_module_infer!("pub fn go(x) -> Int { x }", vec![("go", "fn(Int) -> Int")],); assert_module_infer!( "pub fn id(x: x) { x } pub fn float() { id(1.0) } pub fn int() { id(1) }", vec![ ("float", "fn() -> Float"), ("id", "fn(a) -> a"), ("int", "fn() -> Int"), ], ); } #[test] fn early_function_generalisation() { assert_module_infer!( "pub fn id(x) { x } pub fn int() { id(1) }", vec![("id", "fn(a) -> a"), ("int", "fn() -> Int")], ); } #[test] fn early_function_generalisation2() { assert_module_infer!( "pub fn id(x) { x } pub fn int() { id(1) } pub fn float() { id(1.0) } ", vec![ ("float", "fn() -> Float"), ("id", "fn(a) -> a"), ("int", "fn() -> Int"), ], ); } // https://github.com/gleam-lang/gleam/issues/970 #[test] fn bit_array_pattern_unification() { assert_module_infer!( "pub fn m(x) { case x { <<>> -> Nil _ -> Nil} }", vec![("m", "fn(BitArray) -> Nil")], ); } // https://github.com/gleam-lang/gleam/issues/970 #[test] fn bit_array_pattern_unification2() { assert_module_infer!( "pub fn m(x) { case x { <<>> -> Nil _ -> Nil} }", vec![("m", "fn(BitArray) -> Nil")], ); } // https://github.com/gleam-lang/gleam/issues/983 #[test] fn qualified_prelude() { assert_module_infer!( "import gleam pub fn a() { gleam.Ok(1) }", vec![("a", "fn() -> Result(Int, a)")], ); } // https://github.com/gleam-lang/gleam/issues/1029 #[test] fn empty_list_const() { assert_module_infer!( "pub const empty = [] pub fn a() { empty }", vec![("a", "fn() -> List(a)"), ("empty", "List(a)")], ); } #[test] fn let_as_expression() { assert_infer!("let x = 1", "Int"); } #[test] fn let_as_expression1() { assert_infer!("let x = { let x = 1 }", "Int"); } #[test] fn let_as_expression2() { assert_infer!("let x = { let x = 1. }", "Float"); } #[test] fn string_concat_ok() { assert_infer!(r#" "1" <> "2" "#, "String"); } #[test] fn string_concat_ko_1() { assert_error!(r#" "1" <> 2 "#); } #[test] fn string_concat_ko_2() { assert_error!(r#" 1 <> "2" "#); } // https://github.com/gleam-lang/gleam/issues/1087 #[test] fn generic_inner_access() { assert_module_infer!( "pub type B(b) { B(value: b) } pub fn b_get_first(b: B(#(a))) { b.value.0 }", vec![("B", "fn(a) -> B(a)"), ("b_get_first", "fn(B(#(a))) -> a")], ); } // https://github.com/gleam-lang/gleam/issues/1093 #[test] fn fn_contextual_info() { assert_module_infer!( " type Box { Box(inner: Int) } fn call(argument: t, function: fn(t) -> tt) -> tt { function(argument) } pub fn main() { call(Box(1), fn(box) { box.inner }) } ", vec![("main", "fn() -> Int")], ); } // https://github.com/gleam-lang/gleam/issues/1519 #[test] fn permit_holes_in_fn_args_and_returns() { assert_module_infer!( "pub fn run(args: List(_)) -> List(_) { todo }", vec![("run", "fn(List(a)) -> List(b)")], ); } // Rattard's parser issue #[test] fn block_maths() { assert_module_infer!( "pub fn do(max, min) { { max -. min } /. { max +. min } }", vec![("do", "fn(Float, Float) -> Float")], ); } #[test] fn infer_label_shorthand_in_call_arg() { assert_module_infer!( " pub fn main() { let arg1 = 1 let arg2 = 1.0 let arg3 = False wibble(arg2:, arg3:, arg1:) } pub fn wibble(arg1 arg1: Int, arg2 arg2: Float, arg3 arg3: Bool) { Nil } ", vec![ ("main", "fn() -> Nil"), ("wibble", "fn(Int, Float, Bool) -> Nil") ], ); } #[test] fn infer_label_shorthand_in_constructor_arg() { assert_module_infer!( " pub type Wibble { Wibble(arg1: Int, arg2: Bool, arg3: Float) } pub fn main() { let arg1 = 1 let arg2 = True let arg3 = 1.0 Wibble(arg2:, arg3:, arg1:) } ", vec![ ("Wibble", "fn(Int, Bool, Float) -> Wibble"), ("main", "fn() -> Wibble"), ], ); } #[test] fn infer_label_shorthand_in_constant_constructor_arg() { assert_module_infer!( " pub type Wibble { Wibble(arg1: Int, arg2: Bool, arg3: Float) } pub const arg1 = 1 pub const arg2 = True pub const arg3 = 1.0 pub const wibble = Wibble(arg2:, arg3:, arg1:) ", vec![ ("Wibble", "fn(Int, Bool, Float) -> Wibble"), ("arg1", "Int"), ("arg2", "Bool"), ("arg3", "Float"), ("wibble", "Wibble") ], ); } #[test] fn infer_label_shorthand_in_pattern_arg() { assert_module_infer!( " pub type Wibble { Wibble(arg1: Int, arg2: Bool, arg3: Int) } pub fn main() { case Wibble(1, True, 2) { Wibble(arg2:, arg3:, arg1:) if arg2 -> arg1 * arg3 _ -> 0 } } ", vec![ ("Wibble", "fn(Int, Bool, Int) -> Wibble"), ("main", "fn() -> Int") ], ); } #[test] fn infer_label_shorthand_in_record_update_arg() { assert_module_infer!( " pub type Wibble { Wibble(arg1: Int, arg2: Bool, arg3: Float) } pub fn main() { let wibble = Wibble(1, True, 2.0) let arg3 = 3.0 let arg2 = False Wibble(..wibble, arg3:, arg2:) } ", vec![ ("Wibble", "fn(Int, Bool, Float) -> Wibble"), ("main", "fn() -> Wibble") ], ); } #[test] fn public_type_from_internal_module_has_internal_publicity() { let module = compile_module("thepackage/internal", "pub type Wibble", None, vec![]).unwrap(); let type_ = module.type_info.get_public_type("Wibble").unwrap(); assert!(type_.publicity.is_internal()); } #[test] fn internal_type_from_internal_module_has_internal_publicity() { let module = compile_module( "thepackage/internal", "@internal pub type Wibble", None, vec![], ) .unwrap(); let type_ = module.type_info.get_public_type("Wibble").unwrap(); assert!(type_.publicity.is_internal()); } #[test] fn private_type_from_internal_module_is_not_exposed_as_internal() { let module = compile_module("thepackage/internal", "type Wibble", None, vec![]).unwrap(); assert!(module.type_info.get_public_type("Wibble").is_none()); } #[test] fn assert_suitable_main_function_not_module_function() { let value = ValueConstructor { publicity: Publicity::Public, deprecation: Deprecation::NotDeprecated, type_: fn_(vec![], int()), variant: ValueConstructorVariant::ModuleConstant { documentation: None, location: Default::default(), module: "module".into(), literal: Constant::Int { location: Default::default(), value: "1".into(), int_value: 1.into(), }, implementations: Implementations { gleam: true, uses_erlang_externals: false, uses_javascript_externals: false, can_run_on_erlang: true, can_run_on_javascript: true, }, name: "main".into(), }, }; assert!( assert_suitable_main_function(&value, &"module".into(), Origin::Src, Target::Erlang) .is_err(), ); } #[test] fn assert_suitable_main_function_wrong_arity() { let value = ValueConstructor { publicity: Publicity::Public, deprecation: Deprecation::NotDeprecated, type_: fn_(vec![], int()), variant: ValueConstructorVariant::ModuleFn { name: "name".into(), field_map: None, arity: 1, documentation: None, location: Default::default(), module: "module".into(), external_erlang: None, external_javascript: None, implementations: Implementations { gleam: true, uses_erlang_externals: false, uses_javascript_externals: false, can_run_on_erlang: true, can_run_on_javascript: true, }, purity: Purity::Pure, }, }; assert!( assert_suitable_main_function(&value, &"module".into(), Origin::Src, Target::Erlang) .is_err(), ); } #[test] fn assert_suitable_main_function_ok() { let value = ValueConstructor { publicity: Publicity::Public, deprecation: Deprecation::NotDeprecated, type_: fn_(vec![], int()), variant: ValueConstructorVariant::ModuleFn { name: "name".into(), field_map: None, arity: 0, documentation: None, location: Default::default(), module: "module".into(), external_erlang: None, external_javascript: None, implementations: Implementations { gleam: true, uses_erlang_externals: false, uses_javascript_externals: false, can_run_on_erlang: true, can_run_on_javascript: true, }, purity: Purity::Pure, }, }; assert!( assert_suitable_main_function(&value, &"module".into(), Origin::Src, Target::Erlang) .is_ok(), ); } #[test] fn assert_suitable_main_function_erlang_not_supported() { let value = ValueConstructor { publicity: Publicity::Public, deprecation: Deprecation::NotDeprecated, type_: fn_(vec![], int()), variant: ValueConstructorVariant::ModuleFn { name: "name".into(), field_map: None, arity: 0, documentation: None, location: Default::default(), module: "module".into(), external_erlang: Some(("wibble".into(), "wobble".into())), external_javascript: Some(("wobble".into(), "wibble".into())), implementations: Implementations { gleam: false, uses_erlang_externals: true, uses_javascript_externals: true, can_run_on_erlang: false, can_run_on_javascript: true, }, purity: Purity::Impure, }, }; assert!( assert_suitable_main_function(&value, &"module".into(), Origin::Src, Target::Erlang) .is_err(), ); } #[test] fn assert_suitable_main_function_javascript_not_supported() { let value = ValueConstructor { publicity: Publicity::Public, deprecation: Deprecation::NotDeprecated, type_: fn_(vec![], int()), variant: ValueConstructorVariant::ModuleFn { name: "name".into(), field_map: None, arity: 0, documentation: None, location: Default::default(), module: "module".into(), external_erlang: Some(("wibble".into(), "wobble".into())), external_javascript: Some(("wobble".into(), "wibble".into())), implementations: Implementations { gleam: false, uses_erlang_externals: true, uses_javascript_externals: true, can_run_on_erlang: true, can_run_on_javascript: false, }, purity: Purity::Impure, }, }; assert!( assert_suitable_main_function(&value, &"module".into(), Origin::Src, Target::JavaScript) .is_err(), ); } #[test] fn pipe_with_annonymous_unannotated_functions() { assert_module_infer!( r#" pub fn main() { let a = 1 |> fn (x) { #(x, x + 1) } |> fn (x) { x.0 } |> fn (x) { x } } "#, vec![("main", "fn() -> Int")] ); } #[test] fn pipe_with_annonymous_unannotated_functions_wrong_arity1() { assert_module_error!( r#" pub fn main() { let a = 1 |> fn (x) { #(x, x + 1) } |> fn (x, y) { x.0 } |> fn (x) { x } } "# ); } #[test] fn pipe_with_annonymous_unannotated_functions_wrong_arity2() { assert_module_error!( r#" pub fn main() { let a = 1 |> fn (x) { #(x, x + 1) } |> fn (x) { x.0 } |> fn (x, y) { x } } "# ); } #[test] fn pipe_with_annonymous_unannotated_functions_wrong_arity3() { assert_module_error!( r#" pub fn main() { let a = 1 |> fn (x) { #(x, x + 1) } |> fn (x) { x.0 } |> fn () { x } } "# ); } #[test] fn pipe_with_annonymous_mixed_functions() { assert_module_infer!( r#" pub fn main() { let a = "abc" |> fn (x) { #(x, x <> "d") } |> fn (x) { x.0 } |> fn (x: String) { x } } "#, vec![("main", "fn() -> String")] ); } #[test] fn pipe_with_annonymous_functions_using_structs() { // https://github.com/gleam-lang/gleam/issues/2504 assert_module_infer!( r#" type Date { Date(day: Day) } type Day { Day(year: Int) } fn now() -> Date { Date(Day(2024)) } fn get_day(date: Date) -> Day { date.day } pub fn main() { now() |> get_day() |> fn (it) { it.year } } "#, vec![("main", "fn() -> Int")] ); } #[test] fn labelled_argument_ordering() { // https://github.com/gleam-lang/gleam/issues/3671 assert_module_infer!( " type A { A } type B { B } type C { C } type D { D } fn wibble(a a: A, b b: B, c c: C, d d: D) { Nil } pub fn main() { wibble(A, C, D, b: B) wibble(A, C, D, b: B) wibble(B, C, D, a: A) wibble(B, C, a: A, d: D) wibble(B, C, d: D, a: A) wibble(B, D, a: A, c: C) wibble(B, D, c: C, a: A) wibble(C, D, b: B, a: A) } ", vec![("main", "fn() -> Nil")] ); } #[test] fn variant_inference_allows_inference() { // https://github.com/gleam-lang/gleam/pull/3647#issuecomment-2423146977 assert_module_infer!( " pub type Wibble { Wibble(a: Int) Wobble(b: Int) } pub fn do_a_thing(wibble) { case wibble { Wibble(..) -> wibble.a _ -> todo } wibble } ", vec![ ("Wibble", "fn(Int) -> Wibble"), ("Wobble", "fn(Int) -> Wibble"), ("do_a_thing", "fn(Wibble) -> Wibble") ] ); } #[test] fn variant_inference_allows_inference2() { // https://github.com/gleam-lang/gleam/pull/3647#issuecomment-2423146977 assert_module_infer!( " pub type Box(a) { Box(inner: a) UnBox } pub fn rebox(box) { case box { Box(..) -> Box(box.inner + 1) UnBox -> UnBox } } ", vec![ ("Box", "fn(a) -> Box(a)"), ("UnBox", "Box(a)"), ("rebox", "fn(Box(Int)) -> Box(Int)") ] ); } #[test] // https://github.com/gleam-lang/gleam/issues/3861 fn variant_inference_on_literal_record() { assert_module_error!( " pub type Wibble { Wibble Wobble } pub fn main() { case Wibble, Wobble { Wibble, Wibble -> todo } } " ); } #[test] fn variant_inference_on_prelude_types() { assert_module_infer!( " pub fn main() { let always_ok = Ok(10) case always_ok { Ok(1) -> 1 Ok(2) -> 3 _ -> panic } } ", vec![("main", "fn() -> Int")] ); } #[test] fn variant_inference_with_let_assert() { assert_module_infer!( " type Wibble { Wibble(n: Int, b: Bool) Wobble } pub fn main() { let wibble = wibble() let assert Wibble(..) = wibble wibble.n } fn wibble() { Wibble(1, True) } ", vec![("main", "fn() -> Int")] ); } #[test] fn variant_inference_with_let_assert_and_alias() { assert_module_infer!( " type Wibble { Wibble(n: Int, b: Bool) Wobble } pub fn main() { let assert Wibble(..) as wibble = wibble() wibble.n } fn wibble() { Wibble(1, True) } ", vec![("main", "fn() -> Int")] ); } #[test] fn private_types_not_available_in_other_modules() { assert_module_error!( ("wibble", "type Wibble"), " import wibble type Wibble { Wibble(wibble.Wibble) } " ); } #[test] fn unlabelled_argument_not_allowed_after_labelled_argument() { assert_module_error!( " pub type Bad { Bad(labelled: Int, Float) } " ); } #[test] fn function_parameter_errors_do_not_stop_inference() { assert_module_error!( " pub fn wibble(x: NonExistent) { 1 + False } " ); } // https://github.com/gleam-lang/gleam/issues/4287 #[test] fn no_stack_overflow_for_nested_use() { assert_module_infer!( ( "gleam/dynamic/decode", " pub fn string() { Nil } pub fn field(name, decoder, callback) { callback(Nil) } " ), r#" import gleam/dynamic/decode pub fn main() { use _n1 <- decode.field("n1", decode.string) use _n2 <- decode.field("n2", decode.string) use _n3 <- decode.field("n3", decode.string) use _n4 <- decode.field("n4", decode.string) use _n5 <- decode.field("n5", decode.string) use _n6 <- decode.field("n6", decode.string) use _n7 <- decode.field("n7", decode.string) use _n8 <- decode.field("n8", decode.string) use _n9 <- decode.field("n9", decode.string) use _n10 <- decode.field("n10", decode.string) use _n11 <- decode.field("n11", decode.string) use _n12 <- decode.field("n12", decode.string) use _n13 <- decode.field("n13", decode.string) use _n14 <- decode.field("n14", decode.string) use _n15 <- decode.field("n15", decode.string) use _n16 <- decode.field("n16", decode.string) use _n17 <- decode.field("n17", decode.string) use _n18 <- decode.field("n18", decode.string) use _n19 <- decode.field("n19", decode.string) use _n20 <- decode.field("n20", decode.string) use _n21 <- decode.field("n21", decode.string) use _n22 <- decode.field("n22", decode.string) use _n23 <- decode.field("n23", decode.string) use _n24 <- decode.field("n24", decode.string) use _n25 <- decode.field("n25", decode.string) use _n26 <- decode.field("n26", decode.string) use _n27 <- decode.field("n27", decode.string) use _n28 <- decode.field("n28", decode.string) use _n29 <- decode.field("n29", decode.string) use _n30 <- decode.field("n30", decode.string) use _n31 <- decode.field("n31", decode.string) use _n32 <- decode.field("n32", decode.string) use _n33 <- decode.field("n33", decode.string) use _n34 <- decode.field("n34", decode.string) use _n35 <- decode.field("n35", decode.string) use _n36 <- decode.field("n36", decode.string) use _n37 <- decode.field("n37", decode.string) use _n38 <- decode.field("n38", decode.string) use _n39 <- decode.field("n39", decode.string) use _n40 <- decode.field("n40", decode.string) use _n41 <- decode.field("n41", decode.string) use _n42 <- decode.field("n42", decode.string) use _n43 <- decode.field("n43", decode.string) use _n44 <- decode.field("n44", decode.string) use _n45 <- decode.field("n45", decode.string) use _n46 <- decode.field("n46", decode.string) use _n47 <- decode.field("n47", decode.string) use _n48 <- decode.field("n48", decode.string) use _n49 <- decode.field("n49", decode.string) use _n50 <- decode.field("n50", decode.string) use _n51 <- decode.field("n51", decode.string) use _n52 <- decode.field("n52", decode.string) use _n53 <- decode.field("n53", decode.string) use _n54 <- decode.field("n54", decode.string) use _n55 <- decode.field("n55", decode.string) Nil } "#, vec![("main", "fn() -> Nil")] ); } // https://github.com/gleam-lang/gleam/issues/4883 #[test] fn variant_inference_ignores_echo() { assert_module_infer!( " pub type Wibble { Wibble(wibble: Int, wobble: String) Wobble(a: String, b: Int) } pub fn get_int(w: Wibble) { case echo w { Wibble(..) -> w.wibble Wobble(..) -> w.b } } ", vec![ ("Wibble", "fn(Int, String) -> Wibble"), ("Wobble", "fn(String, Int) -> Wibble"), ("get_int", "fn(Wibble) -> Int"), ] ); } #[test] fn correct_type_check_for_multiple_mutually_recursive_functions() { assert_module_error!( r#" pub fn wibble(id) { wobble(id, True) } pub fn wobble(id, bool) { case bool { True -> wibble(id) False -> wubble(id, bool) } } pub fn wubble(id, bool) { case bool { True -> final(id) False -> wobble(id, bool) } } pub fn final(id) { let #(a, b) = id echo #(a, b) } pub fn main() { wibble(#("a", "b")) wibble(2) } "# ); } #[test] fn prepend_constant_list() { assert_module_infer!( " const list = [3, 4, 5] pub const full_list = [1, 2, ..list] ", vec![("full_list", "List(Int)")], ); } #[test] fn prepend_constant_list_from_other_module() { assert_module_infer!( ("mod", "pub const list = [3, 4, 5]"), " import mod pub const full_list = [1, 2, ..mod.list] ", vec![("full_list", "List(Int)")], ); } #[test] fn prepend_constant_list_wrong_type() { assert_module_error!( " const pi = 3.14 pub const full_list = [1.0, 2.0, ..pi] " ); } #[test] fn prepend_constant_list_wrong_element_type() { assert_module_error!( " const list = [3, 4, 5] pub const full_list = [1.0, 2.0, ..list] " ); } ================================================ FILE: compiler-core/src/type_.rs ================================================ pub(crate) mod environment; pub mod error; pub(crate) mod expression; pub(crate) mod fields; pub(crate) mod hydrator; pub(crate) mod pattern; pub(crate) mod pipe; pub(crate) mod prelude; pub mod pretty; pub mod printer; #[cfg(test)] pub mod tests; use camino::Utf8PathBuf; use ecow::EcoString; pub use environment::*; pub use error::{Error, Problems, UnifyErrorSituation, Warning}; pub(crate) use expression::ExprTyper; use expression::Purity; pub use fields::FieldMap; use hexpm::version::Version; pub use prelude::*; use printer::Names; use crate::{ ast::{ ArgNames, BitArraySegment, CallArg, Constant, DefinitionLocation, Pattern, Publicity, SrcSpan, TypedConstant, TypedExpr, TypedPattern, TypedPatternBitArraySegment, UntypedMultiPattern, UntypedPattern, UntypedRecordUpdateArg, }, bit_array, build::{Origin, Target}, inline::InlinableFunction, line_numbers::LineNumbers, reference::ReferenceMap, type_::expression::Implementations, }; use error::*; use hydrator::Hydrator; use itertools::Itertools; use std::{ cell::RefCell, collections::{HashMap, HashSet}, ops::Deref, sync::Arc, }; pub trait HasType { fn type_(&self) -> Arc; } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum Type { /// A nominal (named) type such as `Int`, `Float`, or a programmer defined /// custom type such as `Person`. The type can take other types as /// arguments (aka "generics" or "parametric polymorphism"). /// /// If the type is defined in the Gleam prelude the `module` field will be /// the string "gleam", otherwise it will contain the name of the module /// that defines the type. /// Named { publicity: Publicity, package: EcoString, module: EcoString, name: EcoString, arguments: Vec>, /// Which variant of the types this value is, if it is known from variant inference. /// This allows us to permit certain operations when we know this, /// such as record updates for multi-constructor types, or field access /// for fields not shared between type variants. For example: /// /// ```gleam /// type Wibble { /// Wibble(wibble: Int, other, Int) /// Wobble(wobble: Int, something: Int) /// } /// /// fn add_one(some_wibble: Wibble) -> Wibble { /// case some_wibble { /// Wibble(..) as wibble -> Wibble(..wibble, other: wibble.other + 1) /// Wobble(..) as wobble -> Wobble(..wobble, something: wobble.something + 1) /// } /// } /// ``` /// /// Here, the `wibble` variable has an inferred variant of `0`, since we know it's /// of the `Wibble` variant. This means we can safely update it using the `Wibble` /// constructor, and access the `other` field, which is only present in that variant. /// /// However, the parameter `some_wibble` has no known variant; it could be either of the variants, /// so we can't allow any of that until we pattern match on it. /// inferred_variant: Option, }, /// The type of a function. It takes arguments and returns a value. /// Fn { arguments: Vec>, return_: Arc, }, /// A type variable. See the contained `TypeVar` enum for more information. /// Var { type_: Arc> }, /// A tuple is an ordered collection of 0 or more values, each of which /// can have a different type, so the `tuple` type is the sum of all the /// contained types. /// Tuple { elements: Vec> }, } impl Type { pub fn is_result_constructor(&self) -> bool { match self { Type::Fn { return_, .. } => return_.is_result(), Type::Var { type_ } => type_.borrow().is_result_constructor(), Type::Named { .. } | Type::Tuple { .. } => false, } } pub fn is_result(&self) -> bool { match self { Self::Named { name, module, .. } => "Result" == name && is_prelude_module(module), Self::Var { type_ } => type_.borrow().is_result(), Self::Fn { .. } | Self::Tuple { .. } => false, } } pub fn is_named(&self) -> bool { match self { Self::Named { .. } => true, Self::Var { .. } | Self::Fn { .. } | Self::Tuple { .. } => false, } } pub fn result_ok_type(&self) -> Option> { match self { Self::Named { module, name, arguments, .. } if "Result" == name && is_prelude_module(module) => arguments.first().cloned(), Self::Var { type_ } => type_.borrow().result_ok_type(), Self::Named { .. } | Self::Tuple { .. } | Type::Fn { .. } => None, } } pub fn result_types(&self) -> Option<(Arc, Arc)> { match self { Self::Named { module, name, arguments, .. } if "Result" == name && is_prelude_module(module) => { Some((arguments.first().cloned()?, arguments.get(1).cloned()?)) } Self::Var { type_ } => type_.borrow().result_types(), Self::Named { .. } | Self::Tuple { .. } | Type::Fn { .. } => None, } } pub fn is_unbound(&self) -> bool { match self { Self::Var { type_ } => type_.borrow().is_unbound(), Self::Named { .. } | Self::Fn { .. } | Self::Tuple { .. } => false, } } pub fn is_variable(&self) -> bool { match self { Self::Var { type_ } => type_.borrow().is_variable(), Self::Named { .. } | Self::Fn { .. } | Self::Tuple { .. } => false, } } pub fn return_type(&self) -> Option> { match self { Self::Fn { return_, .. } => Some(return_.clone()), Self::Var { type_ } => type_.borrow().return_type(), Self::Named { .. } | Self::Tuple { .. } => None, } } pub fn fn_types(&self) -> Option<(Vec>, Arc)> { match self { Self::Fn { arguments, return_, .. } => Some((arguments.clone(), return_.clone())), Self::Var { type_ } => type_.borrow().fn_types(), Self::Named { .. } | Self::Tuple { .. } => None, } } /// Gets the types inside of a tuple. Returns `None` if the type is not a tuple. pub fn tuple_types(&self) -> Option>> { match self { Self::Tuple { elements } => Some(elements.clone()), Self::Var { type_, .. } => type_.borrow().tuple_types(), Self::Named { .. } | Self::Fn { .. } => None, } } /// Gets the argument types for a type constructor. Returns `None` if the type /// does not lead to a type constructor. pub fn constructor_types(&self) -> Option>> { match self { Self::Named { arguments, .. } => Some(arguments.clone()), Self::Var { type_, .. } => type_.borrow().constructor_types(), Self::Fn { .. } | Self::Tuple { .. } => None, } } /// If the type is a Gleam's prelude's List this will return its wrapped /// type. pub fn list_type(&self) -> Option> { match self { Type::Named { publicity: Publicity::Public, name, module, package, arguments, inferred_variant: _, } if package == PRELUDE_PACKAGE_NAME && module == PRELUDE_MODULE_NAME && name == LIST => { match arguments.as_slice() { [inner_type] => Some(inner_type.clone()), [] | [_, _, ..] => None, } } Type::Named { .. } | Type::Fn { .. } | Type::Var { .. } | Type::Tuple { .. } => None, } } pub fn list(inner_type: Arc) -> Self { Type::Named { publicity: Publicity::Public, package: PRELUDE_PACKAGE_NAME.into(), module: PRELUDE_MODULE_NAME.into(), name: LIST.into(), arguments: vec![inner_type], inferred_variant: None, } } #[must_use] fn is_fun(&self) -> bool { match self { Self::Fn { .. } => true, Self::Var { type_ } => type_.borrow().is_fun(), Self::Named { .. } | Self::Tuple { .. } => false, } } pub fn is_nil(&self) -> bool { match self { Self::Named { module, name, .. } if "Nil" == name && is_prelude_module(module) => true, Self::Var { type_ } => type_.borrow().is_nil(), Self::Named { .. } | Self::Fn { .. } | Self::Tuple { .. } => false, } } pub fn is_bit_array(&self) -> bool { match self { Self::Named { module, name, .. } if "BitArray" == name && is_prelude_module(module) => { true } Self::Var { type_ } => type_.borrow().is_bit_array(), Self::Named { .. } | Self::Fn { .. } | Self::Tuple { .. } => false, } } pub fn is_utf_codepoint(&self) -> bool { match self { Self::Named { module, name, .. } if "UtfCodepoint" == name && is_prelude_module(module) => { true } Self::Var { type_ } => type_.borrow().is_utf_codepoint(), Self::Named { .. } | Self::Fn { .. } | Self::Tuple { .. } => false, } } pub fn is_bool(&self) -> bool { match self { Self::Named { module, name, .. } if "Bool" == name && is_prelude_module(module) => true, Self::Var { type_ } => type_.borrow().is_bool(), Self::Named { .. } | Self::Fn { .. } | Self::Tuple { .. } => false, } } pub fn is_int(&self) -> bool { match self { Self::Named { module, name, .. } if "Int" == name && is_prelude_module(module) => true, Self::Var { type_ } => type_.borrow().is_int(), Self::Named { .. } | Self::Fn { .. } | Self::Tuple { .. } => false, } } pub fn is_float(&self) -> bool { match self { Self::Named { module, name, .. } if "Float" == name && is_prelude_module(module) => { true } Self::Var { type_ } => type_.borrow().is_float(), Self::Named { .. } | Self::Fn { .. } | Self::Tuple { .. } => false, } } pub fn is_string(&self) -> bool { match self { Self::Named { module, name, .. } if "String" == name && is_prelude_module(module) => { true } Self::Var { type_ } => type_.borrow().is_string(), Self::Named { .. } | Self::Fn { .. } | Self::Tuple { .. } => false, } } pub fn is_list(&self) -> bool { match self { Self::Named { module, name, .. } if "List" == name && is_prelude_module(module) => true, Self::Var { type_ } => type_.borrow().is_list(), Self::Named { .. } | Self::Fn { .. } | Self::Tuple { .. } => false, } } pub fn named_type_name(&self) -> Option<(EcoString, EcoString)> { match self { Self::Named { module, name, .. } => Some((module.clone(), name.clone())), Self::Var { type_ } => type_.borrow().named_type_name(), Self::Fn { .. } | Self::Tuple { .. } => None, } } pub fn named_type_information(&self) -> Option<(EcoString, EcoString, Vec>)> { match self { Self::Named { module, name, arguments, .. } => Some((module.clone(), name.clone(), arguments.clone())), Self::Var { type_ } => type_.borrow().named_type_information(), Self::Fn { .. } | Self::Tuple { .. } => None, } } pub fn set_custom_type_variant(&mut self, index: u16) { match self { Type::Named { inferred_variant, .. } => *inferred_variant = Some(index), Type::Var { type_ } => type_.borrow_mut().set_custom_type_variant(index), Type::Fn { .. } | Type::Tuple { .. } => {} } } pub fn generalise_custom_type_variant(&mut self) { match self { Type::Named { inferred_variant, .. } => *inferred_variant = None, Type::Var { type_ } => type_.borrow_mut().generalise_custom_type_variant(), Type::Tuple { elements } => { for element in elements { Arc::make_mut(element).generalise_custom_type_variant(); } } Type::Fn { arguments, return_ } => { for argument in arguments { Arc::make_mut(argument).generalise_custom_type_variant(); } Arc::make_mut(return_).generalise_custom_type_variant(); } } } pub fn custom_type_inferred_variant(&self) -> Option { match self { Type::Named { inferred_variant, .. } => *inferred_variant, Type::Var { type_ } => type_.borrow().custom_type_inferred_variant(), Type::Fn { .. } | Type::Tuple { .. } => None, } } /// Get the args for the type if the type is a specific `Type::Named`. /// Returns None if the type is not a `Type::Named` or is an incorrect `Type:Named` /// /// This function is currently only used for finding the `List` type. /// // TODO: specialise this to just List. pub fn named_type_arguments( &self, publicity: Publicity, package: &str, module: &str, name: &str, arity: usize, environment: &mut Environment<'_>, ) -> Option>> { match self { Self::Named { module: m, name: n, arguments, .. } => { if module == m && name == n && arguments.len() == arity { Some(arguments.clone()) } else { None } } Self::Var { type_ } => { let arguments: Vec<_> = match type_.borrow().deref() { TypeVar::Link { type_ } => { return type_.named_type_arguments( publicity, package, module, name, arity, environment, ); } TypeVar::Unbound { .. } => { (0..arity).map(|_| environment.new_unbound_var()).collect() } TypeVar::Generic { .. } => return None, }; // We are an unbound type variable! So convert us to a type link // to the desired type. *type_.borrow_mut() = TypeVar::Link { type_: Arc::new(Self::Named { name: name.into(), package: package.into(), module: module.into(), arguments: arguments.clone(), publicity, inferred_variant: None, }), }; Some(arguments) } Self::Fn { .. } | Self::Tuple { .. } => None, } } pub fn find_private_type(&self) -> Option { match self { Self::Named { publicity: Publicity::Private, .. } => Some(self.clone()), Self::Named { arguments, .. } => { arguments.iter().find_map(|type_| type_.find_private_type()) } Self::Tuple { elements, .. } => { elements.iter().find_map(|type_| type_.find_private_type()) } Self::Fn { return_, arguments, .. } => return_ .find_private_type() .or_else(|| arguments.iter().find_map(|type_| type_.find_private_type())), Self::Var { type_, .. } => match type_.borrow().deref() { TypeVar::Unbound { .. } => None, TypeVar::Generic { .. } => None, TypeVar::Link { type_, .. } => type_.find_private_type(), }, } } pub fn find_internal_type(&self) -> Option { match self { Self::Named { publicity, .. } if publicity.is_internal() => Some(self.clone()), Self::Named { arguments, .. } => arguments .iter() .find_map(|type_| type_.find_internal_type()), Self::Tuple { elements, .. } => { elements.iter().find_map(|type_| type_.find_internal_type()) } Self::Fn { return_, arguments, .. } => return_.find_internal_type().or_else(|| { arguments .iter() .find_map(|type_| type_.find_internal_type()) }), Self::Var { type_, .. } => match type_.borrow().deref() { TypeVar::Unbound { .. } | TypeVar::Generic { .. } => None, TypeVar::Link { type_, .. } => type_.find_internal_type(), }, } } pub fn fn_arity(&self) -> Option { match self { Self::Fn { arguments, .. } => Some(arguments.len()), Self::Named { .. } | Self::Var { .. } | Self::Tuple { .. } => None, } } /// If the type is named, return its publicity. /// pub fn named_type_publicity(&self) -> Option { match self { Type::Named { publicity, .. } => Some(*publicity), Type::Fn { .. } | Type::Var { .. } | Type::Tuple { .. } => None, } } #[must_use] /// Returns `true` is the two types are the same. This differs from the /// standard `Eq` implementation as it also follows all links to check if /// two types are really the same. /// pub fn same_as(&self, other: &Self) -> bool { match (self, other) { (Type::Named { .. }, Type::Fn { .. } | Type::Tuple { .. }) => false, (one @ Type::Named { .. }, Type::Var { type_ }) => { type_.as_ref().borrow().same_as_other_type(one) } // When comparing two types we don't care about the inferred variant: // `True` has the same type as `False`, even if the inferred variants // differ. ( Type::Named { publicity, package, module, name, arguments, inferred_variant: _, }, Type::Named { publicity: other_publicity, package: other_package, module: other_module, name: other_name, arguments: other_arguments, inferred_variant: _, }, ) => { publicity == other_publicity && package == other_package && module == other_module && name == other_name && arguments.len() == other_arguments.len() && arguments .iter() .zip(other_arguments) .all(|(one, other)| one.same_as(other)) } (Type::Fn { .. }, Type::Named { .. } | Type::Tuple { .. }) => false, (one @ Type::Fn { .. }, Type::Var { type_ }) => { type_.as_ref().borrow().same_as_other_type(one) } ( Type::Fn { arguments, return_ }, Type::Fn { arguments: other_arguments, return_: other_return, }, ) => { arguments.len() == other_arguments.len() && arguments .iter() .zip(other_arguments) .all(|(one, other)| one.same_as(other)) && return_.same_as(other_return) } (Type::Var { type_ }, other) => type_.as_ref().borrow().same_as_other_type(other), (Type::Tuple { .. }, Type::Fn { .. } | Type::Named { .. }) => false, (one @ Type::Tuple { .. }, Type::Var { type_ }) => { type_.as_ref().borrow().same_as_other_type(one) } ( Type::Tuple { elements }, Type::Tuple { elements: other_elements, }, ) => { elements.len() == other_elements.len() && elements .iter() .zip(other_elements) .all(|(one, other)| one.same_as(other)) } } } } impl TypeVar { #[must_use] fn same_as_other_type(&self, other: &Type) -> bool { match (self, other) { (TypeVar::Unbound { .. }, _) => true, (TypeVar::Link { type_ }, other) => type_.same_as(other), ( TypeVar::Generic { .. }, Type::Named { .. } | Type::Fn { .. } | Type::Tuple { .. }, ) => false, (one @ TypeVar::Generic { .. }, Type::Var { type_ }) => { one.same_as(&type_.as_ref().borrow()) } } } #[must_use] fn same_as(&self, other: &Self) -> bool { match (self, other) { (TypeVar::Unbound { .. }, _) | (_, TypeVar::Unbound { .. }) => true, (TypeVar::Link { type_ }, TypeVar::Link { type_: other_type }) => { type_.same_as(other_type) } (TypeVar::Link { type_ }, other @ TypeVar::Generic { .. }) => { other.same_as_other_type(type_) } (TypeVar::Generic { id }, TypeVar::Generic { id: other_id }) => id == other_id, (one @ TypeVar::Generic { .. }, TypeVar::Link { type_ }) => { one.same_as_other_type(type_) } } } } pub fn collapse_links(t: Arc) -> Arc { if let Type::Var { type_ } = t.deref() && let TypeVar::Link { type_ } = type_.borrow().deref() { return collapse_links(type_.clone()); } t } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct AccessorsMap { pub publicity: Publicity, pub type_: Arc, pub shared_accessors: HashMap, pub variant_specific_accessors: Vec>, pub variant_positional_accessors: Vec>>, } impl AccessorsMap { pub fn accessors_for_variant( &self, inferred_variant: Option, ) -> &HashMap { inferred_variant .and_then(|index| self.variant_specific_accessors.get(index as usize)) .unwrap_or(&self.shared_accessors) } pub fn positional_accessors(&self, inferred_variant: u16) -> Option<&Vec>> { self.variant_positional_accessors .get(inferred_variant as usize) } } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct RecordAccessor { // TODO: smaller int. Doesn't need to be this big pub index: u64, pub label: EcoString, pub type_: Arc, pub documentation: Option, } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum ValueConstructorVariant { /// A locally defined variable or function parameter LocalVariable { location: SrcSpan, origin: VariableOrigin, }, /// A module constant ModuleConstant { documentation: Option, location: SrcSpan, module: EcoString, name: EcoString, literal: Constant, EcoString>, implementations: Implementations, }, /// A function belonging to the module ModuleFn { name: EcoString, field_map: Option, module: EcoString, arity: usize, location: SrcSpan, documentation: Option, implementations: Implementations, external_erlang: Option<(EcoString, EcoString)>, external_javascript: Option<(EcoString, EcoString)>, purity: Purity, }, /// A constructor for a custom type Record { name: EcoString, arity: u16, field_map: Option, location: SrcSpan, module: EcoString, variants_count: u16, variant_index: u16, documentation: Option, }, } impl ValueConstructorVariant { fn to_module_value_constructor( &self, type_: Arc, module_name: &EcoString, function_name: &EcoString, ) -> ModuleValueConstructor { match self { Self::Record { name, arity, field_map, location, documentation, variant_index, .. } => ModuleValueConstructor::Record { name: name.clone(), variant_index: *variant_index, field_map: field_map.clone(), arity: *arity, type_, location: *location, documentation: documentation.clone(), }, // TODO: remove this clone with an rc clone Self::ModuleConstant { documentation, literal, location, .. } => ModuleValueConstructor::Constant { literal: literal.clone(), location: *location, documentation: documentation.clone(), }, Self::LocalVariable { location, .. } => ModuleValueConstructor::Fn { name: function_name.clone(), module: module_name.clone(), external_erlang: None, external_javascript: None, documentation: None, location: *location, field_map: None, purity: Purity::Impure, }, Self::ModuleFn { name, module, location, documentation, field_map, external_erlang, external_javascript, purity, .. } => ModuleValueConstructor::Fn { name: name.clone(), module: module.clone(), documentation: documentation.clone(), external_erlang: external_erlang.clone(), external_javascript: external_javascript.clone(), location: *location, field_map: field_map.clone(), purity: *purity, }, } } pub fn definition_location(&self) -> SrcSpan { match self { ValueConstructorVariant::LocalVariable { location, .. } | ValueConstructorVariant::ModuleConstant { location, .. } | ValueConstructorVariant::ModuleFn { location, .. } | ValueConstructorVariant::Record { location, .. } => *location, } } /// Returns `true` if the variant is [`LocalVariable`]. pub fn is_local_variable(&self) -> bool { matches!(self, Self::LocalVariable { .. }) } /// Returns `true` if the variant is a local variable generated by the compiler. #[must_use] pub fn is_generated_variable(&self) -> bool { match self { ValueConstructorVariant::LocalVariable { origin, .. } => { matches!(origin.syntax, VariableSyntax::Generated) } ValueConstructorVariant::ModuleConstant { .. } | ValueConstructorVariant::ModuleFn { .. } | ValueConstructorVariant::Record { .. } => false, } } /// Returns `true` if the value constructor variant is [`ModuleFn`]. /// /// [`ModuleFn`]: ValueConstructorVariant::ModuleFn #[must_use] pub fn is_module_fn(&self) -> bool { matches!(self, Self::ModuleFn { .. }) } pub fn is_record(&self) -> bool { matches!(self, Self::Record { .. }) } pub fn implementations(&self) -> Implementations { match self { ValueConstructorVariant::Record { .. } | ValueConstructorVariant::LocalVariable { .. } => Implementations { gleam: true, can_run_on_erlang: true, can_run_on_javascript: true, uses_javascript_externals: false, uses_erlang_externals: false, }, ValueConstructorVariant::ModuleFn { implementations, .. } | ValueConstructorVariant::ModuleConstant { implementations, .. } => *implementations, } } fn record_field_map(&self) -> Option<&FieldMap> { match self { ValueConstructorVariant::LocalVariable { .. } | ValueConstructorVariant::ModuleConstant { .. } | ValueConstructorVariant::ModuleFn { .. } => None, ValueConstructorVariant::Record { field_map, .. } => field_map.as_ref(), } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ModuleValueConstructor { Record { name: EcoString, variant_index: u16, arity: u16, type_: Arc, field_map: Option, location: SrcSpan, documentation: Option, }, Fn { location: SrcSpan, /// The name of the module and the function /// This will be the module that this constructor belongs to /// and the name that was used for the function. module: EcoString, name: EcoString, /// If this is an `external` function, these will hold the name of the /// external module and function. /// /// This function has module "themodule" and name "wibble" /// pub fn wibble() { Nil } /// /// This function has module "themodule" and name "wibble" /// and erlang external "other" and "whoop". /// @external(erlang, "other", "whoop") /// pub fn wibble() -> Nil /// external_erlang: Option<(EcoString, EcoString)>, external_javascript: Option<(EcoString, EcoString)>, field_map: Option, documentation: Option, purity: Purity, }, Constant { literal: TypedConstant, location: SrcSpan, documentation: Option, }, } impl ModuleValueConstructor { pub fn location(&self) -> SrcSpan { match self { ModuleValueConstructor::Fn { location, .. } | ModuleValueConstructor::Record { location, .. } | ModuleValueConstructor::Constant { location, .. } => *location, } } pub fn get_documentation(&self) -> Option<&str> { match self { ModuleValueConstructor::Record { documentation, .. } | ModuleValueConstructor::Fn { documentation, .. } | ModuleValueConstructor::Constant { documentation, .. } => documentation.as_deref(), } } /// Returns the purity of this value constructor if it is called as a function. /// Referencing a module value by itself is always pure, but calling is as a /// function might not be. pub fn called_function_purity(&self) -> Purity { match self { // If we call a module constant or local variable as a function, we // no longer have enough information to determine its purity. For // example: // // ```gleam // const function1 = io.println // const function2 = function.identity // // pub fn main() { // function1("Hello") // function2("Hello") // } // ``` // // At this point, we don't have any information about the purity of // the `function1` and `function2` functions, and must return // `Purity::Unknown`. See the documentation for the `Purity` type // for more information on why this is the case. ModuleValueConstructor::Constant { .. } => Purity::Unknown, // Constructing records is always pure ModuleValueConstructor::Record { .. } => Purity::Pure, ModuleValueConstructor::Fn { purity, .. } => *purity, } } } #[derive(Debug, Clone)] pub struct ModuleFunction { pub package: EcoString, } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct ModuleInterface { pub name: EcoString, pub origin: Origin, pub package: EcoString, pub types: HashMap, pub types_value_constructors: HashMap, pub values: HashMap, pub accessors: HashMap, /// Used for mapping to original source locations on disk pub line_numbers: LineNumbers, /// Used for determining the source path of the module on disk pub src_path: Utf8PathBuf, /// Whether the module is internal or not. Internal modules are technically /// importable by other packages but to do so is violating the contract of /// the package and as such is not recommended. pub is_internal: bool, /// Warnings emitted during analysis of this module. pub warnings: Vec, /// The minimum Gleam version needed to use this module. pub minimum_required_version: Version, pub type_aliases: HashMap, pub documentation: Vec, /// Wether there's any echo in the module. pub contains_echo: bool, pub references: References, /// Functions which can be inlined pub inline_functions: HashMap, } impl ModuleInterface { pub fn contains_todo(&self) -> bool { self.warnings.iter().any(|warning| warning.is_todo()) } } #[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)] pub struct References { pub imported_modules: HashSet, pub value_references: ReferenceMap, pub type_references: ReferenceMap, } #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum Opaque { Opaque, NotOpaque, } /// Information on the constructors of a custom type. #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct TypeVariantConstructors { /// The id of the generic type variables of the generic version of the type that these /// constructors belong to. /// For example, if we have this type: /// /// ```gleam /// pub type Option(a) { /// Some(a) /// None /// } /// ``` /// /// and `a` is a Generic type variable with id 1, then this field will be `[1]`. /// pub type_parameters_ids: Vec, pub opaque: Opaque, pub variants: Vec, } impl TypeVariantConstructors { pub(crate) fn new( variants: Vec, type_parameters: &[&EcoString], opaque: Opaque, hydrator: Hydrator, ) -> TypeVariantConstructors { let named_types = hydrator.named_type_variables(); let type_parameters = type_parameters .iter() .map(|&p| { let t = named_types .get(p) .expect("Type parameter not found in hydrator"); let error = "Hydrator must not store non generic types here"; match t.type_.as_ref() { Type::Var { type_ } => match type_.borrow().deref() { TypeVar::Generic { id } => *id, TypeVar::Unbound { .. } | TypeVar::Link { .. } => panic!("{}", error), }, Type::Named { .. } | Type::Fn { .. } | Type::Tuple { .. } => { panic!("{}", error) } } }) .collect_vec(); Self { type_parameters_ids: type_parameters, variants, opaque, } } } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct TypeValueConstructor { pub name: EcoString, pub parameters: Vec, pub documentation: Option, } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct TypeValueConstructorField { /// This type of this parameter pub type_: Arc, pub label: Option, pub documentation: Option, } impl ModuleInterface { pub fn get_public_value(&self, name: &str) -> Option<&ValueConstructor> { let value = self.values.get(name)?; if value.publicity.is_importable() { Some(value) } else { None } } pub fn get_public_type(&self, name: &str) -> Option<&TypeConstructor> { let type_ = self.types.get(name)?; if type_.publicity.is_importable() { Some(type_) } else { None } } pub fn get_main_function(&self, target: Target) -> Result { // Module must have a value with the name "main" let Some(value) = self.values.get(&EcoString::from("main")) else { return Err(crate::Error::ModuleDoesNotHaveMainFunction { module: self.name.clone(), origin: self.origin, }); }; assert_suitable_main_function(value, &self.name, self.origin, target)?; Ok(ModuleFunction { package: self.package.clone(), }) } pub fn public_value_names(&self) -> Vec { self.values .iter() .filter(|(_, v)| v.publicity.is_importable()) .map(|(k, _)| k) .cloned() .collect_vec() } pub fn public_type_names(&self) -> Vec { self.types .iter() .filter(|(_, v)| v.publicity.is_importable()) .map(|(k, _)| k) .cloned() .collect_vec() } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct PatternConstructor { pub name: EcoString, pub field_map: Option, pub documentation: Option, pub module: EcoString, pub location: SrcSpan, pub constructor_index: u16, } impl PatternConstructor { pub fn definition_location(&self) -> Option { Some(DefinitionLocation { module: Some(self.module.clone()), span: self.location, }) } pub fn get_documentation(&self) -> Option<&str> { self.documentation.as_deref() } } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum TypeVar { /// Unbound is an unbound variable. It is one specific type but we don't /// know what yet in the inference process. It has a unique id which can be used to /// identify if two unbound variable Rust values are the same Gleam type variable /// instance or not. /// Unbound { id: u64 }, /// Link is type variable where it was an unbound variable but we worked out /// that it is some other type and now we point to that one. /// Link { type_: Arc }, /// A Generic variable stands in for any possible type and cannot be /// specialised to any one type /// /// # Example /// /// ```gleam /// type Cat(a) { /// Cat(name: a) /// } /// // a is TypeVar::Generic /// ``` /// Generic { id: u64 }, } impl TypeVar { pub fn is_unbound(&self) -> bool { match self { Self::Unbound { .. } => true, Self::Link { type_ } => type_.is_unbound(), Self::Generic { .. } => false, } } pub fn is_variable(&self) -> bool { match self { Self::Unbound { .. } | Self::Generic { .. } => true, Self::Link { type_ } => type_.is_variable(), } } pub fn return_type(&self) -> Option> { match self { Self::Link { type_ } => type_.return_type(), Self::Unbound { .. } | Self::Generic { .. } => None, } } pub fn tuple_types(&self) -> Option>> { match self { Self::Link { type_ } => type_.tuple_types(), Self::Unbound { .. } | Self::Generic { .. } => None, } } pub fn constructor_types(&self) -> Option>> { match self { Self::Link { type_ } => type_.constructor_types(), Self::Unbound { .. } | Self::Generic { .. } => None, } } pub fn custom_type_inferred_variant(&self) -> Option { match self { Self::Link { type_ } => type_.custom_type_inferred_variant(), Self::Unbound { .. } | Self::Generic { .. } => None, } } pub fn is_result(&self) -> bool { match self { Self::Link { type_ } => type_.is_result(), Self::Unbound { .. } | Self::Generic { .. } => false, } } pub fn is_result_constructor(&self) -> bool { match self { Self::Link { type_ } => type_.is_result_constructor(), Self::Unbound { .. } | Self::Generic { .. } => false, } } pub fn is_list(&self) -> bool { match self { TypeVar::Link { type_ } => type_.is_list(), TypeVar::Unbound { .. } | TypeVar::Generic { .. } => false, } } pub fn result_ok_type(&self) -> Option> { match self { TypeVar::Link { type_ } => type_.result_ok_type(), TypeVar::Unbound { .. } | TypeVar::Generic { .. } => None, } } pub fn result_types(&self) -> Option<(Arc, Arc)> { match self { TypeVar::Link { type_ } => type_.result_types(), TypeVar::Unbound { .. } | TypeVar::Generic { .. } => None, } } pub fn fn_types(&self) -> Option<(Vec>, Arc)> { match self { Self::Link { type_ } => type_.fn_types(), Self::Unbound { .. } | Self::Generic { .. } => None, } } #[must_use] pub fn is_fun(&self) -> bool { match self { TypeVar::Link { type_ } => type_.is_fun(), TypeVar::Unbound { .. } | TypeVar::Generic { .. } => false, } } pub fn is_nil(&self) -> bool { match self { Self::Link { type_ } => type_.is_nil(), Self::Unbound { .. } | Self::Generic { .. } => false, } } pub fn is_bool(&self) -> bool { match self { Self::Link { type_ } => type_.is_bool(), Self::Unbound { .. } | Self::Generic { .. } => false, } } pub fn is_int(&self) -> bool { match self { Self::Link { type_ } => type_.is_int(), Self::Unbound { .. } | Self::Generic { .. } => false, } } pub fn is_float(&self) -> bool { match self { Self::Link { type_ } => type_.is_float(), Self::Unbound { .. } | Self::Generic { .. } => false, } } pub fn is_string(&self) -> bool { match self { Self::Link { type_ } => type_.is_string(), Self::Unbound { .. } | Self::Generic { .. } => false, } } pub fn is_bit_array(&self) -> bool { match self { Self::Link { type_ } => type_.is_bit_array(), Self::Unbound { .. } | Self::Generic { .. } => false, } } pub fn is_utf_codepoint(&self) -> bool { match self { Self::Link { type_ } => type_.is_utf_codepoint(), Self::Unbound { .. } | Self::Generic { .. } => false, } } pub fn named_type_name(&self) -> Option<(EcoString, EcoString)> { match self { Self::Link { type_ } => type_.named_type_name(), Self::Unbound { .. } | Self::Generic { .. } => None, } } pub fn named_type_information(&self) -> Option<(EcoString, EcoString, Vec>)> { match self { Self::Link { type_ } => type_.named_type_information(), Self::Unbound { .. } | Self::Generic { .. } => None, } } pub fn set_custom_type_variant(&mut self, index: u16) { match self { Self::Link { type_ } => Arc::make_mut(type_).set_custom_type_variant(index), Self::Unbound { .. } | Self::Generic { .. } => {} } } pub fn generalise_custom_type_variant(&mut self) { match self { Self::Link { type_ } => Arc::make_mut(type_).generalise_custom_type_variant(), Self::Unbound { .. } | Self::Generic { .. } => {} } } } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct TypeConstructor { pub publicity: Publicity, pub origin: SrcSpan, pub module: EcoString, pub parameters: Vec>, pub type_: Arc, pub deprecation: Deprecation, pub documentation: Option, } impl TypeConstructor { pub(crate) fn with_location(mut self, location: SrcSpan) -> Self { self.origin = location; self } } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct ValueConstructor { pub publicity: Publicity, pub deprecation: Deprecation, pub variant: ValueConstructorVariant, pub type_: Arc, } #[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)] pub enum Deprecation { #[default] NotDeprecated, Deprecated { message: EcoString, }, } impl Deprecation { /// Returns `true` if the deprecation is [`Deprecated`]. /// /// [`Deprecated`]: Deprecation::Deprecated #[must_use] pub fn is_deprecated(&self) -> bool { matches!(self, Self::Deprecated { .. }) } } impl ValueConstructor { pub fn local_variable(location: SrcSpan, origin: VariableOrigin, type_: Arc) -> Self { Self { publicity: Publicity::Private, deprecation: Deprecation::NotDeprecated, variant: ValueConstructorVariant::LocalVariable { location, origin }, type_, } } pub fn is_local_variable(&self) -> bool { self.variant.is_local_variable() } pub fn definition_location(&self) -> DefinitionLocation { match &self.variant { ValueConstructorVariant::Record { module, location, .. } | ValueConstructorVariant::ModuleConstant { location, module, .. } | ValueConstructorVariant::ModuleFn { location, module, .. } => DefinitionLocation { module: Some(module.clone()), span: *location, }, ValueConstructorVariant::LocalVariable { location, .. } => DefinitionLocation { module: None, span: *location, }, } } pub fn get_documentation(&self) -> Option<&str> { match &self.variant { ValueConstructorVariant::LocalVariable { .. } => Some("A locally defined variable."), ValueConstructorVariant::ModuleFn { documentation, .. } | ValueConstructorVariant::Record { documentation, .. } | ValueConstructorVariant::ModuleConstant { documentation, .. } => { Some(documentation.as_ref()?.as_str()) } } } /// Returns the purity of this value constructor if it is called as a function. /// Referencing a value constructor by itself is always pure, but calling is as a /// function might not be. pub fn called_function_purity(&self) -> Purity { match &self.variant { // If we call a module constant or local variable as a function, we // no longer have enough information to determine its purity. For // example: // // ```gleam // const function1 = io.println // const function2 = function.identity // // pub fn main() { // function1("Hello") // function2("Hello") // } // ``` // // At this point, we don't have any information about the purity of // the `function1` and `function2` functions, and must return // `Purity::Unknown`. See the documentation for the `Purity` type // for more information on why this is the case. ValueConstructorVariant::LocalVariable { .. } | ValueConstructorVariant::ModuleConstant { .. } => Purity::Unknown, // Constructing records is always pure ValueConstructorVariant::Record { .. } => Purity::Pure, ValueConstructorVariant::ModuleFn { purity, .. } => *purity, } } } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct TypeAliasConstructor { pub publicity: Publicity, pub module: EcoString, pub type_: Arc, pub arity: usize, pub deprecation: Deprecation, pub documentation: Option, pub origin: SrcSpan, pub parameters: Vec>, } impl ValueConstructor { pub fn field_map(&self) -> Option<&FieldMap> { match &self.variant { ValueConstructorVariant::ModuleFn { field_map, .. } | ValueConstructorVariant::Record { field_map, .. } => field_map.as_ref(), ValueConstructorVariant::LocalVariable { .. } | ValueConstructorVariant::ModuleConstant { .. } => None, } } } pub type TypedCallArg = CallArg; fn assert_no_labelled_arguments( arguments: &[CallArg], kind: UnexpectedLabelledArgKind, ) -> Result<(), Error> { for argument in arguments { if let Some(label) = &argument.label { return Err(Error::UnexpectedLabelledArg { location: argument.location, label: label.clone(), kind, }); } } Ok(()) } /// This function makes sure that the type variable being unified /// doesn't occur within the type it is being unified with. This /// prevents the algorithm from inferring recursive types, which /// could cause naively-implemented type checking to diverge. /// While traversing the type tree. /// fn unify_unbound_type(type_: &Type, own_id: u64) -> Result<(), UnifyError> { if let Type::Var { type_ } = type_ { return match type_.borrow().deref() { TypeVar::Link { type_, .. } => unify_unbound_type(type_, own_id), TypeVar::Unbound { id } => { if id == &own_id { Err(UnifyError::RecursiveType) } else { Ok(()) } } TypeVar::Generic { .. } => Ok(()), }; } match type_ { Type::Named { arguments, .. } => { for argument in arguments { unify_unbound_type(argument, own_id)? } Ok(()) } Type::Fn { arguments, return_ } => { for argument in arguments { unify_unbound_type(argument, own_id)?; } unify_unbound_type(return_, own_id) } Type::Tuple { elements, .. } => { for element in elements { unify_unbound_type(element, own_id)? } Ok(()) } Type::Var { .. } => unreachable!(), } } fn match_fun_type( type_: Arc, arity: usize, environment: &mut Environment<'_>, ) -> Result<(Vec>, Arc), MatchFunTypeError> { if let Type::Var { type_ } = type_.deref() { let new_value = match type_.borrow().deref() { TypeVar::Link { type_, .. } => { return match_fun_type(type_.clone(), arity, environment); } TypeVar::Unbound { .. } => { let arguments: Vec<_> = (0..arity).map(|_| environment.new_unbound_var()).collect(); let return_ = environment.new_unbound_var(); Some((arguments, return_)) } TypeVar::Generic { .. } => None, }; if let Some((arguments, return_)) = new_value { *type_.borrow_mut() = TypeVar::Link { type_: fn_(arguments.clone(), return_.clone()), }; return Ok((arguments, return_)); } } if let Type::Fn { arguments, return_ } = type_.deref() { return if arguments.len() != arity { Err(MatchFunTypeError::IncorrectArity { expected: arguments.len(), given: arity, arguments: arguments.clone(), return_type: return_.clone(), }) } else { Ok((arguments.clone(), return_.clone())) }; } Err(MatchFunTypeError::NotFn { type_ }) } pub fn generalise(t: Arc) -> Arc { match t.deref() { Type::Var { type_ } => match type_.borrow().deref() { TypeVar::Unbound { id } => generic_var(*id), TypeVar::Link { type_ } => generalise(type_.clone()), TypeVar::Generic { .. } => Arc::new(Type::Var { type_: type_.clone(), }), }, Type::Named { publicity, module, package, name, arguments, inferred_variant: _, } => { let arguments = arguments .iter() .map(|type_| generalise(type_.clone())) .collect(); Arc::new(Type::Named { publicity: *publicity, module: module.clone(), package: package.clone(), name: name.clone(), arguments, inferred_variant: None, }) } Type::Fn { arguments, return_ } => fn_( arguments .iter() .map(|type_| generalise(type_.clone())) .collect(), generalise(return_.clone()), ), Type::Tuple { elements } => tuple( elements .iter() .map(|type_| generalise(type_.clone())) .collect(), ), } } #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum FieldAccessUsage { /// Used as `thing.field()` MethodCall, /// Used internally when trying to access a field when performing record updates. /// The `infer_record_update` function uses this to determine which fields exist /// on a certain record to know whether a certain record update updates correct fields. /// Without this distinction, we get confusing error messages in certain cases. /// RecordUpdate, /// Used as `thing.field` Other, } /// Verify that a value is suitable to be used as a main function. fn assert_suitable_main_function( value: &ValueConstructor, module_name: &EcoString, origin: Origin, target: Target, ) -> Result<(), crate::Error> { // The value must be a module function let ValueConstructorVariant::ModuleFn { arity, implementations, .. } = &value.variant else { return Err(crate::Error::ModuleDoesNotHaveMainFunction { module: module_name.clone(), origin, }); }; // The target must be supported if !implementations.supports(target) { return Err(crate::Error::MainFunctionDoesNotSupportTarget { module: module_name.clone(), target, }); } // The function must be zero arity if *arity != 0 { return Err(crate::Error::MainFunctionHasWrongArity { module: module_name.clone(), arity: *arity, }); } // The function must be public, or trying to run it would result in a // runtime crash if !value.publicity.is_importable() { return Err(crate::Error::MainFunctionIsPrivate { module: module_name.clone(), }); } Ok(()) } ================================================ FILE: compiler-core/src/uid.rs ================================================ use std::sync::{ Arc, atomic::{AtomicU64, Ordering}, }; /// A generator of unique ids. Only one should be used per compilation run to /// ensure ids do not get reused. #[derive(Debug, Clone, Default)] pub struct UniqueIdGenerator { id: Arc, } impl UniqueIdGenerator { pub fn new() -> Self { Self::default() } pub fn next(&self) -> u64 { self.id.fetch_add(1, Ordering::Relaxed) } } #[test] fn id_geneation() { let ids = UniqueIdGenerator::new(); let ids2 = ids.clone(); assert_eq!(ids.next(), 0); assert_eq!(ids.next(), 1); assert_eq!(ids.next(), 2); // Cloned ones use the same counter assert_eq!(ids2.next(), 3); assert_eq!(ids2.next(), 4); assert_eq!(ids2.next(), 5); // The original is updated assert_eq!(ids.next(), 6); assert_eq!(ids.next(), 7); } ================================================ FILE: compiler-core/src/version.rs ================================================ /// The current version of the gleam compiler. If this does not match what is /// already in the build folder we will not reuse any cached artifacts and /// instead build from scratch pub const COMPILER_VERSION: &str = env!("CARGO_PKG_VERSION"); ================================================ FILE: compiler-core/src/warning.rs ================================================ use crate::{ ast::{BitArraySegmentTruncation, SrcSpan, TodoKind}, build::Target, diagnostic::{self, Diagnostic, ExtraLabel, Location}, error::wrap, exhaustiveness::ImpossibleBitArraySegmentPattern, type_::{ self, error::{ AssertImpossiblePattern, FeatureKind, LiteralCollectionKind, PanicPosition, TodoOrPanic, UnreachablePatternReason, }, expression::ComparisonOutcome, pretty::Printer, }, }; use camino::Utf8PathBuf; use debug_ignore::DebugIgnore; use ecow::EcoString; use itertools::Itertools; use std::{ io::Write, sync::{Arc, atomic::Ordering}, }; use std::{rc::Rc, sync::atomic::AtomicUsize}; use termcolor::Buffer; pub trait WarningEmitterIO { fn emit_warning(&self, warning: Warning); } #[derive(Debug, Clone, Copy)] pub struct NullWarningEmitterIO; impl WarningEmitterIO for NullWarningEmitterIO { fn emit_warning(&self, _warning: Warning) {} } #[derive(Debug, Clone, Default)] pub struct VectorWarningEmitterIO { pub warnings: Arc>>, } impl VectorWarningEmitterIO { pub fn new() -> Self { Self::default() } pub fn take(&self) -> Vec { let mut warnings = self.write_lock(); std::mem::take(&mut *warnings) } pub fn reset(&self) { let mut warnings = self.write_lock(); warnings.clear(); } pub fn pop(&self) -> Option { let mut warnings = self.write_lock(); warnings.pop() } fn write_lock(&self) -> std::sync::RwLockWriteGuard<'_, Vec> { self.warnings.write().expect("Vector lock poisoned") } } impl WarningEmitterIO for VectorWarningEmitterIO { fn emit_warning(&self, warning: Warning) { let mut warnings = self.write_lock(); warnings.push(warning); } } #[derive(Debug, Clone)] pub struct WarningEmitter { /// The number of warnings emitted. /// In the context of the project compiler this is the count for the root /// package only, the count is reset back to zero after the dependencies are /// compiled. count: Arc, emitter: DebugIgnore>, } impl WarningEmitter { pub fn new(emitter: Rc) -> Self { Self { count: Arc::new(AtomicUsize::new(0)), emitter: DebugIgnore(emitter), } } pub fn null() -> Self { Self::new(Rc::new(NullWarningEmitterIO)) } pub fn reset_count(&self) { self.count.store(0, Ordering::Relaxed); } pub fn count(&self) -> usize { self.count.load(Ordering::Relaxed) } pub fn emit(&self, warning: Warning) { _ = self.count.fetch_add(1, Ordering::Relaxed); self.emitter.emit_warning(warning); } pub fn vector() -> (Self, Rc) { let io = Rc::new(VectorWarningEmitterIO::default()); let emitter = Self::new(io.clone()); (emitter, Rc::clone(&io)) } } #[derive(Debug, Clone)] pub struct TypeWarningEmitter { module_path: Utf8PathBuf, module_src: EcoString, emitter: WarningEmitter, } impl TypeWarningEmitter { pub fn new(module_path: Utf8PathBuf, module_src: EcoString, emitter: WarningEmitter) -> Self { Self { module_path, module_src, emitter, } } pub fn null() -> Self { Self { module_path: Utf8PathBuf::new(), module_src: EcoString::from(""), emitter: WarningEmitter::new(Rc::new(NullWarningEmitterIO)), } } pub fn emit(&self, warning: type_::Warning) { self.emitter.emit(Warning::Type { path: self.module_path.clone(), src: self.module_src.clone(), warning, }); } } #[derive(Debug, Clone, Eq, PartialEq)] pub enum Warning { Type { path: Utf8PathBuf, src: EcoString, warning: type_::Warning, }, InvalidSource { path: Utf8PathBuf, }, DeprecatedSyntax { path: Utf8PathBuf, src: EcoString, warning: DeprecatedSyntaxWarning, }, EmptyModule { path: Utf8PathBuf, name: EcoString, }, DetachedDocComment { path: Utf8PathBuf, src: EcoString, location: SrcSpan, }, } #[derive(Debug, Clone, Eq, PartialEq, Copy)] pub enum DeprecatedSyntaxWarning { /// If someone uses the deprecated syntax to append to a list: /// `["a"..rest]`, notice how there's no comma! DeprecatedListPrepend { location: SrcSpan, }, /// If someone uses the deprecated syntax to pattern match on a list: /// ```gleam /// case list { /// [first..rest] -> todo /// // ^^ notice there's no comma! /// _ -> /// } /// ``` /// DeprecatedListPattern { location: SrcSpan, }, /// If someone uses the deprecated syntax to match on all lists instead of /// a common `_`: /// ```gleam /// case list { /// [..] -> todo /// //^^^^ this matches on all lists so a `_` should be used instead! /// _ -> /// } /// ``` /// DeprecatedListCatchAllPattern { location: SrcSpan, }, /// If a record pattern has a spread that is not preceded by a comma: /// ```gleam /// case wibble { /// Wibble(arg1: name ..) -> todo /// // ^^ this should be preceded by a comma! /// } /// ``` /// DeprecatedRecordSpreadPattern { location: SrcSpan, }, /// If a guard has an empty clause : /// ```gleam /// case wibble { /// big if -> True /// ^^ This can be removed. /// } /// ``` DeprecatedEmptyClauseGuard { location: SrcSpan, }, DeprecatedTargetShorthand { target: Target, location: SrcSpan, }, } impl Warning { pub fn to_diagnostic(&self) -> Diagnostic { match self { Warning::InvalidSource { path } => Diagnostic { title: "Invalid module name".into(), text: "\ Module names must begin with a lowercase letter and contain only lowercase alphanumeric characters or underscores." .into(), level: diagnostic::Level::Warning, location: None, hint: Some(format!( "Rename `{path}` to be valid, or remove this file from the project source." )), }, Warning::DeprecatedSyntax { path, src, warning: DeprecatedSyntaxWarning::DeprecatedListPrepend { location }, } => Diagnostic { title: "Deprecated prepend syntax".into(), text: wrap( "This syntax for prepending to a list is deprecated. When prepending an item to a list it should be preceded by a comma, \ like this: `[item, ..list]`.", ), hint: None, level: diagnostic::Level::Warning, location: Some(Location { label: diagnostic::Label { text: Some("This spread should be preceded by a comma".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, Warning::DeprecatedSyntax { path, src, warning: DeprecatedSyntaxWarning::DeprecatedListPattern { location }, } => Diagnostic { title: "Deprecated list pattern matching syntax".into(), text: wrap( "This syntax for pattern matching on a list is deprecated. When matching on the rest of a list it should always be preceded by a comma, \ like this: `[item, ..list]`.", ), hint: None, level: diagnostic::Level::Warning, location: Some(Location { label: diagnostic::Label { text: Some("This spread should be preceded by a comma".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, Warning::DeprecatedSyntax { path, src, warning: DeprecatedSyntaxWarning::DeprecatedRecordSpreadPattern { location }, } => Diagnostic { title: "Deprecated record pattern matching syntax".into(), text: wrap("This syntax for pattern matching on a record is deprecated."), hint: None, level: diagnostic::Level::Warning, location: Some(Location { label: diagnostic::Label { text: Some("This should be preceded by a comma".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, Warning::DeprecatedSyntax { path, src, warning: DeprecatedSyntaxWarning::DeprecatedListCatchAllPattern { location }, } => Diagnostic { title: "Deprecated list pattern matching syntax".into(), text: wrap( "This syntax for pattern matching on lists is deprecated. To match on all possible lists, use the `_` catch-all pattern instead.", ), hint: None, level: diagnostic::Level::Warning, location: Some(Location { label: diagnostic::Label { text: Some("This can be replaced with `_`".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, Warning::DeprecatedSyntax { path, src, warning: DeprecatedSyntaxWarning::DeprecatedEmptyClauseGuard { location }, } => Diagnostic { title: "Deprecated empty guard syntax".into(), text: wrap( "This syntax for an empty guard is deprecated. \ To have a clause without a guard, remove this.", ), hint: None, level: diagnostic::Level::Warning, location: Some(Location { label: diagnostic::Label { text: Some("This can be removed.".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, Warning::DeprecatedSyntax { path, src, warning: DeprecatedSyntaxWarning::DeprecatedTargetShorthand { location, target }, } => { let full_name = match target { Target::Erlang => "erlang", Target::JavaScript => "javascript", }; Diagnostic { title: "Deprecated target shorthand syntax".into(), text: wrap(&format!( "This shorthand target name is deprecated. Use the full name: `{full_name}` instead." )), hint: None, level: diagnostic::Level::Warning, location: Some(Location { label: diagnostic::Label { text: Some(format!("This should be replaced with `{full_name}`")), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } Warning::DetachedDocComment { path, src, location, } => Diagnostic { title: "Detached doc comment".into(), text: wrap( "This doc comment is followed by a regular \ comment so it is not attached to any definition.", ), level: diagnostic::Level::Warning, location: Some(Location { path: path.to_path_buf(), src: src.clone(), label: diagnostic::Label { text: Some("This is not attached to a definition".into()), span: *location, }, extra_labels: Vec::new(), }), hint: Some("Move the comment above the doc comment".into()), }, Warning::Type { path, warning, src } => match warning { type_::Warning::Todo { kind, location, type_, } => { let mut text = String::new(); text.push_str( "\ This code will crash if it is run. Be sure to finish it before running your program.", ); let title = match kind { TodoKind::Keyword => "Todo found", TodoKind::EmptyBlock => { text.push_str( " A block must always contain at least one expression.", ); "Incomplete block" } TodoKind::EmptyFunction { .. } => "Unimplemented function", TodoKind::IncompleteUse => { text.push_str( " A use expression must always be followed by at least one expression.", ); "Incomplete use expression" } } .into(); if !type_.is_variable() { text.push_str(&format!( "\n\nHint: I think its type is `{}`.\n", Printer::new().pretty_print(type_, 0) )); } Diagnostic { title, text, level: diagnostic::Level::Warning, location: Some(Location { path: path.to_path_buf(), src: src.clone(), label: diagnostic::Label { text: Some("This code is incomplete".into()), span: *location, }, extra_labels: Vec::new(), }), hint: None, } } type_::Warning::ImplicitlyDiscardedResult { location } => Diagnostic { title: "Unused result value".into(), text: "".into(), hint: Some( "If you are sure you don't need it you can assign it to `_`.".into(), ), level: diagnostic::Level::Warning, location: Some(Location { path: path.to_path_buf(), src: src.clone(), label: diagnostic::Label { text: Some("The Result value created here is unused".into()), span: *location, }, extra_labels: Vec::new(), }), }, type_::Warning::UnusedLiteral { location } => Diagnostic { title: "Unused literal".into(), text: "".into(), hint: Some("You can safely remove it.".into()), level: diagnostic::Level::Warning, location: Some(Location { path: path.to_path_buf(), src: src.clone(), label: diagnostic::Label { text: Some("This value is never used".into()), span: *location, }, extra_labels: Vec::new(), }), }, type_::Warning::NoFieldsRecordUpdate { location } => Diagnostic { title: "Fieldless record update".into(), text: "".into(), hint: Some( "Add some fields to change or replace it with the record itself.".into(), ), level: diagnostic::Level::Warning, location: Some(Location { path: path.to_path_buf(), src: src.clone(), label: diagnostic::Label { text: Some("This record update doesn't change any fields".into()), span: *location, }, extra_labels: Vec::new(), }), }, type_::Warning::AllFieldsRecordUpdate { location } => Diagnostic { title: "Redundant record update".into(), text: "".into(), hint: Some("It is better style to use the record creation syntax.".into()), level: diagnostic::Level::Warning, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: diagnostic::Label { text: Some("This record update specifies all fields".into()), span: *location, }, extra_labels: Vec::new(), }), }, type_::Warning::UnusedType { location, imported, .. } => { let title = if *imported { "Unused imported type".into() } else { "Unused private type".into() }; let label = if *imported { "This imported type is never used".into() } else { "This private type is never used".into() }; Diagnostic { title, text: "".into(), hint: Some("You can safely remove it.".into()), level: diagnostic::Level::Warning, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: diagnostic::Label { text: Some(label), span: *location, }, extra_labels: Vec::new(), }), } } type_::Warning::UnusedConstructor { location, imported, .. } => { let title = if *imported { "Unused imported item".into() } else { "Unused private constructor".into() }; let label = if *imported { "This imported constructor is never used".into() } else { "This private constructor is never used".into() }; Diagnostic { title, text: "".into(), hint: Some("You can safely remove it.".into()), level: diagnostic::Level::Warning, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: diagnostic::Label { text: Some(label), span: *location, }, extra_labels: Vec::new(), }), } } type_::Warning::UnusedImportedModule { location, .. } => Diagnostic { title: "Unused imported module".into(), text: "".into(), hint: Some("You can safely remove it.".into()), level: diagnostic::Level::Warning, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: diagnostic::Label { text: Some("This imported module is never used".into()), span: *location, }, extra_labels: Vec::new(), }), }, type_::Warning::UnusedImportedModuleAlias { location, module_name, .. } => { let text = format!( "\ Hint: You can safely remove it. import {module_name} as _ " ); Diagnostic { title: "Unused imported module alias".into(), text, hint: None, level: diagnostic::Level::Warning, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: diagnostic::Label { text: Some("This alias is never used".into()), span: *location, }, extra_labels: Vec::new(), }), } } type_::Warning::UnusedImportedValue { location, .. } => Diagnostic { title: "Unused imported value".into(), text: "".into(), hint: Some("You can safely remove it.".into()), level: diagnostic::Level::Warning, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: diagnostic::Label { text: Some("This imported value is never used".into()), span: *location, }, extra_labels: Vec::new(), }), }, type_::Warning::UnusedPrivateModuleConstant { location, .. } => Diagnostic { title: "Unused private constant".into(), text: "".into(), hint: Some("You can safely remove it.".into()), level: diagnostic::Level::Warning, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: diagnostic::Label { text: Some("This private constant is never used".into()), span: *location, }, extra_labels: Vec::new(), }), }, type_::Warning::UnusedPrivateFunction { location, .. } => Diagnostic { title: "Unused private function".into(), text: "".into(), hint: Some("You can safely remove it.".into()), level: diagnostic::Level::Warning, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: diagnostic::Label { text: Some("This private function is never used".into()), span: *location, }, extra_labels: Vec::new(), }), }, type_::Warning::UnusedVariable { location, origin } => Diagnostic { title: if origin.is_function_parameter() { "Unused function argument".into() } else { "Unused variable".into() }, text: "".into(), hint: origin.how_to_ignore(), level: diagnostic::Level::Warning, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: diagnostic::Label { text: if origin.is_function_parameter() { Some("This argument is never used".into()) } else { Some("This variable is never used".into()) }, span: *location, }, extra_labels: Vec::new(), }), }, type_::Warning::UnusedRecursiveArgument { location } => Diagnostic { title: "Unused function argument".into(), text: wrap( "This argument is passed to the function when recursing, \ but it's never used for anything.", ), hint: None, level: diagnostic::Level::Warning, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: diagnostic::Label { text: Some("This argument is never used".into()), span: *location, }, extra_labels: vec![], }), }, type_::Warning::UnnecessaryDoubleIntNegation { location } => Diagnostic { title: "Unnecessary double negation (--) on integer".into(), text: "".into(), hint: None, level: diagnostic::Level::Warning, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: diagnostic::Label { text: Some("You can safely remove this.".into()), span: *location, }, extra_labels: Vec::new(), }), }, type_::Warning::UnnecessaryDoubleBoolNegation { location } => Diagnostic { title: "Unnecessary double negation (!!) on bool".into(), text: "".into(), hint: None, level: diagnostic::Level::Warning, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: diagnostic::Label { text: Some("You can safely remove this.".into()), span: *location, }, extra_labels: Vec::new(), }), }, type_::Warning::InefficientEmptyListCheck { location, kind } => { use type_::error::EmptyListCheckKind; let text = "The `list.length` function has to iterate across the whole list to calculate the length, which is wasteful if you only need to know if the list is empty or not. " .into(); let hint = Some(match kind { EmptyListCheckKind::Empty => "You can use `the_list == []` instead.".into(), EmptyListCheckKind::NonEmpty => { "You can use `the_list != []` instead.".into() } }); Diagnostic { title: "Inefficient use of `list.length`".into(), text, hint, level: diagnostic::Level::Warning, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: diagnostic::Label { text: None, span: *location, }, extra_labels: Vec::new(), }), } } type_::Warning::TransitiveDependencyImported { location, module, package, } => { let text = wrap(&format!( "The module `{module}` is being imported, but \ `{package}`, the package it belongs to, is not a direct dependency of your \ package. In a future version of Gleam this may become a compile error. Run this command to add it to your dependencies: gleam add {package} " )); Diagnostic { title: "Transitive dependency imported".into(), text, hint: None, level: diagnostic::Level::Warning, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: diagnostic::Label { text: None, span: *location, }, extra_labels: Vec::new(), }), } } type_::Warning::DeprecatedItem { location, message, layer, } => { let text = wrap(&format!("It was deprecated with this message: {message}")); let (title, diagnostic_label_text) = if layer.is_value() { ( "Deprecated value used".into(), Some("This value has been deprecated".into()), ) } else { ( "Deprecated type used".into(), Some("This type has been deprecated".into()), ) }; Diagnostic { title, text, hint: None, level: diagnostic::Level::Warning, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: diagnostic::Label { text: diagnostic_label_text, span: *location, }, extra_labels: Vec::new(), }), } } type_::Warning::UnreachableCasePattern { location, reason } => { let text = match reason { UnreachablePatternReason::DuplicatePattern => wrap( "This pattern cannot be reached as a previous \ pattern matches the same values.\n", ), UnreachablePatternReason::ImpossibleVariant => wrap( "This pattern cannot be reached as it matches on \ a variant of a type which is never present.\n", ), UnreachablePatternReason::ImpossibleSegments(_) => wrap( "This pattern cannot be reached as it contains \ segments that will never match.\n", ), }; let extra_labels = match reason { UnreachablePatternReason::DuplicatePattern | UnreachablePatternReason::ImpossibleVariant => vec![], UnreachablePatternReason::ImpossibleSegments(segments) => segments .iter() .map(|segment| ExtraLabel { src_info: None, label: diagnostic::Label { text: Some(explain_impossible_segment(segment)), span: segment.location(), }, }) .collect_vec(), }; Diagnostic { title: "Unreachable pattern".into(), text, hint: Some("It can be safely removed.".into()), level: diagnostic::Level::Warning, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: diagnostic::Label { text: None, span: *location, }, extra_labels, }), } } type_::Warning::CaseMatchOnLiteralCollection { kind, location } => { let kind = match kind { LiteralCollectionKind::List => "list", LiteralCollectionKind::Tuple => "tuple", LiteralCollectionKind::Record => "record", }; let title = format!("Redundant {kind}"); let text = wrap(&format!( "Instead of building a {kind} and matching on it, \ you can match on its contents directly. A case expression can take multiple subjects separated by commas like this: case one_subject, another_subject {{ _, _ -> todo }} See: https://tour.gleam.run/flow-control/multiple-subjects/" )); Diagnostic { title, text, hint: None, level: diagnostic::Level::Warning, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: diagnostic::Label { text: Some(format!("You can remove this {kind} wrapper")), span: *location, }, extra_labels: Vec::new(), }), } } type_::Warning::CaseMatchOnLiteralValue { location } => Diagnostic { title: "Match on a literal value".into(), text: wrap( "Matching on a literal value is redundant since you \ can already tell which branch is going to match with this value.", ), hint: None, level: diagnostic::Level::Warning, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: diagnostic::Label { text: Some("There's no need to pattern match on this value".into()), span: *location, }, extra_labels: Vec::new(), }), }, type_::Warning::OpaqueExternalType { location } => Diagnostic { title: "Opaque external type".into(), text: "This type has no constructors so making it opaque is redundant.".into(), hint: Some("Remove the `opaque` qualifier from the type definition.".into()), level: diagnostic::Level::Warning, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: diagnostic::Label { text: None, span: *location, }, extra_labels: Vec::new(), }), }, type_::Warning::UnusedValue { location } => Diagnostic { title: "Unused value".into(), text: wrap( "This expression computes a value without any side \ effects, but then the value isn't used at all. You might want to assign it to a \ variable, or delete the expression entirely if it's not needed.", ), hint: None, level: diagnostic::Level::Warning, location: Some(Location { path: path.to_path_buf(), src: src.clone(), label: diagnostic::Label { text: Some("This value is never used".into()), span: *location, }, extra_labels: Vec::new(), }), }, type_::Warning::InternalTypeLeak { location, leaked } => { let mut printer = Printer::new(); // TODO: be more precise. // - is being returned by this public function // - is taken as an argument by this public function // - is taken as an argument by this public enum constructor // etc let text = format!( "The following type is internal, but is being used by this public export. {} Internal types should not be used in a public facing function since they are hidden from the package's documentation.", printer.pretty_print(leaked, 4), ); Diagnostic { title: "Internal type used in public interface".into(), text, hint: None, level: diagnostic::Level::Warning, location: Some(Location { label: diagnostic::Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } type_::Warning::RedundantAssertAssignment { location } => Diagnostic { title: "Redundant assertion".into(), text: "This assertion is redundant since the pattern covers all possibilities." .into(), hint: None, level: diagnostic::Level::Warning, location: Some(Location { label: diagnostic::Label { text: Some("You can remove this".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, type_::Warning::AssertAssignmentOnImpossiblePattern { location, reason } => { let extra_labels = match reason { AssertImpossiblePattern::InferredVariant => vec![], AssertImpossiblePattern::ImpossibleSegments { segments } => segments .iter() .map(|segment| ExtraLabel { src_info: None, label: diagnostic::Label { text: Some(explain_impossible_segment(segment)), span: segment.location(), }, }) .collect_vec(), }; Diagnostic { title: "Assertion that will always fail".into(), text: wrap( "We can tell from the code above that the value will never match \ this pattern and that this code will always crash. Either change the pattern or use `panic` to unconditionally fail.", ), hint: None, level: diagnostic::Level::Warning, location: Some(Location { label: diagnostic::Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels, }), } } type_::Warning::TodoOrPanicUsedAsFunction { kind, location, arguments_location, arguments, } => { let title = match kind { TodoOrPanic::Todo => "Todo used as a function".into(), TodoOrPanic::Panic => "Panic used as a function".into(), }; let label_location = match arguments_location { None => location, Some(location) => location, }; let name = match kind { TodoOrPanic::Todo => "todo", TodoOrPanic::Panic => "panic", }; let mut text = format!("`{name}` is not a function"); match arguments { 0 => text.push_str(&format!( ", you can just write `{name}` instead of `{name}()`." )), 1 => text.push_str( " and will crash before it can do anything with this argument.", ), _ => text.push_str( " and will crash before it can do anything with these arguments.", ), }; match arguments { 0 => {} _ => text.push_str(&format!( "\n\nHint: if you want to display an error message you should write `{name} as \"my error message\"` See: https://tour.gleam.run/advanced-features/{name}/" )), } Diagnostic { title, text: wrap(&text), hint: None, level: diagnostic::Level::Warning, location: Some(Location { label: diagnostic::Label { text: None, span: *label_location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } type_::Warning::UnreachableCodeAfterPanic { location, panic_position: unreachable_code_kind, } => { let text = match unreachable_code_kind { PanicPosition::PreviousExpression => { "This code is unreachable because it comes after a `panic`." } PanicPosition::PreviousFunctionArgument => { "This argument is unreachable because the previous one always panics. \ Your code will crash before reaching this point." } PanicPosition::LastFunctionArgument => { "This function call is unreachable because its last argument always panics. \ Your code will crash before reaching this point." } PanicPosition::EchoExpression => { "This `echo` won't print anything because the expression it \ should be printing always panics." } }; Diagnostic { title: "Unreachable code".into(), text: wrap(text), hint: None, level: diagnostic::Level::Warning, location: Some(Location { label: diagnostic::Label { text: None, span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } type_::Warning::RedundantPipeFunctionCapture { location } => Diagnostic { title: "Redundant function capture".into(), text: wrap( "This function capture is redundant since the value is already piped as \ the first argument of this call. See: https://tour.gleam.run/functions/pipelines/", ), hint: None, level: diagnostic::Level::Warning, location: Some(Location { label: diagnostic::Label { text: Some("You can safely remove this".into()), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, type_::Warning::FeatureRequiresHigherGleamVersion { location, minimum_required_version, wrongfully_allowed_version, feature_kind, } => { let feature = match feature_kind { FeatureKind::LabelShorthandSyntax => "The label shorthand syntax was", FeatureKind::ConstantStringConcatenation => { "Constant strings concatenation was" } FeatureKind::ArithmeticInGuards => "Arithmetic operations in guards were", FeatureKind::ConcatenateInGuards => "String concatenation in guards was", FeatureKind::UnannotatedUtf8StringSegment => { "The ability to omit the `utf8` annotation for string segments was" } FeatureKind::UnannotatedFloatSegment => { "The ability to omit the `float` annotation for float segments was" } FeatureKind::NestedTupleAccess => { "The ability to access nested tuple fields was" } FeatureKind::InternalAnnotation => "The `@internal` annotation was", FeatureKind::AtInJavascriptModules => { "The ability to have `@` in a Javascript module's name was" } FeatureKind::RecordUpdateVariantInference => { "Record updates for custom types when the variant is known was" } FeatureKind::RecordAccessVariantInference => { "Field access on custom types when the variant is known was" } FeatureKind::LetAssertWithMessage => { "Specifying a custom panic message when using let assert was" } FeatureKind::VariantWithDeprecatedAnnotation => { "Deprecating individual custom type variants was" } FeatureKind::JavaScriptUnalignedBitArray => { "Use of unaligned bit arrays on the JavaScript target was" } FeatureKind::BoolAssert => "The bool `assert` statement was", FeatureKind::ExpressionInSegmentSize => "Expressions in segment sizes were", FeatureKind::ExternalCustomType => { "The `@external` annotation on custom types was" } FeatureKind::ConstantRecordUpdate => { "The record update syntax for constants was" } }; Diagnostic { title: "Incompatible gleam version range".into(), text: wrap(&format!( "{feature} introduced in version v{minimum_required_version}. But the Gleam version range \ specified in your `gleam.toml` would allow this code to run on an earlier \ version like v{wrongfully_allowed_version}, resulting in compilation errors!", )), hint: Some(format!( "Remove the version constraint from your `gleam.toml` or update it to be: gleam = \">= {minimum_required_version}\"" )), level: diagnostic::Level::Warning, location: Some(Location { label: diagnostic::Label { text: Some(format!( "This requires a Gleam version >= {minimum_required_version}" )), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), } } type_::Warning::JavaScriptIntUnsafe { location } => Diagnostic { title: "Int is outside JavaScript's safe integer range".into(), text: wrap( "This integer value is too large to be represented accurately by \ JavaScript's number type. To avoid this warning integer values must be in the range \ -(2^53 - 1) - (2^53 - 1). See JavaScript's Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER properties for more \ information.", ), hint: None, level: diagnostic::Level::Warning, location: Some(Location { path: path.to_path_buf(), src: src.clone(), label: diagnostic::Label { text: Some("This is not a safe integer value on JavaScript".into()), span: *location, }, extra_labels: Vec::new(), }), }, type_::Warning::BitArraySegmentTruncatedValue { location: _, truncation: BitArraySegmentTruncation { truncated_value, truncated_into, segment_bits, value_location, }, } => { let (unit, segment_size, taken) = if segment_bits % 8 == 0 { let bytes = segment_bits / 8; let segment_size = pluralise(format!("{bytes} byte"), bytes); let taken = if bytes == 1 { "first byte".into() } else { format!("first {bytes} bytes") }; ("bytes", segment_size, taken) } else { let segment_size = pluralise(format!("{segment_bits} bit"), *segment_bits); let taken = if *segment_bits == 1 { "first bit".into() } else { format!("first {segment_bits} bits") }; ("bits", segment_size, taken) }; let text = format!( "This segment is {segment_size} long, but {truncated_value} \ doesn't fit in that many {unit}. It would be truncated by taking its {taken}, resulting in the value {truncated_into}." ); Diagnostic { title: "Truncated bit array segment".into(), text: wrap(&text), hint: None, level: diagnostic::Level::Warning, location: Some(Location { path: path.to_path_buf(), src: src.clone(), label: diagnostic::Label { text: Some(format!( "You can safely replace this with {truncated_into}" )), span: *value_location, }, extra_labels: vec![], }), } } type_::Warning::AssertLiteralBool { location } => Diagnostic { title: "Assertion of a literal value".into(), text: wrap( "Asserting on a literal bool is redundant since you \ can already tell whether it will be `True` or `False`.", ), hint: None, level: diagnostic::Level::Warning, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: diagnostic::Label { text: None, span: *location, }, extra_labels: Vec::new(), }), }, type_::Warning::ModuleImportedTwice { name, first, second, } => Diagnostic { title: "Duplicate import".into(), text: format!("The {name} module has been imported twice."), hint: None, level: diagnostic::Level::Warning, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: diagnostic::Label { text: Some("Reimported here".into()), span: *second, }, extra_labels: vec![ExtraLabel { src_info: None, label: diagnostic::Label { text: Some("First imported here".into()), span: *first, }, }], }), }, type_::Warning::UnusedDiscardPattern { location, name } => Diagnostic { title: "Unused discard pattern".into(), text: format!("`_ as {name}` can be written more concisely as `{name}`"), level: diagnostic::Level::Warning, location: Some(Location { src: src.clone(), path: path.to_path_buf(), label: diagnostic::Label { text: None, span: SrcSpan { start: location.start - "_ as ".len() as u32, end: location.end, }, }, extra_labels: vec![], }), hint: None, }, type_::Warning::TopLevelDefinitionShadowsImport { location, name } => { let text = format!( "Definition of {name} shadows an imported value. The imported value could not be used in this module anyway." ); Diagnostic { title: "Shadowed Import".into(), text: wrap(&text), level: diagnostic::Level::Warning, location: Some(Location { path: path.clone(), src: src.clone(), label: diagnostic::Label { text: Some(wrap(&format!("`{name}` is defined here"))), span: *location, }, extra_labels: Vec::new(), }), hint: Some("Either rename the definition or remove the import.".into()), } } type_::Warning::RedundantComparison { location, outcome } => Diagnostic { title: "Redundant comparison".into(), text: format!( "This comparison is redundant since it always {}.", match outcome { ComparisonOutcome::AlwaysSucceeds => "succeeds", ComparisonOutcome::AlwaysFails => "fails", } ), hint: None, level: diagnostic::Level::Warning, location: Some(Location { label: diagnostic::Label { text: Some(format!( "This is always `{}`", match outcome { ComparisonOutcome::AlwaysSucceeds => "True", ComparisonOutcome::AlwaysFails => "False", } )), span: *location, }, path: path.clone(), src: src.clone(), extra_labels: vec![], }), }, }, Warning::EmptyModule { path: _, name } => Diagnostic { title: "Empty module".into(), text: format!("Module '{name}' contains no public definitions."), hint: Some("You can safely remove this module.".into()), level: diagnostic::Level::Warning, location: None, }, } } pub fn pretty(&self, buffer: &mut Buffer) { self.to_diagnostic().write(buffer); buffer .write_all(b"\n") .expect("error pretty buffer write space after"); } pub fn to_pretty_string(&self) -> String { let mut nocolor = Buffer::no_color(); self.pretty(&mut nocolor); String::from_utf8(nocolor.into_inner()).expect("Warning printing produced invalid utf8") } } fn explain_impossible_segment(segment: &ImpossibleBitArraySegmentPattern) -> String { match segment { ImpossibleBitArraySegmentPattern::UnrepresentableInteger { size, signed, value: _, location: _, } => { let human_readable_size = match size { 1 => "1 bit".into(), 8 => "1 byte".into(), n if n % 8 == 0 => format!("{} bytes", n / 8), n => format!("{n} bits"), }; let sign = if *signed { "signed" } else { "unsigned" }; format!("A {human_readable_size} {sign} integer will never match this value") } } } fn pluralise(string: String, quantity: i64) -> String { if quantity == 1 { string } else { format!("{string}s") } } ================================================ FILE: compiler-core/templates/docs-css/index.css ================================================ /* karla regular latin-ext */ @font-face { font-family: "Karla"; font-style: normal; font-weight: 400; font-display: swap; src: url("../fonts/karla-v23-regular-latin-ext.woff2") format("woff2"); unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; } /* karla regular latin */ @font-face { font-family: "Karla"; font-style: normal; font-weight: 400; font-display: swap; src: url("../fonts/karla-v23-regular-latin.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } /* karla bold latin-ext */ @font-face { font-family: "Karla"; font-style: normal; font-weight: 700; font-display: swap; src: url("../fonts/karla-v23-bold-latin-ext.woff2") format("woff2"); unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; } /* karla bold latin */ @font-face { font-family: "Karla"; font-style: normal; font-weight: 700; font-display: swap; src: url("../fonts/karla-v23-bold-latin.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } /* ubuntu mono cyrillic-ext */ @font-face { font-family: "Ubuntu Mono"; font-style: normal; font-weight: 400; font-display: swap; src: url("../fonts/ubuntu-mono-v15-regular-cyrillic-ext.woff2") format("woff2"); unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; } /* ubuntu mono cyrillic */ @font-face { font-family: "Ubuntu Mono"; font-style: normal; font-weight: 400; font-display: swap; src: url("../fonts/ubuntu-mono-v15-regular-cyrillic.woff2") format("woff2"); unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } /* ubuntu mono greek-ext */ @font-face { font-family: "Ubuntu Mono"; font-style: normal; font-weight: 400; font-display: swap; src: url("../fonts/ubuntu-mono-v15-regular-greek-ext.woff2") format("woff2"); unicode-range: U+1F00-1FFF; } /* ubuntu mono greek */ @font-face { font-family: "Ubuntu Mono"; font-style: normal; font-weight: 400; font-display: swap; src: url("../fonts/ubuntu-mono-v15-regular-greek.woff2") format("woff2"); unicode-range: U+0370-03FF; } /* ubuntu mono latin-ext */ @font-face { font-family: "Ubuntu Mono"; font-style: normal; font-weight: 400; font-display: swap; src: url("../fonts/ubuntu-mono-v15-regular-latin-ext.woff2") format("woff2"); unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; } /* ubuntu mono latin */ @font-face { font-family: "Ubuntu Mono"; font-style: normal; font-weight: 400; font-display: swap; src: url("../fonts/ubuntu-mono-v15-regular-latin.woff2") format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } :root { /* Colours */ --black: #2a2020; --hard-black: #000; --pink: #ffaff3; --hot-pink: #d900b8; --white: #fff; --pink-white: #fff8fe; --mid-grey: #dfe2e5; --light-grey: #f5f5f5; --boi-blue: #a6f0fc; /* Derived colours */ --text: var(--black); --background: var(--white); --accented-background: var(--pink-white); --code-border: var(--pink); --code-background: var(--light-grey); --table-border: var(--mid-grey); --table-background: var(--pink-white); --links: var(--hot-pink); --accent: var(--pink); /* Sizes */ --content-width: 772px; --header-height: 60px; --hash-offset: calc(var(--header-height) * 1.67); --sidebar-width: 240px; --gap: 24px; --small-gap: calc(var(--gap) / 2); --tiny-gap: calc(var(--small-gap) / 2); --large-gap: calc(var(--gap) * 2); --sidebar-toggle-size: 33px; --search-height: 4rem; /* etc */ --shadow: 0 0 0 1px rgba(50, 50, 93, 0.075), 0 0 1px #e9ecef, 0 2px 4px -2px rgba(138, 141, 151, 0.6); --nav-shadow: 0 0 6px 2px rgba(0, 0, 0, 0.1); } * { box-sizing: border-box; } body, html { padding: 0; margin: 0; font-family: "Karla", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, system-ui, ui-sans-serif, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 17px; line-height: 1.4; position: relative; min-height: 100vh; word-break: break-word; } html { /* This is necessary so hash targets appear below the fixed header */ scroll-padding-top: var(--hash-offset); } a, a:visited { color: var(--links); text-decoration: none; } a:hover { text-decoration: underline; } button, select { background: transparent; border: 0 none; cursor: pointer; font-family: inherit; font-size: 100%; line-height: 1.15; margin: 0; text-transform: none; } button::-moz-focus-inner { border-style: none; padding: 0; } button:-moz-focusring { outline: 1px dotted ButtonText; } button { -webkit-appearance: button; appearance: button; line-height: 1; margin: 0; overflow: visible; padding: 0; } button:active, select:active { outline: 0 none; } li { margin-bottom: 4px; } p { margin: var(--small-gap) 0; } .rendered-markdown h1, .rendered-markdown h2, .rendered-markdown h3, .rendered-markdown h4, .rendered-markdown h5 { font-size: 1.3rem; } blockquote { border-left: 4px solid var(--accent); padding: var(--small-gap) var(--gap); background-color: var(--accented-background); margin: var(--small-gap) 0; } /* Code */ pre, code { font-family: "Ubuntu Mono", SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", ui-monospace, monospace; line-height: 1.2; background-color: var(--code-background); } pre { margin: var(--gap) 0; border-radius: 1px; overflow: auto; box-shadow: var(--shadow); } pre > code, code.hljs { padding: var(--small-gap) var(--gap); background: transparent; } p code { margin: 0 2px; border-radius: 3px; padding: 0 0.2em; color: var(--inline-code); } p a code { color: var(--links); } /* Page layout */ .page { display: flex; } .content { margin-left: var(--sidebar-width); margin-bottom: var(--large-gap); padding: calc(var(--header-height) + var(--gap)) var(--gap) 0 var(--gap); width: calc(100% - var(--sidebar-width)); max-width: var(--content-width); } .content img { max-width: 100%; } /* Page header */ .page-header { box-shadow: var(--nav-shadow); height: var(--header-height); color: black; color: var(--hard-black); background-color: var(--pink); display: flex; padding: var(--small-gap) var(--gap); position: fixed; left: 0; right: 0; top: 0; z-index: 300; } .page-header h2 { align-items: baseline; display: flex; margin: 0; width: var(--sidebar-width); } .page-header a, .page-header a:visited { color: black; color: var(--hard-black); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .sidebar-toggle { display: none; font-size: var(--sidebar-toggle-size); opacity: 0; transition: opacity 1s ease; } .search-nav-button { display: none; font-size: var(--sidebar-toggle-size); opacity: 0; transition: opacity 1s ease; } .page-header .sidebar-toggle { color: white; color: var(--white); margin: 0 var(--small-gap) 0 0; } .page-header .search-nav-button { color: white; color: var(--white); margin: 0 var(--small-gap) 0 0; } /* Version selector */ #project-version { --half-small-gap: calc(var(--small-gap) / 2); --icon-size: 0.75em; flex-shrink: 0; font-size: 0.9rem; font-weight: normal; margin-left: var(--half-small-gap); } #project-version > span { padding-left: var(--half-small-gap); } #project-version form { align-items: center; display: inline-flex; justify-content: flex-end; } #project-version select { appearance: none; -webkit-appearance: none; padding: 0.6rem calc(1.3 * var(--icon-size)) 0.6rem var(--half-small-gap); position: relative; z-index: 1; } #project-version option { background-color: var(--code-background); } #project-version .icon { font-size: var(--icon-size); margin-left: calc(-1.65 * var(--icon-size)); } /* Module doc */ .module-name > a, .module-member-kind > a { color: inherit; } .module-name > a:hover, .module-member-kind > a:hover { text-decoration: none; } .module-name > .icon-gleam-chasse, .module-member-kind > .icon-gleam-chasse, .module-member-kind > .icon-gleam-chasse-2 { color: var(--pink); display: block; font-size: 1rem; margin: var(--small-gap) 0 0; } .module-name { color: var(--hard-black); margin: 0 0 var(--gap); font-weight: 700; } /* Sidebar */ .sidebar { background-color: var(--background); font-size: 0.95rem; max-height: calc(100vh - var(--header-height)); overflow-y: auto; overscroll-behavior: contain; padding-top: var(--gap); padding-bottom: calc(3 * var(--gap)); padding-left: var(--gap); padding-right: var(--gap); position: fixed; top: var(--header-height); transition: transform 0.5s ease; width: var(--sidebar-width); z-index: 100; } .sidebar h2 { margin: 0; } .sidebar ul { list-style: none; margin: var(--small-gap) 0; padding: 0; } .sidebar li { line-height: 1.2; margin-bottom: 4px; } .module-link { display: inline-block; padding-left: 0.5em; text-indent: -0.5em; line-height: 1.2; } .sidebar .sidebar-toggle { color: var(--pink); font-size: calc(0.8 * var(--sidebar-toggle-size)); } body.drawer-closed .label-open, body.drawer-open .label-closed { display: none; } .display-controls { display: flex; flex-wrap: wrap; margin-top: var(--small-gap); padding-right: var(--gap); } .display-controls .control { margin: 0.5rem 0; } .display-controls .control:not(:first-child) { margin-left: 1rem; } .toggle { align-items: center; display: flex; font-size: 0.96rem; } .toggle-0 .label:not(.label-0), .toggle-1 .label:not(.label-1) { display: none; } .label { display: flex; } .label .icon { margin: 0 0.28rem; } /* Module members (types, functions) */ .module-members { margin-top: var(--large-gap); } .module-members:last-of-type .member:last-of-type { margin-bottom: 0; } .module-member-kind { font-size: 2rem; color: var(--text); } .member { margin: var(--large-gap) 0; padding-bottom: var(--gap); } .member-name { display: flex; align-items: center; justify-content: space-between; border-left: 4px solid var(--accent); padding: var(--small-gap) var(--gap); background-color: var(--accented-background); } .member-name h2 { display: flex; font-size: 1.5rem; margin: 0; } .member-name h2 a { color: var(--text); } .member-source { align-self: baseline; flex-shrink: 0; line-height: calc(1.4 * 1.5rem); margin: 0 0 0 var(--small-gap); } .visibility-tag { background-color: var(--bg-shade-3); color: var(--text); padding: 0px 6px 4px; border-radius: 4px; font-size: 0.9em; margin-left: auto; line-height: normal; } /* Custom type constructors */ .constructor-list { list-style: none; padding: 0; } .constructor-row { align-items: center; display: flex; } .constructor-item { margin-bottom: var(--small-gap); } .constructor-argument-label { font-style: italic; } .constructor-argument-list { margin-bottom: var(--small-gap); } .constructor-item-docs { margin-left: var(--large-gap); margin-bottom: var(--gap); } .constructor-item .icon { flex-shrink: 0; font-size: 0.7rem; margin: 0 0.88rem; } .constructor-name { box-shadow: unset; margin: 0; } .constructor-name > code { padding: var(--small-gap); } /* Tables */ table { border-spacing: 0; border-collapse: collapse; } table td, table th { padding: 6px 13px; border: 1px solid var(--table-border); } table tr:nth-child(2n) { background-color: var(--table-background); } /* Footer */ .pride { width: 100%; display: none; flex-direction: row; position: absolute; bottom: 0; z-index: 100; } .show-pride .pride { display: flex; } .show-pride .sidebar { margin-bottom: var(--gap); } .pride div { flex: 1; text-align: center; padding: var(--tiny-gap); } .pride .white { background-color: var(--white); } .pride .pink { background-color: var(--pink); } .pride .blue { background-color: var(--boi-blue); } .pride-button { position: absolute; right: 2px; bottom: 2px; opacity: 0.2; font-size: 0.9rem; } .pride-button { text-decoration: none; cursor: default; } /* Icons */ .svg-lib { height: 0; overflow: hidden; position: absolute; width: 0; } .icon { display: inline-block; fill: currentColor; height: 1em; stroke: currentColor; stroke-width: 0; width: 1em; } .icon-gleam-chasse { width: 8.182em; } .icon-gleam-chasse-2 { width: 4.909em; } /* Pre-Wrap Option */ body.prewrap-on code, body.prewrap-on pre { white-space: pre-wrap; } /* Dark Theme Option */ body.theme-dark { /* Colour palette adapted from: * https://github.com/dustypomerleau/yarra-valley */ --argument-atom: #c651e5; --class-module: #ff89b5; --comment: #7e818b; --escape: #7cdf89; --function-call: #abb8c0; --function-definition: #8af899; --interpolation-regex: #ee37aa; --keyword-operator: #ff9d35; --number-boolean: #f14360; --object: #99c2eb; --punctuation: #4ce7ff; --string: #aecc00; --inline-code: #ff9d35; --bg: #292d3e; --bg-tint-1: #3e4251; --bg-tint-2: #535664; --bg-tint-3: #696c77; --bg-tint-4: #7e818b; --bg-shade-1: #242837; --bg-shade-2: #202431; --bg-shade-3: #1c1f2b; --bg-mono-1: #33384d; --bg-mono-2: #3d435d; --bg-mono-3: #474e6c; --bg-mono-4: #51597b; --fg: #cac0a9; --fg-tint-1: #fdf2d8; --fg-tint-2: #fdf3dc; --fg-tint-3: #fdf5e0; --fg-shade-1: #e3d8be; --fg-shade-2: #cac0a9; --fg-shade-3: #b1a894; --fg-shade-4: #97907f; --orange-shade-1: #e58d2f; --orange-shade-2: #cc7d2a; --orange-shade-3: #b26d25; --taupe-mono-1: #fdf1d4; --taupe-mono-2: #fce9bc; --taupe-mono-3: #fbe1a3; /* Theme Overrides */ --accent: var(--pink); --accented-background: var(--bg-shade-1); --background: var(--bg); --code-background: var(--bg-shade-2); --table-background: var(--bg-mono-1); --hard-black: var(--taupe-mono-1); --links: var(--pink); --text: var(--taupe-mono-1); --shadow: 0 0 0 1px rgba(50, 50, 93, 0.075), 0 0 1px var(--fg-shade-3), 0 2px 4px -2px rgba(138, 141, 151, 0.2); --nav-shadow: 0 0 5px 5px rgba(0, 0, 0, 0.1); } body.theme-dark { background-color: var(--bg); color: var(--fg-shade-1); } body.theme-dark .page-header { background-color: var(--bg-mono-1); } body.theme-dark .page-header h2 { color: var(--fg-shade-1); } body.theme-dark .page-header a, body.theme-dark .page-header a:visited { color: var(--pink); } body.theme-dark .page-header .sidebar-toggle { color: var(--fg-shade-1); } body.theme-dark .page-header .search-nav-button { color: var(--fg-shade-1); } body.theme-dark #project-version select, body.theme-dark .control { color: var(--fg-shade-1); } body.theme-dark .module-name { color: var(--taupe-mono-1); } body.theme-dark .pride { color: var(--bg-shade-3); } body.theme-dark .pride .white { background-color: var(--fg-shade-1); } body.theme-dark .pride .pink { background-color: var(--argument-atom); } body.theme-dark .pride .blue { background-color: var(--punctuation); } /* Medium and larger displays */ @media (min-width: 680px) { #prewrap-toggle { display: none; } } /* Small displays */ @media (max-width: 920px) { .page-header { padding-left: var(--small-gap); padding-right: var(--small-gap); } .page-header h2 { width: calc( 100% - var(--sidebar-toggle-size) - var(--small-gap) - var(--sidebar-toggle-size) - var(--small-gap) ); } .content { width: 100%; max-width: unset; margin-left: unset; } .sidebar { box-shadow: var(--nav-shadow); height: 100vh; max-height: unset; top: 0; transform: translate(calc(-10px - var(--sidebar-width))); z-index: 500; } body.drawer-open .sidebar { transform: translate(0); } .sidebar-toggle { display: block; opacity: 1; } .search-nav-button { display: block; opacity: 1; } .sidebar .sidebar-toggle { height: var(--sidebar-toggle-size); position: absolute; right: var(--small-gap); top: var(--small-gap); width: var(--sidebar-toggle-size); } } /* Search */ .search { display: none; position: relative; z-index: 2; flex-grow: 1; height: var(--search-height); padding: 0.5rem; transition: padding linear 200ms; } @media (min-width: 919px) { .search { margin-left: var(--small-gap); display: block; position: relative !important; width: auto !important; height: 100% !important; padding: 0; transition: none; } } .search-input-wrap { position: relative; z-index: 1; height: 3rem; overflow: hidden; border-radius: 4px; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12), 0 3px 10px rgba(0, 0, 0, 0.08); transition: height linear 200ms; } @media (min-width: 919px) { .search-input-wrap { position: absolute; width: 100%; max-width: calc(var(--content-width) - var(--gap) - var(--gap)); height: 100% !important; border-radius: 0; box-shadow: none; transition: width ease 400ms; } } .search-input { position: absolute; width: 100%; height: 100%; padding: 0.5rem 1rem; font-size: 16px; background-color: var(--background); color: var(--text); border-top: 0; border-right: 0; border-bottom: 0; border-left: 0; border-radius: 0; } @media (min-width: 919px) { .search-input { padding: 1rem; font-size: 14px; background-color: var(--background); transition: padding-left linear 200ms; } } .search-input:focus { outline: 0; } .search-input:focus + .search-label .search-icon { color: var(--pink); } .search-label { position: absolute; right: 0; display: flex; height: 100%; padding-right: 1rem; cursor: pointer; } @media (min-width: 919px) { .search-label { padding-right: 0.6rem; transition: padding-left linear 200ms; } } .search-label .search-icon { width: 1.2rem; height: 1.2rem; align-self: center; color: var(--text); } .search-results { position: absolute; left: 0; display: none; width: 100%; max-height: calc(100% - var(--search-height)); overflow-y: auto; background-color: var(--background); border-bottom-right-radius: 4px; border-bottom-left-radius: 4px; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12), 0 3px 10px rgba(0, 0, 0, 0.08); } @media (min-width: 919px) { .search-results { top: 100%; width: calc(var(--content-width) - var(--gap) - var(--gap)); max-height: calc(100vh - 200%) !important; } } .search-results-list { padding-left: 0; margin-bottom: 0.25rem; list-style: none; font-size: 14px !important; } @media (min-width: 31.25rem) { .search-results-list { font-size: 16px !important; } } @media (min-width: 919px) { .search-results-list { font-size: 12px !important; } } @media (min-width: 919px) and (min-width: 31.25rem) { .search-results-list { font-size: 14px !important; } } .search-results-list-item { padding: 0; margin: 0; } .search-result { display: block; padding-top: 0.25rem; padding-right: 0.75rem; padding-bottom: 0.25rem; padding-left: 0.75rem; } .search-result:hover, .search-result.active { background-color: var(--code-background); } .search-result-title { display: block; padding-top: 0.5rem; padding-bottom: 0.5rem; } @media (min-width: 31.25rem) { .search-result-title { display: inline-block; width: 40%; padding-right: 0.5rem; vertical-align: top; } } .search-result-doc { display: flex; align-items: center; word-wrap: break-word; } .search-result-doc.search-result-doc-parent { opacity: 0.5; font-size: 12px !important; } @media (min-width: 31.25rem) { .search-result-doc.search-result-doc-parent { font-size: 14px !important; } } @media (min-width: 919px) { .search-result-doc.search-result-doc-parent { font-size: 11px !important; } } @media (min-width: 919px) and (min-width: 31.25rem) { .search-result-doc.search-result-doc-parent { font-size: 12px !important; } } .search-result-doc .search-result-icon { width: 1rem; height: 1rem; margin-right: 0.5rem; color: var(--pink); flex-shrink: 0; } .search-result-doc .search-result-doc-title { overflow: auto; } .search-result-section { margin-left: 1.5rem; word-wrap: break-word; } .search-result-rel-url { display: block; margin-left: 1.5rem; overflow: hidden; color: var(--text); text-overflow: ellipsis; white-space: nowrap; font-size: 9px !important; } @media (min-width: 31.25rem) { .search-result-rel-url { font-size: 10px !important; } } .search-result-previews { display: block; padding-top: 0.5rem; padding-bottom: 0.5rem; padding-left: 1rem; margin-left: 0.5rem; color: var(--text); word-wrap: break-word; border-left: 1px solid; border-left-color: #eeebee; font-size: 11px !important; /* TODO: fix it by not adding at the parent? */ white-space: initial !important; } @media (min-width: 31.25rem) { .search-result-previews { font-size: 12px !important; } } @media (min-width: 31.25rem) { .search-result-previews { display: inline-block; width: 60%; padding-left: 0.5rem; margin-left: 0; vertical-align: top; } } .search-result-preview + .search-result-preview { margin-top: 0.25rem; } .search-result-highlight { font-weight: bold; } .search-no-result { padding-top: 0.5rem; padding-right: 0.75rem; padding-bottom: 0.5rem; padding-left: 0.75rem; font-size: 12px !important; } @media (min-width: 31.25rem) { .search-no-result { font-size: 14px !important; } } .search-button { position: fixed; right: 1rem; bottom: 1rem; display: flex; width: 3.5rem; height: 3.5rem; background-color: var(--background); border: 1px solid rgba(114, 83, 237, 0.3); border-radius: 1.75rem; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12), 0 3px 10px rgba(0, 0, 0, 0.08); align-items: center; justify-content: center; } .search-overlay { position: fixed; top: 0; left: 0; z-index: 101; width: 0; height: 0; background-color: rgba(0, 0, 0, 0.3); opacity: 0; transition: opacity ease 400ms, width 0s 400ms, height 0s 400ms; } .search-active .search { display: block; position: fixed; top: 0; left: 0; width: 100%; height: 100%; padding: 0; } .search-active .search-input-wrap { height: var(--search-height); border-radius: 0; } @media (min-width: 919px) { .search-active .search-input-wrap { width: calc(var(--content-width) - var(--gap) - var(--gap)); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12), 0 3px 10px rgba(0, 0, 0, 0.08); } } @media (min-width: 919px) { .search-active .search-label { padding-left: 0.6rem; } } .search-active .search-results { display: block; } .search-active .search-overlay { width: 100%; height: 100%; opacity: 1; transition: opacity ease 400ms, width 0s, height 0s; } @media (min-width: 919px) { .search-active .main { position: fixed; right: 0; left: 0; } } .search-active .main-header { padding-top: var(--search-height); } @media (min-width: 919px) { .search-active .main-header { padding-top: 0; } } ================================================ FILE: compiler-core/templates/docs-js/highlightjs-gleam.js ================================================ hljs.registerLanguage("gleam", function (hljs) { const KEYWORDS = { className: "keyword", beginKeywords: "as assert auto case const delegate derive echo else fn if " + "implement import let macro opaque panic pub test todo type use", }; const STRING = { className: "string", variants: [{ begin: /"/, end: /"/ }], contains: [hljs.BACKSLASH_ESCAPE], relevance: 0, }; const NAME = { className: "variable", begin: "\\b[a-z][a-z0-9_]*\\b", relevance: 0, }; const DISCARD_NAME = { className: "comment", begin: "\\b_[a-z][a-z0-9_]*\\b", relevance: 0, }; const NUMBER = { className: "number", variants: [ { // binary begin: "\\b0[bB](?:_?[01]+)+", }, { // octal begin: "\\b0[oO](?:_?[0-7]+)+", }, { // hex begin: "\\b0[xX](?:_?[0-9a-fA-F]+)+", }, { // dec, float begin: "\\b\\d(?:_?\\d+)*(?:\\.(?:\\d(?:_?\\d+)*)*)?", }, ], relevance: 0, }; return { name: "Gleam", aliases: ["gleam"], contains: [ hljs.C_LINE_COMMENT_MODE, STRING, { // bit array begin: "<<", end: ">>", contains: [ { className: "keyword", beginKeywords: "binary bits bytes int float bit_string bit_array bits utf8 utf16 " + "utf32 utf8_codepoint utf16_codepoint utf32_codepoint signed " + "unsigned big little native unit size", }, KEYWORDS, STRING, NAME, DISCARD_NAME, NUMBER, ], relevance: 10, }, { className: "function", beginKeywords: "fn", end: "\\(", excludeEnd: true, contains: [ { className: "title", begin: "[a-z][a-z0-9_]*\\w*", relevance: 0, }, ], }, { className: "attribute", begin: "@", end: "\\(", excludeEnd: true, }, KEYWORDS, { // Type names and constructors className: "title", begin: "\\b[A-Z][A-Za-z0-9]*\\b", relevance: 0, }, { className: "operator", begin: "[+\\-*/%!=<>&|.]+", relevance: 0, }, NAME, DISCARD_NAME, NUMBER, ], }; }); ================================================ FILE: compiler-core/templates/docs-js/index.js ================================================ "use strict"; window.Gleam = (function () { /* Global Object */ const self = {}; /* Public Properties */ self.hashOffset = undefined; /* Public Methods */ self.getProperty = function (property) { let value; try { value = localStorage.getItem(`Gleam.${property}`); } catch (_error) { } if (-1 < [null, undefined].indexOf(value)) { return gleamConfig[property].values[0].value; } return value; }; self.icons = function () { return Array.from(arguments).reduce( (acc, name) => `${acc} `, "", ); }; self.scrollToHash = function () { const locationHash = arguments[0] || window.location.hash; const query = locationHash ? locationHash : "body"; const hashTop = document.querySelector(query).offsetTop; window.scrollTo(0, hashTop - self.hashOffset); return locationHash; }; self.toggleSidebar = function () { const previousState = bodyClasses.contains("drawer-open") ? "open" : "closed"; let state; if (0 < arguments.length) { state = false === arguments[0] ? "closed" : "open"; } else { state = "open" === previousState ? "closed" : "open"; } bodyClasses.remove(`drawer-${previousState}`); bodyClasses.add(`drawer-${state}`); if ("open" === state) { document.addEventListener("click", closeSidebar, false); } }; /* Private Properties */ const html = document.documentElement; const body = document.body; const bodyClasses = body.classList; const sidebar = document.querySelector(".sidebar"); const sidebarToggles = document.querySelectorAll(".sidebar-toggle"); const displayControls = document.createElement("div"); const isMac = navigator?.userAgentData?.platform === 'macOS' || /mac/i.test(navigator.userAgent); displayControls.classList.add("display-controls"); sidebar.appendChild(displayControls); /* Private Methods */ const initProperty = function (property) { const config = gleamConfig[property]; displayControls.insertAdjacentHTML( "beforeend", config.values.reduce( (acc, item, index) => { const tooltip = item.label ? `alt="${item.label}" title="${item.label}"` : ""; let inner; if (item.icons) { inner = self.icons(...item.icons); } else if (item.label) { inner = item.label; } else { inner = ""; } return ` ${acc} ${inner} `; }, ` `, ); setProperty(null, property, function () { return self.getProperty(property); }); }; const setProperty = function (_event, property) { const previousValue = self.getProperty(property); const update = 2 < arguments.length ? arguments[2] : gleamConfig[property].update; const value = update(); try { localStorage.setItem("Gleam." + property, value); } catch (_error) { } bodyClasses.remove(`${property}-${previousValue}`); bodyClasses.add(`${property}-${value}`); const isDefault = value === gleamConfig[property].values[0].value; const toggleClasses = document.querySelector( `#${property}-toggle`, ).classList; toggleClasses.remove(`toggle-${isDefault ? 1 : 0}`); toggleClasses.add(`toggle-${isDefault ? 0 : 1}`); try { gleamConfig[property].callback(value); } catch (_error) { } return value; }; const setHashOffset = function () { const el = document.createElement("div"); el.style.cssText = ` height: var(--hash-offset); pointer-events: none; position: absolute; visibility: hidden; width: 0; `; body.appendChild(el); self.hashOffset = parseInt( getComputedStyle(el).getPropertyValue("height") || "0", ); body.removeChild(el); }; const closeSidebar = function (event) { if (!event.target.closest(".sidebar-toggle")) { document.removeEventListener("click", closeSidebar, false); self.toggleSidebar(false); } }; const addEvent = function (el, type, handler) { if (el.attachEvent) el.attachEvent("on" + type, handler); else el.addEventListener(type, handler); }; const searchLoaded = function (index, searchItems) { const preview_words_after = 10; const preview_words_before = 5; const previews = 3; const searchInput = document.getElementById("search-input"); const searchNavButton = document.getElementById("search-nav-button"); const searchResults = document.getElementById("search-results"); let currentInput; let currentSearchIndex = 0; // Set search input placeholder based on OS searchInput.setAttribute("placeholder", isMac ? "Search ⌘ + K" : "Search Ctrl + K") function showSearch() { document.documentElement.classList.add("search-active"); } searchNavButton.addEventListener("click", function (e) { e.stopPropagation(); showSearch(); setTimeout(function () { searchInput.focus(); }, 0); }); function hideSearch() { document.documentElement.classList.remove("search-active"); } function update() { currentSearchIndex++; const input = searchInput.value; showSearch(); if (input === currentInput) { return; } currentInput = input; searchResults.innerHTML = ""; if (input === "") { return; } let results = index.query(function (query) { const tokens = lunr.tokenizer(input); query.term(tokens, { boost: 10, }); query.term(tokens, { boost: 5, wildcard: lunr.Query.wildcard.TRAILING, }); query.term(tokens, { wildcard: lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING, }); }); if (results.length == 0 && input.length > 2) { const tokens = lunr.tokenizer(input).filter(function (token, i) { return token.str.length < 20; }); if (tokens.length > 0) { results = index.query(function (query) { query.term(tokens, { editDistance: Math.round(Math.sqrt(input.length / 2 - 1)), }); }); } } if (results.length == 0) { const noResultsDiv = document.createElement("div"); noResultsDiv.classList.add("search-no-result"); noResultsDiv.innerText = "No results found"; searchResults.appendChild(noResultsDiv); } else { const resultsList = document.createElement("ul"); resultsList.classList.add("search-results-list"); searchResults.appendChild(resultsList); addResults(resultsList, results, 0, 10, 100, currentSearchIndex); } function addResults( resultsList, results, start, batchSize, batchMillis, searchIndex, ) { if (searchIndex != currentSearchIndex) { return; } for (let i = start; i < start + batchSize; i++) { if (i == results.length) { return; } addResult(resultsList, results[i]); } setTimeout(function () { addResults( resultsList, results, start + batchSize, batchSize, batchMillis, searchIndex, ); }, batchMillis); } function addResult(resultsList, result) { const searchItem = searchItems[result.ref]; const resultsListItem = document.createElement("li"); resultsListItem.classList.add("search-results-list-item"); resultsList.appendChild(resultsListItem); const resultLink = document.createElement("a"); resultLink.classList.add("search-result"); resultLink.setAttribute("href", `${window.unnest}/${searchItem.ref}`); resultsListItem.appendChild(resultLink); const resultTitle = document.createElement("div"); resultTitle.classList.add("search-result-title"); resultLink.appendChild(resultTitle); const resultDoc = document.createElement("div"); resultDoc.classList.add("search-result-doc"); resultDoc.innerHTML = ''; resultTitle.appendChild(resultDoc); const resultDocTitle = document.createElement("div"); resultDocTitle.classList.add("search-result-doc-title"); resultDocTitle.innerHTML = searchItem.parentTitle; resultDoc.appendChild(resultDocTitle); let resultDocOrSection = resultDocTitle; if (searchItem.parentTitle != searchItem.title) { resultDoc.classList.add("search-result-doc-parent"); const resultSection = document.createElement("div"); resultSection.classList.add("search-result-section"); resultSection.innerHTML = searchItem.title; resultTitle.appendChild(resultSection); resultDocOrSection = resultSection; } const metadata = result.matchData.metadata; const titlePositions = []; const contentPositions = []; for (let j in metadata) { const meta = metadata[j]; if (meta.title) { const positions = meta.title.position; for (let k in positions) { titlePositions.push(positions[k]); } } if (meta.content) { const positions = meta.content.position; for (let k in positions) { const position = positions[k]; let previewStart = position[0]; let previewEnd = position[0] + position[1]; let ellipsesBefore = true; let ellipsesAfter = true; for (let k = 0; k < preview_words_before; k++) { const nextSpace = searchItem.doc.lastIndexOf( " ", previewStart - 2, ); const nextDot = searchItem.doc.lastIndexOf(". ", previewStart - 2); if (nextDot >= 0 && nextDot > nextSpace) { previewStart = nextDot + 1; ellipsesBefore = false; break; } if (nextSpace < 0) { previewStart = 0; ellipsesBefore = false; break; } previewStart = nextSpace + 1; } for (let k = 0; k < preview_words_after; k++) { const nextSpace = searchItem.doc.indexOf(" ", previewEnd + 1); const nextDot = searchItem.doc.indexOf(". ", previewEnd + 1); if (nextDot >= 0 && nextDot < nextSpace) { previewEnd = nextDot; ellipsesAfter = false; break; } if (nextSpace < 0) { previewEnd = searchItem.doc.length; ellipsesAfter = false; break; } previewEnd = nextSpace; } contentPositions.push({ highlight: position, previewStart: previewStart, previewEnd: previewEnd, ellipsesBefore: ellipsesBefore, ellipsesAfter: ellipsesAfter, }); } } } if (titlePositions.length > 0) { titlePositions.sort(function (p1, p2) { return p1[0] - p2[0]; }); resultDocOrSection.innerHTML = ""; addHighlightedText( resultDocOrSection, searchItem.title, 0, searchItem.title.length, titlePositions, ); } if (contentPositions.length > 0) { contentPositions.sort(function (p1, p2) { return p1.highlight[0] - p2.highlight[0]; }); let contentPosition = contentPositions[0]; let previewPosition = { highlight: [contentPosition.highlight], previewStart: contentPosition.previewStart, previewEnd: contentPosition.previewEnd, ellipsesBefore: contentPosition.ellipsesBefore, ellipsesAfter: contentPosition.ellipsesAfter, }; const previewPositions = [previewPosition]; for (let j = 1; j < contentPositions.length; j++) { contentPosition = contentPositions[j]; if (previewPosition.previewEnd < contentPosition.previewStart) { previewPosition = { highlight: [contentPosition.highlight], previewStart: contentPosition.previewStart, previewEnd: contentPosition.previewEnd, ellipsesBefore: contentPosition.ellipsesBefore, ellipsesAfter: contentPosition.ellipsesAfter, }; previewPositions.push(previewPosition); } else { previewPosition.highlight.push(contentPosition.highlight); previewPosition.previewEnd = contentPosition.previewEnd; previewPosition.ellipsesAfter = contentPosition.ellipsesAfter; } } const resultPreviews = document.createElement("div"); resultPreviews.classList.add("search-result-previews"); resultLink.appendChild(resultPreviews); const content = searchItem.doc; for ( let j = 0; j < Math.min(previewPositions.length, previews); j++ ) { const position = previewPositions[j]; const resultPreview = document.createElement("div"); resultPreview.classList.add("search-result-preview"); resultPreviews.appendChild(resultPreview); if (position.ellipsesBefore) { resultPreview.appendChild(document.createTextNode("... ")); } addHighlightedText( resultPreview, content, position.previewStart, position.previewEnd, position.highlight, ); if (position.ellipsesAfter) { resultPreview.appendChild(document.createTextNode(" ...")); } } } const resultRelUrl = document.createElement("span"); resultRelUrl.classList.add("search-result-rel-url"); resultRelUrl.innerText = searchItem.ref; resultTitle.appendChild(resultRelUrl); } function addHighlightedText(parent, text, start, end, positions) { let index = start; for (let i in positions) { const position = positions[i]; const span = document.createElement("span"); span.innerHTML = text.substring(index, position[0]); parent.appendChild(span); index = position[0] + position[1]; const highlight = document.createElement("span"); highlight.classList.add("search-result-highlight"); highlight.innerHTML = text.substring(position[0], index); parent.appendChild(highlight); } const span = document.createElement("span"); span.innerHTML = text.substring(index, end); parent.appendChild(span); } } addEvent(document, "keydown", function (event) { // Don't do anything when search is already in focus if (document.activeElement == searchInput) { return } if ( // Handle cmd/ctrl + k ((event.metaKey || event.ctrlKey) && event.key === 'k') || // Handle s or / event.key === 's' || event.key === '/' ) { event.preventDefault(); searchInput.focus(); return; } }) addEvent(searchInput, "focus", function () { setTimeout(update, 0); }); addEvent(searchInput, "keyup", function (e) { switch (e.keyCode) { case 27: // When esc key is pressed, hide the results and clear the field searchInput.value = ""; break; case 38: // arrow up case 40: // arrow down case 13: // enter e.preventDefault(); return; } update(); }); addEvent(searchInput, "keydown", function (e) { let active; switch (e.keyCode) { case 38: // arrow up e.preventDefault(); active = document.querySelector(".search-result.active"); if (active) { active.classList.remove("active"); if (active.parentElement.previousSibling) { const previous = active.parentElement.previousSibling.querySelector( ".search-result", ); previous.classList.add("active"); } } return; case 40: // arrow down e.preventDefault(); active = document.querySelector(".search-result.active"); if (active) { if (active.parentElement.nextSibling) { const next = active.parentElement.nextSibling.querySelector( ".search-result", ); active.classList.remove("active"); next.classList.add("active"); } } else { const next = document.querySelector(".search-result"); if (next) { next.classList.add("active"); } } return; case 13: // enter e.preventDefault(); active = document.querySelector(".search-result.active"); if (active) { active.click(); } else { const first = document.querySelector(".search-result"); if (first) { first.click(); } } return; } }); addEvent(document, "click", function (e) { if (e.target != searchInput) { hideSearch(); } }); }; self.initSearch = function initSeach(searchData) { // enable support for hyphenated search words lunr.tokenizer.separator = /[\s/]+/; const index = lunr(function () { this.ref("id"); // this.field("type"); // this.field("parentTitle"); this.field("title", { boost: 200 }); this.field("doc", { boost: 2 }); this.field("ref"); this.metadataWhitelist = ["position"]; for (let [i, entry] of searchData.items.entries()) { this.add({ id: i, // type: entry.type, parentTitle: entry.parentTitle, title: entry.title, doc: entry.doc, ref: `${window.unnest}/${entry.ref}`, }); } }); searchLoaded(index, searchData.items); }; const init = function () { for (let property in gleamConfig) { initProperty(property); const toggle = document.querySelector(`#${property}-toggle`); toggle.addEventListener("click", function (event) { setProperty(event, property); }); } sidebarToggles.forEach(function (sidebarToggle) { sidebarToggle.addEventListener("click", function (event) { event.preventDefault(); self.toggleSidebar(); }); }); setHashOffset(); window.addEventListener("load", function (_event) { self.scrollToHash(); }); window.addEventListener("hashchange", function (_event) { self.scrollToHash(); }); document .querySelectorAll( ` .module-name > a, .member-name a[href^='#'] `, ) .forEach(function (title) { title.innerHTML = title.innerHTML.replace( /([A-Z])|([_/])/g, "$2$1", ); }); }; /* Initialise */ init(); return self; })(); ================================================ FILE: compiler-core/templates/documentation_layout.html ================================================ {{ page_title }} {% if !host.is_empty() && !project_name.is_empty() -%}{%- endif %}
{% block content %}{% endblock %}
Lucy
says
trans
rights
now
Search Document ================================================ FILE: compiler-core/templates/documentation_module.html ================================================ {% extends "documentation_layout.html" %} {% block sidebar_content %} {% if !types.is_empty() %}

Types

{% endif %} {% if !values.is_empty() %}

Values

{% endif %} {% endblock %} {% block content %}

{{ module_name }}

{{ documentation|safe }} {% if !types.is_empty() %}

Types

{% for typ in types %}

{{ typ.name }}

{% if typ.opaque %} opaque {% endif %} {% if !typ.source_url.is_empty() %} </> {% endif %}
{% if !typ.deprecation_message.is_empty() %}

Deprecated: {{ typ.deprecation_message }}

{% endif %}
{{ typ.documentation|safe }}
{{ typ.definition|safe }}
{% if !typ.constructors.is_empty() %}

Constructors

    {% for constructor in typ.constructors %}
  • {{ constructor.definition|safe }}
    {{ constructor.documentation|safe }} {% if !constructor.arguments.is_empty() %}

    Arguments

    {% for argument in constructor.arguments %}
    {{ argument.name }}
    {{ argument.doc|safe }}
    {% endfor %}
    {% endif %}
  • {% endfor %}
{% endif %}
{% endfor %}
{% endif %} {% if !values.is_empty() %}

Values

{% for value in values %}

{{ value.name }}

{% if !value.source_url.is_empty() %} </> {% endif %}
{{ value.definition|safe }}
{% if !value.deprecation_message.is_empty() %}

Deprecated: {{ value.deprecation_message }}

{% endif %}
{{ value.documentation|safe }}
{% endfor %}
{% endif %} {% endblock %} ================================================ FILE: compiler-core/templates/documentation_page.html ================================================ {% extends "documentation_layout.html" %} {% block title %} {{ title }} - {{ project_name }} {% endblock %} {% block content %} {{ content|safe }} {% endblock %} ================================================ FILE: compiler-core/templates/echo.erl ================================================ -define(is_lowercase_char(X), (X > 96 andalso X < 123)). -define(is_underscore_char(X), (X == 95)). -define(is_digit_char(X), (X > 47 andalso X < 58)). -define(is_ascii_character(X), (erlang:is_integer(X) andalso X >= 32 andalso X =< 126)). -define(could_be_record(Tuple), erlang:is_tuple(Tuple) andalso erlang:is_atom(erlang:element(1, Tuple)) andalso erlang:element(1, Tuple) =/= false andalso erlang:element(1, Tuple) =/= true andalso erlang:element(1, Tuple) =/= nil ). -define(is_atom_char(C), (?is_lowercase_char(C) orelse ?is_underscore_char(C) orelse ?is_digit_char(C)) ). -define(grey, "\e[90m"). -define(reset_color, "\e[39m"). echo(Value, Message, Line) -> StringLine = erlang:integer_to_list(Line), StringValue = echo@inspect(Value), StringMessage = case Message of nil -> ""; M -> [" ", M] end, io:put_chars( standard_error, [ ?grey, ?FILEPATH, $:, StringLine, ?reset_color, StringMessage, $\n, StringValue, $\n ] ), Value. echo@inspect(Value) -> case Value of nil -> "Nil"; true -> "True"; false -> "False"; Int when erlang:is_integer(Int) -> erlang:integer_to_list(Int); Float when erlang:is_float(Float) -> io_lib_format:fwrite_g(Float); Binary when erlang:is_binary(Binary) -> inspect@binary(Binary); Bits when erlang:is_bitstring(Bits) -> inspect@bit_array(Bits); Atom when erlang:is_atom(Atom) -> inspect@atom(Atom); List when erlang:is_list(List) -> inspect@list(List); Map when erlang:is_map(Map) -> inspect@map(Map); Record when ?could_be_record(Record) -> inspect@record(Record); Tuple when erlang:is_tuple(Tuple) -> inspect@tuple(Tuple); Function when erlang:is_function(Function) -> inspect@function(Function); Any -> ["//erl(", io_lib:format("~p", [Any]), ")"] end. inspect@bit_array(Bits) -> Pieces = inspect@bit_array_pieces(Bits, []), Inner = lists:join(", ", lists:reverse(Pieces)), ["<<", Inner, ">>"]. inspect@bit_array_pieces(Bits, Acc) -> case Bits of <<>> -> Acc; <> -> inspect@bit_array_pieces(Rest, [erlang:integer_to_binary(Byte) | Acc]); _ -> Size = erlang:bit_size(Bits), <> = Bits, SizeString = [":size(", erlang:integer_to_binary(Size), ")"], Piece = [erlang:integer_to_binary(RemainingBits), SizeString], [Piece | Acc] end. inspect@binary(Binary) -> case inspect@maybe_utf8_string(Binary, <<>>) of {ok, InspectedUtf8String} -> InspectedUtf8String; {error, not_a_utf8_string} -> Segments = [erlang:integer_to_list(X) || <> <= Binary], ["<<", lists:join(", ", Segments), ">>"] end. inspect@atom(Atom) -> Binary = erlang:atom_to_binary(Atom), case inspect@maybe_gleam_atom(Binary, none, <<>>) of {ok, Inspected} -> Inspected; {error, _} -> ["atom.create(\"", Binary, "\")"] end. inspect@list(List) -> case inspect@list_loop(List, true) of {charlist, _} -> ["charlist.from_string(\"", erlang:list_to_binary(List), "\")"]; {proper, Elements} -> ["[", Elements, "]"]; {improper, Elements} -> ["//erl([", Elements, "])"] end. inspect@map(Map) -> Fields = [ [<<"#(">>, echo@inspect(Key), <<", ">>, echo@inspect(Value), <<")">>] || {Key, Value} <- maps:to_list(Map) ], ["dict.from_list([", lists:join(", ", Fields), "])"]. inspect@record(Record) -> [Atom | ArgsList] = Tuple = erlang:tuple_to_list(Record), case inspect@maybe_gleam_atom(Atom, none, <<>>) of {ok, Tag} -> Args = lists:join(", ", lists:map(fun echo@inspect/1, ArgsList)), [Tag, "(", Args, ")"]; _ -> inspect@tuple(Tuple) end. inspect@tuple(Tuple) when erlang:is_tuple(Tuple) -> inspect@tuple(erlang:tuple_to_list(Tuple)); inspect@tuple(Tuple) -> Elements = lists:map(fun echo@inspect/1, Tuple), ["#(", lists:join(", ", Elements), ")"]. inspect@function(Function) -> {arity, Arity} = erlang:fun_info(Function, arity), ArgsAsciiCodes = lists:seq($a, $a + Arity - 1), Args = lists:join(", ", lists:map(fun(Arg) -> <> end, ArgsAsciiCodes)), ["//fn(", Args, ") { ... }"]. inspect@maybe_utf8_string(Binary, Acc) -> case Binary of <<>> -> {ok, <<$", Acc/binary, $">>}; <> -> Escaped = inspect@escape_grapheme(First), inspect@maybe_utf8_string(Rest, <>); _ -> {error, not_a_utf8_string} end. inspect@escape_grapheme(Char) -> case Char of $" -> <<$\\, $">>; $\\ -> <<$\\, $\\>>; $\r -> <<$\\, $r>>; $\n -> <<$\\, $n>>; $\t -> <<$\\, $t>>; $\f -> <<$\\, $f>>; X when X > 126, X < 160 -> inspect@convert_to_u(X); X when X < 32 -> inspect@convert_to_u(X); Other -> <> end. inspect@convert_to_u(Code) -> erlang:list_to_binary(io_lib:format("\\u{~4.16.0B}", [Code])). inspect@list_loop(List, Ascii) -> case List of [] -> {proper, []}; [First] when Ascii andalso ?is_ascii_character(First) -> {charlist, nil}; [First] -> {proper, [echo@inspect(First)]}; [First | Rest] when erlang:is_list(Rest) -> StillAscii = Ascii andalso ?is_ascii_character(First), {Kind, Inspected} = inspect@list_loop(Rest, StillAscii), {Kind, [echo@inspect(First), ", " | Inspected]}; [First | ImproperRest] -> {improper, [echo@inspect(First), " | ", echo@inspect(ImproperRest)]} end. inspect@maybe_gleam_atom(Atom, PrevChar, Acc) when erlang:is_atom(Atom) -> Binary = erlang:atom_to_binary(Atom), inspect@maybe_gleam_atom(Binary, PrevChar, Acc); inspect@maybe_gleam_atom(Atom, PrevChar, Acc) -> case {Atom, PrevChar} of {<<>>, none} -> {error, nil}; {<>, none} when ?is_digit_char(First) -> {error, nil}; {<<"_", _/binary>>, none} -> {error, nil}; {<<"_">>, _} -> {error, nil}; {<<"_", _/binary>>, $_} -> {error, nil}; {<>, _} when not ?is_atom_char(First) -> {error, nil}; {<>, none} -> inspect@maybe_gleam_atom(Rest, First, <>); {<<"_", Rest/binary>>, _} -> inspect@maybe_gleam_atom(Rest, $_, Acc); {<>, $_} -> inspect@maybe_gleam_atom(Rest, First, <>); {<>, _} -> inspect@maybe_gleam_atom(Rest, First, <>); {<<>>, _} -> {ok, Acc}; _ -> erlang:throw({gleam_error, echo, Atom, PrevChar, Acc}) end. inspect@uppercase(X) -> X - 32. ================================================ FILE: compiler-core/templates/echo.mjs ================================================ function echo(value, message, file, line) { const grey = "\u001b[90m"; const reset_color = "\u001b[39m"; const file_line = `${file}:${line}`; const inspector = new Echo$Inspector(); const string_value = inspector.inspect(value); const string_message = message === undefined ? "" : " " + message; if (globalThis.process?.stderr?.write) { // If we're in Node.js, use `stderr` const string = `${grey}${file_line}${reset_color}${string_message}\n${string_value}\n`; globalThis.process.stderr.write(string); } else if (globalThis.Deno) { // If we're in Deno, use `stderr` const string = `${grey}${file_line}${reset_color}${string_message}\n${string_value}\n`; globalThis.Deno.stderr.writeSync(new TextEncoder().encode(string)); } else { // Otherwise, use `console.log` // The browser's console.log doesn't support ansi escape codes const string = `${file_line}${string_message}\n${string_value}`; globalThis.console.log(string); } return value; } class Echo$Inspector { #references = new globalThis.Set(); #isDict(value) { try { // We can only check if an object is a stdlib Dict if it is one of the // project's dependencies. // We import the public gleam/dict module, so to check if something is a // dict we compare the `constructor` field on the object with that of a // new dict. const empty_dict = $stdlib$dict.new$(); const dict_class = empty_dict.constructor; return value instanceof dict_class; } catch { // If stdlib is not one of the project's dependencies then `$stdlib$dict` // will not have been imported and the check will throw an exception meaning // we can't check if something is actually a `Dict`. return false; } } #float(float) { const string = float.toString().replace("+", ""); if (string.indexOf(".") >= 0) { return string; } else { const index = string.indexOf("e"); if (index >= 0) { return string.slice(0, index) + ".0" + string.slice(index); } else { return string + ".0"; } } } inspect(v) { const t = typeof v; if (v === true) return "True"; if (v === false) return "False"; if (v === null) return "//js(null)"; if (v === undefined) return "Nil"; if (t === "string") return this.#string(v); if (t === "bigint" || globalThis.Number.isInteger(v)) return v.toString(); if (t === "number") return this.#float(v); if (v instanceof $UtfCodepoint) return this.#utfCodepoint(v); if (v instanceof $BitArray) return this.#bit_array(v); if (v instanceof globalThis.RegExp) return `//js(${v})`; if (v instanceof globalThis.Date) return `//js(Date("${v.toISOString()}"))`; if (v instanceof globalThis.Error) return `//js(${v.toString()})`; if (v instanceof globalThis.Function) { const args = []; for (const i of globalThis.Array(v.length).keys()) args.push(globalThis.String.fromCharCode(i + 97)); return `//fn(${args.join(", ")}) { ... }`; } if (this.#references.size === this.#references.add(v).size) { return "//js(circular reference)"; } let printed; if (globalThis.Array.isArray(v)) { printed = `#(${v.map((v) => this.inspect(v)).join(", ")})`; } else if (v instanceof $List) { printed = this.#list(v); } else if (v instanceof $CustomType) { printed = this.#customType(v); } else if (this.#isDict(v)) { printed = this.#dict(v); } else if (v instanceof Set) { return `//js(Set(${[...v].map((v) => this.inspect(v)).join(", ")}))`; } else { printed = this.#object(v); } this.#references.delete(v); return printed; } #object(v) { const name = globalThis.Object.getPrototypeOf(v)?.constructor?.name || "Object"; const props = []; for (const k of globalThis.Object.keys(v)) { props.push(`${this.inspect(k)}: ${this.inspect(v[k])}`); } const body = props.length ? " " + props.join(", ") + " " : ""; const head = name === "Object" ? "" : name + " "; return `//js(${head}{${body}})`; } #dict(map) { let body = "dict.from_list(["; let first = true; let key_value_pairs = $stdlib$dict.fold(map, [], (pairs, key, value) => { pairs.push([key, value]); return pairs; }); key_value_pairs.sort(); key_value_pairs.forEach(([key, value]) => { if (!first) body = body + ", "; body = body + "#(" + this.inspect(key) + ", " + this.inspect(value) + ")"; first = false; }); return body + "])"; } #customType(record) { const props = globalThis.Object.keys(record) .map((label) => { const value = this.inspect(record[label]); return isNaN(parseInt(label)) ? `${label}: ${value}` : value; }) .join(", "); return props ? `${record.constructor.name}(${props})` : record.constructor.name; } #list(list) { if (list instanceof $Empty) { return "[]"; } let char_out = 'charlist.from_string("'; let list_out = "["; let current = list; while (current instanceof $NonEmpty) { let element = current.head; current = current.tail; if (list_out !== "[") { list_out += ", "; } list_out += this.inspect(element); if (char_out) { if ( globalThis.Number.isInteger(element) && element >= 32 && element <= 126 ) { char_out += globalThis.String.fromCharCode(element); } else { char_out = null; } } } if (char_out) { return char_out + '")'; } else { return list_out + "]"; } } #string(str) { let new_str = '"'; for (let i = 0; i < str.length; i++) { const char = str[i]; switch (char) { case "\n": new_str += "\\n"; break; case "\r": new_str += "\\r"; break; case "\t": new_str += "\\t"; break; case "\f": new_str += "\\f"; break; case "\\": new_str += "\\\\"; break; case '"': new_str += '\\"'; break; default: if (char < " " || (char > "~" && char < "\u{00A0}")) { new_str += "\\u{" + char.charCodeAt(0).toString(16).toUpperCase().padStart(4, "0") + "}"; } else { new_str += char; } } } new_str += '"'; return new_str; } #utfCodepoint(codepoint) { return `//utfcodepoint(${globalThis.String.fromCodePoint(codepoint.value)})`; } #bit_array(bits) { if (bits.bitSize === 0) { return "<<>>"; } let acc = "<<"; for (let i = 0; i < bits.byteSize - 1; i++) { acc += bits.byteAt(i).toString(); acc += ", "; } if (bits.byteSize * 8 === bits.bitSize) { acc += bits.byteAt(bits.byteSize - 1).toString(); } else { const trailingBitsCount = bits.bitSize % 8; acc += bits.byteAt(bits.byteSize - 1) >> (8 - trailingBitsCount); acc += `:size(${trailingBitsCount})`; } acc += ">>"; return acc; } } ================================================ FILE: compiler-core/templates/ejected.mk ================================================ ifndef GLEAM_NO_COMPILE GLEAM_INSTALLED := $(shell command -v gleam) endif ERLANG_FILES = $(wildcard {src,build}/*.erl) GLEAM_FILES = $(wildcard {src,build}/**/*.gleam) .PHONY: ebin check ifdef GLEAM_INSTALLED ebin: check $(GLEAM_FILES) $(ERLANG_FILES) gleam compile-package --target erlang else ebin: check $(ERLANG_FILES) @mkdir -p ./ebin @cp build/*.app ebin/ @erlc -server -o ebin $(ERLANG_FILES) || (rm -rf ebin && false) endif check: ifndef ERL_LIBS $(error "ERL_LIBS environment variable not set") endif ================================================ FILE: compiler-core/templates/gleam@@main.erl ================================================ -module('{{ application }}@@main'). -export([run/1]). -define(red, "\e[31;1m"). -define(grey, "\e[90m"). -define(reset_color, "\e[39m"). -define(reset_all, "\e[0m"). run(Module) -> io:setopts(standard_io, [binary, {encoding, utf8}]), io:setopts(standard_error, [{encoding, utf8}]), process_flag(trap_exit, true), Pid = spawn_link(fun() -> run_module(Module) end), receive {'EXIT', Pid, {Reason, [First|_] = StackTrace}} when is_tuple(First) -> print_error_with_stacktrace(exit, Reason, StackTrace), init:stop(1); {'EXIT', Pid, Reason} when Reason =/= normal -> print_error(exit, Reason), init:stop(1) end. run_module(Module) -> try {ok, _} = application:ensure_all_started('{{ application }}'), erlang:process_flag(trap_exit, false), Module:main(), init:stop(0) catch Class:Reason:StackTrace -> print_error_with_stacktrace(Class, Reason, StackTrace), init:stop(1) end. print_error_with_stacktrace(Class, Error, Stacktrace) -> Printed = [ ?red, "runtime error", ?reset_color, ": ", error_class(Class, Error), ?reset_all, "\n\n", error_message(Error), "\n\n", error_details(Class, Error), "stacktrace:\n", [error_frame(Line) || Line <- refine_first(Error, Stacktrace)] ], io:format(standard_error, "~ts~n", [Printed]). print_error(Class, Error) -> Printed = [ ?red, "runtime error", ?reset_color, ": ", error_class(Class, Error), ?reset_all, "\n\n", error_message(Error), "\n\n", "exit reason:\n ", print_term(Error), $\n ], io:format(standard_error, "~ts~n", [Printed]). refine_first(#{gleam_error := _, line := L}, [{M, F, A, [{file, Fi} | _]} | S]) -> [{M, F, A, [{file, Fi}, {line, L}]} | S]; refine_first(_, S) -> S. error_class(_, #{gleam_error := panic}) -> "panic"; error_class(_, #{gleam_error := todo}) -> "todo"; error_class(_, #{gleam_error := let_assert}) -> "let assert"; error_class(_, #{gleam_error := assert}) -> "assert"; error_class(Class, _) -> ["Erlang ", atom_to_binary(Class)]. error_message(#{gleam_error := _, message := M}) -> M; error_message(undef) -> <<"A function was called but it did not exist."/utf8 >>; error_message({case_clause, _}) -> <<"No pattern matched in an Erlang case expression."/utf8>>; error_message({badmatch, _}) -> <<"An Erlang assignment pattern did not match."/utf8>>; error_message(function_clause) -> <<"No Erlang function clause matched the arguments it was called with."/utf8>>; error_message(_) -> <<"An error occurred outside of Gleam."/utf8>>. error_details(_, #{gleam_error := let_assert, value := V}) -> ["unmatched value:\n ", print_term(V), $\n, $\n]; error_details(_, {case_clause, V}) -> ["unmatched value:\n ", print_term(V), $\n, $\n]; error_details(_, {badmatch, V}) -> ["unmatched value:\n ", print_term(V), $\n, $\n]; error_details(_, #{gleam_error := _}) -> []; error_details(error, function_clause) -> []; error_details(error, undef) -> []; error_details(C, E) -> ["erlang:", atom_to_binary(C), $(, print_term(E), $), $\n, $\n]. print_term(T) -> try gleam@string:inspect(T) catch _:_ -> io_lib:format("~p", [T]) end. error_frame({?MODULE, _, _, _}) -> []; error_frame({erl_eval, _, _, _}) -> []; error_frame({init, _, _, _}) -> []; error_frame({M, F, _, O}) -> M1 = string:replace(atom_to_binary(M), "@", "/", all), [" ", M1, $., atom_to_binary(F), error_frame_end(O), $\n]. error_frame_end([{file, Fi}, {line, L} | _]) -> [?grey, $\s, Fi, $:, integer_to_binary(L), ?reset_all]; error_frame_end(_) -> [?grey, " unknown source", ?reset_all]. ================================================ FILE: compiler-core/templates/prelude.d.mts ================================================ /** @deprecated */ export class CustomType { /** @deprecated */ withFields(fields: { [P in K]: this[P] }): this; } export interface List { readonly __gleam: unique symbol; /** @deprecated */ head?: T; /** @deprecated */ tail?: List; /** @deprecated */ toArray(): Array; /** @deprecated */ atLeastLength(desired: number): boolean; /** @deprecated */ hasLength(desired: number): boolean; /** @deprecated */ countLength(): number; /** @deprecated */ [Symbol.iterator](): Iterator; } /** @deprecated */ export const List: { /** @deprecated */ new (): List; /** @deprecated */ fromArray(array: Array): List; } export function List$Empty(): List; export function List$NonEmpty(head: T, tail: List): List; export function List$isEmpty(list: any): list is List; export function List$isNonEmpty(list: any): list is List; export function List$NonEmpty$first(list: List): T | undefined; export function List$NonEmpty$rest(list: List): List | undefined; /** @deprecated */ export class Empty extends List { } /** @deprecated */ export class NonEmpty extends List { } export interface BitArray { readonly __gleam: unique symbol; /** @deprecated */ bitSize: number; /** @deprecated */ byteSize: number; /** @deprecated */ bitOffset: number; /** @deprecated */ rawBuffer: Uint8Array; /** @deprecated */ constructor(buffer: Uint8Array, bitSize?: number, bitOffset?: number): BitArray; /** @deprecated */ byteAt(index: number): number; /** @deprecated */ equals(other: BitArray): boolean; /** @deprecated */ get buffer(): Uint8Array; /** @deprecated */ get length(): number; } /** @deprecated */ export const BitArray: { /** @deprecated */ new(buffer: Uint8Array, bitSize?: number, bitOffset?: number): BitArray; } export function BitArray$BitArray( buffer: Uint8Array, bitSize?: number, bitOffset?: number, ): BitArray; export function BitArray$isBitArray(value: any): value is BitArray; export function BitArray$BitArray$data(value: BitArray): DataView; export interface UtfCodepoint { readonly __gleam: unique symbol; /** @deprecated */ value: string; } /** @deprecated */ export const UtfCodepoint: { /** @deprecated */ new(value: string): UtfCodepoint } export interface Result { readonly __gleam: unique symbol; /** @deprecated */ isOk(): boolean; } /** @deprecated */ export const Result: { new (): Result } export function Result$Ok(value: T): Result; export function Result$Error(error: E): Result; export function Result$isError(data: any): data is Result; export function Result$isOk(data: any): data is Result; export function Result$Ok$0(result: Result): T | undefined; export function Result$Error$0(result: Result): E | undefined; /** @deprecated */ export class Ok extends Result { /** @deprecated */ 0: T; /** @deprecated */ constructor(value: T); /** @deprecated */ withFields(fields: { [P in K]: this[P] }): this; /** @deprecated */ static isResult(data: unknown): boolean; } /** @deprecated */ export class Error extends Result { /** @deprecated */ 0: E; /** @deprecated */ constructor(value: E); /** @deprecated */ withFields(fields: { [P in K]: this[P] }): this; /** @deprecated */ static isResult(data: unknown): boolean; } /** @deprecated */ export function prepend(element: T, tail: List): List; /** @deprecated */ export function toList(array: Array): List; /** @deprecated */ export function toBitArray(segments: Array): BitArray; /** @deprecated */ export function sizedInt( /** @deprecated */ value: number, /** @deprecated */ size: number, /** @deprecated */ isBigEndian: boolean ): Uint8Array | BitArray; /** @deprecated */ export function stringBits(string: string): Uint8Array; /** @deprecated */ export function codepointBits(codepoint: UtfCodepoint): Uint8Array; /** @deprecated */ export function sizedFloat( value: number, size: number, isBigEndian: boolean ): Uint8Array; /** @deprecated */ export function isEqual(a: any, b: any): boolean; /** @deprecated */ export function remainderInt(a: number, b: number): number; /** @deprecated */ export function divideInt(a: number, b: number): number; /** @deprecated */ export function divideFloat(a: number, b: number): number; ================================================ FILE: compiler-core/templates/prelude.mjs ================================================ export class CustomType { withFields(fields) { let properties = Object.keys(this).map((label) => label in fields ? fields[label] : this[label], ); return new this.constructor(...properties); } } export class List { static fromArray(array, tail) { let t = tail || new Empty(); for (let i = array.length - 1; i >= 0; --i) { t = new NonEmpty(array[i], t); } return t; } [Symbol.iterator]() { return new ListIterator(this); } toArray() { return [...this]; } atLeastLength(desired) { let current = this; while (desired-- > 0 && current) current = current.tail; return current !== undefined; } hasLength(desired) { let current = this; while (desired-- > 0 && current) current = current.tail; return desired === -1 && current instanceof Empty; } countLength() { let current = this; let length = 0; while (current) { current = current.tail; length++; } return length - 1; } } export function prepend(element, tail) { return new NonEmpty(element, tail); } export function toList(elements, tail) { return List.fromArray(elements, tail); } class ListIterator { #current; constructor(current) { this.#current = current; } next() { if (this.#current instanceof Empty) { return { done: true }; } else { let { head, tail } = this.#current; this.#current = tail; return { value: head, done: false }; } } } export class Empty extends List {} export const List$Empty = () => new Empty(); export const List$isEmpty = (value) => value instanceof Empty; export class NonEmpty extends List { constructor(head, tail) { super(); this.head = head; this.tail = tail; } } export const List$NonEmpty = (head, tail) => new NonEmpty(head, tail); export const List$isNonEmpty = (value) => value instanceof NonEmpty; export const List$NonEmpty$first = (value) => value.head; export const List$NonEmpty$rest = (value) => value.tail; /** * A bit array is a contiguous sequence of bits similar to Erlang's Binary type. */ export class BitArray { /** * The size in bits of this bit array's data. * * @type {number} */ bitSize; /** * The size in bytes of this bit array's data. If this bit array doesn't store * a whole number of bytes then this value is rounded up. * * @type {number} */ byteSize; /** * The number of unused high bits in the first byte of this bit array's * buffer prior to the start of its data. The value of any unused high bits is * undefined. * * The bit offset will be in the range 0-7. * * @type {number} */ bitOffset; /** * The raw bytes that hold this bit array's data. * * If `bitOffset` is not zero then there are unused high bits in the first * byte of this buffer. * * If `bitOffset + bitSize` is not a multiple of 8 then there are unused low * bits in the last byte of this buffer. * * @type {Uint8Array} */ rawBuffer; /** * Constructs a new bit array from a `Uint8Array`, an optional size in * bits, and an optional bit offset. * * If no bit size is specified it is taken as `buffer.length * 8`, i.e. all * bytes in the buffer make up the new bit array's data. * * If no bit offset is specified it defaults to zero, i.e. there are no unused * high bits in the first byte of the buffer. * * @param {Uint8Array} buffer * @param {number} [bitSize] * @param {number} [bitOffset] */ constructor(buffer, bitSize, bitOffset) { if (!(buffer instanceof Uint8Array)) { throw globalThis.Error( "BitArray can only be constructed from a Uint8Array", ); } this.bitSize = bitSize ?? buffer.length * 8; this.byteSize = Math.trunc((this.bitSize + 7) / 8); this.bitOffset = bitOffset ?? 0; // Validate the bit size if (this.bitSize < 0) { throw globalThis.Error(`BitArray bit size is invalid: ${this.bitSize}`); } // Validate the bit offset if (this.bitOffset < 0 || this.bitOffset > 7) { throw globalThis.Error( `BitArray bit offset is invalid: ${this.bitOffset}`, ); } // Validate the length of the buffer if (buffer.length !== Math.trunc((this.bitOffset + this.bitSize + 7) / 8)) { throw globalThis.Error("BitArray buffer length is invalid"); } this.rawBuffer = buffer; } /** * Returns a specific byte in this bit array. If the byte index is out of * range then `undefined` is returned. * * When returning the final byte of a bit array with a bit size that's not a * multiple of 8, the content of the unused low bits are undefined. * * @param {number} index * @returns {number | undefined} */ byteAt(index) { if (index < 0 || index >= this.byteSize) { return undefined; } return bitArrayByteAt(this.rawBuffer, this.bitOffset, index); } equals(other) { if (this.bitSize !== other.bitSize) { return false; } const wholeByteCount = Math.trunc(this.bitSize / 8); // If both bit offsets are zero do a byte-aligned equality check which is // faster if (this.bitOffset === 0 && other.bitOffset === 0) { // Compare any whole bytes for (let i = 0; i < wholeByteCount; i++) { if (this.rawBuffer[i] !== other.rawBuffer[i]) { return false; } } // Compare any trailing bits, excluding unused low bits const trailingBitsCount = this.bitSize % 8; if (trailingBitsCount) { const unusedLowBitCount = 8 - trailingBitsCount; if ( this.rawBuffer[wholeByteCount] >> unusedLowBitCount !== other.rawBuffer[wholeByteCount] >> unusedLowBitCount ) { return false; } } } else { // Compare any whole bytes for (let i = 0; i < wholeByteCount; i++) { const a = bitArrayByteAt(this.rawBuffer, this.bitOffset, i); const b = bitArrayByteAt(other.rawBuffer, other.bitOffset, i); if (a !== b) { return false; } } // Compare any trailing bits const trailingBitsCount = this.bitSize % 8; if (trailingBitsCount) { const a = bitArrayByteAt( this.rawBuffer, this.bitOffset, wholeByteCount, ); const b = bitArrayByteAt( other.rawBuffer, other.bitOffset, wholeByteCount, ); const unusedLowBitCount = 8 - trailingBitsCount; if (a >> unusedLowBitCount !== b >> unusedLowBitCount) { return false; } } } return true; } /** * Returns this bit array's internal buffer. * * @deprecated * * @returns {Uint8Array} */ get buffer() { if (this.bitOffset !== 0 || this.bitSize % 8 !== 0) { throw new globalThis.Error( "BitArray.buffer does not support unaligned bit arrays", ); } return this.rawBuffer; } /** * Returns the length in bytes of this bit array's internal buffer. * * @deprecated * * @returns {number} */ get length() { if (this.bitOffset !== 0 || this.bitSize % 8 !== 0) { throw new globalThis.Error( "BitArray.length does not support unaligned bit arrays", ); } return this.rawBuffer.length; } } export const BitArray$BitArray = (buffer, bitSize, bitOffset) => new BitArray(buffer, bitSize, bitOffset); export const BitArray$isBitArray = (value) => value instanceof BitArray; export const BitArray$BitArray$data = (bitArray) => { if (bitArray.bitSize % 8 !== 0) throw new Error("BitArray$BitArray$data called on un-aligned bit array"); const array = bitArray.rawBuffer; return new DataView(array.buffer, array.byteOffset, bitArray.byteSize); }; /** * Returns the nth byte in the given buffer, after applying the specified bit * offset. If the index is out of bounds then zero is returned. * * @param {Uint8Array} buffer * @param {number} bitOffset * @param {number} index * @returns {number} */ function bitArrayByteAt(buffer, bitOffset, index) { if (bitOffset === 0) { return buffer[index] ?? 0; } else { const a = (buffer[index] << bitOffset) & 0xff; const b = buffer[index + 1] >> (8 - bitOffset); return a | b; } } export class UtfCodepoint { constructor(value) { this.value = value; } } /** * Slices a bit array to produce a new bit array. If `end` is not supplied then * all bits from `start` onward are returned. * * If the slice is out of bounds then an exception is thrown. * * @param {BitArray} bitArray * @param {number} start * @param {number} [end] * @returns {BitArray} */ export function bitArraySlice(bitArray, start, end) { end ??= bitArray.bitSize; bitArrayValidateRange(bitArray, start, end); // Handle zero-length slices if (start === end) { return new BitArray(new Uint8Array()); } // Early return for slices that cover the whole bit array if (start === 0 && end === bitArray.bitSize) { return bitArray; } start += bitArray.bitOffset; end += bitArray.bitOffset; const startByteIndex = Math.trunc(start / 8); const endByteIndex = Math.trunc((end + 7) / 8); const byteLength = endByteIndex - startByteIndex; // Avoid creating a new Uint8Array if the view of the underlying ArrayBuffer // is the same. This can occur when slicing off just the first or last bit of // a bit array, i.e. when only the bit offset or bit size need to be updated. let buffer; if (startByteIndex === 0 && byteLength === bitArray.rawBuffer.byteLength) { buffer = bitArray.rawBuffer; } else { buffer = new Uint8Array( bitArray.rawBuffer.buffer, bitArray.rawBuffer.byteOffset + startByteIndex, byteLength, ); } return new BitArray(buffer, end - start, start % 8); } /** * Interprets a slice of this bit array as a floating point number, either * 32-bit or 64-bit, with the specified endianness. * * The value of `end - start` must be exactly 32 or 64, otherwise an exception * will be thrown. * * @param {BitArray} bitArray * @param {number} start * @param {number} end * @param {boolean} isBigEndian * @returns {number} */ export function bitArraySliceToFloat(bitArray, start, end, isBigEndian) { bitArrayValidateRange(bitArray, start, end); const floatSize = end - start; // Check size is valid if (floatSize !== 16 && floatSize !== 32 && floatSize !== 64) { const msg = `Sized floats must be 16-bit, 32-bit or 64-bit, got size of ` + `${floatSize} bits`; throw new globalThis.Error(msg); } start += bitArray.bitOffset; const isStartByteAligned = start % 8 === 0; // If the bit range is byte aligned then the float can be read directly out // of the existing buffer if (isStartByteAligned) { const view = new DataView( bitArray.rawBuffer.buffer, bitArray.rawBuffer.byteOffset + start / 8, ); if (floatSize === 64) { return view.getFloat64(0, !isBigEndian); } else if (floatSize === 32) { return view.getFloat32(0, !isBigEndian); } else if (floatSize === 16) { return fp16UintToNumber(view.getUint16(0, !isBigEndian)); } } // Copy the unaligned bytes into an aligned array so a DataView can be used const alignedBytes = new Uint8Array(floatSize / 8); const byteOffset = Math.trunc(start / 8); for (let i = 0; i < alignedBytes.length; i++) { alignedBytes[i] = bitArrayByteAt( bitArray.rawBuffer, start % 8, byteOffset + i, ); } // Read the float out of the aligned buffer const view = new DataView(alignedBytes.buffer); if (floatSize === 64) { return view.getFloat64(0, !isBigEndian); } else if (floatSize === 32) { return view.getFloat32(0, !isBigEndian); } else { return fp16UintToNumber(view.getUint16(0, !isBigEndian)); } } /** * Interprets a slice of this bit array as a signed or unsigned integer with the * specified endianness. * * @param {BitArray} bitArray * @param {number} start * @param {number} end * @param {boolean} isBigEndian * @param {boolean} isSigned * @returns {number} */ export function bitArraySliceToInt( bitArray, start, end, isBigEndian, isSigned, ) { bitArrayValidateRange(bitArray, start, end); if (start === end) { return 0; } start += bitArray.bitOffset; end += bitArray.bitOffset; const isStartByteAligned = start % 8 === 0; const isEndByteAligned = end % 8 === 0; // If the slice is byte-aligned then there is no need to handle unaligned // slices, meaning a simpler and faster implementation can be used instead if (isStartByteAligned && isEndByteAligned) { return intFromAlignedSlice( bitArray, start / 8, end / 8, isBigEndian, isSigned, ); } const size = end - start; const startByteIndex = Math.trunc(start / 8); const endByteIndex = Math.trunc((end - 1) / 8); // Handle the case of the slice being completely contained in a single byte if (startByteIndex == endByteIndex) { const mask = 0xff >> (start % 8); const unusedLowBitCount = (8 - (end % 8)) % 8; let value = (bitArray.rawBuffer[startByteIndex] & mask) >> unusedLowBitCount; // For signed integers, if the high bit is set reinterpret as two's // complement if (isSigned) { const highBit = 2 ** (size - 1); if (value >= highBit) { value -= highBit * 2; } } return value; } // The integer value to be read is not aligned and crosses at least one byte // boundary in the input array if (size <= 53) { return intFromUnalignedSliceUsingNumber( bitArray.rawBuffer, start, end, isBigEndian, isSigned, ); } else { return intFromUnalignedSliceUsingBigInt( bitArray.rawBuffer, start, end, isBigEndian, isSigned, ); } } /** * Joins the given segments into a new bit array, tightly packing them together. * Each segment must be one of the following types: * * - A `number`: A single byte value in the range 0-255. Values outside this * range will be wrapped. * - A `Uint8Array`: A sequence of byte values of any length. * - A `BitArray`: A sequence of bits of any length, which may not be byte * aligned. * * The bit size of the returned bit array will be the sum of the size in bits * of the input segments. * * @param {(number | Uint8Array | BitArray)[]} segments * @returns {BitArray} */ export function toBitArray(segments) { if (segments.length === 0) { return new BitArray(new Uint8Array()); } if (segments.length === 1) { const segment = segments[0]; // When there is a single BitArray segment it can be returned as-is if (segment instanceof BitArray) { return segment; } // When there is a single Uint8Array segment, pass it directly to the bit // array constructor to avoid a copy if (segment instanceof Uint8Array) { return new BitArray(segment); } return new BitArray(new Uint8Array(/** @type {number[]} */ (segments))); } // Count the total number of bits and check if all segments are numbers, i.e. // single bytes let bitSize = 0; let areAllSegmentsNumbers = true; for (const segment of segments) { if (segment instanceof BitArray) { bitSize += segment.bitSize; areAllSegmentsNumbers = false; } else if (segment instanceof Uint8Array) { bitSize += segment.byteLength * 8; areAllSegmentsNumbers = false; } else { bitSize += 8; } } // If all segments are numbers then pass the segments array directly to the // Uint8Array constructor if (areAllSegmentsNumbers) { return new BitArray(new Uint8Array(/** @type {number[]} */ (segments))); } // Pack the segments into a Uint8Array const buffer = new Uint8Array(Math.trunc((bitSize + 7) / 8)); // The current write position in bits into the above array. Byte-aligned // segments, i.e. when the cursor is a multiple of 8, are able to be processed // faster due to being able to copy bytes directly. let cursor = 0; for (let segment of segments) { const isCursorByteAligned = cursor % 8 === 0; if (segment instanceof BitArray) { if (isCursorByteAligned && segment.bitOffset === 0) { buffer.set(segment.rawBuffer, cursor / 8); cursor += segment.bitSize; // Zero any unused bits in the last byte of the buffer. Their content is // undefined and shouldn't be included in the output. const trailingBitsCount = segment.bitSize % 8; if (trailingBitsCount !== 0) { const lastByteIndex = Math.trunc(cursor / 8); buffer[lastByteIndex] >>= 8 - trailingBitsCount; buffer[lastByteIndex] <<= 8 - trailingBitsCount; } } else { appendUnalignedBits( segment.rawBuffer, segment.bitSize, segment.bitOffset, ); } } else if (segment instanceof Uint8Array) { if (isCursorByteAligned) { buffer.set(segment, cursor / 8); cursor += segment.byteLength * 8; } else { appendUnalignedBits(segment, segment.byteLength * 8, 0); } } else { if (isCursorByteAligned) { buffer[cursor / 8] = segment; cursor += 8; } else { appendUnalignedBits(new Uint8Array([segment]), 8, 0); } } } function appendUnalignedBits(unalignedBits, size, offset) { if (size === 0) { return; } const byteSize = Math.trunc(size + 7 / 8); const highBitsCount = cursor % 8; const lowBitsCount = 8 - highBitsCount; let byteIndex = Math.trunc(cursor / 8); for (let i = 0; i < byteSize; i++) { let byte = bitArrayByteAt(unalignedBits, offset, i); // If this is a partial byte then zero out the trailing bits as their // content is undefined and shouldn't be included in the output if (size < 8) { byte >>= 8 - size; byte <<= 8 - size; } // Copy the high bits of the input byte to the low bits of the current // output byte buffer[byteIndex] |= byte >> highBitsCount; let appendedBitsCount = size - Math.max(0, size - lowBitsCount); size -= appendedBitsCount; cursor += appendedBitsCount; if (size === 0) { break; } // Copy the low bits of the input byte to the high bits of the next output // byte buffer[++byteIndex] = byte << lowBitsCount; appendedBitsCount = size - Math.max(0, size - highBitsCount); size -= appendedBitsCount; cursor += appendedBitsCount; } } return new BitArray(buffer, bitSize); } /** * Encodes a floating point value into a `Uint8Array`. This is used to create * float segments that are part of bit array expressions. * * @param {number} value * @param {number} size * @param {boolean} isBigEndian * @returns {Uint8Array} */ export function sizedFloat(value, size, isBigEndian) { if (size !== 16 && size !== 32 && size !== 64) { const msg = `Sized floats must be 16-bit, 32-bit or 64-bit, got size of ${size} bits`; throw new globalThis.Error(msg); } if (size === 16) { return numberToFp16Uint(value, isBigEndian); } const buffer = new Uint8Array(size / 8); const view = new DataView(buffer.buffer); if (size == 64) { view.setFloat64(0, value, !isBigEndian); } else { view.setFloat32(0, value, !isBigEndian); } return buffer; } /** * Encodes an integer value into a `Uint8Array`, or a `BitArray` if the size in * bits is not a multiple of 8. This is used to create integer segments used in * bit array expressions. * * @param {number} value * @param {number} size * @param {boolean} isBigEndian * @returns {Uint8Array | BitArray} */ export function sizedInt(value, size, isBigEndian) { if (size <= 0) { return new Uint8Array(); } // Fast path when size is 8 bits. This relies on the rounding behavior of the // Uint8Array constructor. if (size === 8) { return new Uint8Array([value]); } // Fast path when size is less than 8 bits: shift the value up to the high // bits if (size < 8) { value <<= 8 - size; return new BitArray(new Uint8Array([value]), size); } // Allocate output buffer const buffer = new Uint8Array(Math.trunc((size + 7) / 8)); // The number of trailing bits in the final byte. Will be zero if the size is // an exact number of bytes. const trailingBitsCount = size % 8; // The number of unused bits in the final byte of the buffer const unusedBitsCount = 8 - trailingBitsCount; // For output sizes not exceeding 32 bits the number type is used. For larger // output sizes the BigInt type is needed. // // The code in each of these two paths must be kept in sync. if (size <= 32) { if (isBigEndian) { let i = buffer.length - 1; // Set the trailing bits at the end of the output buffer if (trailingBitsCount) { buffer[i--] = (value << unusedBitsCount) & 0xff; value >>= trailingBitsCount; } for (; i >= 0; i--) { buffer[i] = value; value >>= 8; } } else { let i = 0; const wholeByteCount = Math.trunc(size / 8); for (; i < wholeByteCount; i++) { buffer[i] = value; value >>= 8; } // Set the trailing bits at the end of the output buffer if (trailingBitsCount) { buffer[i] = value << unusedBitsCount; } } } else { const bigTrailingBitsCount = BigInt(trailingBitsCount); const bigUnusedBitsCount = BigInt(unusedBitsCount); let bigValue = BigInt(value); if (isBigEndian) { let i = buffer.length - 1; // Set the trailing bits at the end of the output buffer if (trailingBitsCount) { buffer[i--] = Number(bigValue << bigUnusedBitsCount); bigValue >>= bigTrailingBitsCount; } for (; i >= 0; i--) { buffer[i] = Number(bigValue); bigValue >>= 8n; } } else { let i = 0; const wholeByteCount = Math.trunc(size / 8); for (; i < wholeByteCount; i++) { buffer[i] = Number(bigValue); bigValue >>= 8n; } // Set the trailing bits at the end of the output buffer if (trailingBitsCount) { buffer[i] = Number(bigValue << bigUnusedBitsCount); } } } // Integers that aren't a whole number of bytes are returned as a BitArray so // their size in bits is tracked if (trailingBitsCount) { return new BitArray(buffer, size); } return buffer; } /** * Reads an aligned slice of any size as an integer. * * @param {BitArray} bitArray * @param {number} start * @param {number} end * @param {boolean} isBigEndian * @param {boolean} isSigned * @returns {number} */ function intFromAlignedSlice(bitArray, start, end, isBigEndian, isSigned) { const byteSize = end - start; if (byteSize <= 6) { return intFromAlignedSliceUsingNumber( bitArray.rawBuffer, start, end, isBigEndian, isSigned, ); } else { return intFromAlignedSliceUsingBigInt( bitArray.rawBuffer, start, end, isBigEndian, isSigned, ); } } /** * Reads an aligned slice up to 48 bits in size as an integer. Uses the * JavaScript `number` type internally. * * @param {Uint8Array} buffer * @param {number} start * @param {number} end * @param {boolean} isBigEndian * @param {boolean} isSigned * @returns {number} */ function intFromAlignedSliceUsingNumber( buffer, start, end, isBigEndian, isSigned, ) { const byteSize = end - start; let value = 0; // Read bytes as an unsigned integer if (isBigEndian) { for (let i = start; i < end; i++) { value *= 256; value += buffer[i]; } } else { for (let i = end - 1; i >= start; i--) { value *= 256; value += buffer[i]; } } // For signed integers, if the high bit is set reinterpret as two's // complement if (isSigned) { const highBit = 2 ** (byteSize * 8 - 1); if (value >= highBit) { value -= highBit * 2; } } return value; } /** * Reads an aligned slice of any size as an integer. Uses the JavaScript * `BigInt` type internally. * * @param {Uint8Array} buffer * @param {number} start * @param {number} end * @param {boolean} isBigEndian * @param {boolean} isSigned * @returns {number} */ function intFromAlignedSliceUsingBigInt( buffer, start, end, isBigEndian, isSigned, ) { const byteSize = end - start; let value = 0n; // Read bytes as an unsigned integer value if (isBigEndian) { for (let i = start; i < end; i++) { value *= 256n; value += BigInt(buffer[i]); } } else { for (let i = end - 1; i >= start; i--) { value *= 256n; value += BigInt(buffer[i]); } } // For signed integers, if the high bit is set reinterpret as two's // complement if (isSigned) { const highBit = 1n << BigInt(byteSize * 8 - 1); if (value >= highBit) { value -= highBit * 2n; } } // Convert the result into a JS number. This may cause quantizing/error on // values outside JavaScript's safe integer range. return Number(value); } /** * Reads an unaligned slice up to 53 bits in size as an integer. Uses the * JavaScript `number` type internally. * * This function assumes that the slice crosses at least one byte boundary in * the input. * * @param {Uint8Array} buffer * @param {number} start * @param {number} end * @param {boolean} isBigEndian * @param {boolean} isSigned * @returns {number} */ function intFromUnalignedSliceUsingNumber( buffer, start, end, isBigEndian, isSigned, ) { const isStartByteAligned = start % 8 === 0; let size = end - start; let byteIndex = Math.trunc(start / 8); let value = 0; if (isBigEndian) { // Read any leading bits if (!isStartByteAligned) { const leadingBitsCount = 8 - (start % 8); value = buffer[byteIndex++] & ((1 << leadingBitsCount) - 1); size -= leadingBitsCount; } // Read any whole bytes while (size >= 8) { value *= 256; value += buffer[byteIndex++]; size -= 8; } // Read any trailing bits if (size > 0) { value *= 2 ** size; value += buffer[byteIndex] >> (8 - size); } } else { // For little endian, if the start is aligned then whole bytes can be read // directly out of the input array, with the trailing bits handled at the // end if (isStartByteAligned) { let size = end - start; let scale = 1; // Read whole bytes while (size >= 8) { value += buffer[byteIndex++] * scale; scale *= 256; size -= 8; } // Read trailing bits value += (buffer[byteIndex] >> (8 - size)) * scale; } else { // Read little endian data where the start is not byte-aligned. This is // done by reading whole bytes that cross a byte boundary in the input // data, then reading any trailing bits. const highBitsCount = start % 8; const lowBitsCount = 8 - highBitsCount; let size = end - start; let scale = 1; // Extract whole bytes while (size >= 8) { const byte = (buffer[byteIndex] << highBitsCount) | (buffer[byteIndex + 1] >> lowBitsCount); value += (byte & 0xff) * scale; scale *= 256; size -= 8; byteIndex++; } // Read any trailing bits. These trailing bits may cross a byte boundary // in the input buffer. if (size > 0) { const lowBitsUsed = size - Math.max(0, size - lowBitsCount); let trailingByte = (buffer[byteIndex] & ((1 << lowBitsCount) - 1)) >> (lowBitsCount - lowBitsUsed); size -= lowBitsUsed; if (size > 0) { trailingByte *= 2 ** size; trailingByte += buffer[byteIndex + 1] >> (8 - size); } value += trailingByte * scale; } } } // For signed integers, if the high bit is set reinterpret as two's // complement if (isSigned) { const highBit = 2 ** (end - start - 1); if (value >= highBit) { value -= highBit * 2; } } return value; } /** * Reads an unaligned slice of any size as an integer. Uses the JavaScript * `BigInt` type internally. * * This function assumes that the slice crosses at least one byte boundary in * the input. * * @param {Uint8Array} buffer * @param {number} start * @param {number} end * @param {boolean} isBigEndian * @param {boolean} isSigned * @returns {number} */ function intFromUnalignedSliceUsingBigInt( buffer, start, end, isBigEndian, isSigned, ) { const isStartByteAligned = start % 8 === 0; let size = end - start; let byteIndex = Math.trunc(start / 8); let value = 0n; if (isBigEndian) { // Read any leading bits if (!isStartByteAligned) { const leadingBitsCount = 8 - (start % 8); value = BigInt(buffer[byteIndex++] & ((1 << leadingBitsCount) - 1)); size -= leadingBitsCount; } // Read any whole bytes while (size >= 8) { value *= 256n; value += BigInt(buffer[byteIndex++]); size -= 8; } // Read any trailing bits if (size > 0) { value <<= BigInt(size); value += BigInt(buffer[byteIndex] >> (8 - size)); } } else { // For little endian, if the start is aligned then whole bytes can be read // directly out of the input array, with the trailing bits handled at the // end if (isStartByteAligned) { let size = end - start; let shift = 0n; // Read whole bytes while (size >= 8) { value += BigInt(buffer[byteIndex++]) << shift; shift += 8n; size -= 8; } // Read trailing bits value += BigInt(buffer[byteIndex] >> (8 - size)) << shift; } else { // Read little endian data where the start is not byte-aligned. This is // done by reading whole bytes that cross a byte boundary in the input // data, then reading any trailing bits. const highBitsCount = start % 8; const lowBitsCount = 8 - highBitsCount; let size = end - start; let shift = 0n; // Extract whole bytes while (size >= 8) { const byte = (buffer[byteIndex] << highBitsCount) | (buffer[byteIndex + 1] >> lowBitsCount); value += BigInt(byte & 0xff) << shift; shift += 8n; size -= 8; byteIndex++; } // Read any trailing bits. These trailing bits may cross a byte boundary // in the input buffer. if (size > 0) { const lowBitsUsed = size - Math.max(0, size - lowBitsCount); let trailingByte = (buffer[byteIndex] & ((1 << lowBitsCount) - 1)) >> (lowBitsCount - lowBitsUsed); size -= lowBitsUsed; if (size > 0) { trailingByte <<= size; trailingByte += buffer[byteIndex + 1] >> (8 - size); } value += BigInt(trailingByte) << shift; } } } // For signed integers, if the high bit is set reinterpret as two's // complement if (isSigned) { const highBit = 2n ** BigInt(end - start - 1); if (value >= highBit) { value -= highBit * 2n; } } // Convert the result into a JS number. This may cause quantizing/error on // values outside JavaScript's safe integer range. return Number(value); } /** * Interprets a 16-bit unsigned integer value as a 16-bit floating point value. * * @param {number} intValue * @returns {number} */ function fp16UintToNumber(intValue) { const sign = intValue >= 0x8000 ? -1 : 1; const exponent = (intValue & 0x7c00) >> 10; const fraction = intValue & 0x03ff; let value; if (exponent === 0) { value = 6.103515625e-5 * (fraction / 0x400); } else if (exponent === 0x1f) { value = fraction === 0 ? Infinity : NaN; } else { value = Math.pow(2, exponent - 15) * (1 + fraction / 0x400); } return sign * value; } /** * Converts a floating point number to bytes for a 16-bit floating point value. * * @param {number} intValue * @param {boolean} isBigEndian * @returns {Uint8Array} */ function numberToFp16Uint(value, isBigEndian) { const buffer = new Uint8Array(2); if (isNaN(value)) { buffer[1] = 0x7e; } else if (value === Infinity) { buffer[1] = 0x7c; } else if (value === -Infinity) { buffer[1] = 0xfc; } else if (value === 0) { // Both values are already zero } else { const sign = value < 0 ? 1 : 0; value = Math.abs(value); let exponent = Math.floor(Math.log2(value)); let fraction = value / Math.pow(2, exponent) - 1; exponent += 15; if (exponent <= 0) { exponent = 0; fraction = value / Math.pow(2, -14); } else if (exponent >= 31) { exponent = 31; fraction = 0; } fraction = Math.round(fraction * 1024); buffer[1] = (sign << 7) | ((exponent & 0x1f) << 2) | ((fraction >> 8) & 0x03); buffer[0] = fraction & 0xff; } if (isBigEndian) { const a = buffer[0]; buffer[0] = buffer[1]; buffer[1] = a; } return buffer; } /** * Throws an exception if the given start and end values are out of bounds for * a bit array. * * @param {BitArray} bitArray * @param {number} start * @param {number} end */ function bitArrayValidateRange(bitArray, start, end) { if ( start < 0 || start > bitArray.bitSize || end < start || end > bitArray.bitSize ) { const msg = `Invalid bit array slice: start = ${start}, end = ${end}, ` + `bit size = ${bitArray.bitSize}`; throw new globalThis.Error(msg); } } /** @type {TextEncoder | undefined} */ let utf8Encoder; /** * Returns the UTF-8 bytes for a string. * * @param {string} string * @returns {Uint8Array} */ export function stringBits(string) { utf8Encoder ??= new TextEncoder(); return utf8Encoder.encode(string); } /** * Returns the UTF-8 bytes for a single UTF codepoint. * * @param {UtfCodepoint} codepoint * @returns {Uint8Array} */ export function codepointBits(codepoint) { return stringBits(String.fromCodePoint(codepoint.value)); } /** * Returns the UTF-16 bytes for a string. * * @param {string} string * @param {boolean} isBigEndian * @returns {Uint8Array} */ export function stringToUtf16(string, isBigEndian) { const buffer = new ArrayBuffer(string.length * 2); const bufferView = new DataView(buffer); for (let i = 0; i < string.length; i++) { bufferView.setUint16(i * 2, string.charCodeAt(i), !isBigEndian); } return new Uint8Array(buffer); } /** * Returns the UTF-16 bytes for a single UTF codepoint. * * @param {UtfCodepoint} codepoint * @param {boolean} isBigEndian * @returns {Uint8Array} */ export function codepointToUtf16(codepoint, isBigEndian) { return stringToUtf16(String.fromCodePoint(codepoint.value), isBigEndian); } /** * Returns the UTF-32 bytes for a string. * * @param {string} string * @param {boolean} isBigEndian * @returns {Uint8Array} */ export function stringToUtf32(string, isBigEndian) { const buffer = new ArrayBuffer(string.length * 4); const bufferView = new DataView(buffer); let length = 0; for (let i = 0; i < string.length; i++) { const codepoint = string.codePointAt(i); bufferView.setUint32(length * 4, codepoint, !isBigEndian); length++; if (codepoint > 0xffff) { i++; } } return new Uint8Array(buffer.slice(0, length * 4)); } /** * Returns the UTF-32 bytes for a single UTF codepoint. * * @param {UtfCodepoint} codepoint * @param {boolean} isBigEndian * @returns {Uint8Array} */ export function codepointToUtf32(codepoint, isBigEndian) { return stringToUtf32(String.fromCodePoint(codepoint.value), isBigEndian); } export class Result extends CustomType { static isResult(data) { return data instanceof Result; } } export class Ok extends Result { constructor(value) { super(); this[0] = value; } isOk() { return true; } } export const Result$Ok = (value) => new Ok(value); export const Result$isOk = (value) => value instanceof Ok; export const Result$Ok$0 = (value) => value[0]; export class Error extends Result { constructor(detail) { super(); this[0] = detail; } isOk() { return false; } } export const Result$Error = (detail) => new Error(detail); export const Result$isError = (value) => value instanceof Error; export const Result$Error$0 = (value) => value[0]; export function isEqual(x, y) { let values = [x, y]; while (values.length) { let a = values.pop(); let b = values.pop(); if (a === b) continue; if (!isObject(a) || !isObject(b)) return false; let unequal = !structurallyCompatibleObjects(a, b) || unequalDates(a, b) || unequalBuffers(a, b) || unequalArrays(a, b) || unequalMaps(a, b) || unequalSets(a, b) || unequalRegExps(a, b); if (unequal) return false; const proto = Object.getPrototypeOf(a); if (proto !== null && typeof proto.equals === "function") { try { if (a.equals(b)) continue; else return false; } catch {} } let [keys, get] = getters(a); const ka = keys(a); const kb = keys(b); if (ka.length !== kb.length) return false; for (let k of ka) { values.push(get(a, k), get(b, k)); } } return true; } function getters(object) { if (object instanceof Map) { return [(x) => x.keys(), (x, y) => x.get(y)]; } else { let extra = object instanceof globalThis.Error ? ["message"] : []; return [(x) => [...extra, ...Object.keys(x)], (x, y) => x[y]]; } } function unequalDates(a, b) { return a instanceof Date && (a > b || a < b); } function unequalBuffers(a, b) { return ( !(a instanceof BitArray) && a.buffer instanceof ArrayBuffer && a.BYTES_PER_ELEMENT && !(a.byteLength === b.byteLength && a.every((n, i) => n === b[i])) ); } function unequalArrays(a, b) { return Array.isArray(a) && a.length !== b.length; } function unequalMaps(a, b) { return a instanceof Map && a.size !== b.size; } function unequalSets(a, b) { return ( a instanceof Set && (a.size != b.size || [...a].some((e) => !b.has(e))) ); } function unequalRegExps(a, b) { return a instanceof RegExp && (a.source !== b.source || a.flags !== b.flags); } function isObject(a) { return typeof a === "object" && a !== null; } function structurallyCompatibleObjects(a, b) { if (typeof a !== "object" && typeof b !== "object" && (!a || !b)) return false; let nonstructural = [Promise, WeakSet, WeakMap, Function]; if (nonstructural.some((c) => a instanceof c)) return false; return a.constructor === b.constructor; } export function remainderInt(a, b) { if (b === 0) { return 0; } else { return a % b; } } export function divideInt(a, b) { return Math.trunc(divideFloat(a, b)); } export function divideFloat(a, b) { if (b === 0) { return 0; } else { return a / b; } } export function makeError(variant, file, module, line, fn, message, extra) { let error = new globalThis.Error(message); error.gleam_error = variant; error.file = file; error.module = module; error.line = line; error.function = fn; // TODO: Remove this with Gleam v2.0.0 error.fn = fn; for (let k in extra) error[k] = extra[k]; return error; } ================================================ FILE: compiler-wasm/Cargo.toml ================================================ [package] name = "gleam-wasm" version = "1.15.1" authors = ["Louis Pilfold "] edition = "2024" license = "Apache-2.0" [lib] # This package compiles to wasm crate-type = ["cdylib", "rlib"] [dependencies] gleam-core = { path = "../compiler-core" } console_error_panic_hook = "0" serde-wasm-bindgen = "0" wasm-bindgen = { version = "0.2", features = ["serde-serialize"] } tracing-wasm = "*" camino.workspace = true hexpm = { path = "../hexpm" } im.workspace = true itertools.workspace = true serde.workspace = true termcolor.workspace = true tracing.workspace = true getrandom.workspace = true [dev-dependencies] wasm-bindgen-test = "0.3.42" ================================================ FILE: compiler-wasm/README.md ================================================ # Compiler WASM ```sh # Install the build tool with cargo or brew etc cargo install wasm-pack # Build the wasm library wasm-pack build --release --target web # Make a tarball to attach to a release tar -C pkg/ -czvf gleam-v1.1.0-browser.tar.gz . ``` ================================================ FILE: compiler-wasm/src/lib.rs ================================================ #[cfg(test)] mod tests; mod wasm_filesystem; use camino::Utf8PathBuf; use gleam_core::{ Error, analyse::TargetSupport, build::{ Mode, NullTelemetry, PackageCompiler, StaleTracker, Target, TargetCodegenConfiguration, }, config::PackageConfig, io::{FileSystemReader, FileSystemWriter}, uid::UniqueIdGenerator, warning::{VectorWarningEmitterIO, WarningEmitter}, }; use hexpm::version::Version; use im::HashMap; use std::{cell::RefCell, collections::HashSet, rc::Rc}; use wasm_filesystem::WasmFileSystem; use wasm_bindgen::prelude::*; #[derive(Debug, Clone, Default)] struct Project { fs: WasmFileSystem, warnings: VectorWarningEmitterIO, } thread_local! { static PROJECTS: RefCell> = RefCell::new(HashMap::new()); } /// You should call this once to ensure that if the compiler crashes it gets /// reported in JavaScript. /// #[cfg(target_arch = "wasm32")] #[wasm_bindgen] pub fn initialise_panic_hook(debug: bool) { console_error_panic_hook::set_once(); if debug { let _ = tracing_wasm::try_set_as_global_default(); } } /// Reset the virtual file system to an empty state. /// #[wasm_bindgen] pub fn reset_filesystem(project_id: usize) { let fs = get_filesystem(project_id); fs.reset(); } /// Delete project, freeing any memory associated with it. /// #[wasm_bindgen] pub fn delete_project(project_id: usize) { PROJECTS.with(|lock| { _ = lock.borrow_mut().remove(&project_id); }) } fn get_project(project_id: usize) -> Project { PROJECTS.with(|lock| lock.borrow_mut().entry(project_id).or_default().clone()) } fn get_filesystem(project_id: usize) -> WasmFileSystem { get_project(project_id).fs } fn get_warnings(project_id: usize) -> VectorWarningEmitterIO { get_project(project_id).warnings } /// Write a Gleam module to the `/src` directory of the virtual file system. /// #[wasm_bindgen] pub fn write_module(project_id: usize, module_name: &str, code: &str) { let fs = get_filesystem(project_id); let path = format!("/src/{module_name}.gleam"); fs.write(&Utf8PathBuf::from(path), code) .expect("writing file") } /// Write a file to the virtual file system. /// #[wasm_bindgen] pub fn write_file(project_id: usize, path: &str, content: &str) { let fs = get_filesystem(project_id); fs.write(&Utf8PathBuf::from(path), content) .expect("writing file") } /// Write a non-text file to the virtual file system. /// #[wasm_bindgen] pub fn write_file_bytes(project_id: usize, path: &str, content: &[u8]) { let fs = get_filesystem(project_id); fs.write_bytes(&Utf8PathBuf::from(path), content) .expect("writing file") } /// Read a file from the virtual file system. /// #[wasm_bindgen] pub fn read_file_bytes(project_id: usize, path: &str) -> Option> { let fs = get_filesystem(project_id); fs.read_bytes(&Utf8PathBuf::from(path)).ok() } /// Run the package compiler. If this succeeds you can use /// #[wasm_bindgen] pub fn compile_package(project_id: usize, target: &str) -> Result<(), String> { let target = match target.to_lowercase().as_str() { "erl" | "erlang" => Target::Erlang, "js" | "javascript" => Target::JavaScript, _ => { let msg = format!("Unknown target `{target}`, expected `erlang` or `javascript`"); return Err(msg); } }; do_compile_package(get_project(project_id), target).map_err(|e| e.pretty_string()) } /// Get the compiled JavaScript output for a given module. /// /// You need to call `compile_package` before calling this function. /// #[wasm_bindgen] pub fn read_compiled_javascript(project_id: usize, module_name: &str) -> Option { let fs = get_filesystem(project_id); let path = format!("/build/{module_name}.mjs"); fs.read(&Utf8PathBuf::from(path)).ok() } /// Get the compiled Erlang output for a given module. /// /// You need to call `compile_package` before calling this function. /// #[wasm_bindgen] pub fn read_compiled_erlang(project_id: usize, module_name: &str) -> Option { let fs = get_filesystem(project_id); let path = format!( "/build/_gleam_artefacts/{}.erl", module_name.replace('/', "@") ); fs.read(&Utf8PathBuf::from(path)).ok() } /// Clear any stored warnings. This is performed automatically when before compilation. /// #[wasm_bindgen] pub fn reset_warnings(project_id: usize) { get_warnings(project_id).reset(); } /// Pop the latest warning from the compiler. /// #[wasm_bindgen] pub fn pop_warning(project_id: usize) -> Option { get_warnings(project_id).pop().map(|w| w.to_pretty_string()) } fn do_compile_package(project: Project, target: Target) -> Result<(), Error> { let ids = UniqueIdGenerator::new(); let mut type_manifests = im::HashMap::new(); let mut defined_modules = im::HashMap::new(); #[allow(clippy::arc_with_non_send_sync)] let warning_emitter = WarningEmitter::new(Rc::new(project.warnings)); let config = PackageConfig { name: "library".into(), version: Version::new(1, 0, 0), target, ..Default::default() }; let target = match target { Target::Erlang => TargetCodegenConfiguration::Erlang { app_file: None }, Target::JavaScript => TargetCodegenConfiguration::JavaScript { emit_typescript_definitions: false, prelude_location: Utf8PathBuf::from("./gleam_prelude.mjs"), }, }; tracing::info!("Compiling package"); let lib = Utf8PathBuf::from("/lib"); let out = Utf8PathBuf::from("/build"); let package = Utf8PathBuf::from("/"); let mut compiler = PackageCompiler::new( &config, Mode::Dev, &package, &out, &lib, &target, ids, project.fs, ); compiler.write_entrypoint = false; compiler.write_metadata = false; compiler.compile_beam_bytecode = true; compiler.target_support = TargetSupport::Enforced; compiler .compile( &warning_emitter, &mut type_manifests, &mut defined_modules, &mut StaleTracker::default(), &mut HashSet::new(), &NullTelemetry, ) .into_result() .map(|_| ()) } ================================================ FILE: compiler-wasm/src/tests.rs ================================================ use super::*; use wasm_bindgen_test::wasm_bindgen_test; #[wasm_bindgen_test] fn test_reset_filesystem() { reset_filesystem(0); assert_eq!(read_file_bytes(0, "hello"), None); write_file_bytes(0, "hello", vec![1, 2, 3].as_slice()); assert_eq!(read_file_bytes(0, "hello"), Some(vec![1, 2, 3])); reset_filesystem(0); assert_eq!(read_file_bytes(0, "hello"), None); } #[wasm_bindgen_test] fn test_write_module() { reset_filesystem(0); assert_eq!(read_file_bytes(0, "/src/some/module.gleam"), None); write_module(0, "some/module", "const x = 1"); assert_eq!( read_file_bytes(0, "/src/some/module.gleam"), Some(vec![99, 111, 110, 115, 116, 32, 120, 32, 61, 32, 49]), ); reset_filesystem(0); assert_eq!(read_file_bytes(0, "/src/some/module.gleam"), None); } #[wasm_bindgen_test] fn test_compile_package_bad_target() { reset_filesystem(0); assert!(compile_package(0, "ruby").is_err()); } #[wasm_bindgen_test] fn test_compile_package_empty() { reset_filesystem(0); assert!(compile_package(0, "javascript").is_ok()); } #[wasm_bindgen_test] fn test_compile_package_js() { reset_filesystem(0); write_module(0, "one/two", "pub const x = 1"); write_module(0, "up/down", "import one/two pub fn go() { two.x }"); assert!(compile_package(0, "javascript").is_ok()); assert_eq!( read_compiled_javascript(0, "one/two"), Some("export const x = 1;\n".into()) ); assert_eq!( read_compiled_javascript(0, "up/down"), Some( r#"import * as $two from "../one/two.mjs"; export function go() { return $two.x; } "# .into() ) ); // And now an error! write_module(0, "up/down", "import one/two/three"); assert!(compile_package(0, "javascript").is_err()); // Let's fix that. write_module(0, "up/down", "pub const y = 1"); assert!(compile_package(0, "javascript").is_ok()); assert_eq!( read_compiled_javascript(0, "up/down"), Some("export const y = 1;\n".into()) ); } #[wasm_bindgen_test] fn test_compile_package_js_unsupported_feature() { reset_filesystem(0); write_module( 0, "one", r#" fn wibble() { <<0:16-native>> } pub fn main() { wibble() } "#, ); assert!( compile_package(0, "javascript") .unwrap_err() .contains("The javascript target does not support") ); } #[wasm_bindgen_test] fn test_warnings() { reset_filesystem(0); write_module(0, "one", "const x = 1"); assert!(pop_warning(0).is_none()); assert!(compile_package(0, "javascript").is_ok()); assert!(pop_warning(0).is_some()); assert!(pop_warning(0).is_none()); } ================================================ FILE: compiler-wasm/src/wasm_filesystem.rs ================================================ use camino::{Utf8Path, Utf8PathBuf}; use gleam_core::{ Error, Result, io::{ BeamCompiler, Command, CommandExecutor, FileSystemReader, FileSystemWriter, ReadDir, Stdio, WrappedReader, memory::InMemoryFileSystem, }, }; use std::collections::HashSet; #[derive(Clone, Debug, Default)] pub struct WasmFileSystem { imfs: InMemoryFileSystem, } impl WasmFileSystem { pub fn reset(&self) { self.imfs.reset(); } } impl CommandExecutor for WasmFileSystem { fn exec(&self, _command: Command) -> Result { Ok(0) // Always succeed. } } impl BeamCompiler for WasmFileSystem { fn compile_beam( &self, _out: &Utf8Path, _lib: &Utf8Path, _modules: &HashSet, _stdio: Stdio, ) -> Result, Error> { Ok(Vec::new()) // Always succeed. } } impl FileSystemWriter for WasmFileSystem { fn delete_directory(&self, path: &Utf8Path) -> Result<(), Error> { tracing::trace!("delete {:?}", path); self.imfs.delete_directory(path) } fn copy(&self, _from: &Utf8Path, _to: &Utf8Path) -> Result<(), Error> { Ok(()) } fn copy_dir(&self, _: &Utf8Path, _: &Utf8Path) -> Result<(), Error> { Ok(()) } fn mkdir(&self, path: &Utf8Path) -> Result<(), Error> { tracing::trace!("mkdir {:?}", path); self.imfs.mkdir(path) } fn hardlink(&self, _: &Utf8Path, _: &Utf8Path) -> Result<(), Error> { Ok(()) } fn symlink_dir(&self, _: &Utf8Path, _: &Utf8Path) -> Result<(), Error> { Ok(()) } fn delete_file(&self, path: &Utf8Path) -> Result<(), Error> { tracing::trace!("delete file {:?}", path); self.imfs.delete_file(path) } fn write(&self, path: &Utf8Path, content: &str) -> Result<(), Error> { tracing::trace!("write {:?}", path); self.imfs.write(path, content) } fn write_bytes(&self, path: &Utf8Path, content: &[u8]) -> Result<(), Error> { tracing::trace!("write_bytes {:?}", path); self.imfs.write_bytes(path, content) } fn exists(&self, path: &Utf8Path) -> bool { self.imfs.exists(path) } } impl FileSystemReader for WasmFileSystem { fn read(&self, path: &Utf8Path) -> Result { tracing::trace!("read {:?}", path); self.imfs.read(path) } fn is_file(&self, path: &Utf8Path) -> bool { tracing::info!("is_file {:?}", path); self.imfs.is_file(path) } fn is_directory(&self, path: &Utf8Path) -> bool { tracing::trace!("is_directory {:?}", path); self.imfs.is_directory(path) } fn reader(&self, path: &Utf8Path) -> Result { tracing::trace!("reader {:?}", path); self.imfs.reader(path) } fn read_dir(&self, path: &Utf8Path) -> Result { tracing::trace!("read_dir {:?}", path); self.imfs.read_dir(path) } fn modification_time(&self, path: &Utf8Path) -> Result { self.imfs.modification_time(path) } fn read_bytes(&self, path: &Utf8Path) -> Result, Error> { self.imfs.read_bytes(path) } fn canonicalise(&self, path: &Utf8Path) -> Result { self.imfs.canonicalise(path) } } ================================================ FILE: containers/elixir-alpine.dockerfile ================================================ FROM elixir:alpine ARG TARGETARCH COPY gleam-${TARGETARCH} /bin/gleam COPY gleam-${TARGETARCH}.sbom.spdx.json /opt/sbom/ CMD ["gleam"] ================================================ FILE: containers/elixir-slim.dockerfile ================================================ FROM elixir:slim ARG TARGETARCH COPY gleam-${TARGETARCH} /bin/gleam COPY gleam-${TARGETARCH}.sbom.spdx.json /opt/sbom/ CMD ["gleam"] ================================================ FILE: containers/elixir.dockerfile ================================================ FROM elixir:latest ARG TARGETARCH COPY gleam-${TARGETARCH} /bin/gleam COPY gleam-${TARGETARCH}.sbom.spdx.json /opt/sbom/ CMD ["gleam"] ================================================ FILE: containers/erlang-alpine.dockerfile ================================================ FROM erlang:alpine ARG TARGETARCH COPY gleam-${TARGETARCH} /bin/gleam COPY gleam-${TARGETARCH}.sbom.spdx.json /opt/sbom/ CMD ["gleam"] ================================================ FILE: containers/erlang-slim.dockerfile ================================================ FROM erlang:slim ARG TARGETARCH COPY gleam-${TARGETARCH} /bin/gleam COPY gleam-${TARGETARCH}.sbom.spdx.json /opt/sbom/ CMD ["gleam"] ================================================ FILE: containers/erlang.dockerfile ================================================ FROM erlang:latest ARG TARGETARCH COPY gleam-${TARGETARCH} /bin/gleam COPY gleam-${TARGETARCH}.sbom.spdx.json /opt/sbom/ CMD ["gleam"] ================================================ FILE: containers/node-alpine.dockerfile ================================================ FROM node:alpine ARG TARGETARCH COPY gleam-${TARGETARCH} /bin/gleam COPY gleam-${TARGETARCH}.sbom.spdx.json /opt/sbom/ CMD ["gleam"] ================================================ FILE: containers/node-slim.dockerfile ================================================ FROM node:slim ARG TARGETARCH COPY gleam-${TARGETARCH} /bin/gleam COPY gleam-${TARGETARCH}.sbom.spdx.json /opt/sbom/ CMD ["gleam"] ================================================ FILE: containers/node.dockerfile ================================================ FROM node:latest ARG TARGETARCH COPY gleam-${TARGETARCH} /bin/gleam COPY gleam-${TARGETARCH}.sbom.spdx.json /opt/sbom/ CMD ["gleam"] ================================================ FILE: containers/scratch.dockerfile ================================================ FROM scratch ARG TARGETARCH COPY gleam-${TARGETARCH} /bin/gleam COPY gleam-${TARGETARCH}.sbom.spdx.json /opt/sbom/ CMD ["gleam"] ================================================ FILE: deny.toml ================================================ # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html [advisories] db-path = "~/.cargo/advisory-db" db-urls = ["https://github.com/rustsec/advisory-db"] ignore = [ "RUSTSEC-2025-0141" ] # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html [licenses] allow = [ "Apache-2.0 WITH LLVM-exception", "Apache-2.0", "BSD-3-Clause", "BSL-1.0", "CC0-1.0", "CDLA-Permissive-2.0", "ISC", "MIT", "MPL-2.0", "Unicode-3.0", "Unicode-DFS-2016", "Zlib", ] confidence-threshold = 0.8 [[licenses.clarify]] name = "ring" version = "*" expression = "MIT AND ISC AND OpenSSL" license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] [licenses.private] ignore = false registries = [] # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html [bans] multiple-versions = "allow" wildcards = "allow" highlight = "all" allow = [] deny = [] skip = [] skip-tree = [] # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html [sources] unknown-registry = "deny" unknown-git = "deny" allow-registry = ["https://github.com/rust-lang/crates.io-index"] allow-git = [ # "https://github.com/gleam-lang/hexpm-rust", ] [[licenses.clarify]] name = "encoding_rs" version = "*" expression = "(Apache-2.0 OR MIT) AND BSD-3-Clause" license-files = [ { path = "COPYRIGHT", hash = 0x39f8ad31 } ] ================================================ FILE: docs/annoyances.md ================================================ # Annoyances This document contains a list of issues and annoyances that we have writing Gleam code today, so that we can devise solutions to them in future. There are other annoyances that have known solutions that are yet to be implemented. These are tracked in the Gleam issue tracker instead. ## Bundling compiled JavaScript is non-obvious ## Cannot shift repeated work to compile or initialise time For example, regex compilation. ## JavaScript custom type representation could be faster There's performance improvements to be had. Would would be optimal? ================================================ FILE: docs/compiler/README.md ================================================ # Compiler documentation Hello! Welcome to the documentation for the Gleam compiler. I hope you have fun with the project! * [Project structure](#project-structure) * [Compilation flow](#compilation-flow) * [Testing](#testing) * [Running the tests](#running-the-tests) * [Snapshot testing](#snapshot-testing) ## Project structure The project is made up of several Rust crates (projects): - `compiler-core`: This project parses, analyses, and compiles Gleam projects. It is entirely pure and has no IO so that is provided by the other Rust crates that wrap this one. - `language-server`: This project contains code for the Gleam language server, including autocomplete, code actions, hover and other features. - `compiler-cli`: A command line interface that wraps the core compiler and language server, providing IO to files and to the console. - `compiler-wasm`: A JavaScript interface to the core compiler via web assembly. Suitable for running in a web browser. In addition to the Rust code there are these components: - `Makefile`: A makefile that defines shortcut commands for common tasks when working in the Gleam codebase. Run `make help` to view them. - `test`: A collection of (mostly) Gleam projects that serve as integration tests for the compiler. - `deny.toml`: Configuration for the `cargo-deny` tool which is used to ensure that the Rust libraries the compiler depends on adhere to our expectations. - `containers`: A collection of docker-files used to produce OCI containers for each Gleam release. - `.github/workflows`: GitHub Actions workflow definitions, used to build, test, and release new versions of the project. - `docs`: You're looking at it pal. ## Compilation flow The process for compiling Gleam modules within the compiler looks roughly like this: ```txt Gleam source code .cache binaries ▼ ▼ ┌────────────────────┐ ┌───────────────────────┐ │ Parser │ │ Metadata deserializer │ └────────────────────┘ └───────────────────────┘ │ │ Untyped AST Module metadata └─────────┐ ┌────────┘ │ ▼ ▼ │ ┌─────────────────────┐ │ │ Dependency sorter │ │ └─────────────────────┘ │ │ │ Untyped AST │ (sorted by deps) │ ▼ │ ┌───────────────────┐ │ │ Type checker │◄─────┘ └───────────────────┘ │ ┌────── Typed AST ──────┐ ▼ ▼ ┌────────────────────┐ ┌─────────────────────┐ │ Code generator │ │ Metadata serializer │ └────────────────────┘ └─────────────────────┘ │ │ │ ▼ Erlang or JavaScript .cache binaries printing algebra ▼ ┌────────────────────┐ │ Pretty printer │ └────────────────────┘ │ ▼ Erlang or JavaScript source code ``` ## Testing We like automated tests! They're a great way to verify that the compiler is doing what we expect it do as we make changes. ### Running the tests - `make test`: Run all the tests. Worth doing before committing any changes. - `make test-watch`: Run the Rust unit tests when files are saved. - `make language-test`: Run the cross-platform language integration tests in `test/language`. - `make language-test-watch`: Run said tests when files are saved. - `make javascript-prelude-test`: Run the unit tests for the JavaScript code that implements the Gleam prelude when compiling to JavaScript. - `make javascript-prelude-test-watch`: Run said tests when files are saved. The `*-watch` commands require the [`watchexec`][watchexec] program to be installed. ### Snapshot testing The compiler makes heavy use of snapshot testing using the [`cargo-insta`][cargo-insta] tool. [cargo-insta]: https://github.com/mitsuhiko/insta [watchexec]: https://github.com/watchexec/watchexec If you're not familiar with snapshot testing instead of writing an input and an expected output (as in normal example based tests) you write only the input. The snapshot testing tool can then be used to mark any new outputs as accepted and saved into the repository as snap files. If an output for one of the tests changes then it is considered a failed test and the programmer has the option to either reject the new version (as it is an incorrect result) or accept it as the new correct output. This style of testing saves us huge amounts of time as manually updating all the expected output when we make changes to the output format of the compiler or error messaging is time-consuming and very dull. With snapshot testing it takes seconds. ```sh # Run the tests make test # Interactively verify changes to the snapshots cargo insta review ``` ================================================ FILE: docs/runtime-errors.md ================================================ # Runtime errors There are several runtime errors that Gleam code can throw. This documentation lists them and their runtime properties. On Erlang runtime errors are Erlang maps thrown with `erlang:error/1`, having at least these properties: | Key | Type | Value | | --- | ---- | ----- | | gleam_error | Atom | See individual errors | | message | String | See individual errors | | file | String | A path to the original source file location | | module | String | The module the error occurred in | | function | String | The function the error occurred in | | line | Int | The line number the error occurred on | On JavaScript runtime errors are instances of the JavaScript `Error` class, having at least these properties added to them: | Key | Type | Value | | --- | ---- | ----- | | gleam_error | String | See individual errors | | message | String | See individual errors | | module | String | The module the error occurred in | | function | String | The function the error occurred in | | line | Number | The line number the error occurred on | ## Todo A panic that indicates that the code has not yet been completed, intended for use in development. ```gleam todo todo as "some message" ``` | Key | Erlang Value | JavaScript Value | | --- | ------------ | ---------------- | | gleam_error | `todo` | `"todo"` | | message | The given message | The given message | ## Panic An explicit panic to unconditionally error. ```gleam panic panic as "some message" ``` | Key | Erlang Value | JavaScript Value | | --- | ------------ | ---------------- | | gleam_error | `panic` | `"panic"` | | message | The given message | The given message | ## Let assert An inexhaustive pattern match, erroring if the pattern does not match. ```gleam let assert Ok(x) = something() let assert Error(e) = something() as "This should fail" ``` | Key | Erlang Value | JavaScript Value | | --- | ------------ | ---------------- | | gleam_error | `let_assert` | `"let_assert"` | | message | The given message | The given message | | value | The unmatched value | The unmatched value | | start | The byte-index of the start of the `let assert` statement | The byte-index of the start of the `let assert` statement | | end | The byte-index of the end of the `let assert` statement | The byte-index of the end of the `let assert` statement | | pattern_start | The byte-index of the start of the asserted pattern | The byte-index of the start of the asserted pattern | | pattern_end | The byte-index of the end of the asserted pattern | The byte-index of the end of the asserted pattern | ## Assert An assertion of a boolean value. The error format of `assert` differs based on the expression that is asserted. It always has these fields: | Key | Erlang Value | JavaScript Value | | --- | ------------ | ---------------- | | gleam_error | `assert` | `"assert"` | | message | The given message | The given message | | kind | The kind of asserted expression | The kind of asserted expression | | start | The byte-index of the start of the `assert` statement | The byte-index of the start of the `assert` statement | | end | The byte-index of the end of the `assert` expression | The byte-index of the end of the `assert` expression | | expression_start | The byte-index of the start of the asserted expression | The byte-index of the start of the asserted expression | But, depending on the expression that was asserted, it contains additional information which can be used to diagnose the error. ### Binary operators ```gleam assert level >= 30 ``` | Key | Erlang Type | JavaScript Type | Value | | --- | ----------- | --------------- | ----- | | kind | Atom | String | `binary_operator` | | operator | Atom | String | The binary operator that was used | | left | Map | Object | The left hand side of the operator | | right | Map | Object | The left hand side of the operator | ### Function calls ```gleam assert check_some_property(a, b, c) ``` | Key | Erlang Type | JavaScript Type | Value | | --- | ----------- | --------------- | ----- | | kind | Atom | String | `function_call` | | arguments | List of map | Array of objects | The arguments of the asserted function | ### Other expressions ```gleam assert other_expression ``` | Key | Erlang Type | JavaScript Type | Value | | --- | ----------- | --------------- | ----- | | kind | Atom | String | `expression` | | expression | Map | Object | The value of the asserted expression | The expression maps have this structure: | Key | Erlang Type | JavaScript Type | Value | | --- | ----------- | --------------- | ----- | | value | any | any | the value the expression evaluated to at runtime | | kind | Atom | String | `literal` or `expression` or `unevaluated` | | start | Int | Number | The byte-index of the start of this expression in the source code | | end | Int | Number | The byte-index of the end of this expression in the source code | If the expression is a literal, such as `True` or `15`, it will have the `literal` kind. This signals that its value is not runtime dependent, and may not need to be printed. If the expression is on the right hand side of a short-circuiting operator, like `||` or `&&`, it might not be evaluated. If the operator short-circuits, the right hand side expression will have kind `unevaluated`. ================================================ FILE: docs/v2.md ================================================ # Breaking changes to make for v2 ## `[1 ..]` syntax Due to a bug in the parser we accept `[1, ..]` as a valid list value. - [x] Emits warning when used. - [x] Formatter rewrites it to desired syntax. ## `_ as a` syntax This pattern doesn't make sense as one could write `a` instead. We don't want two ways of doing the same thing. - [x] Emits warning when used. - [x] Formatter rewrites it to desired syntax. ## Shadowing imported values Do not allow shadowing an imported value, the same way one can't define two top level functions with the same name. - [x] Emits warning when used. ## Import one module multiple times Do not allow one module to be imported multiple times. This is currently accepted so long as each import uses a different alias. - [x] Emits warning when used. ## JavaScript runtime error `fn` property On JavaScript there is a deprecated `fn` property. This was a mistake, it should have been `function`. It still exists today due to backwards compatibility. ## Do not allow guard with no condition It doesn't make sense to have an `if` guard followed by no condition, but the compiler allows this: `case wibble { big if -> True }` - [x] Emits warning when used. - [x] Formatter rewrites it to desired syntax. ================================================ FILE: gleam-bin/.cargo/config.toml ================================================ # Statically link the CRT on Windows with MSVC toolchain # (only in release mode) [target.'cfg(all(windows, target_env = "msvc"))'] rustflags = ["-C", "target-feature=+crt-static"] ================================================ FILE: gleam-bin/Cargo.toml ================================================ [package] name = "gleam" version = "1.15.1" authors = ["Louis Pilfold "] edition = "2024" license = "Apache-2.0" [dependencies] gleam-cli = { path = "../compiler-cli" } [target.'cfg(windows)'.build-dependencies] # For statically linking the VCRuntime on Windows when # using the MSVC toolchain static_vcruntime = "3" ================================================ FILE: gleam-bin/build.rs ================================================ fn main() { #[cfg(windows)] static_vcruntime::metabuild(); } ================================================ FILE: gleam-bin/src/main.rs ================================================ pub fn main() { gleam_cli::main(); } ================================================ FILE: hexpm/.github/workflows/ci.yml ================================================ name: ci on: pull_request: push: branches: - main workflow_dispatch: permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: test: name: test runs-on: ${{ matrix.os }} strategy: matrix: toolchain: [stable] build: [linux-amd64, macos, windows] include: - build: linux-amd64 os: ubuntu-latest target: x86_64-unknown-linux-gnu - build: macos os: macos-latest target: x86_64-apple-darwin - build: windows os: windows-latest target: x86_64-pc-windows-msvc steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ matrix.toolchain }} target: ${{ matrix.target }} - name: Run tests uses: clechasseur/rs-cargo@v2 with: command: test args: --workspace --target ${{ matrix.target }} format-lint: name: format-lint runs-on: ubuntu-latest timeout-minutes: 10 steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable with: toolchain: stable components: clippy, rustfmt - name: Check formatting run: cargo fmt --all -- --check - name: Run linter run: cargo clippy --workspace ================================================ FILE: hexpm/.gitignore ================================================ /target Cargo.lock ================================================ FILE: hexpm/CHANGELOG.md ================================================ # Changelog ## v5.1.1 - 2025-12-01 - Fixed a bug with request path construction. ## v5.1.0 - 2025-11-06 - Updated dependencies. ## v5.0.1 - 2025-11-06 - Fixed a bug where ranges with `~>` and `and` or `or` would fail to parse. ## v5.0.0 - 2025-09-19 - Functions renamed to make it more clear if the JSON API is used or if the repository endpoints are used. - Added `repository_v2_package_parse_body`, and `repository_v2_get_versions_body`, ## v4.2.0 - 2025-08-27 - `Version`'s serde deserializer can now work with `String` as well as `str`. ## v4.1.0 - 2025-06-03 - Added `impl From for pubgrub::Range` ## v4.0.0 - 2025-05-09 - Removed `version::{pubgrub_report, Version::bump, PackageVersions, resolve_versions, PackageFetcher}`. ## v3.3.0 - 2025-03-10 - Replaced protobuf dependency with prost to avoid a security vulnerability. ## v3.2.0 - 2025-03-07 - Updated protobuf to 3.7.1 and regenerated code from .proto files ## v3.1.0 - 2024-06-23 - Added specific errors for missing minor or patch versions. ## v3.0.0 - 2024-05-30 - Added `add_owner_request`, `add_owner_response` `transfer_owner_request`, `transfer_owner_response`, - `revert_package_request`, and `revert_package_response` are renamed to `remove_package_request`, and `remove_package_response` to accurately describe what they do. ## v2.4.1 - 2024-05-01 - Fixed a bug where prerelease versions would not `bump` correctly. This caused a panic in the Gleam compiler. ## v2.4.0 - 2024-05-01 - Added the `revert_release_request`, `revert_release_response`, `revert_package_request`, and `revert_package_response` functions. ## v2.3.0 - 2024-04-26 - Updated all dependencies. ## v2.2.0 - 2024-04-10 - Updated for latest version of Reqwest. ## v2.1.1 - 2023-11-02 - Fixed a bug where the package name validation regex was too strict. ## v2.1.0 - 2023-08-30 - Updated for latest Hex API. ## v2.0.0 - 2022-04-12 - The `publish_package_request` now takes an additional argument signifying whether an existing package can be replaced for now. ## v1.4.1 - 2021-12-03 - Fixed a bug where locked versions would discard incompatible requirements during version resolution. ## v1.4.0 - 2021-11-25 - The HTTP client has been removed from this library in favour of request building and response parsing functions. Bring your own HTTP client of choice. - `Version` and `Requirement` structs have been added for representing information about requests versions of packages. - Protobuf code updated to use `cfg_attr` rather than deprecated attributes. - The `get_repository_versions` method returngit@github.com:gleam-lang/hexpm-rust.gits `Version`s rather than strings. - The `get_package` uses the `version::Range` type to represent `requirement`s. - A pubgrub based dependency resolver has been added. - Fixed a bug where a forbidden error was returned for a not-found package by the get-package API. - Swapped the word "token" for "key" to match Hex. - Added `unretire_release_*`, `retire_release_*`, `remove_api_key_*` and `publish_package_*` functions. ## v1.3.0 - 2020-03-30 - The `get_package_tarball` method has been added. ## v1.2.0 - 2020-01-17 - The `get_package` method has been added. - Update tokio to 1.0 - Update reqwest to 0.11 - Update url to 2.2 - Update bytes to 1.0 - The generated protobuf code has been regenerated. ## v1.1.1 - 2020-07-20 - Updated generated protobuffer deserialisation code. ## v1.1.0 - 2020-07-20 - The `get_repository_versions` method has been added. - The `repository_base` field has been added to both authenticated and unauthenticated clients. ## v1.0.0 - 2020-05-03 - Initial release ================================================ FILE: hexpm/CONTRIBUTING.md ================================================ # How to Contribute ## Adding a New API Function 1. Figure out what you want to do - Go to https://hexdocs.pm/hex/Mix.Tasks.Hex.html and find what you want to do 2. Once you find the page, click on the code icon on the top-right to go to the corresponding source code like so: https://github.com/hexpm/hex/blob/main/lib/mix/tasks/hex.owner.ex#L125 ```elixir defp transfer_owner(organization, package, owner) do auth = Mix.Tasks.Hex.auth_info(:write) Hex.Shell.info("Transferring ownership to #{owner} for #{package}") case Hex.API.Package.Owner.add(organization, package, owner, "full", true, auth) do {:ok, {code, _body, _headers}} when code in 200..299 -> :ok other -> Hex.Shell.error("Transferring ownership failed") Hex.Utils.print_error_result(other) end end ``` 3. The API function this is calling is `Hex.API.Package.Owner.add` so we go to https://github.com/hexpm/hex/blob/main/lib/hex/api/package.ex, scroll down to the defmodule for Owner and then find the `add` function: ```elixir def add(repo, package, owner, level, transfer, auth) when package != "" do Hex.API.check_write_api() owner = URI.encode_www_form(owner) path = "packages/#{URI.encode(package)}/owners/#{URI.encode(owner)}" params = %{level: level, transfer: transfer} API.erlang_put_request(repo, path, params, auth) end ``` `path` tells us what path and variables we will be using while `params` tells us what the body of the request should contain. The `API.erlang_put_request` at the bottom of this tells us the method of our request needs to be `PUT`. Now we have all of the information we need to create our request function like so: ```elixir pub fn transfer_owner_request( package_name: &str, owner: &str, api_key: &str, config: &Config, ) -> http::Request> { let body = json!({ "level": OwnerLevel::Full.to_string(), "transfer": true, }); config .api_request( Method::PUT, &format!("packages/{}/owners/{}", package_name, owner), Some(api_key), ) .body(body.to_string().into_bytes()) .expect("transfer_owner_request request") } ``` Note that the `api_key` and `config` fields will always be present in these request functions while the other fields are tailored to the specific request we want to make. 4. TODO: How to figure out what to write for the response function? ================================================ FILE: hexpm/Cargo.toml ================================================ [package] name = "hexpm" version = "1.15.1" authors = ["Louis Pilfold "] edition = "2024" license = "Apache-2.0" readme = "README.md" repository = "https://github.com/gleam-lang/hexpm-rust" description = "A Rust client for the Hex package manager" keywords = ["erlang", "gleam", "elixir", "hex", "api-client"] categories = ["api-bindings"] [dependencies] # HTTP types url = "2.2" # Byte collections bytes = "1" # gzip (de)compression flate2 = "1.0" # RSA signature and SHA256 checksum verification ring = "0.17" # PEM -> DER conversion x509-parser = "0.18" # Basic auth HTTP helper http-auth-basic = "0.3" # Protobuf runtime prost = "0.13.5" base16 = { workspace = true, features = ["alloc"] } ecow = { workspace = true, features = ["serde"] } http.workspace = true pubgrub.workspace = true regex.workspace = true serde.workspace = true serde_json.workspace = true thiserror.workspace = true [dev-dependencies] # toml encoding toml = "0.8" [build-dependencies] # Protobuf codegen prost-build = "0" ================================================ FILE: hexpm/LICENCE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2020 - present Louis Pilfold Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ## Runtime Library Exception to the Apache 2.0 License: ## As an exception, if you use this Software to compile your source code and portions of this Software are embedded into the binary product as a result, you may redistribute such product without providing attribution as would otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. ================================================ FILE: hexpm/README.md ================================================ # hexpm-rust ![license](https://img.shields.io/crates/l/hexpm.svg) [![crates.io](https://img.shields.io/crates/v/hexpm.svg?logo=rust)][crates] [![docs.rs](https://img.shields.io/badge/docs.rs-hexpm-blue)][docs] A Rust client for [Hex][hex], the package manager for the Erlang ecosystem. This library was created for use in the [Gleam programming language][gleam] compiler. The API is not overly well considered and breaking API changes may occur depending on the needs of the Gleam compiler. [hex]: https://hex.pm/ [gleam]: https://gleam.run/ [crates]: https://crates.io/crates/hexpm [docs]: https://docs.rs/hexpm ================================================ FILE: hexpm/build.rs ================================================ /// `prost_build` generates files in the output directory, which means that if we want /// to use it, we would need the protoc compiler as a build dependency. /// To get around this, we need run the build script and manually copy the generated files /// into the `src` folder. To do so, uncomment the below lines, then copy the files from the /// output directory into the `src/proto` folder. The path to the output directory /// will be printed in the terminal. /// fn main() { // prost_build::compile_protos( // &[ // "proto/signed.proto", // "proto/package.proto", // "proto/versions.proto", // ], // &["proto/"], // ) // .expect("Failed to generate prost code from .proto files"); // println!( // "cargo::warning=Regenerated proto files, which must be manually copied into the `src` directory. Generated files can be found in {}", // std::env::var("OUT_DIR").unwrap_or_default() // ); } ================================================ FILE: hexpm/proto/names.proto ================================================ syntax = "proto2"; package names; message Names { // All packages in the repository repeated Package packages = 1; // Name of repository required string repository = 2; } message Package { // Package name required string name = 1; // If set, the name of the package repository (NEVER USED, DEPRECATED) // string repository = 2; } ================================================ FILE: hexpm/proto/package.proto ================================================ syntax = "proto2"; package package; message Package { // All releases of the package repeated Release releases = 1; // Name of package required string name = 2; // Name of repository required string repository = 3; } message Release { // Release version required string version = 1; // sha256 checksum of "inner" package tarball // deprecated in favor of outer_checksum required bytes inner_checksum = 2; // All dependencies of the release repeated Dependency dependencies = 3; // If set the release is retired, a retired release should only be // resolved if it has already been locked in a project optional RetirementStatus retired = 4; // sha256 checksum of outer package tarball // required when encoding but optional when decoding optional bytes outer_checksum = 5; } message RetirementStatus { required RetirementReason reason = 1; optional string message = 2; } enum RetirementReason { RETIRED_OTHER = 0; RETIRED_INVALID = 1; RETIRED_SECURITY = 2; RETIRED_DEPRECATED = 3; RETIRED_RENAMED = 4; } message Dependency { // Package name of dependency required string package = 1; // Version requirement of dependency required string requirement = 2; // If set and true the package is optional (see dependency resolution) optional bool optional = 3; // If set is the OTP application name of the dependency, if not set the // application name is the same as the package name optional string app = 4; // If set, the repository where the dependency is located optional string repository = 5; } ================================================ FILE: hexpm/proto/signed.proto ================================================ syntax = "proto2"; package signed; message Signed { // Signed contents required bytes payload = 1; // The signature optional bytes signature = 2; } ================================================ FILE: hexpm/proto/versions.proto ================================================ syntax = "proto2"; package versions; message Versions { // All packages in the repository repeated VersionsPackage packages = 1; // Name of repository required string repository = 2; } message VersionsPackage { // Package name required string name = 1; // All released versions of the package repeated string versions = 2; // Zero-based indexes of retired versions in the versions field, see package.proto repeated int32 retired = 3 [packed=true]; // If set, the name of the package repository (NEVER USED, DEPRECATED) // string repository = 4; } ================================================ FILE: hexpm/src/lib.rs ================================================ mod proto; #[cfg(test)] mod tests; pub mod version; use crate::proto::{signed::Signed, versions::Versions}; use bytes::buf::Buf; use ecow::EcoString; use flate2::read::GzDecoder; use http::{Method, StatusCode}; use prost::Message; use regex::Regex; use ring::digest::{Context, SHA256}; use serde::{Deserialize, Serialize}; use serde_json::json; use std::sync::OnceLock; use std::time::{Duration, Instant}; use std::{ collections::HashMap, convert::{TryFrom, TryInto}, fmt::Display, io::{BufReader, Read}, }; use thiserror::Error; use version::{Range, Version}; use x509_parser::prelude::FromDer; #[derive(Debug, Clone)] pub struct Config { /// Defaults to https://hex.pm/api/ pub api_base: http::Uri, /// Defaults to https://repo.hex.pm/ pub repository_base: http::Uri, } impl Config { pub fn new() -> Self { Self { api_base: http::Uri::from_static("https://hex.pm/api/"), repository_base: http::Uri::from_static("https://repo.hex.pm/"), } } fn api_request(&self, method: http::Method, path_suffix: &str) -> RequestBuilder { RequestBuilder { builder: make_request(self.api_base.clone(), method, path_suffix) .header("content-type", "application/json") .header("accept", "application/json"), } } fn repository_request( &self, method: http::Method, path_suffix: &str, credentials: Option<&Credentials>, ) -> http::request::Builder { RequestBuilder { builder: make_request(self.repository_base.clone(), method, path_suffix), } .read_credentials(credentials) } } impl Default for Config { fn default() -> Self { Self::new() } } struct RequestBuilder { builder: http::request::Builder, } impl RequestBuilder { pub fn read_credentials(self, credentials: Option<&Credentials>) -> http::request::Builder { let mut builder = self.builder; match credentials { Some(Credentials::OAuthAccessToken(token)) => { builder = builder.header("authorization", format!("Bearer {token}")); } Some(Credentials::ApiKey(key)) => { builder = builder.header("authorization", key.to_string()); } None => (), } builder } pub fn write_credentials(self, credentials: &WriteActionCredentials) -> http::request::Builder { let mut builder = self.builder; match credentials { WriteActionCredentials::OAuthAccessToken { access_token, one_time_password, } => { builder = builder.header("authorization", format!("Bearer {access_token}")); builder = builder.header("x-hex-otp", one_time_password.to_string()); } WriteActionCredentials::ApiKey(key) => { builder = builder.header("authorization", key.to_string()); } } builder } } #[derive(Debug, Clone)] pub enum Credentials { // Short lived credential from OAuth OAuthAccessToken(EcoString), // Long lived API key ApiKey(EcoString), } #[derive(Debug, Clone)] pub enum WriteActionCredentials { // Short lived credential from OAuth. // A one-time-password is required for write actions when using OAuth OAuthAccessToken { access_token: EcoString, one_time_password: EcoString, }, // Long lived API key ApiKey(EcoString), } fn make_request( base: http::Uri, method: http::Method, path_suffix: &str, ) -> http::request::Builder { let mut parts = base.into_parts(); parts.path_and_query = Some( match parts.path_and_query { Some(path_and_query) => { let mut path = path_and_query.path().to_owned(); if !path.ends_with('/') { path.push('/'); } path += path_suffix; // Drop query parameters path.try_into() } None => path_suffix.try_into(), } .expect("api_uri path"), ); let uri = http::Uri::from_parts(parts).expect("api_uri building"); http::Request::builder() .method(method) .uri(uri) .header("user-agent", USER_AGENT) } /// Create a request that deletes an Hex API key. /// /// API Docs: /// /// https://github.com/hexpm/hex/blob/main/lib/mix/tasks/hex.user.ex#L291 /// /// https://github.com/hexpm/hex/blob/main/lib/hex/api/key.ex#L15 pub fn api_remove_api_key_request( name_of_key_to_delete: &str, credentials: &WriteActionCredentials, config: &Config, ) -> http::Request> { let path = format!("keys/{name_of_key_to_delete}"); config .api_request(Method::DELETE, &path) .write_credentials(credentials) .body(vec![]) .expect("remove_api_key_request request") } /// Parses a request that deleted a Hex API key. pub fn api_remove_api_key_response(response: http::Response>) -> Result<(), ApiError> { let (parts, body) = response.into_parts(); match parts.status { // Key was removed StatusCode::NO_CONTENT | StatusCode::OK => Ok(()), // Key has already been removed StatusCode::NOT_FOUND => Ok(()), StatusCode::TOO_MANY_REQUESTS => Err(ApiError::RateLimited), StatusCode::UNAUTHORIZED => Err(unauthorised_response(&parts.headers)), status => Err(ApiError::unexpected_response(status, body)), } } fn unauthorised_response(headers: &http::HeaderMap) -> ApiError { let authenticate_header = headers .get("www-authenticate") .and_then(|header| header.to_str().ok()) .unwrap_or_default(); if authenticate_header.starts_with("Bearer realm=\"hex\", error=\"invalid_totp\"") { return ApiError::IncorrectOneTimePassword; } ApiError::InvalidCredentials } /// Retire an existing package release from Hex. /// /// API Docs: /// /// https://github.com/hexpm/hex/blob/main/lib/mix/tasks/hex.retire.ex#L75 /// /// https://github.com/hexpm/hex/blob/main/lib/hex/api/release.ex#L28 pub fn api_retire_release_request( package: &str, version: &str, reason: RetirementReason, message: Option<&str>, credentials: &WriteActionCredentials, config: &Config, ) -> http::Request> { let body = json!({ "reason": reason.to_str(), "message": message, }); let path = format!("packages/{package}/releases/{version}/retire"); config .api_request(Method::POST, &path) .write_credentials(credentials) .body(body.to_string().into_bytes()) .expect("retire_release_request request") } /// Parses a request that retired a release. pub fn api_retire_release_response(response: http::Response>) -> Result<(), ApiError> { let (parts, body) = response.into_parts(); match parts.status { StatusCode::NO_CONTENT | StatusCode::OK => Ok(()), StatusCode::TOO_MANY_REQUESTS => Err(ApiError::RateLimited), StatusCode::UNAUTHORIZED => Err(unauthorised_response(&parts.headers)), status => Err(ApiError::unexpected_response(status, body)), } } /// Un-retire an existing retired package release from Hex. /// /// API Docs: /// /// https://github.com/hexpm/hex/blob/main/lib/mix/tasks/hex.retire.ex#L89 /// /// https://github.com/hexpm/hex/blob/main/lib/hex/api/release.ex#L35 pub fn api_unretire_release_request( package: &str, version: &str, credentials: &WriteActionCredentials, config: &Config, ) -> http::Request> { let path = format!("packages/{package}/releases/{version}/retire"); config .api_request(Method::DELETE, &path) .write_credentials(credentials) .body(vec![]) .expect("unretire_release_request request") } /// Parses a request that un-retired a package version. pub fn api_unretire_release_response(response: http::Response>) -> Result<(), ApiError> { let (parts, body) = response.into_parts(); match parts.status { StatusCode::NO_CONTENT | StatusCode::OK => Ok(()), StatusCode::TOO_MANY_REQUESTS => Err(ApiError::RateLimited), StatusCode::UNAUTHORIZED => Err(unauthorised_response(&parts.headers)), status => Err(ApiError::unexpected_response(status, body)), } } /// Create a request that get the names and versions of all of the packages on /// the package registry. /// /// https://github.com/hexpm/specifications/blob/main/registry-v2.md /// /// TODO: Where are the API docs for this? pub fn repository_v2_get_versions_request( credentials: Option<&Credentials>, config: &Config, ) -> http::Request> { config .repository_request(Method::GET, "versions", credentials) .header("accept", "application/json") .body(vec![]) .expect("get_repository_versions_request request") } /// Parse a request that gets the names and versions of all of the packages on /// the package registry. pub fn repository_v2_get_versions_response( response: http::Response>, public_key: &[u8], ) -> Result>, ApiError> { let (parts, body) = response.into_parts(); match parts.status { StatusCode::OK => (), status => return Err(ApiError::unexpected_response(status, body)), }; let mut decoder = GzDecoder::new(body.reader()); let mut body = Vec::new(); decoder.read_to_end(&mut body)?; repository_v2_get_versions_body(&body, public_key) } /// Parse a signed binary message containing all of the packages on the package registry. pub fn repository_v2_get_versions_body( protobuf_bytes: &Vec, public_key: &[u8], ) -> Result>, ApiError> { let signed = Signed::decode(protobuf_bytes.as_slice())?; let payload = verify_payload(signed, public_key).map_err(|_| ApiError::IncorrectPayloadSignature)?; let versions = Versions::decode(payload.as_slice())? .packages .into_iter() .map(|n| { let parse_version = |v: &str| { let err = |_| ApiError::InvalidVersionFormat(v.to_string()); Version::parse(v).map_err(err) }; let versions = n .versions .iter() .map(|v| parse_version(v.as_str())) .collect::, ApiError>>()?; Ok((n.name, versions)) }) .collect::, ApiError>>()?; Ok(versions) } /// Create a request to get the information for a package in the repository. /// /// https://github.com/hexpm/specifications/blob/main/registry-v2.md /// pub fn repository_v2_get_package_request( name: &str, credentials: Option<&Credentials>, config: &Config, ) -> http::Request> { config .repository_request(Method::GET, &format!("packages/{name}"), credentials) .header("accept", "application/json") .body(vec![]) .expect("get_package_request request") } /// Parse a response to get the information for a package in the repository. /// pub fn repository_v2_get_package_response( response: http::Response>, public_key: &[u8], ) -> Result { let (parts, body) = response.into_parts(); match parts.status { StatusCode::OK => (), StatusCode::FORBIDDEN => return Err(ApiError::NotFound), StatusCode::NOT_FOUND => return Err(ApiError::NotFound), status => { return Err(ApiError::unexpected_response(status, body)); } }; let mut decoder = GzDecoder::new(body.reader()); let mut body = Vec::new(); decoder.read_to_end(&mut body)?; repository_v2_package_parse_body(&body, public_key) } /// Parse a signed binary message containing the information for a package in the repository. pub fn repository_v2_package_parse_body( protobuf_bytes: &Vec, public_key: &[u8], ) -> Result { let signed = Signed::decode(protobuf_bytes.as_slice())?; let payload = verify_payload(signed, public_key).map_err(|_| ApiError::IncorrectPayloadSignature)?; let package = proto::package::Package::decode(payload.as_slice())?; let releases = package .releases .clone() .into_iter() .map(proto_to_release) .collect::, _>>()?; let package = Package { name: package.name, repository: package.repository, releases, }; Ok(package) } /// Create a request to download a version of a package as a tarball /// TODO: Where are the API docs for this? pub fn repository_get_package_tarball_request( name: &str, version: &str, credentials: Option<&Credentials>, config: &Config, ) -> http::Request> { config .repository_request( Method::GET, &format!("tarballs/{name}-{version}.tar"), credentials, ) .header("accept", "application/x-tar") .body(vec![]) .expect("get_package_tarball_request request") } /// Parse a response to download a version of a package as a tarball /// pub fn repository_get_package_tarball_response( response: http::Response>, checksum: &[u8], ) -> Result, ApiError> { let (parts, body) = response.into_parts(); match parts.status { StatusCode::OK => (), StatusCode::FORBIDDEN => return Err(ApiError::NotFound), StatusCode::NOT_FOUND => return Err(ApiError::NotFound), status => { return Err(ApiError::unexpected_response(status, body)); } }; let body = read_and_check_body(body.reader(), checksum)?; Ok(body) } /// API Docs: /// /// https://github.com/hexpm/hex/blob/main/lib/mix/tasks/hex.publish.ex#L384 /// /// https://github.com/hexpm/hex/blob/main/lib/hex/api/release_docs.ex#L19 pub fn api_remove_docs_request( package_name: &str, version: &str, credentials: &WriteActionCredentials, config: &Config, ) -> Result>, ApiError> { validate_package_and_version(package_name, version)?; let path = format!("packages/{package_name}/releases/{version}/docs"); Ok(config .api_request(Method::DELETE, &path) .write_credentials(credentials) .body(vec![]) .expect("remove_docs_request request")) } pub fn api_remove_docs_response(response: http::Response>) -> Result<(), ApiError> { let (parts, body) = response.into_parts(); match parts.status { StatusCode::NO_CONTENT => Ok(()), StatusCode::NOT_FOUND => Err(ApiError::NotFound), StatusCode::TOO_MANY_REQUESTS => Err(ApiError::RateLimited), StatusCode::UNAUTHORIZED => Err(unauthorised_response(&parts.headers)), StatusCode::FORBIDDEN => Err(ApiError::Forbidden), status => Err(ApiError::unexpected_response(status, body)), } } /// API Docs: /// /// https://github.com/hexpm/hex/blob/main/lib/mix/tasks/hex.publish.ex#L429 /// /// https://github.com/hexpm/hex/blob/main/lib/hex/api/release_docs.ex#L11 pub fn api_publish_docs_request( package_name: &str, version: &str, gzipped_tarball: Vec, credentials: &WriteActionCredentials, config: &Config, ) -> Result>, ApiError> { validate_package_and_version(package_name, version)?; let path = format!("packages/{package_name}/releases/{version}/docs"); let mut builder = config .api_request(Method::POST, &path) .write_credentials(credentials); let headers = builder.headers_mut().expect("headers"); headers.insert("content-encoding", "x-gzip".parse().unwrap()); headers.insert("content-type", "application/x-tar".parse().unwrap()); Ok(builder .body(gzipped_tarball) .expect("publish_docs_request request")) } pub fn api_publish_docs_response(response: http::Response>) -> Result<(), ApiError> { let (parts, body) = response.into_parts(); match parts.status { StatusCode::CREATED => Ok(()), StatusCode::NOT_FOUND => Err(ApiError::NotFound), StatusCode::TOO_MANY_REQUESTS => Err(ApiError::RateLimited), StatusCode::UNAUTHORIZED => Err(unauthorised_response(&parts.headers)), StatusCode::FORBIDDEN => Err(ApiError::Forbidden), status => Err(ApiError::unexpected_response(status, body)), } } /// API Docs: /// /// https://github.com/hexpm/hex/blob/main/lib/mix/tasks/hex.publish.ex#L512 /// /// https://github.com/hexpm/hex/blob/main/lib/hex/api/release.ex#L13 pub fn api_publish_package_request( release_tarball: Vec, credentials: &WriteActionCredentials, config: &Config, replace: bool, ) -> http::Request> { let path = format!("publish?replace={replace}"); let mut builder = config .api_request(Method::POST, &path) .write_credentials(credentials); builder .headers_mut() .expect("headers") .insert("content-type", "application/x-tar".parse().unwrap()); builder .body(release_tarball) .expect("publish_package_request request") } pub fn api_publish_package_response(response: http::Response>) -> Result<(), ApiError> { let (parts, body) = response.into_parts(); match parts.status { StatusCode::OK | StatusCode::CREATED => Ok(()), StatusCode::NOT_FOUND => Err(ApiError::NotFound), StatusCode::TOO_MANY_REQUESTS => Err(ApiError::RateLimited), StatusCode::UNAUTHORIZED => Err(unauthorised_response(&parts.headers)), StatusCode::FORBIDDEN => Err(ApiError::Forbidden), StatusCode::UNPROCESSABLE_ENTITY => { let body = &String::from_utf8_lossy(&body).to_string(); if body.contains("--replace") { return Err(ApiError::NotReplacing); } Err(ApiError::LateModification) } status => Err(ApiError::unexpected_response(status, body)), } } /// API Docs: /// /// https://github.com/hexpm/hex/blob/main/lib/mix/tasks/hex.publish.ex#L371 /// /// https://github.com/hexpm/hex/blob/main/lib/hex/api/release.ex#L21 pub fn api_revert_release_request( package_name: &str, version: &str, credentials: &WriteActionCredentials, config: &Config, ) -> Result>, ApiError> { validate_package_and_version(package_name, version)?; let path = format!("packages/{package_name}/releases/{version}"); Ok(config .api_request(Method::DELETE, &path) .write_credentials(credentials) .body(vec![]) .expect("publish_package_request request")) } pub fn api_revert_release_response(response: http::Response>) -> Result<(), ApiError> { let (parts, body) = response.into_parts(); match parts.status { StatusCode::NO_CONTENT => Ok(()), StatusCode::NOT_FOUND => Err(ApiError::NotFound), StatusCode::TOO_MANY_REQUESTS => Err(ApiError::RateLimited), StatusCode::UNAUTHORIZED => Err(unauthorised_response(&parts.headers)), StatusCode::FORBIDDEN => Err(ApiError::Forbidden), status => Err(ApiError::unexpected_response(status, body)), } } /// See: https://github.com/hexpm/hex/blob/main/lib/mix/tasks/hex.owner.ex#L47 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum OwnerLevel { /// Has every package permission EXCEPT the ability to change who owns the package Maintainer, /// Has every package permission including the ability to change who owns the package Full, } impl Display for OwnerLevel { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { OwnerLevel::Maintainer => write!(f, "maintainer"), OwnerLevel::Full => write!(f, "full"), } } } /// API Docs: /// /// https://github.com/hexpm/hex/blob/main/lib/mix/tasks/hex.owner.ex#L107 /// /// https://github.com/hexpm/hex/blob/main/lib/hex/api/package.ex#L19 pub fn api_add_owner_request( package_name: &str, owner: &str, level: OwnerLevel, credentials: &WriteActionCredentials, config: &Config, ) -> http::Request> { let body = json!({ "level": level.to_string(), "transfer": false, }); let path = format!("packages/{package_name}/owners/{owner}"); config .api_request(Method::PUT, &path) .write_credentials(credentials) .body(body.to_string().into_bytes()) .expect("add_owner_request request") } pub fn api_add_owner_response(response: http::Response>) -> Result<(), ApiError> { let (parts, body) = response.into_parts(); match parts.status { StatusCode::NO_CONTENT => Ok(()), StatusCode::NOT_FOUND => Err(ApiError::NotFound), StatusCode::TOO_MANY_REQUESTS => Err(ApiError::RateLimited), StatusCode::UNAUTHORIZED => Err(unauthorised_response(&parts.headers)), StatusCode::FORBIDDEN => Err(ApiError::Forbidden), status => Err(ApiError::unexpected_response(status, body)), } } /// API Docs: /// /// https://github.com/hexpm/hex/blob/main/lib/mix/tasks/hex.owner.ex#L125 /// /// https://github.com/hexpm/hex/blob/main/lib/hex/api/package.ex#L19 pub fn api_transfer_owner_request( package_name: &str, owner: &str, credentials: &WriteActionCredentials, config: &Config, ) -> http::Request> { let body = json!({ "level": OwnerLevel::Full.to_string(), "transfer": true, }); let path = format!("packages/{package_name}/owners/{owner}"); config .api_request(Method::PUT, &path) .write_credentials(credentials) .body(body.to_string().into_bytes()) .expect("transfer_owner_request request") } pub fn api_transfer_owner_response(response: http::Response>) -> Result<(), ApiError> { let (parts, body) = response.into_parts(); match parts.status { StatusCode::NO_CONTENT => Ok(()), StatusCode::NOT_FOUND => Err(ApiError::NotFound), StatusCode::TOO_MANY_REQUESTS => Err(ApiError::RateLimited), StatusCode::UNAUTHORIZED => Err(unauthorised_response(&parts.headers)), StatusCode::FORBIDDEN => Err(ApiError::Forbidden), status => Err(ApiError::unexpected_response(status, body)), } } /// API Docs: /// /// https://github.com/hexpm/hex/blob/main/lib/mix/tasks/hex.owner.ex#L139 /// /// https://github.com/hexpm/hex/blob/main/lib/hex/api/package.ex#L28 pub fn api_remove_owner_request( package_name: &str, owner: &str, credentials: &WriteActionCredentials, config: &Config, ) -> http::Request> { let path = format!("packages/{package_name}/owners/{owner}"); config .api_request(Method::DELETE, &path) .write_credentials(credentials) .body(vec![]) .expect("remove_owner_request request") } pub fn api_remove_owner_response(response: http::Response>) -> Result<(), ApiError> { let (parts, body) = response.into_parts(); match parts.status { StatusCode::NO_CONTENT => Ok(()), StatusCode::NOT_FOUND => Err(ApiError::NotFound), StatusCode::TOO_MANY_REQUESTS => Err(ApiError::RateLimited), StatusCode::UNAUTHORIZED => Err(unauthorised_response(&parts.headers)), StatusCode::FORBIDDEN => Err(ApiError::Forbidden), status => Err(ApiError::unexpected_response(status, body)), } } #[derive(Error, Debug)] pub enum ApiError { #[error(transparent)] Json(#[from] serde_json::Error), #[error(transparent)] Io(#[from] std::io::Error), #[error("The rate limit for the Hex API has been exceeded for this IP")] RateLimited, #[error("Invalid authentication credentials")] InvalidCredentials, #[error("An unexpected response was sent by Hex: {0}: {1}")] UnexpectedResponse(StatusCode, String), #[error("The given package name {0} is not valid")] InvalidPackageNameFormat(String), #[error("The payload signature does not match the downloaded payload")] IncorrectPayloadSignature, #[error(transparent)] InvalidProtobuf(#[from] prost::DecodeError), #[error("Unexpected version format {0}")] InvalidVersionFormat(String), #[error("Resource was not found")] NotFound, #[error("The version requirement format {0} is not valid")] InvalidVersionRequirementFormat(String), #[error("The downloaded data did not have the expected checksum")] IncorrectChecksum, #[error("This account is not authorized for this action")] Forbidden, #[error("Must explicitly express your intention to replace the release")] NotReplacing, #[error("Can only modify a release up to one hour after publication")] LateModification, #[error("The oauth request wasn't approved in time")] OAuthTimeout, #[error("The oauth request was rejected")] OAuthAccessDenied, #[error("The oauth token expired before the request was approved")] ExpiredToken, #[error("The oauth refresh token was expired, revoked, or already used")] OAuthRefreshTokenRejected, #[error("The supplied one-time-password was not correct")] IncorrectOneTimePassword, } impl ApiError { fn unexpected_response(status: StatusCode, body: Vec) -> Self { ApiError::UnexpectedResponse(status, String::from_utf8_lossy(&body).to_string()) } /// Returns `true` if the api error is [`NotFound`]. /// /// [`NotFound`]: ApiError::NotFound pub fn is_not_found(&self) -> bool { matches!(self, Self::NotFound) } pub fn is_invalid_protobuf(&self) -> bool { matches!(self, Self::InvalidProtobuf(_)) } } /// Read a body and ensure it has the given sha256 digest. fn read_and_check_body(reader: impl std::io::Read, checksum: &[u8]) -> Result, ApiError> { use std::io::Read; let mut reader = BufReader::new(reader); let mut context = Context::new(&SHA256); let mut buffer = [0; 1024]; let mut body = Vec::new(); loop { let count = reader.read(&mut buffer)?; if count == 0 { break; } let bytes = &buffer[..count]; context.update(bytes); body.extend_from_slice(bytes); } let digest = context.finish(); if digest.as_ref() == checksum { Ok(body) } else { Err(ApiError::IncorrectChecksum) } } fn proto_to_retirement_status( status: Option, ) -> Option { status.map(|stat| RetirementStatus { message: stat.message().into(), reason: proto_to_retirement_reason(stat.reason()), }) } fn proto_to_retirement_reason(reason: proto::package::RetirementReason) -> RetirementReason { use proto::package::RetirementReason::*; match reason { RetiredOther => RetirementReason::Other, RetiredInvalid => RetirementReason::Invalid, RetiredSecurity => RetirementReason::Security, RetiredDeprecated => RetirementReason::Deprecated, RetiredRenamed => RetirementReason::Renamed, } } fn proto_to_dep(dep: proto::package::Dependency) -> Result<(String, Dependency), ApiError> { let app = dep.app; let repository = dep.repository; let requirement = Range::new(dep.requirement.clone()) .map_err(|_| ApiError::InvalidVersionFormat(dep.requirement))?; Ok(( dep.package, Dependency { requirement, optional: dep.optional.is_some(), app, repository, }, )) } fn proto_to_release(release: proto::package::Release) -> Result, ApiError> { let dependencies = release .dependencies .clone() .into_iter() .map(proto_to_dep) .collect::, _>>()?; let version = Version::try_from(release.version.as_str()) .expect("Failed to parse version format from Hex"); Ok(Release { version, outer_checksum: release.outer_checksum.unwrap_or_default(), retirement_status: proto_to_retirement_status(release.retired), requirements: dependencies, meta: (), }) } #[derive(Debug, PartialEq, Eq, Clone)] pub struct Package { pub name: String, pub repository: String, pub releases: Vec>, } #[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize)] pub struct Release { /// Release version pub version: Version, /// All dependencies of the release pub requirements: HashMap, /// If set the release is retired, a retired release should only be /// resolved if it has already been locked in a project pub retirement_status: Option, /// sha256 checksum of outer package tarball /// required when encoding but optional when decoding #[serde(alias = "checksum", deserialize_with = "deserialize_checksum")] pub outer_checksum: Vec, /// This is not present in all API endpoints so may be absent sometimes. pub meta: Meta, } fn deserialize_checksum<'de, D>(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de>, { let s: &str = serde::de::Deserialize::deserialize(deserializer)?; base16::decode(s).map_err(serde::de::Error::custom) } impl Release { pub fn is_retired(&self) -> bool { self.retirement_status.is_some() } } #[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize)] pub struct ReleaseMeta { pub app: String, pub build_tools: Vec, } #[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize)] pub struct RetirementStatus { pub reason: RetirementReason, pub message: String, } #[derive(Debug, PartialEq, Eq, Clone)] pub enum RetirementReason { Other, Invalid, Security, Deprecated, Renamed, } impl<'de> serde::Deserialize<'de> for RetirementReason { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let s: &str = serde::de::Deserialize::deserialize(deserializer)?; match s { "other" => Ok(RetirementReason::Other), "invalid" => Ok(RetirementReason::Invalid), "security" => Ok(RetirementReason::Security), "deprecated" => Ok(RetirementReason::Deprecated), "renamed" => Ok(RetirementReason::Renamed), _ => Err(serde::de::Error::custom("unknown retirement reason type")), } } } impl RetirementReason { pub fn to_str(&self) -> &'static str { match self { RetirementReason::Other => "other", RetirementReason::Invalid => "invalid", RetirementReason::Security => "security", RetirementReason::Deprecated => "deprecated", RetirementReason::Renamed => "renamed", } } } #[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize)] pub struct Dependency { /// Version requirement of dependency pub requirement: Range, /// If true the package is optional and does not need to be resolved /// unless another package has specified it as a non-optional dependency. pub optional: bool, /// If set is the OTP application name of the dependency, if not set the /// application name is the same as the package name pub app: Option, /// If set, the repository where the dependency is located pub repository: Option, } static USER_AGENT: &str = concat!("Gleam v", env!("CARGO_PKG_VERSION")); fn validate_package_and_version(package: &str, version: &str) -> Result<(), ApiError> { static PACKAGE_PATTERN: OnceLock = OnceLock::new(); static VERSION_PATTERN: OnceLock = OnceLock::new(); let package_pattern = PACKAGE_PATTERN.get_or_init(|| Regex::new(r"^[a-z]\w*$").unwrap()); let version_pattern = VERSION_PATTERN.get_or_init(|| Regex::new(r"^[a-zA-Z-0-9\._-]+$").unwrap()); if !package_pattern.is_match(package) { return Err(ApiError::InvalidPackageNameFormat(package.to_string())); } if !version_pattern.is_match(version) { return Err(ApiError::InvalidVersionFormat(version.to_string())); } Ok(()) } // To quote the docs: // // > All resources will be signed by the repository's private key. // > A signed resource is wrapped in a Signed message. The data under // > the payload field is signed by the signature field. // > // > The signature is an (unencoded) RSA signature of the (unencoded) // > SHA-512 digest of the payload. // // https://github.com/hexpm/specifications/blob/master/registry-v2.md#signing // fn verify_payload(mut signed: Signed, pem_public_key: &[u8]) -> Result, ApiError> { let (_, pem) = x509_parser::pem::parse_x509_pem(pem_public_key) .map_err(|_| ApiError::IncorrectPayloadSignature)?; let (_, spki) = x509_parser::prelude::SubjectPublicKeyInfo::from_der(&pem.contents) .map_err(|_| ApiError::IncorrectPayloadSignature)?; let payload = std::mem::take(&mut signed.payload); let verification = ring::signature::UnparsedPublicKey::new( &ring::signature::RSA_PKCS1_2048_8192_SHA512, &spki.subject_public_key, ) .verify(payload.as_slice(), signed.signature()); if verification.is_ok() { Ok(payload) } else { Err(ApiError::IncorrectPayloadSignature) } } /// Create a request to get the information for a package release. /// pub fn api_get_package_release_request( name: &str, version: &str, credentials: Option<&Credentials>, config: &Config, ) -> http::Request> { let path = format!("packages/{name}/releases/{version}"); config .api_request(Method::GET, &path) .read_credentials(credentials) .header("accept", "application/json") .body(vec![]) .expect("get_package_release request") } /// Parse a response to get the information for a package release. /// pub fn api_get_package_release_response( response: http::Response>, ) -> Result, ApiError> { let (parts, body) = response.into_parts(); match parts.status { StatusCode::OK => Ok(serde_json::from_slice(&body)?), StatusCode::NOT_FOUND => Err(ApiError::NotFound), StatusCode::TOO_MANY_REQUESTS => Err(ApiError::RateLimited), StatusCode::UNAUTHORIZED => Err(unauthorised_response(&parts.headers)), StatusCode::FORBIDDEN => Err(ApiError::Forbidden), status => Err(ApiError::unexpected_response(status, body)), } } /// Create a device authorisation, kicking off the Hex oauth flow. pub fn oauth_device_authorisation_request( hex_oauth_client_id: &str, client_name: &str, config: &Config, ) -> http::Request> { let body = json!({ "client_id": hex_oauth_client_id, "scope": "api:write", "name": client_name, }) .to_string() .into_bytes(); config .api_request(Method::POST, "oauth/device_authorization") .builder .body(body) .expect("oauth_device_authorisation_request") } /// Parse the response of creating a device authorisation, kicking off the Hex oauth flow. pub fn oauth_device_authorisation_response( hex_oauth_client_id: String, response: http::Response>, ) -> Result { let (parts, body) = response.into_parts(); match parts.status { StatusCode::OK => (), StatusCode::TOO_MANY_REQUESTS => return Err(ApiError::RateLimited), status => return Err(ApiError::unexpected_response(status, body)), }; let data: DeviceAuthorisationResponseBody = serde_json::from_slice(&body)?; let poll_interval = Duration::from_secs(data.poll_interval_seconds); let verification_uri = data .verification_uri_complete .unwrap_or(data.verification_uri); Ok(OAuthDeviceAuthorisation { user_code: data.user_code, device_code: data.device_code, start_time: Instant::now(), client_id: hex_oauth_client_id, poll_interval, verification_uri, }) } #[derive(Debug)] pub struct OAuthDeviceAuthorisation { /// Show this code to the user for them to match against the one in the Hex UI. pub user_code: String, /// Send the user to this URI after showing them the code. pub verification_uri: String, client_id: String, device_code: String, poll_interval: Duration, start_time: Instant, } impl OAuthDeviceAuthorisation { pub fn poll_token_request(&self, config: &Config) -> http::Request> { let body = json!({ "grant_type": "urn:ietf:params:oauth:grant-type:device_code", "client_id": &self.client_id, "device_code": &self.device_code, }) .to_string() .into_bytes(); config .api_request(Method::POST, "oauth/token") .builder .body(body) .expect("poll_token_request") } pub fn poll_token_response( &mut self, response: http::Response>, ) -> Result { if self.start_time.elapsed() > Duration::from_mins(10) { return Err(ApiError::OAuthTimeout); } let (parts, body) = response.into_parts(); match parts.status { StatusCode::OK | StatusCode::BAD_REQUEST | StatusCode::FORBIDDEN => (), status => return Err(ApiError::unexpected_response(status, body)), }; let data: PollResponseBody = serde_json::from_slice(&body)?; match data.into_result() { Ok(tokens) => Ok(PollStep::Done(tokens)), Err(PollResponseBodyError::AuthorizationPending) => { Ok(PollStep::SleepThenPollAgain(self.poll_interval)) } Err(PollResponseBodyError::SlowDown) => { let max_interval = Duration::from_secs(30); self.poll_interval = self.poll_interval.saturating_mul(2).min(max_interval); Ok(PollStep::SleepThenPollAgain(self.poll_interval)) } Err(PollResponseBodyError::AccessDenied) => Err(ApiError::OAuthAccessDenied), Err(PollResponseBodyError::ExpiredToken) => Err(ApiError::ExpiredToken), } } } #[derive(Debug)] pub enum PollStep { Done(OAuthTokens), SleepThenPollAgain(Duration), } #[derive(Debug, Serialize, Deserialize)] pub struct OAuthTokens { pub access_token: EcoString, pub refresh_token: EcoString, } impl OAuthTokens { pub fn as_credentials(&self) -> Credentials { Credentials::OAuthAccessToken(self.access_token.clone()) } } #[derive(Debug, Deserialize)] struct DeviceAuthorisationResponseBody { #[serde(default = "default_poll_interval_seconds", rename = "interval")] poll_interval_seconds: u64, device_code: String, user_code: String, verification_uri: String, verification_uri_complete: Option, } #[derive(Debug, Deserialize)] #[serde(untagged)] enum PollResponseBody { Success { access_token: EcoString, refresh_token: EcoString, }, Fail { error: PollResponseBodyError, }, } impl PollResponseBody { pub fn into_result(self) -> Result { match self { PollResponseBody::Success { access_token, refresh_token, } => Ok(OAuthTokens { access_token, refresh_token, }), PollResponseBody::Fail { error } => Err(error), } } } #[derive(Debug, Deserialize)] enum PollResponseBodyError { #[serde(rename = "authorization_pending")] AuthorizationPending, #[serde(rename = "slow_down")] SlowDown, #[serde(rename = "access_denied")] AccessDenied, #[serde(rename = "expired_token")] ExpiredToken, } fn default_poll_interval_seconds() -> u64 { 5 } /// Use a refresh token to get an access token that can be used to /// authenticate with the API. pub fn oauth_refresh_token_request( hex_oauth_client_id: &str, refresh_token: &str, config: &Config, ) -> http::Request> { let body = json!({ "grant_type": "refresh_token", "client_id": hex_oauth_client_id, "refresh_token": refresh_token, }) .to_string() .into_bytes(); config .api_request(Method::POST, "oauth/token") .builder .body(body) .expect("oauth_refresh_token_request") } pub fn oauth_refresh_token_response( response: http::Response>, ) -> Result { let (parts, body) = response.into_parts(); match parts.status { StatusCode::OK => (), StatusCode::TOO_MANY_REQUESTS => return Err(ApiError::RateLimited), StatusCode::BAD_REQUEST => return Err(ApiError::OAuthRefreshTokenRejected), status => return Err(ApiError::unexpected_response(status, body)), }; Ok(serde_json::from_slice(&body)?) } /// Get information about the currently authenticated user. pub fn get_me_request(credentials: &Credentials, config: &Config) -> http::Request> { config .api_request(Method::GET, "users/me") .read_credentials(Some(credentials)) .body(vec![]) .expect("me_request") } /// Get information about the currently authenticated user. pub fn get_me_response(response: http::Response>) -> Result { let (parts, body) = response.into_parts(); match parts.status { StatusCode::OK => (), status => return Err(ApiError::unexpected_response(status, body)), }; Ok(serde_json::from_slice(&body)?) } #[derive(Debug, PartialEq, Eq, Clone, serde::Deserialize)] pub struct Me { pub username: String, } /// Create a request to revoke an OAuth token. /// pub fn revoke_oauth_token_by_hash_request( refresh_token_hash: &str, credentials: &WriteActionCredentials, config: &Config, ) -> http::Request> { let body = json!({ "token_hash": refresh_token_hash, }) .to_string() .into_bytes(); config .api_request(Method::POST, "oauth/revoke_by_hash") .write_credentials(credentials) .header("accept", "application/json") .body(body) .expect("api_get_package_release_request request") } /// Parse a response to revoke an OAuth token. /// pub fn revoke_oauth_token_by_hash_response( response: http::Response>, ) -> Result<(), ApiError> { let (parts, body) = response.into_parts(); match parts.status { StatusCode::OK => (), status => return Err(ApiError::unexpected_response(status, body)), }; Ok(()) } ================================================ FILE: hexpm/src/proto/package.rs ================================================ // This file is @generated by prost-build. #[derive(Clone, PartialEq, ::prost::Message)] pub struct Package { /// All releases of the package #[prost(message, repeated, tag = "1")] pub releases: ::prost::alloc::vec::Vec, /// Name of package #[prost(string, required, tag = "2")] pub name: ::prost::alloc::string::String, /// Name of repository #[prost(string, required, tag = "3")] pub repository: ::prost::alloc::string::String, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct Release { /// Release version #[prost(string, required, tag = "1")] pub version: ::prost::alloc::string::String, /// sha256 checksum of "inner" package tarball /// deprecated in favor of outer_checksum #[prost(bytes = "vec", required, tag = "2")] pub inner_checksum: ::prost::alloc::vec::Vec, /// All dependencies of the release #[prost(message, repeated, tag = "3")] pub dependencies: ::prost::alloc::vec::Vec, /// If set the release is retired, a retired release should only be /// resolved if it has already been locked in a project #[prost(message, optional, tag = "4")] pub retired: ::core::option::Option, /// sha256 checksum of outer package tarball /// required when encoding but optional when decoding #[prost(bytes = "vec", optional, tag = "5")] pub outer_checksum: ::core::option::Option<::prost::alloc::vec::Vec>, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct RetirementStatus { #[prost(enumeration = "RetirementReason", required, tag = "1")] pub reason: i32, #[prost(string, optional, tag = "2")] pub message: ::core::option::Option<::prost::alloc::string::String>, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct Dependency { /// Package name of dependency #[prost(string, required, tag = "1")] pub package: ::prost::alloc::string::String, /// Version requirement of dependency #[prost(string, required, tag = "2")] pub requirement: ::prost::alloc::string::String, /// If set and true the package is optional (see dependency resolution) #[prost(bool, optional, tag = "3")] pub optional: ::core::option::Option, /// If set is the OTP application name of the dependency, if not set the /// application name is the same as the package name #[prost(string, optional, tag = "4")] pub app: ::core::option::Option<::prost::alloc::string::String>, /// If set, the repository where the dependency is located #[prost(string, optional, tag = "5")] pub repository: ::core::option::Option<::prost::alloc::string::String>, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum RetirementReason { RetiredOther = 0, RetiredInvalid = 1, RetiredSecurity = 2, RetiredDeprecated = 3, RetiredRenamed = 4, } impl RetirementReason { /// String value of the enum field names used in the ProtoBuf definition. /// /// The values are not transformed in any way and thus are considered stable /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { Self::RetiredOther => "RETIRED_OTHER", Self::RetiredInvalid => "RETIRED_INVALID", Self::RetiredSecurity => "RETIRED_SECURITY", Self::RetiredDeprecated => "RETIRED_DEPRECATED", Self::RetiredRenamed => "RETIRED_RENAMED", } } /// Creates an enum from field names used in the ProtoBuf definition. pub fn from_str_name(value: &str) -> ::core::option::Option { match value { "RETIRED_OTHER" => Some(Self::RetiredOther), "RETIRED_INVALID" => Some(Self::RetiredInvalid), "RETIRED_SECURITY" => Some(Self::RetiredSecurity), "RETIRED_DEPRECATED" => Some(Self::RetiredDeprecated), "RETIRED_RENAMED" => Some(Self::RetiredRenamed), _ => None, } } } ================================================ FILE: hexpm/src/proto/signed.rs ================================================ // This file is @generated by prost-build. #[derive(Clone, PartialEq, ::prost::Message)] pub struct Signed { /// Signed contents #[prost(bytes = "vec", required, tag = "1")] pub payload: ::prost::alloc::vec::Vec, /// The signature #[prost(bytes = "vec", optional, tag = "2")] pub signature: ::core::option::Option<::prost::alloc::vec::Vec>, } ================================================ FILE: hexpm/src/proto/versions.rs ================================================ // This file is @generated by prost-build. #[derive(Clone, PartialEq, ::prost::Message)] pub struct Versions { /// All packages in the repository #[prost(message, repeated, tag = "1")] pub packages: ::prost::alloc::vec::Vec, /// Name of repository #[prost(string, required, tag = "2")] pub repository: ::prost::alloc::string::String, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct VersionsPackage { /// Package name #[prost(string, required, tag = "1")] pub name: ::prost::alloc::string::String, /// All released versions of the package #[prost(string, repeated, tag = "2")] pub versions: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, /// Zero-based indexes of retired versions in the versions field, see package.proto /// /// If set, the name of the package repository (NEVER USED, DEPRECATED) /// string repository = 4; #[prost(int32, repeated, tag = "3")] pub retired: ::prost::alloc::vec::Vec, } ================================================ FILE: hexpm/src/proto.rs ================================================ #![allow(clippy::enum_variant_names)] pub mod package; pub mod signed; pub mod versions; ================================================ FILE: hexpm/src/tests.rs ================================================ use std::{convert::TryFrom, io::Cursor}; use super::*; use serde_json::json; fn make_response(status: u16, body: Vec) -> http::Response> { http::Response::builder().status(status).body(body).unwrap() } fn make_json_response(status: u16, body: serde_json::Value) -> http::Response> { make_response(status, body.to_string().into_bytes()) } #[test] fn remove_docs_request() { let key = WriteActionCredentials::OAuthAccessToken { access_token: EcoString::from("my-api-key-here"), one_time_password: EcoString::from("test-otp"), }; let package = "gleam_experimental_stdlib"; let version = "0.8.0"; let config = Config::new(); let request = crate::api_remove_docs_request(package, version, &key, &config).unwrap(); assert_eq!(request.method(), http::Method::DELETE); assert_eq!( request.uri().path(), "/api/packages/gleam_experimental_stdlib/releases/0.8.0/docs" ); assert_eq!( request.headers().get("authorization").unwrap(), "Bearer my-api-key-here" ); assert_eq!(request.headers().get("accept").unwrap(), "application/json"); assert_eq!(request.headers().get("x-hex-otp").unwrap(), "test-otp"); } #[test] fn remove_docs_response_success() { let response = make_response(204, vec![]); let result = crate::api_remove_docs_response(response).unwrap(); assert_eq!(result, ()); } #[test] fn revert_release_request() { let key = WriteActionCredentials::OAuthAccessToken { access_token: EcoString::from("my-api-key-here"), one_time_password: EcoString::from("test-otp"), }; let package = "gleam_experimental_stdlib"; let version = "0.8.0"; let config = Config::new(); let request = crate::api_revert_release_request(package, version, &key, &config).unwrap(); assert_eq!(request.method(), http::Method::DELETE); assert_eq!( request.uri().path(), "/api/packages/gleam_experimental_stdlib/releases/0.8.0" ); assert_eq!( request.headers().get("authorization").unwrap(), "Bearer my-api-key-here" ); assert_eq!(request.headers().get("accept").unwrap(), "application/json"); assert_eq!(request.headers().get("x-hex-otp").unwrap(), "test-otp"); } #[test] fn revert_release_response_success() { let response = make_response(204, vec![]); let result = crate::api_revert_release_response(response).unwrap(); assert_eq!(result, ()); } #[test] fn add_owner_request() { let key = WriteActionCredentials::OAuthAccessToken { access_token: EcoString::from("my-api-key-here"), one_time_password: EcoString::from("test-otp"), }; let package = "gleam_experimental_stdlib"; let owner = "lpil"; let level = OwnerLevel::Maintainer; let config = Config::new(); let request = crate::api_add_owner_request(package, owner, level, &key, &config); assert_eq!(request.method(), http::Method::PUT); assert_eq!( request.uri().path(), "/api/packages/gleam_experimental_stdlib/owners/lpil" ); assert_eq!( request.headers().get("authorization").unwrap(), "Bearer my-api-key-here" ); assert_eq!(request.headers().get("accept").unwrap(), "application/json"); assert_eq!(request.headers().get("x-hex-otp").unwrap(), "test-otp"); let body: serde_json::Value = serde_json::from_slice(request.body()).unwrap(); assert_eq!(body["level"], "maintainer"); assert_eq!(body["transfer"], false); } #[test] fn add_owner_response_success() { let response = make_response(204, vec![]); let result = crate::api_add_owner_response(response).unwrap(); assert_eq!(result, ()); } #[test] fn transfer_owner_request() { let key = WriteActionCredentials::OAuthAccessToken { access_token: EcoString::from("my-api-key-here"), one_time_password: EcoString::from("test-otp"), }; let package = "gleam_experimental_stdlib"; let owner = "lpil"; let config = Config::new(); let request = crate::api_transfer_owner_request(package, owner, &key, &config); assert_eq!(request.method(), http::Method::PUT); assert_eq!( request.uri().path(), "/api/packages/gleam_experimental_stdlib/owners/lpil" ); assert_eq!( request.headers().get("authorization").unwrap(), "Bearer my-api-key-here" ); assert_eq!(request.headers().get("accept").unwrap(), "application/json"); assert_eq!(request.headers().get("x-hex-otp").unwrap(), "test-otp"); let body: serde_json::Value = serde_json::from_slice(request.body()).unwrap(); assert_eq!(body["level"], "full"); assert_eq!(body["transfer"], true); } #[test] fn transfer_owner_response_success() { let response = make_response(204, vec![]); let result = crate::api_transfer_owner_response(response).unwrap(); assert_eq!(result, ()); } #[test] fn remove_owner_request() { let key = WriteActionCredentials::OAuthAccessToken { access_token: EcoString::from("my-api-key-here"), one_time_password: EcoString::from("test-otp"), }; let package = "gleam_experimental_stdlib"; let owner = "lpil"; let config = Config::new(); let request = crate::api_remove_owner_request(package, owner, &key, &config); assert_eq!(request.method(), http::Method::DELETE); assert_eq!( request.uri().path(), "/api/packages/gleam_experimental_stdlib/owners/lpil" ); assert_eq!( request.headers().get("authorization").unwrap(), "Bearer my-api-key-here" ); assert_eq!(request.headers().get("accept").unwrap(), "application/json"); assert_eq!(request.headers().get("x-hex-otp").unwrap(), "test-otp"); } #[test] fn remove_owner_response_success() { let response = make_response(204, vec![]); let result = crate::api_remove_owner_response(response).unwrap(); assert_eq!(result, ()); } #[test] fn remove_key_request() { let name = "some-key-name"; let key = WriteActionCredentials::OAuthAccessToken { access_token: EcoString::from("my-api-key-here"), one_time_password: EcoString::from("test-otp"), }; let config = Config::new(); let request = crate::api_remove_api_key_request(name, &key, &config); assert_eq!(request.method(), http::Method::DELETE); assert_eq!(request.uri().path(), "/api/keys/some-key-name"); assert_eq!( request.headers().get("authorization").unwrap(), "Bearer my-api-key-here" ); assert_eq!(request.headers().get("accept").unwrap(), "application/json"); assert_eq!(request.headers().get("x-hex-otp").unwrap(), "test-otp"); } #[test] fn remove_key_response_success_204() { let response = make_response(204, vec![]); let result = crate::api_remove_api_key_response(response).unwrap(); assert_eq!(result, ()); } #[test] fn remove_key_response_success_200() { let response = make_response(200, vec![]); let result = crate::api_remove_api_key_response(response).unwrap(); assert_eq!(result, ()); } #[test] fn remove_docs_response_not_found() { let response = make_response(404, vec![]); let result = crate::api_remove_docs_response(response).unwrap_err(); match result { ApiError::NotFound => (), result => panic!("expected ApiError::NotFound got {result:?}"), } } #[test] fn remove_docs_response_rate_limited() { let response = make_response(429, vec![]); let result = crate::api_remove_docs_response(response).unwrap_err(); match result { ApiError::RateLimited => (), result => panic!("expected ApiError::RateLimited got {result:?}"), } } #[test] fn remove_docs_response_invalid_key() { let resp_body = json!({ "message": "invalid API key", "status": 401, }); let response = make_json_response(401, resp_body); let result = crate::api_remove_docs_response(response).unwrap_err(); match result { ApiError::InvalidCredentials => (), result => panic!("expected ApiError::InvalidApiKey got {result:?}"), } } #[test] fn remove_docs_response_forbidden() { let resp_body = json!({ "message": "account is not authorized for this action", "status": 403, }); let response = make_json_response(403, resp_body); let result = crate::api_remove_docs_response(response).unwrap_err(); match result { ApiError::Forbidden => (), result => panic!("expected ApiError::Forbidden got {result:?}"), } } #[test] fn remove_docs_bad_package_name() { let key = WriteActionCredentials::OAuthAccessToken { access_token: EcoString::from("my-api-key-here"), one_time_password: EcoString::from("test-otp"), }; let package = "not valid"; let version = "1.2.0"; let config = Config::new(); match crate::api_remove_docs_request(package, version, &key, &config).unwrap_err() { ApiError::InvalidPackageNameFormat(p) if p == package => (), result => panic!("expected Err(ApiError::BadPackage), got {result:?}"), } } #[test] fn publish_docs_request() { let key = WriteActionCredentials::OAuthAccessToken { access_token: EcoString::from("my-api-key-here"), one_time_password: EcoString::from("test-otp"), }; let package = "gleam_experimental_stdlib_123"; let version = "0.8.0"; let tarball = std::include_bytes!("../test/example.tar.gz").to_vec(); let config = Config::new(); let request = crate::api_publish_docs_request(package, version, tarball.clone(), &key, &config).unwrap(); assert_eq!(request.method(), http::Method::POST); assert_eq!( request.uri().path(), "/api/packages/gleam_experimental_stdlib_123/releases/0.8.0/docs" ); assert_eq!( request.headers().get("authorization").unwrap(), &"Bearer my-api-key-here" ); assert_eq!(request.headers().get("accept").unwrap(), "application/json"); assert_eq!(request.headers().get("x-hex-otp").unwrap(), "test-otp"); assert_eq!( request.headers().get("content-type").unwrap(), "application/x-tar" ); assert_eq!(request.headers().get("content-encoding").unwrap(), "x-gzip"); assert_eq!(request.body(), &tarball); } #[test] fn publish_docs_response_success() { let response = make_response(201, vec![]); let result = crate::api_publish_docs_response(response); match result { Ok(()) => (), result => panic!("expected Ok(()), got {result:?}"), } } #[test] fn publish_docs_bad_package_name() { let key = WriteActionCredentials::OAuthAccessToken { access_token: EcoString::from("my-api-key-here"), one_time_password: EcoString::from("test-otp"), }; let package = "not valid"; let version = "1.2.0"; let tarball = std::include_bytes!("../test/example.tar.gz").to_vec(); let config = Config::new(); match crate::api_publish_docs_request(package, version, tarball, &key, &config).unwrap_err() { ApiError::InvalidPackageNameFormat(p) if p == package => (), result => panic!("expected Err(ApiError::BadPackage), got {result:?}"), } } #[test] fn publish_docs_bad_package_version() { let key = WriteActionCredentials::OAuthAccessToken { access_token: EcoString::from("my-api-key-here"), one_time_password: EcoString::from("test-otp"), }; let package = "name"; let version = "invalid version"; let tarball = std::include_bytes!("../test/example.tar.gz").to_vec(); let config = Config::new(); match crate::api_publish_docs_request(package, version, tarball, &key, &config).unwrap_err() { ApiError::InvalidVersionFormat(v) if v == version => (), result => panic!("expected ApiError::BadPackage, got {result:?}"), } } #[test] fn publish_docs_response_not_found() { let response = make_response(404, vec![]); let result = crate::api_publish_docs_response(response); match result { Err(ApiError::NotFound) => (), result => panic!("expected ApiError::NotFound, got {result:?}"), } } #[test] fn publish_docs_response_rate_limit() { let response = make_response(429, vec![]); let result = crate::api_publish_docs_response(response); match result { Err(ApiError::RateLimited) => (), result => panic!("expected ApiError::RateLimited, got {result:?}"), } } #[test] fn publish_docs_response_invalid_api_key() { let resp_body = json!({ "message": "invalid API key", "status": 401, }); let response = make_json_response(401, resp_body); let result = crate::api_publish_docs_response(response); match result { Err(ApiError::InvalidCredentials) => (), result => panic!("expected Err(ApiError::InvalidApiKey), got {result:?}"), } } #[test] fn publish_docs_response_incorrect_totp_key() { let resp_body = json!({ "status": 401, }); let mut response = make_json_response(401, resp_body); response.headers_mut().insert( "www-authenticate", "Bearer realm=\"hex\", error=\"invalid_totp\"" .try_into() .expect("Header value"), ); let result = crate::api_publish_docs_response(response); match result { Err(ApiError::IncorrectOneTimePassword) => (), result => panic!("expected Err(ApiError::IncorrectOneTimePassword), got {result:?}"), } } #[test] fn publish_docs_response_forbidden() { let resp_body = json!({ "message": "account is not authorized for this action", "status": 403, }); let response = make_json_response(403, resp_body); let result = crate::api_publish_docs_response(response); match result { Err(ApiError::Forbidden) => (), result => panic!("expected Err(ApiError::Forbidden), got {result:?}"), } } fn expected_package_exfmt() -> Package { Package { name: "exfmt".to_string(), repository: "hexpm".to_string(), releases: vec![ Release { version: Version::try_from("0.0.0").unwrap(), requirements: [].into(), retirement_status: None, outer_checksum: vec![ 82, 48, 191, 145, 92, 172, 0, 108, 238, 71, 57, 23, 101, 177, 161, 83, 91, 182, 18, 232, 249, 225, 29, 12, 246, 5, 215, 165, 32, 57, 179, 110, ], meta: (), }, Release { version: Version::try_from("0.1.0").unwrap(), requirements: [].into(), retirement_status: None, outer_checksum: vec![ 111, 246, 240, 176, 118, 229, 12, 15, 164, 61, 186, 3, 89, 106, 153, 225, 247, 52, 245, 8, 216, 139, 21, 232, 200, 16, 214, 59, 241, 188, 9, 6, ], meta: (), }, Release { version: Version::try_from("0.2.0").unwrap(), requirements: [].into(), retirement_status: None, outer_checksum: vec![ 149, 9, 192, 229, 84, 162, 110, 207, 161, 43, 31, 0, 126, 168, 14, 243, 31, 43, 195, 238, 100, 91, 78, 100, 213, 181, 101, 154, 106, 168, 170, 107, ], meta: (), }, Release { version: Version::try_from("0.2.1").unwrap(), requirements: [].into(), retirement_status: None, outer_checksum: vec![ 157, 229, 28, 212, 92, 249, 14, 240, 235, 104, 31, 12, 160, 199, 83, 195, 154, 105, 222, 37, 221, 80, 181, 183, 113, 240, 234, 107, 144, 85, 255, 65, ], meta: (), }, Release { version: Version::try_from("0.2.2").unwrap(), requirements: [].into(), retirement_status: None, outer_checksum: vec![ 112, 250, 133, 189, 183, 192, 54, 218, 115, 55, 216, 97, 204, 201, 191, 168, 250, 133, 138, 252, 202, 240, 74, 197, 228, 235, 81, 18, 241, 7, 155, 38, ], meta: (), }, Release { version: Version::try_from("0.2.3").unwrap(), requirements: [].into(), retirement_status: None, outer_checksum: vec![ 131, 20, 29, 160, 171, 124, 7, 125, 210, 88, 17, 189, 199, 49, 191, 190, 14, 162, 38, 247, 52, 176, 189, 17, 7, 188, 151, 152, 24, 64, 170, 29, ], meta: (), }, Release { version: Version::try_from("0.2.4").unwrap(), requirements: [].into(), retirement_status: None, outer_checksum: vec![ 109, 162, 185, 169, 26, 4, 62, 60, 167, 54, 182, 161, 140, 197, 75, 113, 183, 117, 247, 201, 218, 228, 14, 160, 115, 157, 196, 51, 108, 16, 96, 217, ], meta: (), }, Release { version: Version::try_from("0.3.0").unwrap(), requirements: [].into(), retirement_status: None, outer_checksum: vec![ 97, 50, 95, 212, 242, 59, 245, 177, 140, 78, 79, 180, 108, 174, 119, 176, 24, 80, 218, 152, 178, 227, 152, 242, 32, 126, 72, 67, 222, 0, 173, 170, ], meta: (), }, Release { version: Version::try_from("0.4.0").unwrap(), requirements: [].into(), retirement_status: None, outer_checksum: vec![ 246, 178, 237, 214, 217, 158, 143, 52, 130, 186, 64, 50, 94, 175, 161, 81, 68, 186, 4, 73, 53, 226, 235, 144, 209, 84, 231, 136, 165, 119, 122, 126, ], meta: (), }, Release { version: Version::try_from("0.5.0").unwrap(), requirements: [].into(), retirement_status: None, outer_checksum: vec![ 151, 86, 157, 218, 218, 131, 240, 119, 198, 216, 202, 240, 65, 17, 57, 228, 84, 252, 59, 207, 246, 49, 22, 21, 52, 47, 51, 139, 190, 9, 95, 109, ], meta: (), }, ], } } #[test] fn get_package_request() { let config = Config::new(); let request = crate::repository_v2_get_package_request("exfmt", None, &config); assert_eq!(request.method(), http::Method::GET); assert_eq!(request.uri().path(), "/packages/exfmt"); assert_eq!(request.headers().get("accept").unwrap(), "application/json"); } #[test] fn get_package_response_ok() { let response_body = std::include_bytes!("../test/package_exfmt"); let response = make_response(200, response_body.to_vec()); let package = crate::repository_v2_get_package_response( response, std::include_bytes!("../test/public_key"), ) .unwrap(); assert_eq!(expected_package_exfmt(), package); } #[test] fn get_package_response_not_found() { let response = make_response(404, vec![]); let error = crate::repository_v2_get_package_response( response, std::include_bytes!("../test/public_key"), ) .unwrap_err(); assert!(error.is_not_found()); } #[test] fn get_package_from_bytes_ok() { let response_body = std::include_bytes!("../test/package_exfmt"); let mut uncompressed = Vec::new(); let mut decoder = GzDecoder::new(Cursor::new(response_body)); let _ = decoder .read_to_end(&mut uncompressed) .expect("failed to decompress body"); let package = crate::repository_v2_package_parse_body( &uncompressed, std::include_bytes!("../test/public_key"), ) .expect("package failed to parse"); assert_eq!(expected_package_exfmt(), package); } #[test] fn get_package_from_bytes_malformed() { // public key should not be a valid protobuf and should therefore fail let bytes = std::include_bytes!("../test/public_key").to_vec(); let package_error = crate::repository_v2_package_parse_body(&bytes, &bytes) .expect_err("parsing failed to fail"); assert!(package_error.is_invalid_protobuf()); } #[test] fn get_repository_versions_request() { let config = Config::new(); let request = crate::repository_v2_get_versions_request(None, &config); assert_eq!(request.method(), http::Method::GET); assert_eq!(request.uri().path(), "/versions"); assert_eq!(request.headers().get("accept").unwrap(), "application/json"); } #[test] fn get_repository_versions_response_ok() { let response_body = std::include_bytes!("../test/versions"); let response = make_response(200, response_body.to_vec()); let versions = crate::repository_v2_get_versions_response( response, std::include_bytes!("../test/public_key"), ); assert_eq!( &vec![ Version::parse("0.0.0").unwrap(), Version::parse("0.1.0").unwrap(), Version::parse("0.2.0").unwrap(), Version::parse("0.2.1").unwrap(), Version::parse("0.2.2").unwrap(), Version::parse("0.2.3").unwrap(), Version::parse("0.2.4").unwrap(), Version::parse("0.3.0").unwrap(), Version::parse("0.4.0").unwrap(), Version::parse("0.5.0").unwrap(), ], versions.unwrap().get("exfmt").unwrap(), ); } #[test] fn get_repository_versions_from_bytes_ok() { let response_body = std::include_bytes!("../test/versions"); let mut uncompressed = Vec::new(); let mut decoder = GzDecoder::new(Cursor::new(response_body)); let _ = decoder .read_to_end(&mut uncompressed) .expect("failed to decompress body"); let versions = crate::repository_v2_get_versions_body( &uncompressed, std::include_bytes!("../test/public_key"), ) .expect("versions failed to parse"); assert_eq!( &vec![ Version::parse("0.0.0").unwrap(), Version::parse("0.1.0").unwrap(), Version::parse("0.2.0").unwrap(), Version::parse("0.2.1").unwrap(), Version::parse("0.2.2").unwrap(), Version::parse("0.2.3").unwrap(), Version::parse("0.2.4").unwrap(), Version::parse("0.3.0").unwrap(), Version::parse("0.4.0").unwrap(), Version::parse("0.5.0").unwrap(), ], versions.get("exfmt").unwrap(), ); } #[test] fn get_repository_versions_from_bytes_malformed() { // public key should not be a valid protobuf and should therefore fail let bytes = std::include_bytes!("../test/public_key").to_vec(); let versions_error = crate::repository_v2_get_versions_body(&bytes, &bytes).expect_err("parsing failed to fail"); assert!(versions_error.is_invalid_protobuf()); } #[test] fn get_repository_tarball_request() { let config = Config::new(); let request = crate::repository_get_package_tarball_request("gleam_stdlib", "0.14.0", None, &config); assert_eq!(request.method(), http::Method::GET); assert_eq!(request.uri().path(), "/tarballs/gleam_stdlib-0.14.0.tar"); assert_eq!( request.headers().get("accept").unwrap(), "application/x-tar" ); } #[test] fn get_repository_tarball_response_ok() { let tarball_bytes = std::include_bytes!("../test/gleam_stdlib-0.14.0.tar"); let checksum = base16::decode("9107f6a859cb96945ad9a099085db028ca2bebb3c8ea42eec227b51c614cc2e0").unwrap(); let response = make_response(200, tarball_bytes.to_vec()); let downloaded = crate::repository_get_package_tarball_response(response, &checksum).unwrap(); assert_eq!(&downloaded, tarball_bytes); } #[test] fn get_repository_tarball_response_bad_checksum() { let tarball_bytes = std::include_bytes!("../test/gleam_stdlib-0.14.0.tar"); let checksum = vec![1, 2, 3, 4, 5]; let response = make_response(200, tarball_bytes.to_vec()); let err = crate::repository_get_package_tarball_response(response, &checksum).unwrap_err(); assert_eq!( err.to_string(), "The downloaded data did not have the expected checksum" ); } #[test] fn get_repository_tarball_response_not_found() { let checksum = vec![1, 2, 3, 4, 5]; let response = make_response(404, vec![]); let err = crate::repository_get_package_tarball_response(response, &checksum).unwrap_err(); assert_eq!(err.to_string(), "Resource was not found"); } #[test] fn publish_package_request() { let key = WriteActionCredentials::ApiKey(EcoString::from("my-api-key-here")); let tarball = std::include_bytes!("../test/example.tar.gz").to_vec(); let config = Config::new(); let request = crate::api_publish_package_request(tarball.clone(), &key, &config, false); assert_eq!(request.method(), http::Method::POST); assert_eq!( request.uri().path_and_query().unwrap(), "/api/publish?replace=false" ); assert_eq!( request.headers().get("authorization").unwrap(), "my-api-key-here" ); assert_eq!(request.headers().get("accept").unwrap(), "application/json"); assert_eq!( request.headers().get("content-type").unwrap(), "application/x-tar" ); assert_eq!(request.body(), &tarball); } #[test] fn publish_package_request_replace() { let key = WriteActionCredentials::OAuthAccessToken { access_token: EcoString::from("my-api-key-here"), one_time_password: EcoString::from("test-otp"), }; let tarball = std::include_bytes!("../test/example.tar.gz").to_vec(); let config = Config::new(); let request = crate::api_publish_package_request(tarball.clone(), &key, &config, true); assert_eq!( request.uri().path_and_query().unwrap(), "/api/publish?replace=true" ); assert_eq!(request.headers().get("x-hex-otp").unwrap(), "test-otp"); } #[test] fn publish_package_response_success() { let response = make_response(201, vec![]); let result = crate::api_publish_package_response(response); match result { Ok(()) => (), result => panic!("expected Ok(()), got {result:?}"), } } #[test] fn modify_package_late() { let resp_body = json!({ "errors": {"inserted_at": "can only modify a release up to one hour after publication"}, "message": "Validation error(s)", "status": 422, }); let response = make_json_response(422, resp_body); let result = crate::api_publish_package_response(response); match result { Err(ApiError::LateModification) => (), result => panic!("expected Err(ApiError::LateModification), got {result:?}"), } } #[test] fn not_replacing() { let resp_body = json!({ "errors": {"inserted_at": "must include the --replace flag to update an existing release"}, "message": "Validation error(s)", "status": 422, }); let response = make_json_response(422, resp_body); let result = crate::api_publish_package_response(response); match result { Err(ApiError::NotReplacing) => (), result => panic!("expected Err(ApiError::NotReplacing), got {result:?}"), } } #[test] fn get_package_release_request() { let config = Config::new(); let request = crate::api_get_package_release_request("clint", "0.0.1", None, &config); assert_eq!(request.method(), http::Method::GET); assert_eq!(request.uri().path(), "/api/packages/clint/releases/0.0.1"); assert_eq!(request.headers().get("accept").unwrap(), "application/json"); } #[test] fn get_package_release_response_not_found() { let response = make_response(404, vec![]); let error = crate::api_get_package_release_response(response).unwrap_err(); assert!(error.is_not_found()); } #[test] fn get_package_release_response_ok() { let resp_body = json!({ "version": "0.0.1", "checksum": "41C6781B5F4B986BCE14C3578D39C497BCB8427F1D36D8CDE5FCAA6E03CAE2B1", "requirements": { "plug": { "requirement": "~>0.11.0", "optional": false, "app": "plug" }, "cowboy": { "requirement": "~>1.0.0", "optional": false, "app": "cowboy" } }, "meta": { "app": "clint", "build_tools": ["mix"] } }); let response = make_json_response(200, resp_body); let resp = crate::api_get_package_release_response(response).unwrap(); assert_eq!( resp, Release { version: Version::new(0, 0, 1), requirements: [ ( "plug".into(), Dependency { requirement: Range::new("~>0.11.0".into()).unwrap(), optional: false, app: Some("plug".into()), repository: None } ), ( "cowboy".into(), Dependency { requirement: Range::new("~>1.0.0".into()).unwrap(), optional: false, app: Some("cowboy".into()), repository: None } ) ] .into(), retirement_status: None, outer_checksum: vec![ 65, 198, 120, 27, 95, 75, 152, 107, 206, 20, 195, 87, 141, 57, 196, 151, 188, 184, 66, 127, 29, 54, 216, 205, 229, 252, 170, 110, 3, 202, 226, 177 ], meta: ReleaseMeta { app: "clint".into(), build_tools: vec!["mix".into()] } } ) } #[test] fn make_request_base_trailing_slash_is_optional() { let slash = http::Uri::from_static("http://host/path/"); let no_slash = http::Uri::from_static("http://host/path"); let suffix = "suffix"; let expect = "/path/suffix"; let slash = make_request(slash, http::Method::GET, suffix); assert_eq!(slash.uri_ref().unwrap().path(), expect); let no_slash = make_request(no_slash, http::Method::GET, suffix); assert_eq!(no_slash.uri_ref().unwrap().path(), expect); } #[test] fn oauth_device_authorisation_request() { let client_id = "test-client-id"; let config = Config::new(); let request = crate::oauth_device_authorisation_request(client_id, "My client", &config); assert_eq!(request.method(), http::Method::POST); assert_eq!(request.uri().path(), "/api/oauth/device_authorization"); assert_eq!( request.headers().get("content-type").unwrap(), "application/json" ); assert_eq!(request.headers().get("accept").unwrap(), "application/json"); let body: serde_json::Value = serde_json::from_slice(request.body()).unwrap(); assert_eq!(body["client_id"], "test-client-id"); assert_eq!(body["scope"], "api:write"); assert_eq!(body["name"], "My client"); } #[test] fn oauth_device_authorisation_response_success() { let client_id = "test-client-id".to_string(); let resp_body = json!({ "device_code": "device-code-123", "user_code": "USER-CODE", "verification_uri": "https://hex.pm/oauth/device", "interval": 5 }); let response = make_json_response(200, resp_body); let auth = crate::oauth_device_authorisation_response(client_id, response).unwrap(); assert_eq!(auth.user_code, "USER-CODE"); assert_eq!(auth.verification_uri, "https://hex.pm/oauth/device"); } #[test] fn oauth_device_authorisation_response_success_with_complete_uri() { let client_id = "test-client-id".to_string(); let resp_body = json!({ "device_code": "device-code-123", "user_code": "USER-CODE", "verification_uri": "https://hex.pm/oauth/device", "verification_uri_complete": "https://hex.pm/oauth/device?user_code=USER-CODE", "interval": 5 }); let response = make_json_response(200, resp_body); let auth = crate::oauth_device_authorisation_response(client_id, response).unwrap(); assert_eq!(auth.user_code, "USER-CODE"); // When verification_uri_complete is present, it should be used instead assert_eq!( auth.verification_uri, "https://hex.pm/oauth/device?user_code=USER-CODE" ); } #[test] fn oauth_device_authorisation_response_default_interval() { let client_id = "test-client-id".to_string(); // Response without "interval" field - should use default of 5 seconds let resp_body = json!({ "device_code": "device-code-123", "user_code": "USER-CODE", "verification_uri": "https://hex.pm/oauth/device" }); let response = make_json_response(200, resp_body); let auth = crate::oauth_device_authorisation_response(client_id, response).unwrap(); assert_eq!(auth.user_code, "USER-CODE"); assert_eq!(auth.verification_uri, "https://hex.pm/oauth/device"); } #[test] fn oauth_device_authorisation_response_rate_limited() { let client_id = "test-client-id".to_string(); let response = make_response(429, vec![]); let result = crate::oauth_device_authorisation_response(client_id, response).unwrap_err(); match result { ApiError::RateLimited => (), result => panic!("expected RateLimited, got {:?}", result), } } #[test] fn oauth_device_authorisation_response_unexpected_status() { let client_id = "test-client-id".to_string(); let resp_body = json!({ "message": "Internal server error", "status": 500, }); let response = make_json_response(500, resp_body); let result = crate::oauth_device_authorisation_response(client_id, response).unwrap_err(); match result { ApiError::UnexpectedResponse(status, _) => { assert_eq!(status, http::StatusCode::INTERNAL_SERVER_ERROR); } result => panic!("expected UnexpectedResponse, got {:?}", result), } } fn make_device_authorisation() -> crate::OAuthDeviceAuthorisation { let client_id = "test-client-id".to_string(); let resp_body = json!({ "device_code": "device-code-123", "user_code": "USER-CODE", "verification_uri": "https://hex.pm/oauth/device", "interval": 5 }); let response = make_json_response(200, resp_body); crate::oauth_device_authorisation_response(client_id, response).unwrap() } #[test] fn poll_token_request() { let auth = make_device_authorisation(); let config = Config::new(); let request = auth.poll_token_request(&config); assert_eq!(request.method(), http::Method::POST); assert_eq!(request.uri().path(), "/api/oauth/token"); assert_eq!( request.headers().get("content-type").unwrap(), "application/json" ); assert_eq!(request.headers().get("accept").unwrap(), "application/json"); let body: serde_json::Value = serde_json::from_slice(request.body()).unwrap(); assert_eq!( body["grant_type"], "urn:ietf:params:oauth:grant-type:device_code" ); assert_eq!(body["client_id"], "test-client-id"); assert_eq!(body["device_code"], "device-code-123"); } #[test] fn poll_token_response_success() { let mut auth = make_device_authorisation(); let resp_body = json!({ "access_token": "access-token-abc", "refresh_token": "refresh-token-xyz" }); let response = make_json_response(200, resp_body); let result = auth.poll_token_response(response).unwrap(); match result { crate::PollStep::Done(tokens) => { assert_eq!(tokens.access_token, "access-token-abc"); assert_eq!(tokens.refresh_token, "refresh-token-xyz"); } result => panic!("expected PollStep::Done, got {:?}", result), } } #[test] fn poll_token_response_authorization_pending() { let mut auth = make_device_authorisation(); let resp_body = json!({ "error": "authorization_pending" }); let response = make_json_response(200, resp_body); let result = auth.poll_token_response(response).unwrap(); match result { crate::PollStep::SleepThenPollAgain(duration) => { assert_eq!(duration, std::time::Duration::from_secs(5)); } result => panic!("expected PollStep::SleepThenPollAgain, got {:?}", result), } } #[test] fn poll_token_response_slow_down() { let mut auth = make_device_authorisation(); let resp_body = json!({ "error": "slow_down" }); let response = make_json_response(200, resp_body); let result = auth.poll_token_response(response).unwrap(); match result { crate::PollStep::SleepThenPollAgain(duration) => { // Interval should be doubled from 5 to 10 seconds assert_eq!(duration, std::time::Duration::from_secs(10)); } result => panic!("expected PollStep::SleepThenPollAgain, got {:?}", result), } } #[test] fn poll_token_response_unexpected_status() { let mut auth = make_device_authorisation(); let resp_body = json!({ "message": "Internal server error", "status": 500, }); let response = make_json_response(500, resp_body); let result = auth.poll_token_response(response).unwrap_err(); match result { ApiError::UnexpectedResponse(status, _) => { assert_eq!(status, http::StatusCode::INTERNAL_SERVER_ERROR); } result => panic!("expected UnexpectedResponse, got {:?}", result), } } ================================================ FILE: hexpm/src/version/lexer.rs ================================================ //! Lexer for semver ranges. //! //! Breaks a string of input into an iterator of tokens that can be used with a parser. //! //! Based off https://github.com/steveklabnik/semver-parser/blob/bee9de80aaa9653c5eb46a83658606cb21151e65/src/lexer.rs //! use self::Error::*; use self::Token::*; use std::str; macro_rules! scan_while { ($slf:expr, $start:expr, $first:pat_param $(| $rest:pat)*) => {{ let mut __end = $start; loop { if let Some((idx, c)) = $slf.one() { __end = idx; match c { $first $(| $rest)* => $slf.step(), _ => break, } continue; } else { __end = $slf.input.len(); } break; } __end }} } /// Semver tokens. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum Token<'input> { /// `==` Eq, /// `!=` NotEq, /// `>` Gt, /// `<` Lt, /// `<=` LtEq, /// `>=` GtEq, /// '~>` Pessimistic, /// `.` Dot, /// `-` Hyphen, /// `+` Plus, /// 'or' Or, /// 'and' And, /// any number of whitespace (`\t\r\n `) and its span. Whitespace(usize, usize), /// Numeric component, like `0` or `42`. Numeric(u32), /// Alphanumeric component, like `alpha1` or `79deadbe`. AlphaNumeric(&'input str), /// An alphanumeric component with a leading zero, like `0alpha1` or `079deadbe`. LeadingZero(&'input str), } #[cfg(test)] impl<'input> Token<'input> { /// Check if the current token is a whitespace token. pub fn is_whitespace(&self) -> bool { match *self { Whitespace(..) => true, _ => false, } } } impl std::fmt::Display for Token<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Eq => write!(f, "=="), NotEq => write!(f, "!="), Gt => write!(f, ">"), Lt => write!(f, "<"), LtEq => write!(f, "<="), GtEq => write!(f, "<="), Pessimistic => write!(f, "~>"), Dot => write!(f, "."), Hyphen => write!(f, "-"), Plus => write!(f, "+"), Or => write!(f, "or"), And => write!(f, "and"), Whitespace(_, _) => write!(f, " "), Numeric(i) => write!(f, "{i}"), AlphaNumeric(a) => write!(f, "{a}"), LeadingZero(z) => write!(f, "{z}"), } } } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, thiserror::Error)] pub enum Error { #[error("Unexpected character {0}")] UnexpectedChar(char), } /// Lexer for semver tokens belonging to a range. #[derive(Debug)] pub struct Lexer<'input> { input: &'input str, chars: str::CharIndices<'input>, // lookahead c1: Option<(usize, char)>, c2: Option<(usize, char)>, } impl<'input> Lexer<'input> { /// Construct a new lexer for the given input. pub fn new(input: &str) -> Lexer<'_> { let mut chars = input.char_indices(); let c1 = chars.next(); let c2 = chars.next(); Lexer { input, chars, c1, c2, } } /// Shift all lookahead storage by one. fn step(&mut self) { self.c1 = self.c2; self.c2 = self.chars.next(); } fn step_n(&mut self, n: usize) { for _ in 0..n { self.step(); } } /// Access the one character, or set it if it is not set. fn one(&mut self) -> Option<(usize, char)> { self.c1 } /// Access two characters. fn two(&mut self) -> Option<(usize, char, char)> { self.c1 .and_then(|(start, c1)| self.c2.map(|(_, c2)| (start, c1, c2))) } /// Consume a component. /// /// A component can either be an alphanumeric or numeric. /// Does not permit leading zeroes. fn component(&mut self, start: usize) -> Result, Error> { let end = scan_while!(self, start, '0'..='9' | 'A'..='Z' | 'a'..='z'); let input = &self.input[start..end]; let mut it = input.chars(); let (a, b) = (it.next(), it.next()); // exactly zero if a == Some('0') && b.is_none() { return Ok(Numeric(0)); } if let Ok(numeric) = input.parse::() { // Only parse as a number if there is no leading zero if a != Some('0') { return Ok(Numeric(numeric)); } else { return Ok(LeadingZero(input)); } } Ok(AlphaNumeric(input)) } fn and(&mut self, start: usize) -> Result, Error> { match self.one() { Some((_, 'd')) => { self.step(); Ok(And) } _ => self.component(start), } } /// Consume whitespace. fn whitespace(&mut self, start: usize) -> Result, Error> { let end = scan_while!(self, start, ' ' | '\t' | '\n' | '\r'); Ok(Whitespace(start, end)) } } impl<'input> Iterator for Lexer<'input> { type Item = Result, Error>; fn next(&mut self) -> Option { #[allow(clippy::never_loop)] loop { // two subsequent char tokens. if let Some((start, a, b)) = self.two() { let two = match (a, b) { ('~', '>') => Some(Pessimistic), ('!', '=') => Some(NotEq), ('<', '=') => Some(LtEq), ('>', '=') => Some(GtEq), ('=', '=') => Some(Eq), ('o', 'r') => Some(Or), ('a', 'n') => { self.step_n(2); return Some(self.and(start)); } _ => None, }; if let Some(two) = two { self.step_n(2); return Some(Ok(two)); } } // single char and start of numeric tokens. if let Some((start, c)) = self.one() { let tok = match c { ' ' | '\t' | '\n' | '\r' => { self.step(); return Some(self.whitespace(start)); } '>' => Gt, '<' => Lt, '.' => Dot, '-' => Hyphen, '+' => Plus, '0'..='9' | 'a'..='z' | 'A'..='Z' => { self.step(); return Some(self.component(start)); } c => return Some(Err(UnexpectedChar(c))), }; self.step(); return Some(Ok(tok)); }; return None; } } } #[cfg(test)] mod tests { use super::*; fn lex(input: &str) -> Vec> { Lexer::new(input).map(Result::unwrap).collect::>() } #[test] pub fn simple_tokens() { assert_eq!( lex("!===><<=>=~>.-+orand"), vec![ NotEq, Eq, Gt, Lt, LtEq, GtEq, Pessimistic, Dot, Hyphen, Plus, Or, And ] ); } #[test] pub fn whitespace() { assert_eq!( lex(" foo \t\n\rbar"), vec![ Whitespace(0, 2), AlphaNumeric("foo"), Whitespace(5, 9), AlphaNumeric("bar"), ] ); } #[test] pub fn components() { assert_eq!(lex("42"), vec![Numeric(42)]); assert_eq!(lex("0"), vec![Numeric(0)]); assert_eq!(lex("5885644aa"), vec![AlphaNumeric("5885644aa")]); assert_eq!(lex("beta2"), vec![AlphaNumeric("beta2")]); assert_eq!(lex("beta.2"), vec![AlphaNumeric("beta"), Dot, Numeric(2)]); } #[test] pub fn empty() { assert_eq!(lex(""), vec![]); } #[test] pub fn numeric_all_numbers() { let expected: Vec = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9] .into_iter() .map(Numeric) .collect::>(); let actual: Vec<_> = lex("0 1 2 3 4 5 6 7 8 9") .into_iter() .filter(|t| !t.is_whitespace()) .collect(); assert_eq!(actual, expected); } } ================================================ FILE: hexpm/src/version/parser.rs ================================================ // Based off of https://github.com/steveklabnik/semver-parser/blob/bee9de80aaa9653c5eb46a83658606cb21151e65/src/parser.rs use std::fmt; use std::mem; use self::Error::*; use super::lexer::{self, Lexer, Token}; use crate::version::{Identifier, Version}; use thiserror::Error; type PubgrubRange = pubgrub::Range; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Error)] pub enum Error { /// Needed more tokens for parsing, but none are available. UnexpectedEnd, /// Unexpected token. UnexpectedToken(String), /// An error occurred in the lexer. Lexer(lexer::Error), /// More input available. MoreInput(String), /// Encountered empty predicate in a set of predicates. EmptyPredicate, /// Encountered an empty range. EmptyRange, /// Encountered a semver that's missing the minor and patch version. MinorVersionMissing(u32), /// Encountered a semver that's missing the patch version. PatchVersionMissing(u32, u32), } impl From for Error { fn from(value: lexer::Error) -> Self { Error::Lexer(value) } } impl fmt::Display for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { use self::Error::*; match *self { UnexpectedEnd => write!(fmt, "expected more input"), UnexpectedToken(ref token) => write!(fmt, "encountered unexpected token: {token:?}"), Lexer(ref error) => write!(fmt, "lexer error: {error:?}"), MoreInput(ref tokens) => write!(fmt, "expected end of input, but got: {tokens:?}"), EmptyPredicate => write!(fmt, "encountered empty predicate"), EmptyRange => write!(fmt, "encountered empty range"), MinorVersionMissing(major) => { write!(fmt, "missing minor and patch versions: {major:?}") } PatchVersionMissing(major, minor) => { write!(fmt, "missing patch version: {major:?}.{minor:?}") } } } } /// impl for backwards compatibility. impl From for String { fn from(value: Error) -> Self { value.to_string() } } /// A recursive-descent parser for parsing version requirements. pub struct Parser<'input> { /// Source of token. lexer: Lexer<'input>, /// Lookaehead. c1: Option>, } impl<'input> Parser<'input> { /// Construct a new parser for the given input. pub fn new(input: &'input str) -> Result, Error> { let mut lexer = Lexer::new(input); let c1 = if let Some(c1) = lexer.next() { Some(c1?) } else { None }; Ok(Parser { lexer, c1 }) } /// Pop one token. #[inline(always)] fn pop(&mut self) -> Result, Error> { let c1 = if let Some(c1) = self.lexer.next() { Some(c1?) } else { None }; mem::replace(&mut self.c1, c1).ok_or(UnexpectedEnd) } /// Peek one token. #[inline(always)] fn peek(&mut self) -> Option<&Token<'input>> { self.c1.as_ref() } /// Skip whitespace if present. fn skip_whitespace(&mut self) -> Result<(), Error> { match self.peek() { Some(&Token::Whitespace(_, _)) => self.pop().map(|_| ()), _ => Ok(()), } } /// Check that some whitespace is next and then discard it fn expect_whitespace(&mut self) -> Result<(), Error> { match self.pop()? { Token::Whitespace(_, _) => Ok(()), token => Err(UnexpectedToken(token.to_string())), } } /// Parse a single numeric. pub fn numeric(&mut self) -> Result { match self.pop()? { Token::Numeric(number) => Ok(number), token => Err(UnexpectedToken(token.to_string())), } } fn dot(&mut self) -> Result<(), Error> { match self.pop()? { Token::Dot => Ok(()), token => Err(UnexpectedToken(token.to_string())), } } /// Parse a dot, then a numeric. fn dot_numeric(&mut self) -> Result { self.dot()?; self.numeric() } /// Parse an string identifier. /// /// Like, `foo`, or `bar`, or `beta-1`. pub fn identifier(&mut self) -> Result { let identifier = match self.pop()? { Token::AlphaNumeric(identifier) => { // TODO: Borrow? Identifier::AlphaNumeric(identifier.to_string()) } Token::Numeric(n) => Identifier::Numeric(n), tok => return Err(UnexpectedToken(tok.to_string())), }; if let Some(&Token::Hyphen) = self.peek() { // pop the peeked hyphen self.pop()?; // concat with any following identifiers Ok(identifier .concat("-") .concat(&self.identifier()?.to_string())) } else { Ok(identifier) } } /// Parse all pre-release identifiers, separated by dots. /// /// Like, `abcdef.1234`. fn pre(&mut self) -> Result, Error> { match self.peek() { Some(&Token::Hyphen) => {} _ => return Ok(vec![]), } // pop the peeked hyphen. self.pop()?; self.parts() } /// Parse a dot-separated set of identifiers. fn parts(&mut self) -> Result, Error> { let mut parts = Vec::new(); parts.push(self.identifier()?); while let Some(&Token::Dot) = self.peek() { self.pop()?; parts.push(self.identifier()?); } Ok(parts) } /// Parse optional build metadata. /// /// Like, `` (empty), or `+abcdef`. fn plus_build_metadata(&mut self) -> Result, Error> { match self.peek() { Some(&Token::Plus) => self.pop()?, _ => return Ok(None), }; let mut buffer = String::new(); loop { match self.pop() { Err(UnexpectedEnd) => break, Ok(Token::LeadingZero(s)) => buffer.push_str(s), Ok(Token::AlphaNumeric(s)) => buffer.push_str(s), Ok(Token::Numeric(s)) => buffer.push_str(&s.to_string()), Ok(Token::Dot) => buffer.push('.'), Ok(token) => return Err(UnexpectedToken(token.to_string())), Err(error) => return Err(error), } } if buffer.is_empty() { Err(UnexpectedEnd) } else { Ok(Some(buffer)) } } /// Parse a version. /// /// Like, `1.0.0` or `3.0.0-beta.1`. pub fn version(&mut self) -> Result { self.skip_whitespace()?; let major = self.numeric()?; let minor = self .dot_numeric() .map_err(|_| Error::MinorVersionMissing(major))?; let patch = self .dot_numeric() .map_err(|_| Error::PatchVersionMissing(major, minor))?; let pre = self.pre()?; let build = self.plus_build_metadata()?; self.skip_whitespace()?; Ok(Version { major, minor, patch, pre, build, }) } /// Parse a version range requirement. /// /// Like, `~> 1.0.0` or `3.0.0-beta.1 or < 1.0 and > 0.2.3`. pub fn range(&mut self) -> Result { let mut range: Option = None; loop { let constraint = self.range_ands_section()?; range = Some(match range { None => constraint, Some(range) => range.union(&constraint), }); if self.peek() == Some(&Token::Or) { self.pop()?; self.expect_whitespace()?; } else { break; } } self.skip_whitespace()?; range.ok_or(UnexpectedEnd) } fn pessimistic_version_constraint(&mut self) -> Result { let mut included_patch = false; let major = self.numeric()?; let minor = self.dot_numeric()?; let patch = match self.peek() { Some(Token::Dot) => { included_patch = true; self.pop()?; self.numeric()? } _ => 0, }; let pre = self.pre()?; let build = self.plus_build_metadata()?; let lower = Version { major, minor, patch, pre, build, }; let upper = if included_patch { lower.bump_minor() } else { lower.bump_major() }; Ok( PubgrubRange::higher_than(lower) .intersection(&PubgrubRange::strictly_lower_than(upper)), ) } fn range_ands_section(&mut self) -> Result { use Token::*; let mut range = None; let and = |range: Option, constraint: PubgrubRange| { Some(match range { None => constraint, Some(range) => range.intersection(&constraint), }) }; loop { self.skip_whitespace()?; match self.peek() { None => break, Some(Numeric(_)) => range = and(range, PubgrubRange::singleton(self.version()?)), Some(Eq) => { self.pop()?; range = and(range, PubgrubRange::singleton(self.version()?)); } Some(NotEq) => { self.pop()?; let version = self.version()?; let bumped = version.bump_patch(); let below = PubgrubRange::strictly_lower_than(version); let above = PubgrubRange::higher_than(bumped); range = and(range, below.union(&above)); } Some(Gt) => { self.pop()?; range = and( range, PubgrubRange::higher_than(self.version()?.bump_patch()), ); } Some(GtEq) => { self.pop()?; range = and(range, PubgrubRange::higher_than(self.version()?)); } Some(Lt) => { self.pop()?; range = and(range, PubgrubRange::strictly_lower_than(self.version()?)); } Some(LtEq) => { self.pop()?; range = and( range, PubgrubRange::strictly_lower_than(self.version()?.bump_patch()), ); } Some(Pessimistic) => { self.pop()?; self.skip_whitespace()?; range = and(range, self.pessimistic_version_constraint()?); } Some(_) => return Err(UnexpectedToken(self.pop()?.to_string())), }; self.skip_whitespace()?; if self.peek() == Some(&Token::And) { self.pop()?; self.expect_whitespace()?; } else { break; } } self.skip_whitespace()?; range.ok_or(UnexpectedEnd) } /// Check if we have reached the end of input. pub fn is_eof(&mut self) -> bool { self.c1.is_none() } /// Get the rest of the tokens in the parser. /// /// Useful for debugging. pub fn tail(&mut self) -> Result>, Error> { let mut out = Vec::new(); if let Some(t) = self.c1.take() { out.push(t); } for t in self.lexer.by_ref() { out.push(t?); } Ok(out) } } ================================================ FILE: hexpm/src/version/tests.rs ================================================ use std::cmp::Ordering::{Equal, Greater, Less}; use std::collections::HashMap; use parser::Error; use super::{ Identifier::{AlphaNumeric, Numeric}, *, }; // Tests adapted from the tests for Elixir's version module macro_rules! version_parse_test { ($name:ident, $input:expr, $major:expr, $minor:expr, $patch:expr,$pre:expr, $build:expr) => { #[test] fn $name() { assert_eq!( Version::parse($input).unwrap(), Version { major: $major, minor: $minor, patch: $patch, pre: $pre, build: $build } ); } }; ($name:ident, $input:expr, $major:expr, $minor:expr, $patch:expr, $pre:expr) => { #[test] fn $name() { assert_eq!( Version::parse($input).unwrap(), Version { major: $major, minor: $minor, patch: $patch, pre: $pre, build: None } ); } }; ($name:ident, $input:expr, $major:expr, $minor:expr, $patch:expr) => { #[test] fn $name() { assert_eq!( Version::parse($input).unwrap(), Version { major: $major, minor: $minor, patch: $patch, pre: vec![], build: None } ); } }; } macro_rules! version_parse_fail_test { ($name:ident, $input:expr) => { #[test] fn $name() { println!("{}", $input); Version::parse($input).unwrap_err(); } }; } macro_rules! version_parse_print { ($name:ident, $input:expr) => { #[test] fn $name() { assert_eq!($input, Version::parse($input).unwrap().to_string().as_str()); } }; } version_parse_test!(triplet, "1.2.3", 1, 2, 3); version_parse_test!( build, "1.4.5+ignore", 1, 4, 5, vec![], Some("ignore".to_string()) ); version_parse_test!( two_part_build, "0.0.1+sha.0702245", 0, 0, 1, vec![], Some("sha.0702245".to_string()) ); version_parse_test!( pre, "1.4.5-6-g3318bd5", 1, 4, 5, vec![AlphaNumeric("6-g3318bd5".to_string())] ); version_parse_test!( multi_part_pre, "1.4.5-6.7.eight", 1, 4, 5, vec![Numeric(6), Numeric(7), AlphaNumeric("eight".to_string())] ); version_parse_test!( pre_and_build, "1.4.5-6-g3318bd5+ignore", 1, 4, 5, vec![AlphaNumeric("6-g3318bd5".to_string())], Some("ignore".to_string()) ); version_parse_fail_test!(just_a_word, "foobar"); version_parse_fail_test!(just_major, "2"); version_parse_fail_test!(major_dor, "2."); version_parse_fail_test!(major_minor, "2.3"); version_parse_fail_test!(major_minor_dot, "2.3."); version_parse_fail_test!(triplet_dash, "2.3.0-"); version_parse_fail_test!(triplet_plus, "2.3.0+"); version_parse_fail_test!(triplet_dot, "2.3.0."); version_parse_fail_test!(quad, "2.3.0.4"); version_parse_fail_test!(missing_minor, "2.3.-rc.1"); version_parse_fail_test!(missing_minor_with_dot, "2.3.+rc.1"); version_parse_fail_test!(zero_pre, "2.3.0-01"); version_parse_fail_test!(double_zero_pre, "2.3.00-1"); version_parse_fail_test!(double_zero, "2.3.00"); version_parse_fail_test!(leading_zero_minor, "2.03.0"); version_parse_fail_test!(leading_zero_major, "02.3.0"); version_parse_fail_test!(extra_whitespace, "0. 0.0"); version_parse_fail_test!(and_in_version, "0.1.0-andpre"); version_parse_print!(print_triplet, "1.100.1000"); version_parse_print!(print_pre, "1.100.4-dev"); version_parse_print!(print_pre_dot, "1.100.4-dev.1.r.t"); version_parse_print!(print_build, "1.100.4+dev.1.r.t"); version_parse_print!(print_pre_build, "1.100.4-ewfjhwefj.wefw.w.1.ff+dev.1.r.t"); macro_rules! parse_range_test { ($name:ident, $input:expr, $expected:expr) => { #[test] fn $name() { assert_eq!(Version::parse_range($input).unwrap(), $expected); } }; } macro_rules! parse_range_fail_test { ($name:ident, $input:expr) => { #[test] fn $name() { Version::parse_range($input).unwrap_err(); } }; } fn v(a: u32, b: u32, c: u32) -> Version { Version::new(a, b, c) } fn v_(major: u32, minor: u32, patch: u32, pre: Vec, build: Option) -> Version { Version { major, minor, patch, pre, build, } } type PubgrubRange = pubgrub::Range; parse_range_test!(leading_space, " 1.2.3", PubgrubRange::singleton(v(1, 2, 3))); parse_range_test!( trailing_space, "1.2.3 ", PubgrubRange::singleton(v(1, 2, 3)) ); parse_range_test!(eq_triplet, "== 1.2.3 ", PubgrubRange::singleton(v(1, 2, 3))); parse_range_test!( eq_triplet_nospace, "==1.2.3 ", PubgrubRange::singleton(v(1, 2, 3)) ); parse_range_test!( neq_triplet, "!= 1.2.3", PubgrubRange::strictly_lower_than(v(1, 2, 3)).union(&PubgrubRange::higher_than(v(1, 2, 4))) ); parse_range_test!(implicit_eq, "2.2.3", PubgrubRange::singleton(v(2, 2, 3))); parse_range_test!( range_pre_build, "1.2.3-thing+oop", PubgrubRange::singleton(v_( 1, 2, 3, vec![Identifier::AlphaNumeric("thing".to_string())], Some("oop".to_string()) )) ); parse_range_test!( and, "< 1.2.3 and > 1.0.1", PubgrubRange::strictly_lower_than(v(1, 2, 3)) .intersection(&PubgrubRange::higher_than(v(1, 0, 2))) ); parse_range_test!( or, "< 1.2.3 or > 1.0.1", PubgrubRange::strictly_lower_than(v(1, 2, 3)).union(&PubgrubRange::higher_than(v(1, 0, 2))) ); parse_range_test!(gt, "> 1.0.0", PubgrubRange::higher_than(v(1, 0, 1))); parse_range_test!(gt_eq, ">= 1.0.0", PubgrubRange::higher_than(v(1, 0, 0))); parse_range_test!(lt, "< 1.0.0", PubgrubRange::strictly_lower_than(v(1, 0, 0))); parse_range_test!( lt_eq, "<= 1.0.0", PubgrubRange::strictly_lower_than(v(1, 0, 1)) ); parse_range_test!( pessimistic_pair, "~> 2.2", PubgrubRange::higher_than(v(2, 2, 0)) .intersection(&PubgrubRange::strictly_lower_than(v(3, 0, 0))) ); parse_range_test!( pessimistic_triplet, "~> 4.6.5", PubgrubRange::higher_than(v(4, 6, 5)) .intersection(&PubgrubRange::strictly_lower_than(v(4, 7, 0))) ); parse_range_test!( pessimistic_triplet_pre, "~> 4.6.5-eee", PubgrubRange::higher_than(v_( 4, 6, 5, vec![Identifier::AlphaNumeric("eee".to_string())], None, )) .intersection(&PubgrubRange::strictly_lower_than(v(4, 7, 0))) ); parse_range_test!( pessimistic_triplet_build, "~> 4.6.5+eee", PubgrubRange::higher_than(v_(4, 6, 5, vec![], Some("eee".to_string()))) .intersection(&PubgrubRange::strictly_lower_than(v(4, 7, 0))) ); parse_range_test!( greater_or_pessimistic, "> 10.0.0 or ~> 3.0.0", PubgrubRange::higher_than(v(10, 0, 1)).union( &PubgrubRange::higher_than(v(3, 0, 0)) .intersection(&PubgrubRange::strictly_lower_than(v(3, 1, 0))) ) ); parse_range_test!( pessimistic_or_pessimistic, "~> 1.0.0 or ~> 3.0.0", PubgrubRange::higher_than(v(1, 0, 0)) .intersection(&PubgrubRange::strictly_lower_than(v(1, 1, 0))) .union( &PubgrubRange::higher_than(v(3, 0, 0)) .intersection(&PubgrubRange::strictly_lower_than(v(3, 1, 0))) ) ); parse_range_test!( pessimistic_and_gt, "~> 0.6 and >= 0.6.16", PubgrubRange::higher_than(v(0, 6, 0)) .intersection(&PubgrubRange::strictly_lower_than(v(1, 0, 0))) .intersection(&PubgrubRange::higher_than(v(0, 6, 16))) ); parse_range_test!( pessimistic_and_gt_pre, "~> 1.0-pre and >= 1.0.0-pre.5", PubgrubRange::higher_than(v_( 1, 0, 0, vec![Identifier::AlphaNumeric("pre".to_string())], None )) .intersection(&PubgrubRange::strictly_lower_than(v(2, 0, 0))) .intersection(&PubgrubRange::higher_than(v_( 1, 0, 0, vec![ Identifier::AlphaNumeric("pre".to_string()), Identifier::Numeric(5) ], None ))) ); parse_range_fail_test!(range_quad, "1.1.1.1"); parse_range_fail_test!(range_just_major, "1"); parse_range_fail_test!(range_just_major_minor, "1.1"); parse_range_fail_test!(alpha_component, "1.1.a"); parse_range_fail_test!(range_word, "foobar"); parse_range_fail_test!(range_major_dot, "2."); parse_range_fail_test!(range_major_minor_dot, "2.3."); parse_range_fail_test!(range_triplet_dash, "2.3.0-"); parse_range_fail_test!(range_triplet_plus, "2.3.0+"); parse_range_fail_test!(range_triplet_dot, "2.3.0."); parse_range_fail_test!(range_dot_dash, "2.3.-rc.1"); parse_range_fail_test!(range_dot_plus, "2.3.+rc.1"); parse_range_fail_test!(range_zero_pre, "2.3.0-01"); parse_range_fail_test!(patch_zerozero_dash, "2.3.00-1"); parse_range_fail_test!(patch_zerozero, "2.3.00"); parse_range_fail_test!(minor_leading_zero, "2.03.0"); parse_range_fail_test!(major_leading_zero, "02.3.0"); parse_range_fail_test!(triplet_containing_whitespace, "0. 0.0"); parse_range_fail_test!(dash_amp, "0.1.0-&&pre"); parse_range_fail_test!(or_whitespace_before, "!= 1.2.3or == 1.0.1"); parse_range_fail_test!(or_whitespace_after, "!= 1.2.3 or== 1.0.1"); parse_range_fail_test!(and_whitespace_before, "!= 1.2.3and == 1.0.1"); parse_range_fail_test!(and_whitespace_after, "!= 1.2.3 and== 1.0.1"); parse_range_fail_test!(trailing_and, "1.1.1 and"); parse_range_fail_test!(trailing_or, "1.1.1 or"); parse_range_fail_test!(leading_and, "and 1.1.1"); parse_range_fail_test!(leading_or, "and 1.1.1"); parse_range_fail_test!(just_and, "and"); parse_range_fail_test!(just_or, "and"); parse_range_fail_test!(duplicate_eq, "== =="); parse_range_fail_test!(just_eq, "=="); parse_range_fail_test!(empty, ""); parse_range_fail_test!(pessimistic_major, "~> 1"); macro_rules! assert_order { ($name:ident, $left:expr, $ord:expr, $right:expr) => { #[test] fn $name() { let left = Version::parse($left); let right = Version::parse($right); assert_eq!(left.cmp(&right), $ord) } }; } assert_order!(ord_same, "1.0.0", Equal, "1.0.0"); assert_order!(ord_same_build_right, "1.0.0", Equal, "1.0.0+1"); assert_order!(ord_same_build_left, "1.0.0+1", Equal, "1.0.0"); assert_order!(ord_same_diff_build, "1.0.0+1", Equal, "1.0.0+2"); assert_order!(ord_diff_build, "1.0.0+2", Equal, "1.0.0+1"); assert_order!(ord_major_greater, "3.0.0", Greater, "2.0.0"); assert_order!(ord_major_lesser, "1.0.0", Less, "2.0.0"); assert_order!(ord_minor_greater, "1.1.0", Greater, "1.0.0"); assert_order!(ord_minor_lesser, "1.0.0", Less, "1.1.0"); assert_order!(ord_patch_greater, "1.0.1", Greater, "1.0.0"); assert_order!(ord_patch_lesser, "1.0.0", Less, "1.0.1"); assert_order!(ord_pre_smaller_than_zero, "1.0.0", Greater, "1.0.0-rc1"); assert_order!(ord_pre_smaller_than_zero_flip, "1.0.0-rc1", Less, "1.0.0"); assert_order!(ord_pre_rc1_2, "1.0.0-rc1", Less, "1.0.0-rc2"); #[test] fn manifest_toml() { let manifest = toml::to_string( &vec![ ( "gleam_stdlib".to_string(), Version { major: 0, minor: 17, patch: 1, pre: vec![], build: None, }, ), ( "thingy".to_string(), Version { major: 0, minor: 1, patch: 0, pre: vec![], build: None, }, ), ] .into_iter() .collect::>(), ) .unwrap(); let expected1 = r#"thingy = "0.1.0" gleam_stdlib = "0.17.1" "#; let expected2 = r#"gleam_stdlib = "0.17.1" thingy = "0.1.0" "#; assert!(manifest == expected1 || manifest == expected2); } #[test] fn missing_minor_has_correct_error_type() { assert_eq!(Version::parse("1"), Err(Error::MinorVersionMissing(1))) } #[test] fn missing_patch_has_correct_error_type() { assert_eq!(Version::parse("1.2"), Err(Error::PatchVersionMissing(1, 2))) } ================================================ FILE: hexpm/src/version.rs ================================================ //! Functions for parsing and matching versions against requirements, based off //! and compatible with the Elixir Version module, which is used by Hex //! internally as well as be the Elixir build tool Hex client. use std::{cmp::Ordering, convert::TryFrom, fmt}; use self::parser::Parser; use serde::{ Deserialize, Serialize, de::{self, Deserializer, Visitor}, }; mod lexer; mod parser; #[cfg(test)] mod tests; /// In a nutshell, a version is represented by three numbers: /// /// MAJOR.MINOR.PATCH /// /// Pre-releases are supported by optionally appending a hyphen and a series of /// period-separated identifiers immediately following the patch version. /// Identifiers consist of only ASCII alphanumeric characters and hyphens (`[0-9A-Za-z-]`): /// /// "1.0.0-alpha.3" /// /// Build information can be added by appending a plus sign and a series of /// dot-separated identifiers immediately following the patch or pre-release version. /// Identifiers consist of only ASCII alphanumeric characters and hyphens (`[0-9A-Za-z-]`): /// /// "1.0.0-alpha.3+20130417140000.amd64" /// #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Version { pub major: u32, pub minor: u32, pub patch: u32, pub pre: Vec, pub build: Option, } impl Version { pub fn new(major: u32, minor: u32, patch: u32) -> Self { Self { major, minor, patch, pre: vec![], build: None, } } fn bump_major(&self) -> Self { Self { major: self.major + 1, minor: 0, patch: 0, pre: vec![], build: None, } } fn bump_minor(&self) -> Self { Self { major: self.major, minor: self.minor + 1, patch: 0, pre: vec![], build: None, } } fn bump_patch(&self) -> Self { Self { major: self.major, minor: self.minor, patch: self.patch + 1, pre: vec![], build: None, } } /// Parse a version. pub fn parse(input: &str) -> Result { let mut parser = Parser::new(input)?; let version = parser.version()?; if !parser.is_eof() { return Err(parser::Error::MoreInput( parser .tail()? .into_iter() .map(|t| t.to_string()) .collect::>() .join(""), )); } Ok(version) } /// Parse a Hex compatible version range. i.e. `> 1 and < 2 or == 4.5.2`. fn parse_range(input: &str) -> Result, parser::Error> { let mut parser = Parser::new(input)?; let version = parser.range()?; if !parser.is_eof() { return Err(parser::Error::MoreInput( parser .tail()? .into_iter() .map(|t| t.to_string()) .collect::>() .join(""), )); } Ok(version) } pub fn lowest() -> Self { Self::new(0, 0, 0) } fn tuple(&self) -> (u32, u32, u32, PreOrder<'_>) { ( self.major, self.minor, self.patch, PreOrder(self.pre.as_slice()), ) } pub fn is_pre(&self) -> bool { !self.pre.is_empty() } } pub trait LowestVersion { fn lowest_version(&self) -> Option; } impl LowestVersion for pubgrub::Range { fn lowest_version(&self) -> Option { self.iter() .flat_map(|(lower, _higher)| match lower { std::ops::Bound::Included(v) => Some(v.clone()), std::ops::Bound::Excluded(_) => None, std::ops::Bound::Unbounded => Some(Version::lowest()), }) .min() } } impl<'de> Deserialize<'de> for Version { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { deserializer.deserialize_str(VersionVisitor) } } struct VersionVisitor; impl<'de> Visitor<'de> for VersionVisitor { type Value = Version; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a Hex version string") } fn visit_str(self, value: &str) -> Result where E: de::Error, { Version::try_from(value).map_err(de::Error::custom) } } impl Serialize for Version { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.serialize_str(&self.to_string()) } } impl std::cmp::PartialOrd for Version { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl std::cmp::Ord for Version { fn cmp(&self, other: &Self) -> Ordering { self.tuple().cmp(&other.tuple()) } } impl<'a> TryFrom<&'a str> for Version { type Error = parser::Error; fn try_from(value: &'a str) -> Result { Self::parse(value) } } impl fmt::Display for Version { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?; if !self.pre.is_empty() { write!(f, "-")?; for (i, identifier) in self.pre.iter().enumerate() { if i != 0 { write!(f, ".")?; } identifier.fmt(f)?; } } if let Some(build) = self.build.as_ref() { write!(f, "+{build}")?; } Ok(()) } } #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub enum Identifier { Numeric(u32), AlphaNumeric(String), } impl fmt::Display for Identifier { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Identifier::Numeric(ref id) => id.fmt(f), Identifier::AlphaNumeric(ref id) => id.fmt(f), } } } impl Identifier { pub fn concat(self, add_str: &str) -> Identifier { match self { Identifier::Numeric(n) => Identifier::AlphaNumeric(format!("{n}{add_str}")), Identifier::AlphaNumeric(mut s) => { s.push_str(add_str); Identifier::AlphaNumeric(s) } } } } #[derive(Clone, PartialEq, Eq)] pub struct Range { spec: String, range: pubgrub::Range, } impl Range { pub fn new(spec: String) -> Result { let range = Version::parse_range(&spec)?; Ok(Self { spec, range }) } } impl Range { pub fn to_pubgrub(&self) -> &pubgrub::Range { &self.range } pub fn as_str(&self) -> &str { &self.spec } } impl From> for Range { fn from(range: pubgrub::Range) -> Self { let spec = range.to_string(); Self { spec, range } } } impl From for Range { fn from(version: Version) -> Self { pubgrub::Range::singleton(version).into() } } impl From for pubgrub::Range { fn from(range: Range) -> Self { range.range } } impl fmt::Debug for Range { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple("Range").field(&self.spec).finish() } } impl<'de> Deserialize<'de> for Range { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let s: &str = Deserialize::deserialize(deserializer)?; Range::new(s.to_string()).map_err(serde::de::Error::custom) } } impl Serialize for Range { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.serialize_str(&self.to_string()) } } impl fmt::Display for Range { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.spec) } } // A wrapper around Vec where an empty vector is greater than a non-empty one. // This is desires as if there is a pre-segment in a version (1.0.0-rc1) it is // lower than the same version with no pre-segments (1.0.0). #[derive(PartialEq, Eq)] pub struct PreOrder<'a>(&'a [Identifier]); impl PreOrder<'_> { fn is_empty(&self) -> bool { self.0.is_empty() } } impl std::cmp::PartialOrd for PreOrder<'_> { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl std::cmp::Ord for PreOrder<'_> { fn cmp(&self, other: &Self) -> Ordering { if self.is_empty() && other.is_empty() { Ordering::Equal } else if self.is_empty() { Ordering::Greater } else if other.is_empty() { Ordering::Less } else { self.0.cmp(other.0) } } } ================================================ FILE: hexpm/test/public_key ================================================ -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApqREcFDt5vV21JVe2QNB Edvzk6w36aNFhVGWN5toNJRjRJ6m4hIuG4KaXtDWVLjnvct6MYMfqhC79HAGwyF+ IqR6Q6a5bbFSsImgBJwz1oadoVKD6ZNetAuCIK84cjMrEFRkELtEIPNHblCzUkkM 3rS9+DPlnfG8hBvGi6tvQIuZmXGCxF/73hU0/MyGhbmEjIKRtG6b0sJYKelRLTPW XgK7s5pESgiwf2YC/2MGDXjAJfpfCd0RpLdvd4eRiXtVlE9qO9bND94E7PgQ/xqZ J1i2xWFndWa6nfFnRxZmCStCOZWYYPlaxr+FZceFbpMwzTNs4g3d4tLNUcbKAIH4 0wIDAQAB -----END PUBLIC KEY----- ================================================ FILE: language-server/Cargo.toml ================================================ [package] name = "gleam-language-server" version = "1.15.1" authors = ["Louis Pilfold "] edition = "2024" license = "Apache-2.0" [dependencies] gleam-core = { path = "../compiler-core" } camino.workspace = true debug-ignore.workspace = true ecow.workspace = true hexpm = { path = "../hexpm" } im.workspace = true itertools.workspace = true lsp-server.workspace = true lsp-types.workspace = true serde_json.workspace = true strum.workspace = true vec1.workspace = true serde.workspace = true tracing.workspace = true toml.workspace = true [dev-dependencies] insta.workspace = true ================================================ FILE: language-server/src/code_action.rs ================================================ use std::{collections::HashSet, iter, sync::Arc}; use ecow::{EcoString, eco_format}; use gleam_core::{ Error, STDLIB_PACKAGE_NAME, analyse::Inferred, ast::{ self, ArgNames, AssignName, AssignmentKind, BitArraySegmentTruncation, BoundVariable, BoundVariableName, CallArg, CustomType, FunctionLiteralKind, ImplicitCallArgOrigin, Import, InvalidExpression, PIPE_PRECEDENCE, Pattern, PatternUnusedArguments, PipelineAssignmentKind, Publicity, RecordConstructor, SrcSpan, TodoKind, TypedArg, TypedAssignment, TypedClauseGuard, TypedDefinitions, TypedExpr, TypedFunction, TypedModuleConstant, TypedPattern, TypedPipelineAssignment, TypedRecordConstructor, TypedStatement, TypedTailPattern, TypedUse, visit::Visit as _, }, build::{Located, Module}, config::PackageConfig, exhaustiveness::CompiledCase, line_numbers::LineNumbers, parse::{extra::ModuleExtra, lexer::str_to_keyword}, strings::to_snake_case, type_::{ self, FieldMap, ModuleValueConstructor, Opaque, Type, TypeVar, TypedCallArg, ValueConstructor, error::{ModuleSuggestion, VariableDeclaration, VariableOrigin}, printer::Printer, }, }; use im::HashMap; use itertools::Itertools; use lsp_types::{CodeAction, CodeActionKind, CodeActionParams, Position, Range, TextEdit, Url}; use vec1::{Vec1, vec1}; use super::{ TextEdits, compiler::LspProjectCompiler, edits, edits::{add_newlines_after_import, get_import_edit, position_of_first_definition_if_import}, engine::{overlaps, within}, files::FileSystemProxy, reference::{FindVariableReferences, VariableReferenceKind}, src_span_to_lsp_range, url_from_path, }; #[derive(Debug)] pub struct CodeActionBuilder { action: CodeAction, } impl CodeActionBuilder { pub fn new(title: &str) -> Self { Self { action: CodeAction { title: title.to_string(), kind: None, diagnostics: None, edit: None, command: None, is_preferred: None, disabled: None, data: None, }, } } pub fn kind(mut self, kind: CodeActionKind) -> Self { self.action.kind = Some(kind); self } pub fn changes(mut self, uri: Url, edits: Vec) -> Self { let mut edit = self.action.edit.take().unwrap_or_default(); let mut changes = edit.changes.take().unwrap_or_default(); _ = changes.insert(uri, edits); edit.changes = Some(changes); self.action.edit = Some(edit); self } pub fn preferred(mut self, is_preferred: bool) -> Self { self.action.is_preferred = Some(is_preferred); self } pub fn push_to(self, actions: &mut Vec) { actions.push(self.action); } } /// A small helper function to get the indentation at a given position. fn count_indentation(code: &str, line_numbers: &LineNumbers, line: u32) -> usize { let mut indent_size = 0; let line_start = *line_numbers .line_starts .get(line as usize) .expect("Line number should be valid"); let mut chars = code[line_start as usize..].chars(); while chars.next() == Some(' ') { indent_size += 1; } indent_size } /// Code action to remove literal tuples in case subjects, essentially making /// the elements of the tuples into the case's subjects. /// /// The code action is only available for the i'th subject if: /// - it is a non-empty tuple, and /// - the i'th pattern (including alternative patterns) is a literal tuple for all clauses. /// /// # Basic example: /// /// The following case expression: /// /// ```gleam /// case #(1, 2) { /// #(a, b) -> 0 /// } /// ``` /// /// Becomes: /// /// ```gleam /// case 1, 2 { /// a, b -> 0 /// } /// ``` /// /// # Another example: /// /// The following case expression does not produce any code action /// /// ```gleam /// case #(1, 2) { /// a -> 0 // <- the pattern is not a tuple /// } /// ``` pub struct RedundantTupleInCaseSubject<'a> { edits: TextEdits<'a>, code: &'a EcoString, extra: &'a ModuleExtra, params: &'a CodeActionParams, module: &'a ast::TypedModule, hovered: bool, } impl<'ast> ast::visit::Visit<'ast> for RedundantTupleInCaseSubject<'_> { fn visit_typed_expr_case( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, subjects: &'ast [TypedExpr], clauses: &'ast [ast::TypedClause], compiled_case: &'ast CompiledCase, ) { for (subject_idx, subject) in subjects.iter().enumerate() { let TypedExpr::Tuple { location, elements, .. } = subject else { continue; }; // Ignore empty tuple if elements.is_empty() { continue; } // We cannot rewrite clauses whose i-th pattern is not a discard or // tuples. let all_ith_patterns_are_tuples_or_discards = clauses .iter() .map(|clause| clause.pattern.get(subject_idx)) .all(|pattern| { matches!( pattern, Some(Pattern::Tuple { .. } | Pattern::Discard { .. }) ) }); if !all_ith_patterns_are_tuples_or_discards { continue; } self.delete_tuple_tokens(*location, elements.last().map(|element| element.location())); for clause in clauses { match clause.pattern.get(subject_idx) { Some(Pattern::Tuple { location, elements }) => self.delete_tuple_tokens( *location, elements.last().map(|element| element.location()), ), Some(Pattern::Discard { location, .. }) => { self.discard_tuple_items(*location, elements.len()) } _ => panic!("safe: we've just checked all patterns must be discards/tuples"), } } let range = self.edits.src_span_to_lsp_range(*location); self.hovered = self.hovered || overlaps(self.params.range, range); } ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case) } } impl<'a> RedundantTupleInCaseSubject<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { edits: TextEdits::new(line_numbers), code: &module.code, extra: &module.extra, params, module: &module.ast, hovered: false, } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(self.module); if !self.hovered { return vec![]; } self.edits.edits.sort_by_key(|edit| edit.range.start); let mut actions = vec![]; CodeActionBuilder::new("Remove redundant tuples") .kind(CodeActionKind::REFACTOR_REWRITE) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(true) .push_to(&mut actions); actions } fn delete_tuple_tokens(&mut self, location: SrcSpan, last_elem_location: Option) { let tuple_code = self .code .get(location.start as usize..location.end as usize) .expect("valid span"); // Delete `#` self.edits .delete(SrcSpan::new(location.start, location.start + 1)); // Delete `(` let (lparen_offset, _) = tuple_code .match_indices('(') // Ignore in comments .find(|(i, _)| !self.extra.is_within_comment(location.start + *i as u32)) .expect("`(` not found in tuple"); self.edits.delete(SrcSpan::new( location.start + lparen_offset as u32, location.start + lparen_offset as u32 + 1, )); // Delete trailing `,` (if applicable) if let Some(last_elem_location) = last_elem_location { // Get the code after the last element until the tuple's `)` let code_after_last_elem = self .code .get(last_elem_location.end as usize..location.end as usize) .expect("valid span"); if let Some((trailing_comma_offset, _)) = code_after_last_elem .rmatch_indices(',') // Ignore in comments .find(|(i, _)| { !self .extra .is_within_comment(last_elem_location.end + *i as u32) }) { self.edits.delete(SrcSpan::new( last_elem_location.end + trailing_comma_offset as u32, last_elem_location.end + trailing_comma_offset as u32 + 1, )); } } // Delete ) self.edits .delete(SrcSpan::new(location.end - 1, location.end)); } fn discard_tuple_items(&mut self, discard_location: SrcSpan, tuple_items: usize) { // Replace the old discard with multiple discard, one for each of the // tuple items. self.edits.replace( discard_location, itertools::intersperse(iter::repeat_n("_", tuple_items), ", ").collect(), ) } } /// Builder for code action to convert `let assert` into a case expression. /// pub struct LetAssertToCase<'a> { module: &'a Module, params: &'a CodeActionParams, actions: Vec, edits: TextEdits<'a>, } impl<'ast> ast::visit::Visit<'ast> for LetAssertToCase<'_> { fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { let assignment_range = self.edits.src_span_to_lsp_range(assignment.location); let assignment_start_range = self.edits.src_span_to_lsp_range(SrcSpan { start: assignment.location.start, end: assignment.value.location().start, }); self.visit_typed_expr(&assignment.value); // Only offer the code action if the cursor is over the statement and // to prevent weird behaviour when `let assert` statements are nested, // we only check for the code action between the `let` and `=`. if !(within(self.params.range, assignment_range) && overlaps(self.params.range, assignment_start_range)) { return; } // This pattern only applies to `let assert` let AssignmentKind::Assert { message, .. } = &assignment.kind else { return; }; // Get the source code for the tested expression let location = assignment.value.location(); let expr = self .module .code .get(location.start as usize..location.end as usize) .expect("Location must be valid"); // Get the source code for the pattern let pattern_location = assignment.pattern.location(); let pattern = self .module .code .get(pattern_location.start as usize..pattern_location.end as usize) .expect("Location must be valid"); let message = message.as_ref().map(|message| { let location = message.location(); self.module .code .get(location.start as usize..location.end as usize) .expect("Location must be valid") }); let range = self.edits.src_span_to_lsp_range(assignment.location); // Figure out which variables are assigned in the pattern let variables = PatternVariableFinder::find_variables_in_pattern(&assignment.pattern); let assigned = match variables.len() { 0 => "_", 1 => variables.first().expect("Variables is length one"), _ => &format!("#({})", variables.join(", ")), }; let mut new_text = format!("let {assigned} = "); let panic_message = if let Some(message) = message { &format!("panic as {message}") } else { "panic" }; let clauses = vec![ // The existing pattern CaseClause { pattern, // `_` is not a valid expression, so if we are not // binding any variables in the pattern, we simply return Nil. expression: if assigned == "_" { "Nil" } else { assigned }, }, CaseClause { pattern: "_", expression: panic_message, }, ]; print_case_expression(range.start.character as usize, expr, clauses, &mut new_text); let uri = &self.params.text_document.uri; CodeActionBuilder::new("Convert to case") .kind(CodeActionKind::REFACTOR_REWRITE) .changes(uri.clone(), vec![TextEdit { range, new_text }]) .preferred(false) .push_to(&mut self.actions); } } impl<'a> LetAssertToCase<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, actions: Vec::new(), edits: TextEdits::new(line_numbers), } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); self.actions } } struct PatternVariableFinder { pattern_variables: Vec, } impl PatternVariableFinder { fn new() -> Self { Self { pattern_variables: Vec::new(), } } fn find_variables_in_pattern(pattern: &TypedPattern) -> Vec { let mut finder = Self::new(); finder.visit_typed_pattern(pattern); finder.pattern_variables } } impl<'ast> ast::visit::Visit<'ast> for PatternVariableFinder { fn visit_typed_pattern_variable( &mut self, _location: &'ast SrcSpan, name: &'ast EcoString, _type: &'ast Arc, _origin: &'ast VariableOrigin, ) { self.pattern_variables.push(name.clone()); } fn visit_typed_pattern_assign( &mut self, location: &'ast SrcSpan, name: &'ast EcoString, pattern: &'ast TypedPattern, ) { self.pattern_variables.push(name.clone()); ast::visit::visit_typed_pattern_assign(self, location, name, pattern); } fn visit_typed_pattern_string_prefix( &mut self, _location: &'ast SrcSpan, _left_location: &'ast SrcSpan, left_side_assignment: &'ast Option<(EcoString, SrcSpan)>, _right_location: &'ast SrcSpan, _left_side_string: &'ast EcoString, right_side_assignment: &'ast AssignName, ) { if let Some((name, _)) = left_side_assignment { self.pattern_variables.push(name.clone()); } if let AssignName::Variable(name) = right_side_assignment { self.pattern_variables.push(name.clone()); } } } pub fn code_action_inexhaustive_let_to_case( module: &Module, line_numbers: &LineNumbers, params: &CodeActionParams, error: &Option, actions: &mut Vec, ) { let Some(Error::Type { errors, .. }) = error else { return; }; let inexhaustive_assignments = errors .iter() .filter_map(|error| { if let type_::Error::InexhaustiveLetAssignment { location, missing } = error { Some((*location, missing)) } else { None } }) .collect_vec(); if inexhaustive_assignments.is_empty() { return; } for (location, missing) in inexhaustive_assignments { let mut text_edits = TextEdits::new(line_numbers); let range = text_edits.src_span_to_lsp_range(location); if !overlaps(params.range, range) { return; } let Some(Located::Statement(TypedStatement::Assignment(assignment))) = module.find_node(location.start) else { continue; }; let TypedAssignment { value, pattern, kind: AssignmentKind::Let, location, compiled_case: _, annotation: _, } = assignment.as_ref() else { continue; }; // Get the source code for the tested expression let value_location = value.location(); let expr = module .code .get(value_location.start as usize..value_location.end as usize) .expect("Location must be valid"); // Get the source code for the pattern let pattern_location = pattern.location(); let pattern_code = module .code .get(pattern_location.start as usize..pattern_location.end as usize) .expect("Location must be valid"); let range = text_edits.src_span_to_lsp_range(*location); // Figure out which variables are assigned in the pattern let variables = PatternVariableFinder::find_variables_in_pattern(pattern); let assigned = match variables.len() { 0 => "_", 1 => variables.first().expect("Variables is length one"), _ => &format!("#({})", variables.join(", ")), }; let mut new_text = format!("let {assigned} = "); print_case_expression( range.start.character as usize, expr, iter::once(CaseClause { pattern: pattern_code, expression: if assigned == "_" { "Nil" } else { assigned }, }) .chain(missing.iter().map(|pattern| CaseClause { pattern, expression: "todo", })) .collect(), &mut new_text, ); let uri = ¶ms.text_document.uri; text_edits.replace(*location, new_text); CodeActionBuilder::new("Convert to case") .kind(CodeActionKind::QUICKFIX) .changes(uri.clone(), text_edits.edits) .preferred(true) .push_to(actions); } } struct CaseClause<'a> { pattern: &'a str, expression: &'a str, } fn print_case_expression( indent_size: usize, subject: &str, clauses: Vec>, buffer: &mut String, ) { let indent = " ".repeat(indent_size); // Print the beginning of the expression buffer.push_str("case "); buffer.push_str(subject); buffer.push_str(" {"); for clause in clauses.iter() { // Print the newline and indentation for this clause buffer.push('\n'); buffer.push_str(&indent); // Indent this clause one level deeper than the case expression buffer.push_str(" "); // Print the clause buffer.push_str(clause.pattern); buffer.push_str(" -> "); buffer.push_str(clause.expression); } // If there are no clauses to print, the closing brace should be // on the same line as the opening one, with no space between. if !clauses.is_empty() { buffer.push('\n'); buffer.push_str(&indent); } buffer.push('}'); } /// Builder for code action to apply the label shorthand syntax on arguments /// where the label has the same name as the variable. /// pub struct UseLabelShorthandSyntax<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, } impl<'a> UseLabelShorthandSyntax<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); if self.edits.edits.is_empty() { return vec![]; } let mut action = Vec::with_capacity(1); CodeActionBuilder::new("Use label shorthand syntax") .kind(CodeActionKind::REFACTOR) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(false) .push_to(&mut action); action } } impl<'ast> ast::visit::Visit<'ast> for UseLabelShorthandSyntax<'_> { fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) { let arg_range = self.edits.src_span_to_lsp_range(arg.location); let is_selected = overlaps(arg_range, self.params.range); match arg { CallArg { label: Some(label), value: TypedExpr::Var { name, location, .. }, .. } if is_selected && !arg.uses_label_shorthand() && label == name => { self.edits.delete(*location) } _ => (), } ast::visit::visit_typed_call_arg(self, arg) } fn visit_typed_pattern_call_arg(&mut self, arg: &'ast CallArg) { let arg_range = self.edits.src_span_to_lsp_range(arg.location); let is_selected = overlaps(arg_range, self.params.range); match arg { CallArg { label: Some(label), value: TypedPattern::Variable { name, location, .. }, .. } if is_selected && !arg.uses_label_shorthand() && label == name => { self.edits.delete(*location) } _ => (), } ast::visit::visit_typed_pattern_call_arg(self, arg) } } /// Builder for code action to apply the fill in the missing labelled arguments /// of the selected function call. /// pub struct FillInMissingLabelledArgs<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, use_right_hand_side_location: Option, selected_call: Option>, current_function: Option<&'a TypedFunction>, } struct SelectedCall<'a> { location: SrcSpan, field_map: &'a FieldMap, arguments: Vec>, kind: SelectedCallKind, fun_type: Option>, enclosing_function: Option<&'a TypedFunction>, } enum SelectedCallKind { Value, Pattern, } impl<'a> FillInMissingLabelledArgs<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), use_right_hand_side_location: None, selected_call: None, current_function: None, } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); if let Some(SelectedCall { location: call_location, field_map, arguments, kind, fun_type, enclosing_function, }) = self.selected_call { let is_use_call = arguments.iter().any(|arg| arg.is_use_implicit_callback()); let missing_labels = field_map.missing_labels(&arguments); // If we're applying the code action to a use call, then we know // that the last missing argument is going to be implicitly inserted // by the compiler, so in that case we don't want to also add that // last label to the completions. let missing_labels = missing_labels.iter().peekable(); let mut missing_labels = if is_use_call { missing_labels.dropping_back(1) } else { missing_labels }; // If we couldn't find any missing label to insert we just return. if missing_labels.peek().is_none() { return vec![]; } // A pattern could have been written with no parentheses at all! // So we need to check for the last character to see if parentheses // are there or not before filling the arguments in let has_parentheses = ")" == code_at( self.module, SrcSpan::new(call_location.end - 1, call_location.end), ); let label_insertion_start = if has_parentheses { // If it ends with a parentheses we'll need to start inserting // right before the closing one... call_location.end - 1 } else { // ...otherwise we just append the result call_location.end }; // Now we need to figure out if there's a comma at the end of the // arguments list: // // call(one, |) // ^ Cursor here, with a comma behind // // call(one|) // ^ Cursor here, no comma behind, we'll have to add one! // let has_comma_after_last_argument = if let Some(last_arg) = arguments.iter().rfind(|arg| !arg.is_implicit()) { self.module .code .get(last_arg.location.end as usize..=label_insertion_start as usize) .is_some_and(|text| text.contains(',')) } else { false }; let variables_in_scope = enclosing_function .map(|fun| { ScopeVariableCollector::new(call_location.start).collect_from_function(fun) }) .unwrap_or_default(); let labels_list = missing_labels .map(|label| { Self::format_label(label, &kind, &fun_type, field_map, &variables_in_scope) }) .join(", "); let has_no_explicit_arguments = arguments .iter() .filter(|arg| !arg.is_implicit()) .peekable() .peek() .is_none(); let labels_list = if has_no_explicit_arguments || has_comma_after_last_argument { labels_list } else { format!(", {labels_list}") }; let edit = if has_parentheses { labels_list } else { // If the variant whose arguments we're filling in was written // with no parentheses we need to add those as well to make it a // valid constructor. format!("({labels_list})") }; self.edits.insert(label_insertion_start, edit); let mut action = Vec::with_capacity(1); CodeActionBuilder::new("Fill labels") .kind(CodeActionKind::QUICKFIX) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(true) .push_to(&mut action); return action; } vec![] } /// Formats a label for insertion. Uses `label:` syntax if there's a variable /// in scope with matching name and type, otherwise uses `label: todo`. fn format_label( label: &EcoString, kind: &SelectedCallKind, fun_type: &Option>, field_map: &FieldMap, variables_in_scope: &HashMap>, ) -> String { if matches!(kind, SelectedCallKind::Pattern) { return format!("{label}:"); } if let Some(var_type) = variables_in_scope.get(label) { let expected_type = fun_type.as_ref().and_then(|ft| { let (arg_types, _) = ft.fn_types()?; let &index = field_map.fields.get(label)?; arg_types.get(index as usize).cloned() }); if let Some(expected) = expected_type && expected.same_as(var_type) { return format!("{label}:"); } } format!("{label}: todo") } fn empty_argument(argument: &CallArg) -> CallArg<()> { CallArg { label: argument.label.clone(), location: argument.location, value: (), implicit: argument.implicit, } } } impl<'ast> ast::visit::Visit<'ast> for FillInMissingLabelledArgs<'ast> { fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { // We store the current function as the containing function while we traverse // it, allowing handling nested functions correctly. let previous_function = self.current_function; self.current_function = Some(fun); ast::visit::visit_typed_function(self, fun); self.current_function = previous_function; } fn visit_typed_use(&mut self, use_: &'ast TypedUse) { // If we're adding labels to a use call the correct location of the // function we need to add labels to is `use_right_hand_side_location`. // So we store it for when we're typing the use call. let previous = self.use_right_hand_side_location; self.use_right_hand_side_location = Some(use_.right_hand_side_location); ast::visit::visit_typed_use(self, use_); self.use_right_hand_side_location = previous; } fn visit_typed_expr_call( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, fun: &'ast TypedExpr, arguments: &'ast [TypedCallArg], ) { let call_range = self.edits.src_span_to_lsp_range(*location); if !within(self.params.range, call_range) { return; } if let Some(field_map) = fun.field_map() { let location = self.use_right_hand_side_location.unwrap_or(*location); self.selected_call = Some(SelectedCall { location, field_map, arguments: arguments.iter().map(Self::empty_argument).collect(), kind: SelectedCallKind::Value, fun_type: Some(fun.type_()), enclosing_function: self.current_function, }) } // We only want to take into account the innermost function call // containing the current selection so we can't stop at the first call // we find (the outermost one) and have to keep traversing it in case // we're inside a nested call. let previous = self.use_right_hand_side_location; self.use_right_hand_side_location = None; ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments); self.use_right_hand_side_location = previous; } fn visit_typed_pattern_constructor( &mut self, location: &'ast SrcSpan, name_location: &'ast SrcSpan, name: &'ast EcoString, arguments: &'ast Vec>, module: &'ast Option<(EcoString, SrcSpan)>, constructor: &'ast Inferred, spread: &'ast Option, type_: &'ast Arc, ) { let call_range = self.edits.src_span_to_lsp_range(*location); if !within(self.params.range, call_range) { return; } if let Some(field_map) = constructor.field_map() { self.selected_call = Some(SelectedCall { location: *location, field_map, arguments: arguments.iter().map(Self::empty_argument).collect(), kind: SelectedCallKind::Pattern, fun_type: None, enclosing_function: self.current_function, }) } ast::visit::visit_typed_pattern_constructor( self, location, name_location, name, arguments, module, constructor, spread, type_, ); } } /// Collects variables that are in scope at a given cursor position. struct ScopeVariableCollector { cursor: u32, variables: HashMap>, } impl ScopeVariableCollector { fn new(cursor: u32) -> Self { Self { cursor, variables: HashMap::new(), } } fn collect_from_function(mut self, fun: &TypedFunction) -> HashMap> { for arg in &fun.arguments { if let Some(name) = arg.get_variable_name() { _ = self.variables.insert(name.clone(), arg.type_.clone()); } } for statement in &fun.body { self.visit_typed_statement(statement); } self.variables } } impl<'ast> ast::visit::Visit<'ast> for ScopeVariableCollector { fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) { // We need to consider variables defined only before the cursor if statement.location().start >= self.cursor { return; } ast::visit::visit_typed_statement(self, statement); } fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { // if cursor is inside the assignment, we only visit the value expression, // because the variable being defined isn't in scope yet. if assignment.location.contains(self.cursor) { self.visit_typed_expr(&assignment.value); } else { self.visit_typed_pattern(&assignment.pattern); } } fn visit_typed_expr_fn( &mut self, _location: &'ast SrcSpan, _type_: &'ast Arc, _kind: &'ast FunctionLiteralKind, _arguments: &'ast [TypedArg], _body: &'ast Vec1, _return_annotation: &'ast Option, ) { // We don't descend into nested functions, as their variables aren't in // our scope } fn visit_typed_expr_block( &mut self, location: &'ast SrcSpan, statements: &'ast [TypedStatement], ) { if self.cursor >= location.end { return; } ast::visit::visit_typed_expr_block(self, location, statements); } fn visit_typed_pattern_variable( &mut self, _location: &'ast SrcSpan, name: &'ast EcoString, type_: &'ast Arc, _origin: &'ast VariableOrigin, ) { if !name.starts_with('_') { _ = self.variables.insert(name.clone(), type_.clone()); } } fn visit_typed_pattern_assign( &mut self, _location: &'ast SrcSpan, name: &'ast EcoString, pattern: &'ast Pattern>, ) { self.visit_typed_pattern(pattern); if !name.starts_with('_') { _ = self.variables.insert(name.clone(), pattern.type_().clone()); } } } struct MissingImport { location: SrcSpan, suggestions: Vec, } struct ImportSuggestion { // The name to replace with, if the user made a typo name: EcoString, // The optional module to import, if suggesting an importable module import: Option, } pub fn code_action_import_module( module: &Module, line_numbers: &LineNumbers, params: &CodeActionParams, error: &Option, actions: &mut Vec, ) { let uri = ¶ms.text_document.uri; let Some(Error::Type { errors, .. }) = error else { return; }; let missing_imports = errors .into_iter() .filter_map(|e| { if let type_::Error::UnknownModule { location, suggestions, .. } = e { suggest_imports(*location, suggestions) } else { None } }) .collect_vec(); if missing_imports.is_empty() { return; } let first_import_pos = position_of_first_definition_if_import(module, line_numbers); let first_is_import = first_import_pos.is_some(); let import_location = first_import_pos.unwrap_or_default(); let after_import_newlines = add_newlines_after_import(import_location, first_is_import, line_numbers, &module.code); for missing_import in missing_imports { let range = src_span_to_lsp_range(missing_import.location, line_numbers); if !overlaps(params.range, range) { continue; } for suggestion in missing_import.suggestions { let mut edits = vec![TextEdit { range, new_text: suggestion.name.to_string(), }]; if let Some(import) = &suggestion.import { edits.push(get_import_edit( import_location, import, &after_import_newlines, )) }; let title = match &suggestion.import { Some(import) => &format!("Import `{import}`"), _ => &format!("Did you mean `{}`", suggestion.name), }; CodeActionBuilder::new(title) .kind(CodeActionKind::QUICKFIX) .changes(uri.clone(), edits) .preferred(true) .push_to(actions); } } } fn suggest_imports( location: SrcSpan, importable_modules: &[ModuleSuggestion], ) -> Option { let suggestions = importable_modules .iter() .map(|suggestion| { let imported_name = suggestion.last_name_component(); match suggestion { ModuleSuggestion::Importable(name) => ImportSuggestion { name: imported_name.into(), import: Some(name.clone()), }, ModuleSuggestion::Imported(_) => ImportSuggestion { name: imported_name.into(), import: None, }, } }) .collect_vec(); if suggestions.is_empty() { None } else { Some(MissingImport { location, suggestions, }) } } pub fn code_action_add_missing_patterns( module: &Module, line_numbers: &LineNumbers, params: &CodeActionParams, error: &Option, actions: &mut Vec, ) { let uri = ¶ms.text_document.uri; let Some(Error::Type { errors, .. }) = error else { return; }; let missing_patterns = errors .iter() .filter_map(|error| { if let type_::Error::InexhaustiveCaseExpression { location, missing } = error { Some((*location, missing)) } else { None } }) .collect_vec(); if missing_patterns.is_empty() { return; } for (location, missing) in missing_patterns { let mut edits = TextEdits::new(line_numbers); let range = edits.src_span_to_lsp_range(location); if !overlaps(params.range, range) { return; } let Some(Located::Expression { expression: TypedExpr::Case { clauses, subjects, .. }, .. }) = module.find_node(location.start) else { continue; }; let indent_size = count_indentation(&module.code, edits.line_numbers, range.start.line); let indent = " ".repeat(indent_size); // Insert the missing patterns just after the final clause, or just before // the closing brace if there are no clauses let insert_at = clauses .last() .map(|clause| clause.location.end) .unwrap_or(location.end - 1); for pattern in missing { let new_text = format!("\n{indent} {pattern} -> todo"); edits.insert(insert_at, new_text); } // Add a newline + indent after the last pattern if there are no clauses // // This improves the generated code for this case: // ```gleam // case True {} // ``` // This produces: // ```gleam // case True { // True -> todo // False -> todo // } // ``` // Instead of: // ```gleam // case True { // True -> todo // False -> todo} // ``` // if clauses.is_empty() { let last_subject_location = subjects .last() .expect("Case expressions have at least one subject") .location() .end; // Find the opening brace of the case expression let chars = module.code[last_subject_location as usize..].chars(); let mut start_brace_location = last_subject_location; for char in chars { start_brace_location += 1; if char == '{' { break; } } // Remove any blank spaces/lines between the start brace and end brace edits.delete(SrcSpan::new(start_brace_location, insert_at)); edits.insert(insert_at, format!("\n{indent}")); } CodeActionBuilder::new("Add missing patterns") .kind(CodeActionKind::QUICKFIX) .changes(uri.clone(), edits.edits) .preferred(true) .push_to(actions); } } /// Builder for code action to add annotations to an assignment or function /// pub struct AddAnnotations<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, printer: Printer<'a>, } impl<'ast> ast::visit::Visit<'ast> for AddAnnotations<'_> { fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { self.visit_typed_expr(&assignment.value); // We only offer this code action between `let` and `=`, because // otherwise it could lead to confusing behaviour if inside a block // which is part of a let binding. let pattern_location = assignment.pattern.location(); let location = SrcSpan::new(assignment.location.start, pattern_location.end); let code_action_range = self.edits.src_span_to_lsp_range(location); // Only offer the code action if the cursor is over the statement if !overlaps(code_action_range, self.params.range) { return; } // We don't need to add an annotation if there already is one if assignment.annotation.is_some() { return; } // Various expressions such as pipelines and `use` expressions generate assignments // internally. However, these cannot be annotated and so we don't offer a code action here. if matches!(assignment.kind, AssignmentKind::Generated) { return; } self.edits.insert( pattern_location.end, format!(": {}", self.printer.print_type(&assignment.type_())), ); } fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) { // Since type variable names are local to definitions, any type variables // in other parts of the module shouldn't affect what we print for the // annotations of this constant. self.printer.clear_type_variables(); let code_action_range = self.edits.src_span_to_lsp_range(constant.location); // Only offer the code action if the cursor is over the statement if !overlaps(code_action_range, self.params.range) { return; } // We don't need to add an annotation if there already is one if constant.annotation.is_some() { return; } self.edits.insert( constant.name_location.end, format!(": {}", self.printer.print_type(&constant.type_)), ); } fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { // Since type variable names are local to definitions, any type variables // in other parts of the module shouldn't affect what we print for the // annotations of this functions. The only variables which cannot clash // are ones defined in the signature of this function, which we register // when we visit the parameters of this function inside `collect_type_variables`. self.printer.clear_type_variables(); collect_type_variables(&mut self.printer, fun); ast::visit::visit_typed_function(self, fun); let code_action_range = self.edits.src_span_to_lsp_range( fun.body_start .map(|body_start| SrcSpan { start: fun.location.start, end: body_start, }) .unwrap_or(fun.location), ); // Only offer the code action if the cursor is over the statement if !overlaps(code_action_range, self.params.range) { return; } // Annotate each argument separately for argument in fun.arguments.iter() { // Don't annotate the argument if it's already annotated if argument.annotation.is_some() { continue; } self.edits.insert( argument.location.end, format!(": {}", self.printer.print_type(&argument.type_)), ); } // Annotate the return type if it isn't already annotated if fun.return_annotation.is_none() { self.edits.insert( fun.location.end, format!(" -> {}", self.printer.print_type(&fun.return_type)), ); } } fn visit_typed_expr_fn( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, kind: &'ast FunctionLiteralKind, arguments: &'ast [TypedArg], body: &'ast Vec1, return_annotation: &'ast Option, ) { ast::visit::visit_typed_expr_fn( self, location, type_, kind, arguments, body, return_annotation, ); // If the function doesn't have a head, we can't annotate it let location = match kind { // Function captures don't need any type annotations FunctionLiteralKind::Capture { .. } => return, FunctionLiteralKind::Anonymous { head } => head, FunctionLiteralKind::Use { location } => location, }; let code_action_range = self.edits.src_span_to_lsp_range(*location); // Only offer the code action if the cursor is over the expression if !overlaps(code_action_range, self.params.range) { return; } // Annotate each argument separately for argument in arguments.iter() { // Don't annotate the argument if it's already annotated if argument.annotation.is_some() { continue; } self.edits.insert( argument.location.end, format!(": {}", self.printer.print_type(&argument.type_)), ); } // Annotate the return type if it isn't already annotated, and this is // an anonymous function. if return_annotation.is_none() && matches!(kind, FunctionLiteralKind::Anonymous { .. }) { let return_type = &type_.return_type().expect("Type must be a function"); let pretty_type = self.printer.print_type(return_type); self.edits .insert(location.end, format!(" -> {pretty_type}")); } } } impl<'a> AddAnnotations<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), // We need to use the same printer for all the edits because otherwise // we could get duplicate type variable names. printer: Printer::new_without_type_variables(&module.ast.names), } } pub fn code_action(mut self, actions: &mut Vec) { self.visit_typed_module(&self.module.ast); let uri = &self.params.text_document.uri; let title = match self.edits.edits.len() { // We don't offer a code action if there is no action to perform 0 => return, 1 => "Add type annotation", _ => "Add type annotations", }; CodeActionBuilder::new(title) .kind(CodeActionKind::REFACTOR) .changes(uri.clone(), self.edits.edits) .preferred(false) .push_to(actions); } } /// Code action to add type annotations to all top level definitions /// pub struct AnnotateTopLevelDefinitions<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, is_hovering_definition_requiring_annotations: bool, } impl<'a> AnnotateTopLevelDefinitions<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), is_hovering_definition_requiring_annotations: false, } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); // We only want to trigger the action if we're over one of the definition // which is lacking some annotations in the module if !self.is_hovering_definition_requiring_annotations { return vec![]; }; let mut action = Vec::with_capacity(1); CodeActionBuilder::new("Annotate all top level definitions") .kind(CodeActionKind::REFACTOR_REWRITE) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(false) .push_to(&mut action); action } } impl<'ast> ast::visit::Visit<'ast> for AnnotateTopLevelDefinitions<'_> { fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) { let code_action_range = self.edits.src_span_to_lsp_range(constant.location); // We don't need to add an annotation if there already is one if constant.annotation.is_some() { return; } // We're hovering definition which needs some annotations if overlaps(code_action_range, self.params.range) { self.is_hovering_definition_requiring_annotations = true; } self.edits.insert( constant.name_location.end, format!( ": {}", // Create new printer to ignore type variables from other definitions Printer::new_without_type_variables(&self.module.ast.names) .print_type(&constant.type_) ), ); } fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { // Don't annotate already annotated arguments let arguments_to_annotate = fun .arguments .iter() .filter(|argument| argument.annotation.is_none()) .collect::>(); let needs_return_annotation = fun.return_annotation.is_none(); if arguments_to_annotate.is_empty() && !needs_return_annotation { return; } let code_action_range = self.edits.src_span_to_lsp_range(fun.location); if overlaps(code_action_range, self.params.range) { self.is_hovering_definition_requiring_annotations = true; } // Create new printer to ignore type variables from other definitions let mut printer = Printer::new_without_type_variables(&self.module.ast.names); collect_type_variables(&mut printer, fun); // Annotate each argument separately for argument in arguments_to_annotate { self.edits.insert( argument.location.end, format!(": {}", printer.print_type(&argument.type_)), ); } // Annotate the return type if it isn't already annotated if needs_return_annotation { self.edits.insert( fun.location.end, format!(" -> {}", printer.print_type(&fun.return_type)), ); } } } struct TypeVariableCollector<'a, 'b> { printer: &'a mut Printer<'b>, } /// Collect type variables defined within a function and register them for a /// `Printer` fn collect_type_variables(printer: &mut Printer<'_>, function: &TypedFunction) { TypeVariableCollector { printer }.visit_typed_function(function); } impl<'ast, 'a, 'b> ast::visit::Visit<'ast> for TypeVariableCollector<'a, 'b> { fn visit_type_ast_var(&mut self, _location: &'ast SrcSpan, name: &'ast EcoString) { // Register this type variable so that we don't duplicate names when // adding annotations. self.printer.register_type_variable(name.clone()); } } pub struct QualifiedConstructor<'a> { import: &'a Import, used_name: EcoString, constructor: EcoString, layer: ast::Layer, } impl QualifiedConstructor<'_> { fn constructor_import(&self) -> String { if self.layer.is_value() { self.constructor.to_string() } else { format!("type {}", self.constructor) } } } pub struct QualifiedToUnqualifiedImportFirstPass<'a, IO> { module: &'a Module, compiler: &'a LspProjectCompiler>, params: &'a CodeActionParams, line_numbers: &'a LineNumbers, qualified_constructor: Option>, } impl<'a, IO> QualifiedToUnqualifiedImportFirstPass<'a, IO> { fn new( module: &'a Module, compiler: &'a LspProjectCompiler>, params: &'a CodeActionParams, line_numbers: &'a LineNumbers, ) -> Self { Self { module, compiler, params, line_numbers, qualified_constructor: None, } } fn get_module_import( &self, module_name: &EcoString, constructor: &EcoString, layer: ast::Layer, ) -> Option<&'a Import> { let mut matching_import = None; for import in &self.module.ast.definitions.imports { if import.used_name().as_deref() == Some(module_name) && let Some(module) = self.compiler.get_module_interface(&import.module) { // If the import is the one we're referring to, we see if the // referred module exports the type/value we are trying to // unqualify: we don't want to offer the action indiscriminately if // it would generate invalid code! let module_exports_constructor = match layer { ast::Layer::Value => module.get_public_value(constructor).is_some(), ast::Layer::Type => module.get_public_type(constructor).is_some(), }; if module_exports_constructor { matching_import = Some(import); } } else { // If the import refers to another module we still want to check // if in its unqualified import list there is a name that's equal // to the one we're trying to unqualify. In this case we can't // offer the action as it would generate invalid code. // // For example: // ```gleam // import wibble.{Some} // import option // // pub fn something() { // option.Some(1) // ^^^^ We can't unqualify this because `Some` is already // imported unqualified from the `wibble` module // } // ``` // let imported = match layer { ast::Layer::Value => &import.unqualified_values, ast::Layer::Type => &import.unqualified_types, }; let constructor_already_imported_by_other_module = imported .iter() .any(|value| value.used_name() == constructor); if constructor_already_imported_by_other_module { return None; } } } matching_import } } impl<'ast, IO> ast::visit::Visit<'ast> for QualifiedToUnqualifiedImportFirstPass<'ast, IO> { fn visit_type_ast_constructor( &mut self, location: &'ast SrcSpan, name_location: &'ast SrcSpan, module: &'ast Option<(EcoString, SrcSpan)>, name: &'ast EcoString, arguments: &'ast [ast::TypeAst], arguments_types: Option>>, ) { let range = src_span_to_lsp_range(*location, self.line_numbers); if overlaps(self.params.range, range) && let Some((module_alias, _)) = module && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Type) { self.qualified_constructor = Some(QualifiedConstructor { import, used_name: module_alias.clone(), constructor: name.clone(), layer: ast::Layer::Type, }); } ast::visit::visit_type_ast_constructor( self, location, name_location, module, name, arguments, arguments_types, ); } fn visit_typed_expr_module_select( &mut self, location: &'ast SrcSpan, field_start: &'ast u32, type_: &'ast Arc, label: &'ast EcoString, module_name: &'ast EcoString, module_alias: &'ast EcoString, constructor: &'ast ModuleValueConstructor, ) { // When hovering over a Record Value Constructor, we want to expand the source span to // include the module name: // option.Some // ↑ // This allows us to offer a code action when hovering over the module name. let range = src_span_to_lsp_range(*location, self.line_numbers); if overlaps(self.params.range, range) && let ModuleValueConstructor::Record { name: constructor_name, .. } = constructor && let Some(import) = self.get_module_import(module_alias, constructor_name, ast::Layer::Value) { self.qualified_constructor = Some(QualifiedConstructor { import, used_name: module_alias.clone(), constructor: constructor_name.clone(), layer: ast::Layer::Value, }); } ast::visit::visit_typed_expr_module_select( self, location, field_start, type_, label, module_name, module_alias, constructor, ) } fn visit_typed_pattern_constructor( &mut self, location: &'ast SrcSpan, name_location: &'ast SrcSpan, name: &'ast EcoString, arguments: &'ast Vec>, module: &'ast Option<(EcoString, SrcSpan)>, constructor: &'ast Inferred, spread: &'ast Option, type_: &'ast Arc, ) { let range = src_span_to_lsp_range(*location, self.line_numbers); if overlaps(self.params.range, range) && let Some((module_alias, _)) = module && let Inferred::Known(_) = constructor && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Value) { self.qualified_constructor = Some(QualifiedConstructor { import, used_name: module_alias.clone(), constructor: name.clone(), layer: ast::Layer::Value, }); } ast::visit::visit_typed_pattern_constructor( self, location, name_location, name, arguments, module, constructor, spread, type_, ); } fn visit_typed_constant_record( &mut self, location: &'ast SrcSpan, module: &'ast Option<(EcoString, SrcSpan)>, name: &'ast EcoString, arguments: &'ast Vec>, tag: &'ast EcoString, type_: &'ast Arc, field_map: &'ast Inferred, record_constructor: &'ast Option>, ) { let range = src_span_to_lsp_range(*location, self.line_numbers); if overlaps(self.params.range, range) && let Some((module_alias, _)) = module && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Value) { self.qualified_constructor = Some(QualifiedConstructor { import, used_name: module_alias.clone(), constructor: name.clone(), layer: ast::Layer::Value, }); } ast::visit::visit_typed_constant_record( self, location, module, name, arguments, tag, type_, field_map, record_constructor, ); } fn visit_typed_constant_var( &mut self, location: &'ast SrcSpan, module: &'ast Option<(EcoString, SrcSpan)>, name: &'ast EcoString, constructor: &'ast Option>, type_: &'ast Arc, ) { let range = src_span_to_lsp_range(*location, self.line_numbers); if overlaps(self.params.range, range) && let Some((module_alias, _)) = module && let Some(constructor) = constructor && let type_::ValueConstructorVariant::Record { .. } = &constructor.variant && let Some(import) = self.get_module_import(module_alias, name, ast::Layer::Value) { self.qualified_constructor = Some(QualifiedConstructor { import, used_name: module_alias.clone(), constructor: name.clone(), layer: ast::Layer::Value, }); } ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_); } } pub struct QualifiedToUnqualifiedImportSecondPass<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, qualified_constructor: QualifiedConstructor<'a>, } impl<'a> QualifiedToUnqualifiedImportSecondPass<'a> { pub fn new( module: &'a Module, params: &'a CodeActionParams, line_numbers: &'a LineNumbers, qualified_constructor: QualifiedConstructor<'a>, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), qualified_constructor, } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); if self.edits.edits.is_empty() { return vec![]; } self.edit_import(); let mut action = Vec::with_capacity(1); CodeActionBuilder::new(&format!( "Unqualify {}.{}", self.qualified_constructor.used_name, self.qualified_constructor.constructor )) .kind(CodeActionKind::REFACTOR) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(false) .push_to(&mut action); action } fn remove_module_qualifier(&mut self, location: SrcSpan) { self.edits.delete(SrcSpan { start: location.start, end: location.start + self.qualified_constructor.used_name.len() as u32 + 1, // plus . }) } fn edit_import(&mut self) { let QualifiedConstructor { constructor, layer, import, .. } = &self.qualified_constructor; let is_imported = if layer.is_value() { import .unqualified_values .iter() .any(|value| value.used_name() == constructor) } else { import .unqualified_types .iter() .any(|type_| type_.used_name() == constructor) }; if is_imported { return; } let (insert_pos, new_text) = edits::insert_unqualified_import( import, &self.module.code, self.qualified_constructor.constructor_import(), ); let span = SrcSpan::new(insert_pos, insert_pos); self.edits.replace(span, new_text); } } impl<'ast> ast::visit::Visit<'ast> for QualifiedToUnqualifiedImportSecondPass<'ast> { fn visit_type_ast_constructor( &mut self, location: &'ast SrcSpan, name_location: &'ast SrcSpan, module: &'ast Option<(EcoString, SrcSpan)>, name: &'ast EcoString, arguments: &'ast [ast::TypeAst], arguments_types: Option>>, ) { if let Some((module_name, _)) = module { let QualifiedConstructor { used_name, constructor, layer, .. } = &self.qualified_constructor; if !layer.is_value() && used_name == module_name && name == constructor { self.remove_module_qualifier(*location); } } ast::visit::visit_type_ast_constructor( self, location, name_location, module, name, arguments, arguments_types, ); } fn visit_typed_expr_module_select( &mut self, location: &'ast SrcSpan, field_start: &'ast u32, type_: &'ast Arc, label: &'ast EcoString, module_name: &'ast EcoString, module_alias: &'ast EcoString, constructor: &'ast ModuleValueConstructor, ) { if let ModuleValueConstructor::Record { name, .. } = constructor { let QualifiedConstructor { used_name, constructor, layer, .. } = &self.qualified_constructor; if layer.is_value() && used_name == module_alias && name == constructor { self.remove_module_qualifier(*location); } } ast::visit::visit_typed_expr_module_select( self, location, field_start, type_, label, module_name, module_alias, constructor, ) } fn visit_typed_pattern_constructor( &mut self, location: &'ast SrcSpan, name_location: &'ast SrcSpan, name: &'ast EcoString, arguments: &'ast Vec>, module: &'ast Option<(EcoString, SrcSpan)>, constructor: &'ast Inferred, spread: &'ast Option, type_: &'ast Arc, ) { if let Some((module_alias, _)) = module && let Inferred::Known(_) = constructor { let QualifiedConstructor { used_name, constructor, layer, .. } = &self.qualified_constructor; if layer.is_value() && used_name == module_alias && name == constructor { self.remove_module_qualifier(*location); } } ast::visit::visit_typed_pattern_constructor( self, location, name_location, name, arguments, module, constructor, spread, type_, ); } fn visit_typed_constant_record( &mut self, location: &'ast SrcSpan, module: &'ast Option<(EcoString, SrcSpan)>, name: &'ast EcoString, arguments: &'ast Vec>, tag: &'ast EcoString, type_: &'ast Arc, field_map: &'ast Inferred, record_constructor: &'ast Option>, ) { if let Some((module_alias, _)) = module { let QualifiedConstructor { used_name, constructor, layer, .. } = &self.qualified_constructor; if layer.is_value() && used_name == module_alias && name == constructor { self.remove_module_qualifier(*location); } } ast::visit::visit_typed_constant_record( self, location, module, name, arguments, tag, type_, field_map, record_constructor, ); } fn visit_typed_constant_var( &mut self, location: &'ast SrcSpan, module: &'ast Option<(EcoString, SrcSpan)>, name: &'ast EcoString, constructor: &'ast Option>, type_: &'ast Arc, ) { if let Some((module_alias, _)) = module { let QualifiedConstructor { used_name, constructor: wanted_constructor, layer, .. } = &self.qualified_constructor; if layer.is_value() && used_name == module_alias && name == wanted_constructor { self.remove_module_qualifier(*location); } } ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_); } } pub fn code_action_convert_qualified_constructor_to_unqualified( module: &Module, compiler: &LspProjectCompiler>, line_numbers: &LineNumbers, params: &CodeActionParams, actions: &mut Vec, ) { let mut first_pass = QualifiedToUnqualifiedImportFirstPass::new(module, compiler, params, line_numbers); first_pass.visit_typed_module(&module.ast); let Some(qualified_constructor) = first_pass.qualified_constructor else { return; }; let second_pass = QualifiedToUnqualifiedImportSecondPass::new( module, params, line_numbers, qualified_constructor, ); let new_actions = second_pass.code_actions(); actions.extend(new_actions); } struct UnqualifiedConstructor<'a> { module_name: EcoString, constructor: &'a ast::UnqualifiedImport, layer: ast::Layer, } struct UnqualifiedToQualifiedImportFirstPass<'a> { module: &'a Module, params: &'a CodeActionParams, line_numbers: &'a LineNumbers, unqualified_constructor: Option>, } impl<'a> UnqualifiedToQualifiedImportFirstPass<'a> { fn new( module: &'a Module, params: &'a CodeActionParams, line_numbers: &'a LineNumbers, ) -> Self { Self { module, params, line_numbers, unqualified_constructor: None, } } fn get_module_import_from_value_constructor( &mut self, module_name: &EcoString, constructor_name: &EcoString, ) { self.unqualified_constructor = self .module .ast .definitions .imports .iter() .filter(|import| import.module == *module_name) .find_map(|import| { import .unqualified_values .iter() .find(|value| value.used_name() == constructor_name) .and_then(|value| { Some(UnqualifiedConstructor { constructor: value, module_name: import.used_name()?, layer: ast::Layer::Value, }) }) }) } fn get_module_import_from_type_constructor(&mut self, constructor_name: &EcoString) { self.unqualified_constructor = self.module .ast .definitions .imports .iter() .find_map(|import| { if let Some(ty) = import .unqualified_types .iter() .find(|ty| ty.used_name() == constructor_name) { return Some(UnqualifiedConstructor { constructor: ty, module_name: import.used_name()?, layer: ast::Layer::Type, }); } None }) } } impl<'ast> ast::visit::Visit<'ast> for UnqualifiedToQualifiedImportFirstPass<'ast> { fn visit_type_ast_constructor( &mut self, location: &'ast SrcSpan, name_location: &'ast SrcSpan, module: &'ast Option<(EcoString, SrcSpan)>, name: &'ast EcoString, arguments: &'ast [ast::TypeAst], arguments_types: Option>>, ) { if module.is_none() && overlaps( self.params.range, src_span_to_lsp_range(*location, self.line_numbers), ) { self.get_module_import_from_type_constructor(name); } ast::visit::visit_type_ast_constructor( self, location, name_location, module, name, arguments, arguments_types, ); } fn visit_typed_expr_var( &mut self, location: &'ast SrcSpan, constructor: &'ast ValueConstructor, name: &'ast EcoString, ) { let range = src_span_to_lsp_range(*location, self.line_numbers); if overlaps(self.params.range, range) && let Some(module_name) = match &constructor.variant { type_::ValueConstructorVariant::ModuleConstant { module, .. } | type_::ValueConstructorVariant::ModuleFn { module, .. } | type_::ValueConstructorVariant::Record { module, .. } => Some(module), type_::ValueConstructorVariant::LocalVariable { .. } => None, } { self.get_module_import_from_value_constructor(module_name, name); } ast::visit::visit_typed_expr_var(self, location, constructor, name); } fn visit_typed_pattern_constructor( &mut self, location: &'ast SrcSpan, name_location: &'ast SrcSpan, name: &'ast EcoString, arguments: &'ast Vec>, module: &'ast Option<(EcoString, SrcSpan)>, constructor: &'ast Inferred, spread: &'ast Option, type_: &'ast Arc, ) { if module.is_none() && overlaps( self.params.range, src_span_to_lsp_range(*location, self.line_numbers), ) && let Inferred::Known(constructor) = constructor { self.get_module_import_from_value_constructor(&constructor.module, name); } ast::visit::visit_typed_pattern_constructor( self, location, name_location, name, arguments, module, constructor, spread, type_, ); } fn visit_typed_constant_record( &mut self, location: &'ast SrcSpan, module: &'ast Option<(EcoString, SrcSpan)>, name: &'ast EcoString, arguments: &'ast Vec>, _tag: &'ast EcoString, _type_: &'ast Arc, _field_map: &'ast Inferred, record_constructor: &'ast Option>, ) { if module.is_none() && overlaps( self.params.range, src_span_to_lsp_range(*location, self.line_numbers), ) && let Some(record_constructor) = record_constructor && let Some(module_name) = match &record_constructor.variant { type_::ValueConstructorVariant::ModuleConstant { module, .. } | type_::ValueConstructorVariant::ModuleFn { module, .. } | type_::ValueConstructorVariant::Record { module, .. } => Some(module), type_::ValueConstructorVariant::LocalVariable { .. } => None, } { self.get_module_import_from_value_constructor(module_name, name); } ast::visit::visit_typed_constant_record( self, location, module, name, arguments, _tag, _type_, _field_map, record_constructor, ); } fn visit_typed_constant_var( &mut self, location: &'ast SrcSpan, module: &'ast Option<(EcoString, SrcSpan)>, name: &'ast EcoString, constructor: &'ast Option>, type_: &'ast Arc, ) { if module.is_none() && overlaps( self.params.range, src_span_to_lsp_range(*location, self.line_numbers), ) && let Some(constructor) = constructor && let Some(module_name) = match &constructor.variant { type_::ValueConstructorVariant::ModuleConstant { module, .. } | type_::ValueConstructorVariant::ModuleFn { module, .. } | type_::ValueConstructorVariant::Record { module, .. } => Some(module), type_::ValueConstructorVariant::LocalVariable { .. } => None, } { self.get_module_import_from_value_constructor(module_name, name); } ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_); } } struct UnqualifiedToQualifiedImportSecondPass<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, unqualified_constructor: UnqualifiedConstructor<'a>, } impl<'a> UnqualifiedToQualifiedImportSecondPass<'a> { pub fn new( module: &'a Module, params: &'a CodeActionParams, line_numbers: &'a LineNumbers, unqualified_constructor: UnqualifiedConstructor<'a>, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), unqualified_constructor, } } fn add_module_qualifier(&mut self, location: SrcSpan) { let src_span = SrcSpan::new( location.start, location.start + self.unqualified_constructor.constructor.used_name().len() as u32, ); self.edits.replace( src_span, format!( "{}.{}", self.unqualified_constructor.module_name, self.unqualified_constructor.constructor.name ), ); } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); if self.edits.edits.is_empty() { return vec![]; } self.edit_import(); let mut action = Vec::with_capacity(1); let UnqualifiedConstructor { module_name, constructor, .. } = self.unqualified_constructor; CodeActionBuilder::new(&format!( "Qualify {} as {}.{}", constructor.used_name(), module_name, constructor.name, )) .kind(CodeActionKind::REFACTOR) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(false) .push_to(&mut action); action } fn edit_import(&mut self) { let UnqualifiedConstructor { constructor: ast::UnqualifiedImport { location: constructor_import_span, .. }, .. } = self.unqualified_constructor; let mut last_char_pos = constructor_import_span.end as usize; while self.module.code.get(last_char_pos..last_char_pos + 1) == Some(" ") { last_char_pos += 1; } if self.module.code.get(last_char_pos..last_char_pos + 1) == Some(",") { last_char_pos += 1; } if self.module.code.get(last_char_pos..last_char_pos + 1) == Some(" ") { last_char_pos += 1; } self.edits.delete(SrcSpan::new( constructor_import_span.start, last_char_pos as u32, )); } } impl<'ast> ast::visit::Visit<'ast> for UnqualifiedToQualifiedImportSecondPass<'ast> { fn visit_type_ast_constructor( &mut self, location: &'ast SrcSpan, name_location: &'ast SrcSpan, module: &'ast Option<(EcoString, SrcSpan)>, name: &'ast EcoString, arguments: &'ast [ast::TypeAst], arguments_types: Option>>, ) { if module.is_none() { let UnqualifiedConstructor { constructor, layer, .. } = &self.unqualified_constructor; if !layer.is_value() && constructor.used_name() == name { self.add_module_qualifier(*location); } } ast::visit::visit_type_ast_constructor( self, location, name_location, module, name, arguments, arguments_types, ); } fn visit_typed_expr_var( &mut self, location: &'ast SrcSpan, constructor: &'ast ValueConstructor, name: &'ast EcoString, ) { let UnqualifiedConstructor { constructor: wanted_constructor, layer, .. } = &self.unqualified_constructor; if layer.is_value() && wanted_constructor.used_name() == name && !constructor.is_local_variable() { self.add_module_qualifier(*location); } ast::visit::visit_typed_expr_var(self, location, constructor, name); } fn visit_typed_pattern_constructor( &mut self, location: &'ast SrcSpan, name_location: &'ast SrcSpan, name: &'ast EcoString, arguments: &'ast Vec>, module: &'ast Option<(EcoString, SrcSpan)>, constructor: &'ast Inferred, spread: &'ast Option, type_: &'ast Arc, ) { if module.is_none() { let UnqualifiedConstructor { constructor: wanted_constructor, layer, .. } = &self.unqualified_constructor; if layer.is_value() && wanted_constructor.used_name() == name { self.add_module_qualifier(*location); } } ast::visit::visit_typed_pattern_constructor( self, location, name_location, name, arguments, module, constructor, spread, type_, ); } fn visit_typed_constant_record( &mut self, location: &'ast SrcSpan, module: &'ast Option<(EcoString, SrcSpan)>, name: &'ast EcoString, arguments: &'ast Vec>, tag: &'ast EcoString, type_: &'ast Arc, field_map: &'ast Inferred, record_constructor: &'ast Option>, ) { if module.is_none() { let UnqualifiedConstructor { constructor: wanted_constructor, layer, .. } = &self.unqualified_constructor; if layer.is_value() && wanted_constructor.used_name() == name { self.add_module_qualifier(*location); } } ast::visit::visit_typed_constant_record( self, location, module, name, arguments, tag, type_, field_map, record_constructor, ); } fn visit_typed_constant_var( &mut self, location: &'ast SrcSpan, module: &'ast Option<(EcoString, SrcSpan)>, name: &'ast EcoString, constructor: &'ast Option>, type_: &'ast Arc, ) { if module.is_none() { let UnqualifiedConstructor { constructor: wanted_constructor, layer, .. } = &self.unqualified_constructor; if layer.is_value() && wanted_constructor.used_name() == name { self.add_module_qualifier(*location); } } ast::visit::visit_typed_constant_var(self, location, module, name, constructor, type_); } } pub fn code_action_convert_unqualified_constructor_to_qualified( module: &Module, line_numbers: &LineNumbers, params: &CodeActionParams, actions: &mut Vec, ) { let mut first_pass = UnqualifiedToQualifiedImportFirstPass::new(module, params, line_numbers); first_pass.visit_typed_module(&module.ast); let Some(unqualified_constructor) = first_pass.unqualified_constructor else { return; }; let second_pass = UnqualifiedToQualifiedImportSecondPass::new( module, params, line_numbers, unqualified_constructor, ); let new_actions = second_pass.code_actions(); actions.extend(new_actions); } /// Builder for code action to apply the convert from use action, turning a use /// expression into a regular function call. /// pub struct ConvertFromUse<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, selected_use: Option<&'a TypedUse>, } impl<'a> ConvertFromUse<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), selected_use: None, } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); let Some(use_) = self.selected_use else { return vec![]; }; let TypedExpr::Call { arguments, fun, .. } = use_.call.as_ref() else { return vec![]; }; // If the use callback we're desugaring is using labels, that means we // have to add the last argument's label when writing the callback; // otherwise, it would result in invalid code. // // use acc, item <- list.fold(over: list, from: 1) // todo // // Needs to be rewritten as: // // list.fold(over: list, from: 1, with: fn(acc, item) { ... }) // ^^^^^ We cannot forget to add this label back! // let callback_label = if arguments.iter().any(|arg| arg.label.is_some()) { fun.field_map() .and_then(|field_map| field_map.missing_labels(arguments).last().cloned()) .map(|label| eco_format!("{label}: ")) .unwrap_or(EcoString::from("")) } else { EcoString::from("") }; // The use callback is not necessarily the last argument. If you have // the following function: `wibble(a a, b b) { todo }` // And use it like this: `use <- wibble(b: 1)`, the first argument `a` // is going to be the use callback, not the last one! let use_callback = arguments.iter().find(|arg| arg.is_use_implicit_callback()); let Some(CallArg { implicit: Some(ImplicitCallArgOrigin::Use), value: TypedExpr::Fn { body, type_, .. }, .. }) = use_callback else { return vec![]; }; // If there's arguments on the left hand side of the function we extract // those so we can paste them back as the anonymous function arguments. let assignments = if type_.fn_arity().is_some_and(|arity| arity >= 1) { let assignments_range = use_.assignments_location.start as usize..use_.assignments_location.end as usize; self.module .code .get(assignments_range) .expect("use assignments") } else { "" }; // We first delete everything on the left hand side of use and the use // arrow. self.edits.delete(SrcSpan { start: use_.location.start, end: use_.right_hand_side_location.start, }); let use_line_end = use_.right_hand_side_location.end; let use_rhs_function_has_some_explicit_arguments = arguments .iter() .filter(|argument| !argument.is_use_implicit_callback()) .peekable() .peek() .is_some(); let use_rhs_function_ends_with_closed_parentheses = self .module .code .get(use_line_end as usize - 1..use_line_end as usize) == Some(")"); let last_explicit_arg = arguments.iter().rfind(|argument| !argument.is_implicit()); let last_arg_end = last_explicit_arg.map_or(use_line_end - 1, |arg| arg.location.end); // This is the piece of code between the end of the last argument and // the end of the use_expression: // // use <- wibble(a, b, ) // ^^^^^ This piece right here, from `,` included // up to `)` excluded. // let text_after_last_argument = self .module .code .get(last_arg_end as usize..use_line_end as usize - 1); let use_rhs_has_comma_after_last_argument = text_after_last_argument.is_some_and(|code| code.contains(',')); let needs_space_before_callback = text_after_last_argument.is_some_and(|code| !code.is_empty() && !code.ends_with(' ')); if use_rhs_function_ends_with_closed_parentheses { // If the function on the right hand side of use ends with a closed // parentheses then we have to remove it and add it later at the end // of the anonymous function we're inserting. // // use <- wibble() // ^ To add the fn() we need to first remove this // // So here we write over the last closed parentheses to remove it. let callback_start = format!("{callback_label}fn({assignments}) {{"); self.edits.replace( SrcSpan { start: use_line_end - 1, end: use_line_end, }, // If the function on the rhs of use has other orguments besides // the implicit fn expression then we need to put a comma after // the last argument. if use_rhs_function_has_some_explicit_arguments && !use_rhs_has_comma_after_last_argument { format!(", {callback_start}") } else if needs_space_before_callback { format!(" {callback_start}") } else { callback_start.to_string() }, ) } else { // On the other hand, if the function on the right hand side doesn't // end with a closed parenthese then we have to manually add it. // // use <- wibble // ^ No parentheses // self.edits .insert(use_line_end, format!("(fn({assignments}) {{")) }; // Then we have to increase indentation for all the lines of the use // body. let first_fn_expression_range = self.edits.src_span_to_lsp_range(body.first().location()); let use_body_range = self.edits.src_span_to_lsp_range(use_.call.location()); for line in first_fn_expression_range.start.line..=use_body_range.end.line { self.edits.edits.push(TextEdit { range: Range { start: Position { line, character: 0 }, end: Position { line, character: 0 }, }, new_text: " ".to_string(), }) } let final_line_indentation = " ".repeat(use_body_range.start.character as usize); self.edits.insert( use_.call.location().end, format!("\n{final_line_indentation}}})"), ); let mut action = Vec::with_capacity(1); CodeActionBuilder::new("Convert from `use`") .kind(CodeActionKind::REFACTOR_REWRITE) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(false) .push_to(&mut action); action } } impl<'ast> ast::visit::Visit<'ast> for ConvertFromUse<'ast> { fn visit_typed_use(&mut self, use_: &'ast TypedUse) { let use_range = self.edits.src_span_to_lsp_range(use_.location); // If the use expression is using patterns that are not just variable // assignments then we can't automatically rewrite it as it would result // in a syntax error as we can't pattern match in an anonymous function // head. // At the same time we can't safely add bindings inside the anonymous // function body by picking placeholder names as we'd risk shadowing // variables coming from the outer scope. // So we just skip those use expressions we can't safely rewrite! if within(self.params.range, use_range) && use_ .assignments .iter() .all(|assignment| assignment.pattern.is_variable()) { self.selected_use = Some(use_); } // We still want to visit the use expression so that we always end up // picking the innermost, most relevant use under the cursor. self.visit_typed_expr(&use_.call); } } /// Builder for code action to apply the convert to use action. /// pub struct ConvertToUse<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, selected_call: Option, } /// All the locations we'll need to transform a function call into a use /// expression. /// struct CallLocations { call_span: SrcSpan, called_function_span: SrcSpan, callback_arguments_span: Option, arg_before_callback_span: Option, callback_body_span: SrcSpan, } impl<'a> ConvertToUse<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), selected_call: None, } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); let Some(CallLocations { call_span, called_function_span, callback_arguments_span, arg_before_callback_span, callback_body_span, }) = self.selected_call else { return vec![]; }; // This is the nesting level of the `use` keyword we've inserted, we // want to move the entire body of the anonymous function to this level. let use_nesting_level = self.edits.src_span_to_lsp_range(call_span).start.character; let indentation = " ".repeat(use_nesting_level as usize); // First we move the callback arguments to the left hand side of the // call and add the `use` keyword. let left_hand_side_text = if let Some(arguments_location) = callback_arguments_span { let arguments_start = arguments_location.start as usize; let arguments_end = arguments_location.end as usize; let arguments_text = self .module .code .get(arguments_start..arguments_end) .expect("fn args"); format!("use {arguments_text} <- ") } else { "use <- ".into() }; self.edits.insert(call_span.start, left_hand_side_text); match arg_before_callback_span { // If the function call has no other arguments besides the callback then // we just have to remove the `fn(...) {` part. // // wibble(fn(...) { ... }) // ^^^^^^^^^^ This goes from the end of the called function // To the start of the first thing in the anonymous // function's body. // None => self.edits.replace( SrcSpan::new(called_function_span.end, callback_body_span.start), format!("\n{indentation}"), ), // If it has other arguments we'll have to remove those and add a closed // parentheses too: // // wibble(1, 2, fn(...) { ... }) // ^^^^^^^^^^^ We have to replace this with a `)`, it // goes from the end of the second-to-last // argument to the start of the first thing // in the anonymous function's body. // Some(arg_before_callback) => self.edits.replace( SrcSpan::new(arg_before_callback.end, callback_body_span.start), format!(")\n{indentation}"), ), }; // Then we have to remove two spaces of indentation from each line of // the callback function's body. let body_range = self.edits.src_span_to_lsp_range(callback_body_span); for line in body_range.start.line + 1..=body_range.end.line { self.edits.delete_range(Range::new( Position { line, character: 0 }, Position { line, character: 2 }, )) } // Then we have to remove the anonymous fn closing `}` and the call's // closing `)`. self.edits .delete(SrcSpan::new(callback_body_span.end, call_span.end)); let mut action = Vec::with_capacity(1); CodeActionBuilder::new("Convert to `use`") .kind(CodeActionKind::REFACTOR_REWRITE) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(false) .push_to(&mut action); action } } impl<'ast> ast::visit::Visit<'ast> for ConvertToUse<'ast> { fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { // The cursor has to be inside the last statement of the function to // offer the code action. if let Some(last) = &fun.body.last() && within( self.params.range, self.edits.src_span_to_lsp_range(last.location()), ) && let Some(call_data) = turn_statement_into_use(last) { self.selected_call = Some(call_data); } ast::visit::visit_typed_function(self, fun) } fn visit_typed_expr_fn( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, kind: &'ast FunctionLiteralKind, arguments: &'ast [TypedArg], body: &'ast Vec1, return_annotation: &'ast Option, ) { // The cursor has to be inside the last statement of the body to // offer the code action. let last_statement_range = self.edits.src_span_to_lsp_range(body.last().location()); if within(self.params.range, last_statement_range) && let Some(call_data) = turn_statement_into_use(body.last()) { self.selected_call = Some(call_data); } ast::visit::visit_typed_expr_fn( self, location, type_, kind, arguments, body, return_annotation, ); } fn visit_typed_expr_block( &mut self, location: &'ast SrcSpan, statements: &'ast [TypedStatement], ) { let Some(last_statement) = statements.last() else { return; }; // The cursor has to be inside the last statement of the block to offer // the code action. let statement_range = self.edits.src_span_to_lsp_range(last_statement.location()); if within(self.params.range, statement_range) { // Only the last statement of a block can be turned into a use! if let Some(selected_call) = turn_statement_into_use(last_statement) { self.selected_call = Some(selected_call) } } ast::visit::visit_typed_expr_block(self, location, statements); } } fn turn_statement_into_use(statement: &TypedStatement) -> Option { match statement { ast::Statement::Use(_) | ast::Statement::Assignment(_) | ast::Statement::Assert(_) => None, ast::Statement::Expression(expression) => turn_expression_into_use(expression), } } fn turn_expression_into_use(expr: &TypedExpr) -> Option { let TypedExpr::Call { arguments, location: call_span, fun: called_function, .. } = expr else { return None; }; // The function arguments in the ast are reordered using function's field map. // This means that in the `args` array they might not appear in the same order // in which they are written by the user. Since the rest of the code relies // on their order in the written code we first have to sort them by their // source position. let arguments = arguments .iter() .sorted_by_key(|argument| argument.location.start) .collect_vec(); let CallArg { value: last_arg, implicit: None, .. } = arguments.last()? else { return None; }; let TypedExpr::Fn { arguments: callback_arguments, body, .. } = last_arg else { return None; }; let callback_arguments_span = match (callback_arguments.first(), callback_arguments.last()) { (Some(first), Some(last)) => Some(first.location.merge(&last.location)), _ => None, }; let arg_before_callback_span = if arguments.len() >= 2 { arguments .get(arguments.len() - 2) .map(|call_arg| call_arg.location) } else { None }; let callback_body_span = body.first().location().merge(&body.last().last_location()); Some(CallLocations { call_span: *call_span, called_function_span: called_function.location(), callback_arguments_span, arg_before_callback_span, callback_body_span, }) } /// Builder for code action to extract expression into a variable. /// The action will wrap the expression in a block if needed in the appropriate scope. /// /// For using the code action on the following selection: /// /// ```gleam /// fn void() { /// case result { /// Ok(value) -> 2 * value + 1 /// // ^^^^^^^^^ /// Error(_) -> panic /// } /// } /// ``` /// /// Will result: /// /// ```gleam /// fn void() { /// case result { /// Ok(value) -> { /// let int = 2 * value /// int + 1 /// } /// Error(_) -> panic /// } /// } /// ``` pub struct ExtractVariable<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, position: Option, selected_expression: Option, statement_before_selected_expression: Option, latest_statement: Option, to_be_wrapped: bool, name_generator: NameGenerator, } pub enum ExtractedToVariable { Expression { location: SrcSpan, name: EcoString }, StartOfPipeline { location: SrcSpan, name: EcoString }, } /// The Position of the selected code #[derive(PartialEq, Eq, Copy, Clone, Debug)] enum ExtractVariablePosition { InsideCaptureBody, /// Full statements (i.e. assignments, `use`s, and simple expressions). TopLevelStatement, /// The call on the right hand side of a pipe `|>`. PipelineCall, /// The right hand side of the `->` in a case expression. InsideCaseClause, /// A call argument. This can also be a `use` callback. CallArg, } impl<'a> ExtractVariable<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), position: None, selected_expression: None, statement_before_selected_expression: None, latest_statement: None, to_be_wrapped: false, name_generator: NameGenerator::new(), } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); let (Some(extracted_value), Some(insert_location)) = ( self.selected_expression, self.statement_before_selected_expression, ) else { return vec![]; }; let variable_name = match &extracted_value { ExtractedToVariable::Expression { name, .. } | ExtractedToVariable::StartOfPipeline { name, .. } => name, }; let expression_span = match &extracted_value { ExtractedToVariable::Expression { location, .. } | ExtractedToVariable::StartOfPipeline { location, .. } => location, }; let content = self .module .code .get(expression_span.start as usize..expression_span.end as usize) .expect("selected expression"); let range = self.edits.src_span_to_lsp_range(insert_location); let indent_size = count_indentation(&self.module.code, self.edits.line_numbers, range.start.line); let mut indent = " ".repeat(indent_size); // We insert the variable declaration // Wrap in a block if needed let mut insertion = match extracted_value { ExtractedToVariable::Expression { .. } => format!("let {variable_name} = {content}"), ExtractedToVariable::StartOfPipeline { .. } => { format!("let {variable_name} =\n{indent} {content}\n") } }; if self.to_be_wrapped { let line_end = self .edits .line_numbers .line_starts .get((range.end.line + 1) as usize) .expect("Line number should be valid"); self.edits.insert(*line_end, format!("{indent}}}\n")); indent += " "; insertion = format!("{{\n{indent}{insertion}"); }; self.edits .insert(insert_location.start, format!("{insertion}\n{indent}")); self.edits .replace(*expression_span, String::from(variable_name)); let mut action = Vec::with_capacity(1); CodeActionBuilder::new("Extract variable") .kind(CodeActionKind::REFACTOR_EXTRACT) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(false) .push_to(&mut action); action } fn inside_new_scope(&mut self, fun: F) where F: Fn(&mut Self), { let names = self.name_generator.clone(); fun(self); self.name_generator = names; } fn generate_candidate_name(&mut self, type_: Arc) -> EcoString { let name = self.name_generator.generate_name_from_type(&type_); // When the generator generates a name, it rightfully inserts it in the // current scope so that it cannot be used again. // However, in our case it's not what we want: the name we're generating // is a candidate for what we might use for a single variable, at the // end of the whole process we're gonna pick just a single name. // If we were to insert this name into scope, that means that all the // other candidates would have a suffix `int_2`, `int_3`, ... // When we finally pick one it would be strange if the picked name had // a suffix but no `int`, `int_1`, ... were in scope! let _ = self.name_generator.used_names.remove(&name); name } fn at_position(&mut self, position: ExtractVariablePosition, fun: F) where F: Fn(&mut Self), { self.at_optional_position(Some(position), fun); } fn at_optional_position(&mut self, position: Option, fun: F) where F: Fn(&mut Self), { let previous_statement = self.latest_statement; let previous_position = self.position; self.position = position; fun(self); self.position = previous_position; self.latest_statement = previous_statement; } } impl<'ast> ast::visit::Visit<'ast> for ExtractVariable<'ast> { fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) { let range = self.edits.src_span_to_lsp_range(statement.location()); if !within(self.params.range, range) { self.latest_statement = Some(statement.location()); ast::visit::visit_typed_statement(self, statement); return; } match self.position { // A capture body is comprised of just a single expression statement // that is inserted by the compiler, we don't really want to put // anything before that; so in this case we avoid tracking it. Some(ExtractVariablePosition::InsideCaptureBody) => {} Some(ExtractVariablePosition::PipelineCall) => { // Insert above the pipeline start self.latest_statement = Some(statement.location()); } _ => { // Insert below the previous statement self.latest_statement = Some(statement.location()); self.statement_before_selected_expression = self.latest_statement; } } self.at_position(ExtractVariablePosition::TopLevelStatement, |this| { ast::visit::visit_typed_statement(this, statement); }); } fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { let fun_range = self.edits.src_span_to_lsp_range(SrcSpan { start: fun.location.start, end: fun.end_position, }); if !within(self.params.range, fun_range) { return; } // We reset the name generator to purge the variable names from other // scopes. // We then reserve the names already used by top level definitions. self.name_generator = NameGenerator::new(); self.name_generator .reserve_module_value_names(&self.module.ast.definitions); ast::visit::visit_typed_function(self, fun); } fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { if let Pattern::Variable { name, .. } = &assignment.pattern { self.name_generator.add_used_name(name.clone()) }; ast::visit::visit_typed_assignment(self, assignment); } fn visit_typed_expr_pipeline( &mut self, location: &'ast SrcSpan, first_value: &'ast TypedPipelineAssignment, assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], finally: &'ast TypedExpr, finally_kind: &'ast PipelineAssignmentKind, ) { let expr_range = self.edits.src_span_to_lsp_range(*location); if !within(self.params.range, expr_range) { ast::visit::visit_typed_expr_pipeline( self, location, first_value, assignments, finally, finally_kind, ); return; }; // Visiting a pipeline requires a bit of care, we don't want to extract // intermediate steps as variables (those are function calls)! // So we start by checking if the selected section contains multiple // steps including the first one: in that case we can extract all those // steps as a single variable. let selection = self.edits.lsp_range_to_src_span(self.params.range); let is_inside_first_step = first_value.location.contains(selection.start); let last_included_step = assignments.iter().find_map(|(assignment, _kind)| { if assignment.location.contains(selection.end) { Some(assignment) } else { None } }); if let Some(last) = last_included_step && is_inside_first_step { let location = first_value.location.merge(&last.value.location()); self.selected_expression = Some(ExtractedToVariable::StartOfPipeline { location, name: self.generate_candidate_name(last.type_()), }); return; } // Otherwise we visit all the steps individually to see if there's // something _inside_ a step that might be extracted. let all_assignments = iter::once(first_value).chain(assignments.iter().map(|(assignment, _kind)| assignment)); for assignment in all_assignments { // With the position as "PipelineCall" we know we can't extract the // pipeline step itself! self.at_position(ExtractVariablePosition::PipelineCall, |this| { this.visit_typed_pipeline_assignment(assignment); }); } self.at_position(ExtractVariablePosition::PipelineCall, |this| { this.visit_typed_expr(finally) }); } fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) { let expr_location = expr.location(); let expr_range = self.edits.src_span_to_lsp_range(expr_location); if !within(self.params.range, expr_range) { ast::visit::visit_typed_expr(self, expr); return; } // If the expression is a top level statement we don't want to extract // it into a variable. It would mean we would turn this: // // ```gleam // pub fn main() { // let wibble = 1 // // ^ cursor here // } // // // into: // // pub fn main() { // let int = 1 // let wibble = int // } // ``` // // Not all that useful! // match self.position { Some( ExtractVariablePosition::TopLevelStatement | ExtractVariablePosition::PipelineCall, ) => { self.at_optional_position(None, |this| { ast::visit::visit_typed_expr(this, expr); }); return; } Some( ExtractVariablePosition::InsideCaptureBody | ExtractVariablePosition::InsideCaseClause | ExtractVariablePosition::CallArg, ) | None => {} } match expr { TypedExpr::Fn { kind: FunctionLiteralKind::Anonymous { .. }, .. } => { self.at_position(ExtractVariablePosition::TopLevelStatement, |this| { ast::visit::visit_typed_expr(this, expr); }); return; } // Expressions that don't make sense to extract TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::Block { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Invalid { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::Var { .. } => (), TypedExpr::Int { location, .. } | TypedExpr::Float { location, .. } | TypedExpr::String { location, .. } | TypedExpr::Pipeline { location, .. } | TypedExpr::Fn { location, .. } | TypedExpr::Todo { location, .. } | TypedExpr::List { location, .. } | TypedExpr::Call { location, .. } | TypedExpr::BinOp { location, .. } | TypedExpr::Case { location, .. } | TypedExpr::RecordAccess { location, .. } | TypedExpr::Tuple { location, .. } | TypedExpr::TupleIndex { location, .. } | TypedExpr::BitArray { location, .. } | TypedExpr::RecordUpdate { location, .. } | TypedExpr::NegateBool { location, .. } | TypedExpr::NegateInt { location, .. } => { if let Some(ExtractVariablePosition::CallArg) = self.position { // Don't update latest statement, we don't want to insert the extracted // variable inside the parenthesis where the call argument is located. } else { self.statement_before_selected_expression = self.latest_statement; }; self.selected_expression = Some(ExtractedToVariable::Expression { location: *location, name: self.generate_candidate_name(expr.type_()), }); } } ast::visit::visit_typed_expr(self, expr); } fn visit_typed_use(&mut self, use_: &'ast TypedUse) { let range = self.edits.src_span_to_lsp_range(use_.call.location()); if !within(self.params.range, range) { ast::visit::visit_typed_use(self, use_); return; } // Insert code under the `use` self.statement_before_selected_expression = Some(use_.call.location()); self.at_position(ExtractVariablePosition::TopLevelStatement, |this| { ast::visit::visit_typed_use(this, use_); }); } fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) { let range = self.edits.src_span_to_lsp_range(clause.location()); if !within(self.params.range, range) { self.inside_new_scope(|this| { ast::visit::visit_typed_clause(this, clause); }); return; } // Insert code after the `->` self.latest_statement = Some(clause.then.location()); self.to_be_wrapped = true; self.at_position(ExtractVariablePosition::InsideCaseClause, |this| { this.inside_new_scope(|this| { ast::visit::visit_typed_clause(this, clause); }); }); } fn visit_typed_expr_block( &mut self, location: &'ast SrcSpan, statements: &'ast [TypedStatement], ) { let range = self.edits.src_span_to_lsp_range(*location); if !within(self.params.range, range) { self.inside_new_scope(|this| { ast::visit::visit_typed_expr_block(this, location, statements); }); return; } // Don't extract block as variable let mut position = self.position; if let Some(ExtractVariablePosition::InsideCaseClause) = position { position = None; self.to_be_wrapped = false; } self.at_optional_position(position, |this| { this.inside_new_scope(|this| { ast::visit::visit_typed_expr_block(this, location, statements); }); }); } fn visit_typed_expr_fn( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, kind: &'ast FunctionLiteralKind, arguments: &'ast [TypedArg], body: &'ast Vec1, return_annotation: &'ast Option, ) { let range = self.edits.src_span_to_lsp_range(*location); if !within(self.params.range, range) { self.inside_new_scope(|this| { ast::visit::visit_typed_expr_fn( this, location, type_, kind, arguments, body, return_annotation, ); }); return; } let position = match kind { // If a fn is a capture `int.wibble(1, _)` its body will consist of // just a single expression statement. When visiting we must record // we're inside a capture body. FunctionLiteralKind::Capture { .. } => Some(ExtractVariablePosition::InsideCaptureBody), FunctionLiteralKind::Use { .. } => Some(ExtractVariablePosition::TopLevelStatement), FunctionLiteralKind::Anonymous { .. } => self.position, }; self.at_optional_position(position, |this| { this.inside_new_scope(|this| { ast::visit::visit_typed_expr_fn( this, location, type_, kind, arguments, body, return_annotation, ); }); }); } fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) { let range = self.edits.src_span_to_lsp_range(arg.location); if !within(self.params.range, range) { ast::visit::visit_typed_call_arg(self, arg); return; } // An implicit record update arg in inserted by the compiler, we don't // want folks to interact with this since it doesn't translate to // anything in the source code despite having a default position. if let Some(ImplicitCallArgOrigin::RecordUpdate) = arg.implicit { return; } let position = if arg.is_use_implicit_callback() { Some(ExtractVariablePosition::TopLevelStatement) } else { Some(ExtractVariablePosition::CallArg) }; self.at_optional_position(position, |this| { ast::visit::visit_typed_call_arg(this, arg); }); } // We don't want to offer the action if the cursor is over some invalid // piece of code. fn visit_typed_expr_invalid( &mut self, location: &'ast SrcSpan, _type_: &'ast Arc, _extra_information: &'ast Option, ) { let invalid_range = self.edits.src_span_to_lsp_range(*location); if within(self.params.range, invalid_range) { self.selected_expression = None; } } } /// Builder for code action to convert a literal use into a const. /// /// For using the code action on each of the following lines: /// /// ```gleam /// fn void() { /// let var = [1, 2, 3] /// let res = function("Statement", var) /// } /// ``` /// /// Both value literals will become: /// /// ```gleam /// const var = [1, 2, 3] /// const string = "Statement" /// /// fn void() { /// let res = function(string, var) /// } /// ``` pub struct ExtractConstant<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, /// The whole selected expression selected_expression: Option, /// The location of the start of the function containing the expression. /// It includes function's documentation as well container_function_start: Option, /// The variant of the extractable expression being extracted (if any) variant_of_extractable: Option, /// The name of the newly created constant name_to_use: Option, /// The right hand side expression of the newly created constant value_to_use: Option, } /// Used when an expression can be extracted to a constant enum ExtractableToConstant { /// Used for collections and operator uses. This means that elements /// inside, are also extractable as constants. ComposedValue, /// Used for single values. Literals in Gleam can be Ints, Floats, Strings /// and type variants (not records). SingleValue, /// Used for whole variable assignments. If the right hand side of the /// expression can be extracted, the whole expression extracted and use the /// local variable as a constant. Assignment, } fn can_be_constant( module: &Module, expr: &TypedExpr, module_constants: Option<&HashSet<&EcoString>>, ) -> bool { // We pass the `module_constants` on recursion to not compute them each time let module_constants = match module_constants { Some(module_constants) => module_constants, None => &module .ast .definitions .constants .iter() .map(|constant| &constant.name) .collect(), }; match expr { // Attempt to extract whole list as long as it's comprised of only literals TypedExpr::List { elements, tail, .. } => { elements .iter() .all(|element| can_be_constant(module, element, Some(module_constants))) && tail.is_none() } // Attempt to extract whole bit array as long as it's made up of literals TypedExpr::BitArray { segments, .. } => { segments .iter() .all(|segment| can_be_constant(module, &segment.value, Some(module_constants))) && segments.iter().all(|segment| { segment.options.iter().all(|option| match option { ast::BitArrayOption::Size { value, .. } => { can_be_constant(module, value, Some(module_constants)) } ast::BitArrayOption::Bytes { .. } | ast::BitArrayOption::Int { .. } | ast::BitArrayOption::Float { .. } | ast::BitArrayOption::Bits { .. } | ast::BitArrayOption::Utf8 { .. } | ast::BitArrayOption::Utf16 { .. } | ast::BitArrayOption::Utf32 { .. } | ast::BitArrayOption::Utf8Codepoint { .. } | ast::BitArrayOption::Utf16Codepoint { .. } | ast::BitArrayOption::Utf32Codepoint { .. } | ast::BitArrayOption::Signed { .. } | ast::BitArrayOption::Unsigned { .. } | ast::BitArrayOption::Big { .. } | ast::BitArrayOption::Little { .. } | ast::BitArrayOption::Native { .. } | ast::BitArrayOption::Unit { .. } => true, }) }) } // Attempt to extract whole tuple as long as it's comprised of only literals TypedExpr::Tuple { elements, .. } => elements .iter() .all(|element| can_be_constant(module, element, Some(module_constants))), // Extract literals directly TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } => true, // Extract non-record types directly TypedExpr::Var { constructor, name, .. } => { matches!( constructor.variant, type_::ValueConstructorVariant::Record { arity: 0, .. } ) || module_constants.contains(name) } // Extract record types as long as arguments can be constant TypedExpr::Call { arguments, fun, .. } => { fun.is_record_literal() && arguments .iter() .all(|arg| can_be_constant(module, &arg.value, Some(module_constants))) } // Extract concat binary operation if both sides can be constants TypedExpr::BinOp { name, left, right, .. } => { matches!(name, ast::BinOp::Concatenate) && can_be_constant(module, left, Some(module_constants)) && can_be_constant(module, right, Some(module_constants)) } TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Fn { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => false, } } /// Takes the list of already existing constants and functions and creates a /// name that doesn't conflict with them /// fn generate_new_name_for_constant(module: &Module, expr: &TypedExpr) -> EcoString { let mut name_generator = NameGenerator::new(); name_generator.reserve_module_value_names(&module.ast.definitions); name_generator.generate_name_from_type(&expr.type_()) } /// Converts the source start position of a documentation comment's contents into /// the position of the leading slash in its marker ('///'). fn get_doc_marker_position(content_pos: u32) -> u32 { content_pos.saturating_sub(3) } impl<'a> ExtractConstant<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), selected_expression: None, container_function_start: None, variant_of_extractable: None, name_to_use: None, value_to_use: None, } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); let ( Some(expr_span), Some(function_start), Some(type_of_extractable), Some(new_const_name), Some(const_value), ) = ( self.selected_expression, self.container_function_start, self.variant_of_extractable, self.name_to_use, self.value_to_use, ) else { return vec![]; }; // We insert the constant declaration self.edits.insert( function_start, format!("const {new_const_name} = {const_value}\n\n"), ); // We remove or replace the selected expression match type_of_extractable { // The whole expression is deleted for assignments ExtractableToConstant::Assignment => { let range = self .edits .src_span_to_lsp_range(self.selected_expression.expect("Real range value")); let indent_size = count_indentation(&self.module.code, self.edits.line_numbers, range.start.line); let expr_span_with_new_line = SrcSpan { // We remove leading indentation + 1 to remove the newline with it start: expr_span.start - (indent_size as u32 + 1), end: expr_span.end, }; self.edits.delete(expr_span_with_new_line); } // Only right hand side is replaced for collection or values ExtractableToConstant::ComposedValue | ExtractableToConstant::SingleValue => { self.edits.replace(expr_span, String::from(new_const_name)); } } let mut action = Vec::with_capacity(1); CodeActionBuilder::new("Extract constant") .kind(CodeActionKind::REFACTOR_EXTRACT) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(false) .push_to(&mut action); action } } impl<'ast> ast::visit::Visit<'ast> for ExtractConstant<'ast> { /// To get the position of the function containing the value or assignment /// to extract fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { let fun_location = fun.location; let fun_range = self.edits.src_span_to_lsp_range(SrcSpan { start: fun_location.start, end: fun.end_position, }); if !within(self.params.range, fun_range) { return; } // Here we need to get position of the function, starting from the leading slash in the // documentation comment's marker ('///'), not from comment's content (of which // we have the position), so we must convert the content start position // to the leading slash's position. self.container_function_start = Some( fun.documentation .as_ref() .map(|(doc_start, _)| get_doc_marker_position(*doc_start)) .unwrap_or(fun_location.start), ); ast::visit::visit_typed_function(self, fun); } /// To extract the whole assignment fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { let expr_location = assignment.location; // We only offer this code action for extracting the whole assignment // between `let` and `=`. let pattern_location = assignment.pattern.location(); let location = SrcSpan::new(assignment.location.start, pattern_location.end); let code_action_range = self.edits.src_span_to_lsp_range(location); if !within(self.params.range, code_action_range) { ast::visit::visit_typed_assignment(self, assignment); return; } // Has to be variable because patterns can't be constants. if assignment.pattern.is_variable() && can_be_constant(self.module, &assignment.value, None) { self.variant_of_extractable = Some(ExtractableToConstant::Assignment); self.selected_expression = Some(expr_location); self.name_to_use = if let Pattern::Variable { name, .. } = &assignment.pattern { Some(name.clone()) } else { None }; self.value_to_use = Some(EcoString::from( self.module .code .get( (assignment.value.location().start as usize) ..(assignment.location.end as usize), ) .expect("selected expression"), )); } } /// To extract only the literal fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) { let expr_location = expr.location(); let expr_range = self.edits.src_span_to_lsp_range(expr_location); if !within(self.params.range, expr_range) { ast::visit::visit_typed_expr(self, expr); return; } // Keep going down recursively if: // - It's no extractable has been found yet (`None`). // - It's a collection, which may or may not contain a value that can // be extracted. // - It's a binary operator, which may or may not operate on // extractable values. if matches!( self.variant_of_extractable, None | Some(ExtractableToConstant::ComposedValue) ) && can_be_constant(self.module, expr, None) { self.variant_of_extractable = match expr { TypedExpr::Var { .. } | TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } => Some(ExtractableToConstant::SingleValue), TypedExpr::List { .. } | TypedExpr::Tuple { .. } | TypedExpr::BitArray { .. } | TypedExpr::BinOp { .. } | TypedExpr::Call { .. } => Some(ExtractableToConstant::ComposedValue), TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Fn { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => None, }; self.selected_expression = Some(expr_location); self.name_to_use = Some(generate_new_name_for_constant(self.module, expr)); self.value_to_use = Some(EcoString::from( self.module .code .get((expr_location.start as usize)..(expr_location.end as usize)) .expect("selected expression"), )); } ast::visit::visit_typed_expr(self, expr); } } /// Builder for code action to apply the "expand function capture" action. /// pub struct ExpandFunctionCapture<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, function_capture_data: Option, } pub struct FunctionCaptureData { function_span: SrcSpan, hole_span: SrcSpan, hole_type: Arc, reserved_names: VariablesNames, } impl<'a> ExpandFunctionCapture<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), function_capture_data: None, } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); let Some(FunctionCaptureData { function_span, hole_span, hole_type, reserved_names, }) = self.function_capture_data else { return vec![]; }; let mut name_generator = NameGenerator::new(); name_generator.reserve_variable_names(reserved_names); let name = name_generator.generate_name_from_type(&hole_type); self.edits.replace(hole_span, name.clone().into()); self.edits.insert(function_span.end, " }".into()); self.edits .insert(function_span.start, format!("fn({name}) {{ ")); let mut action = Vec::with_capacity(1); CodeActionBuilder::new("Expand function capture") .kind(CodeActionKind::REFACTOR_REWRITE) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(false) .push_to(&mut action); action } } impl<'ast> ast::visit::Visit<'ast> for ExpandFunctionCapture<'ast> { fn visit_typed_expr_fn( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, kind: &'ast FunctionLiteralKind, arguments: &'ast [TypedArg], body: &'ast Vec1, return_annotation: &'ast Option, ) { let fn_range = self.edits.src_span_to_lsp_range(*location); if within(self.params.range, fn_range) && kind.is_capture() && let [argument] = arguments { self.function_capture_data = Some(FunctionCaptureData { function_span: *location, hole_span: argument.location, hole_type: argument.type_.clone(), reserved_names: VariablesNames::from_statements(body), }); } ast::visit::visit_typed_expr_fn( self, location, type_, kind, arguments, body, return_annotation, ) } } struct VariablesNames { names: HashSet, } impl VariablesNames { fn from_statements(statements: &[TypedStatement]) -> Self { let mut variables = Self { names: HashSet::new(), }; for statement in statements { variables.visit_typed_statement(statement); } variables } } impl<'ast> ast::visit::Visit<'ast> for VariablesNames { fn visit_typed_expr_var( &mut self, _location: &'ast SrcSpan, _constructor: &'ast ValueConstructor, name: &'ast EcoString, ) { let _ = self.names.insert(name.clone()); } } /// Builder for code action to apply the "generate dynamic decoder action. /// pub struct GenerateDynamicDecoder<'a, IO> { compiler: &'a LspProjectCompiler>, module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, printer: Printer<'a>, actions: &'a mut Vec, } const DECODE_MODULE: &str = "gleam/dynamic/decode"; impl<'a, IO> GenerateDynamicDecoder<'a, IO> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, actions: &'a mut Vec, compiler: &'a LspProjectCompiler>, ) -> Self { // Since we are generating a new function, type variables from other // functions and constants are irrelevant to the types we print. let printer = Printer::new_without_type_variables(&module.ast.names); Self { module, params, edits: TextEdits::new(line_numbers), printer, actions, compiler, } } pub fn code_actions(&mut self) { self.visit_typed_module(&self.module.ast); } fn custom_type_decoder_body( &mut self, custom_type: &CustomType>, ) -> Option { // We cannot generate a decoder for an external type with no constructors! let constructors_size = custom_type.constructors.len(); let (first, rest) = custom_type.constructors.split_first()?; let mode = EncodingMode::for_custom_type(custom_type); // We generate a decoder for a type with a single constructor: it does not // require pattern matching on a tag as there's no variants to tell apart. if rest.is_empty() && mode == EncodingMode::ObjectWithNoTypeTag { return self.constructor_decoder(mode, custom_type, first, 0); } // Otherwise we need to generate a decoder that has to tell apart different // variants, depending on the mode we might have to decode a type field or // plain strings! let module = self.printer.print_module(DECODE_MODULE); let discriminant = if mode == EncodingMode::PlainString { eco_format!("use variant <- {module}.then({module}.string)") } else { eco_format!("use variant <- {module}.field(\"type\", {module}.string)") }; let mut clauses = Vec::with_capacity(constructors_size); for constructor in iter::once(first).chain(rest) { let body = self.constructor_decoder(mode, custom_type, constructor, 4)?; let name = to_snake_case(&constructor.name); clauses.push(eco_format!(r#" "{name}" -> {body}"#)); } let failure_clause = self.failure_clause(custom_type); let cases = clauses.join("\n"); Some(eco_format!( r#"{{ {discriminant} case variant {{ {cases} {failure_clause} }} }}"#, )) } fn constructor_decoder( &mut self, mode: EncodingMode, custom_type: &ast::TypedCustomType, constructor: &TypedRecordConstructor, nesting: usize, ) -> Option { let decode_module = self.printer.print_module(DECODE_MODULE); let constructor_name = &constructor.name; // If the constructor was encoded as a plain string with no additional // fields it means there's nothing else to decode and we can just // succeed. if mode == EncodingMode::PlainString { return Some(eco_format!("{decode_module}.success({constructor_name})")); } // Otherwise we have to decode all the constructor fields to build it. let mut fields = Vec::with_capacity(constructor.arguments.len()); for argument in constructor.arguments.iter() { let (_, name) = argument.label.as_ref()?; let field = RecordField { label: RecordLabel::Labeled(name), type_: &argument.type_, }; fields.push(field); } let mut decoder_printer = DecoderPrinter::new( &mut self.printer, custom_type.name.clone(), self.module.name.clone(), self.compiler, ); let decoders = fields .iter() .map(|field| decoder_printer.decode_field(field, nesting + 2)) .join("\n"); let indent = " ".repeat(nesting); Some(if decoders.is_empty() { eco_format!("{decode_module}.success({constructor_name})") } else { let field_names = fields .iter() .map(|field| format!("{}:", field.label.variable_name())) .join(", "); eco_format!( "{{ {decoders} {indent} {decode_module}.success({constructor_name}({field_names})) {indent}}}", ) }) } /// Generates the failure/catch-all clause in a decoder function for a /// type with `EncodingMode::ObjectWithTypeTag` i.e. the `_ -> decode.failure()` /// clause executed when the `type` field does not match any of the known variants. /// /// # Arguments /// * `custom_type` - The root type we are printing a decoder for fn failure_clause(&mut self, custom_type: &CustomType>) -> EcoString { let decode_module = self.printer.print_module(DECODE_MODULE); let type_name = &custom_type.name; let mut decoder_printer = DecoderPrinter::new( &mut self.printer, type_name.clone(), self.module.name.clone(), self.compiler, ); // The construction of the zero value might necessitate importing // modules that aren't currently in scope. We keep track of them here. let mut modules_to_import = HashSet::new(); // We also need to keep track of the path we are tracing through the types // to make sure we don't get stuck in an infinite loop building a recursive // type. let mut path = vec![]; if let Some(zero_value) = decoder_printer.zero_value_for_custom_type( &self.module.name, type_name, &mut path, &mut modules_to_import, ) { for module_name in modules_to_import { maybe_import(&mut self.edits, self.module, &module_name); } eco_format!(r#" _ -> {decode_module}.failure({zero_value}, "{type_name}")"#,) } else { eco_format!( r#" _ -> {decode_module}.failure(todo as "Zero value for {type_name}", "{type_name}")"# ) } } } impl<'ast, IO> ast::visit::Visit<'ast> for GenerateDynamicDecoder<'ast, IO> { fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) { let range = self.edits.src_span_to_lsp_range(custom_type.location); if !overlaps(self.params.range, range) { return; } let name = eco_format!("{}_decoder", to_snake_case(&custom_type.name)); let Some(function_body) = self.custom_type_decoder_body(custom_type) else { return; }; let parameters = match custom_type.parameters.len() { 0 => EcoString::new(), _ => eco_format!( "({})", custom_type .parameters .iter() .map(|(_, name)| name) .join(", ") ), }; let decoder_type = self.printer.print_type(&Type::Named { publicity: Publicity::Public, package: STDLIB_PACKAGE_NAME.into(), module: DECODE_MODULE.into(), name: "Decoder".into(), arguments: vec![], inferred_variant: None, }); let function = format!( "\n\nfn {name}() -> {decoder_type}({type_name}{parameters}) {function_body}", type_name = custom_type.name, ); self.edits.insert(custom_type.end_position, function); maybe_import(&mut self.edits, self.module, DECODE_MODULE); CodeActionBuilder::new("Generate dynamic decoder") .kind(CodeActionKind::REFACTOR) .preferred(false) .changes( self.params.text_document.uri.clone(), std::mem::take(&mut self.edits.edits), ) .push_to(self.actions); } } /// If `module_name` is not already imported inside `module`, adds an edit to /// add that import. /// This function also makes sure not to import a module in itself. /// fn maybe_import(edits: &mut TextEdits<'_>, module: &Module, module_name: &str) { if module.ast.names.is_imported(module_name) || module.name == module_name { return; } let first_import_pos = position_of_first_definition_if_import(module, edits.line_numbers); let first_is_import = first_import_pos.is_some(); let import_location = first_import_pos.unwrap_or_default(); let after_import_newlines = add_newlines_after_import( import_location, first_is_import, edits.line_numbers, &module.code, ); edits.edits.push(get_import_edit( import_location, module_name, &after_import_newlines, )); } struct DecoderPrinter<'a, 'b, IO> { printer: &'a mut Printer<'b>, /// The name of the root type we are printing a decoder for type_name: EcoString, /// The module name of the root type we are printing a decoder for type_module: EcoString, compiler: &'a LspProjectCompiler>, } const DYNAMIC_MODULE: &str = "gleam/dynamic"; const DICT_MODULE: &str = "gleam/dict"; const OPTION_MODULE: &str = "gleam/option"; struct RecordField<'a> { label: RecordLabel<'a>, type_: &'a Type, } enum RecordLabel<'a> { Labeled(&'a str), Unlabeled(usize), } impl RecordLabel<'_> { fn field_key(&self) -> EcoString { match self { RecordLabel::Labeled(label) => eco_format!("\"{label}\""), RecordLabel::Unlabeled(index) => { eco_format!("{index}") } } } fn variable_name(&self) -> EcoString { match self { RecordLabel::Labeled(label) => (*label).into(), &RecordLabel::Unlabeled(mut index) => { let mut characters = Vec::new(); let alphabet_length = 26; let alphabet_offset = b'a'; loop { let alphabet_index = (index % alphabet_length) as u8; characters.push((alphabet_offset + alphabet_index) as char); index /= alphabet_length; if index == 0 { break; } index -= 1; } characters.into_iter().rev().collect() } } } } impl<'a, 'b, IO> DecoderPrinter<'a, 'b, IO> { fn new( printer: &'a mut Printer<'b>, type_name: EcoString, type_module: EcoString, compiler: &'a LspProjectCompiler>, ) -> Self { Self { type_name, type_module, printer, compiler, } } fn decoder_for(&mut self, type_: &Type, indent: usize) -> EcoString { let module_name = self.printer.print_module(DECODE_MODULE); if type_.is_bit_array() { eco_format!("{module_name}.bit_array") } else if type_.is_bool() { eco_format!("{module_name}.bool") } else if type_.is_float() { eco_format!("{module_name}.float") } else if type_.is_int() { eco_format!("{module_name}.int") } else if type_.is_string() { eco_format!("{module_name}.string") } else if type_.is_nil() { eco_format!("{module_name}.success(Nil)") } else { match type_.tuple_types() { Some(types) => { let fields = types .iter() .enumerate() .map(|(index, type_)| RecordField { type_, label: RecordLabel::Unlabeled(index), }) .collect_vec(); let decoders = fields .iter() .map(|field| self.decode_field(field, indent + 2)) .join("\n"); let mut field_names = fields.iter().map(|field| field.label.variable_name()); eco_format!( "{{ {decoders} {indent} {module_name}.success(#({fields})) {indent}}}", fields = field_names.join(", "), indent = " ".repeat(indent) ) } _ => { let type_information = type_.named_type_information(); let type_information = type_information.as_ref().map(|(module, name, arguments)| { (module.as_str(), name.as_str(), arguments.as_slice()) }); match type_information { Some(("gleam/dynamic", "Dynamic", _)) => { eco_format!("{module_name}.dynamic") } Some(("gleam", "List", [element])) => { eco_format!("{module_name}.list({})", self.decoder_for(element, indent)) } Some(("gleam/option", "Option", [some])) => { eco_format!( "{module_name}.optional({})", self.decoder_for(some, indent) ) } Some(("gleam/dict", "Dict", [key, value])) => { eco_format!( "{module_name}.dict({}, {})", self.decoder_for(key, indent), self.decoder_for(value, indent) ) } Some((module, name, _)) if module == self.type_module && name == self.type_name => { eco_format!("{}_decoder()", to_snake_case(name)) } _ => eco_format!( r#"todo as "Decoder for {}""#, self.printer.print_type(type_) ), } } } } } fn decode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString { let decoder = self.decoder_for(field.type_, indent); eco_format!( r#"{indent}use {variable} <- {module}.field({field}, {decoder})"#, indent = " ".repeat(indent), variable = field.label.variable_name(), field = field.label.field_key(), module = self.printer.print_module(DECODE_MODULE) ) } /// Performs best-effort generation of zero values for a given type. /// Will succeed for all prelude types and most stdlib types. /// For specifics on how custom user-defined types are handled, see /// `zero_value_for_custom_type`. fn zero_value_for_type( &mut self, type_: &Type, // Keeps track of types visited already to ensure we don't end up in an infinite // loop due to recursive types path: &mut Vec<(EcoString, EcoString)>, modules_to_import: &mut HashSet, ) -> Option { if type_.is_bit_array() { return Some("<<>>".into()); } if type_.is_bool() { return Some("False".into()); } if type_.is_float() { return Some("0.0".into()); } if type_.is_int() { return Some("0".into()); } if type_.is_string() { return Some("\"\"".into()); } if type_.is_list() { return Some("[]".into()); } if type_.is_nil() { return Some("Nil".into()); } if let Some(types) = type_.tuple_types() { // We try generating zero values for the tuple members and exit early if any of them fail. let field_zeroes = types .iter() .map_while(|type_| self.zero_value_for_type(type_, path, modules_to_import)) .collect_vec(); if field_zeroes.len() < types.len() { return None; } else { return Some(eco_format!("#({})", field_zeroes.iter().join(", "))); } }; let (module, name, _) = type_.named_type_information()?; match (module.as_str(), name.as_str()) { (OPTION_MODULE, "Option") => { let _ = modules_to_import.insert(OPTION_MODULE.into()); Some(eco_format!( "{}.None", self.printer.print_module(OPTION_MODULE) )) } (DYNAMIC_MODULE, "Dynamic") => { let _ = modules_to_import.insert(DYNAMIC_MODULE.into()); Some(eco_format!( "{}.nil()", self.printer.print_module(DYNAMIC_MODULE) )) } (DICT_MODULE, "Dict") => { let _ = modules_to_import.insert(DICT_MODULE.into()); Some(eco_format!( "{}.new()", self.printer.print_module(DICT_MODULE) )) } _ => self.zero_value_for_custom_type(&module, &name, path, modules_to_import), } } /// Best-effort zero value generation for user-defined types in the /// current package. fn zero_value_for_custom_type( &mut self, custom_type_module: &EcoString, custom_type_name: &EcoString, path: &mut Vec<(EcoString, EcoString)>, modules_to_import: &mut HashSet, ) -> Option { // First we check that we have not already visited this type before to // avoid cycles. let already_seen_type = path.iter().any(|(path_module, path_type_name)| { path_module == custom_type_module && path_type_name == custom_type_name }); if already_seen_type { return None; }; let type_is_inside_current_module = &self.type_module == custom_type_module; let current_module_interface = self .compiler .modules .get(&self.type_module) .map(|module| &module.ast.type_info)?; let type_module_interface = if !type_is_inside_current_module { self.compiler.get_module_interface(custom_type_module)? } else { current_module_interface }; // We only try and generate zero values for user-defined types in the current // package. If you are expanding the scope of this functionality to // remove this limitation, make sure to check for internal modules. if current_module_interface.package != type_module_interface.package { return None; } let constructors = type_module_interface .types_value_constructors .get(custom_type_name)?; // Opaque types cannot be constructed outside the module they were defined in, // so we will be unable to produce a zero value. if !type_is_inside_current_module && constructors.opaque == Opaque::Opaque { return None; } // Ideally, we want to use the "smallest" (i.e. fewest fields) constructor // to construct our zero value to reduce visual noise, but this might not always // be possible. So we check all constructors in increasing order of size to // find the first one that succeeds, and then short circuit. constructors .variants .iter() .sorted_by_key(|v| v.parameters.len()) .find_map(|zero_constructor| { self.zero_value_for_custom_type_constructor( custom_type_module, custom_type_name, zero_constructor, type_is_inside_current_module, path, modules_to_import, ) }) } /// Attempts to construct a zero value for one specific constructor /// of a custom type. Use `zero_value_for_custom_type` instead as it performs /// _important checks on type visibility that this method does NOT_. /// This is a helper method extracted for readability. fn zero_value_for_custom_type_constructor( &mut self, custom_type_module: &EcoString, custom_type_name: &EcoString, zero_constructor: &type_::TypeValueConstructor, type_is_inside_current_module: bool, path: &mut Vec<(EcoString, EcoString)>, modules_to_import: &mut HashSet, ) -> Option { path.push((custom_type_module.clone(), custom_type_name.clone())); // Try to generate zero values for all fields in the constructor let zero_params = zero_constructor .parameters .iter() .map_while(|parameter| { let zero = self.zero_value_for_type(¶meter.type_, path, modules_to_import)?; if let Some(label) = ¶meter.label { Some(eco_format!("{label}: {zero}")) } else { Some(zero) } }) .collect_vec(); // We need to make sure to clean up the path once we've finished // "visiting" this type. let _ = path.pop(); // Only proceed if we were able to construct every field successfully if zero_params.len() < zero_constructor.parameters.len() { return None; }; let zero_constructor = if !type_is_inside_current_module { // Type constructors from other modules need to be qualified appropriately, // and they might need to be brought into scope. let _ = modules_to_import.insert(custom_type_module.clone()); eco_format!( "{}.{}", self.printer.print_module(custom_type_module), zero_constructor.name ) } else { eco_format!("{}", zero_constructor.name) }; if zero_params.is_empty() { Some(eco_format!("{zero_constructor}")) } else { let zero_args = zero_params.iter().join(", "); Some(eco_format!("{zero_constructor}({zero_args})")) } } } /// Builder for code action to apply the "Generate to-JSON function" action. /// pub struct GenerateJsonEncoder<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, printer: Printer<'a>, actions: &'a mut Vec, config: &'a PackageConfig, } const JSON_MODULE: &str = "gleam/json"; const JSON_PACKAGE_NAME: &str = "gleam_json"; #[derive(Eq, PartialEq, Copy, Clone)] enum EncodingMode { PlainString, ObjectWithTypeTag, ObjectWithNoTypeTag, } impl EncodingMode { pub fn for_custom_type(type_: &CustomType>) -> Self { match type_.constructors.as_slice() { [constructor] if constructor.arguments.is_empty() => EncodingMode::PlainString, [_constructor] => EncodingMode::ObjectWithNoTypeTag, constructors if constructors.iter().all(|c| c.arguments.is_empty()) => { EncodingMode::PlainString } _constructors => EncodingMode::ObjectWithTypeTag, } } } impl<'a> GenerateJsonEncoder<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, actions: &'a mut Vec, config: &'a PackageConfig, ) -> Self { // Since we are generating a new function, type variables from other // functions and constants are irrelevant to the types we print. let printer = Printer::new_without_type_variables(&module.ast.names); Self { module, params, edits: TextEdits::new(line_numbers), printer, actions, config, } } pub fn code_actions(&mut self) { if self.config.dependencies.contains_key(JSON_PACKAGE_NAME) || self.config.dev_dependencies.contains_key(JSON_PACKAGE_NAME) { self.visit_typed_module(&self.module.ast); } } fn custom_type_encoder_body( &mut self, record_name: EcoString, custom_type: &CustomType>, ) -> Option { // We cannot generate a decoder for an external type with no constructors! let constructors_size = custom_type.constructors.len(); let (first, rest) = custom_type.constructors.split_first()?; let mode = EncodingMode::for_custom_type(custom_type); // We generate an encoder for a type with a single constructor: it does not // require pattern matching on the argument as we can access all its fields // with the usual record access syntax. if rest.is_empty() { let encoder = self.constructor_encoder(mode, first, custom_type.name.clone(), 2)?; let unpacking = if first.arguments.is_empty() { "" } else { &eco_format!( "let {name}({fields}:) = {record_name}\n ", name = first.name, fields = first .arguments .iter() .filter_map(|argument| { argument.label.as_ref().map(|(_location, label)| label) }) .join(":, ") ) }; return Some(eco_format!("{unpacking}{encoder}")); } // Otherwise we generate an encoder for a type with multiple constructors: // it will need to pattern match on the various constructors and encode each // one separately. let mut clauses = Vec::with_capacity(constructors_size); for constructor in iter::once(first).chain(rest) { let RecordConstructor { name, .. } = constructor; let encoder = self.constructor_encoder(mode, constructor, custom_type.name.clone(), 4)?; if constructor.arguments.is_empty() { clauses.push(eco_format!(" {name} -> {encoder}")); } else { let unpacking = constructor .arguments .iter() .filter_map(|argument| { argument.label.as_ref().map(|(_location, label)| { if is_nil_like(&argument.type_) { eco_format!("{}: _", label) } else { eco_format!("{}:", label) } }) }) .join(", "); clauses.push(eco_format!(" {name}({unpacking}) -> {encoder}")); } } let clauses = clauses.join("\n"); Some(eco_format!( "case {record_name} {{ {clauses} }}", )) } fn constructor_encoder( &mut self, mode: EncodingMode, constructor: &TypedRecordConstructor, type_name: EcoString, nesting: usize, ) -> Option { let json_module = self.printer.print_module(JSON_MODULE); let tag = to_snake_case(&constructor.name); let indent = " ".repeat(nesting); // If the variant is encoded as a simple json string we just call the // `json.string` with the variant tag as an argument. if mode == EncodingMode::PlainString { return Some(eco_format!("{json_module}.string(\"{tag}\")")); } // Otherwise we turn it into an object with a `type` tag field. let mut encoder_printer = JsonEncoderPrinter::new(&mut self.printer, type_name, self.module.name.clone()); // These are the fields of the json object to encode. let mut fields = Vec::with_capacity(constructor.arguments.len()); if mode == EncodingMode::ObjectWithTypeTag { // Any needed type tag is always going to be the first field in the object fields.push(eco_format!( "{indent} #(\"type\", {json_module}.string(\"{tag}\"))" )); } for argument in constructor.arguments.iter() { let (_, label) = argument.label.as_ref()?; let field = RecordField { label: RecordLabel::Labeled(label), type_: &argument.type_, }; let encoder = encoder_printer.encode_field(&field, nesting + 2); fields.push(encoder); } let fields = fields.join(",\n"); Some(eco_format!( "{json_module}.object([ {fields}, {indent}])" )) } } /// When generating an encoder, we need to know when we can ignore the fields /// destructured from a constructor. /// If a field is of type `Nil` or is a tuple type composed entirely of `Nil` /// types, then we will never need to use the field in our encoder function. fn is_nil_like(type_: &Type) -> bool { if type_.is_nil() { return true; } match type_.tuple_types() { Some(types) => types.iter().all(|type_| is_nil_like(type_)), _ => false, } } impl<'ast> ast::visit::Visit<'ast> for GenerateJsonEncoder<'ast> { fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) { let range = self.edits.src_span_to_lsp_range(custom_type.location); if !overlaps(self.params.range, range) { return; } let record_name = to_snake_case(&custom_type.name); let name = eco_format!("{record_name}_to_json"); let Some(encoder) = self.custom_type_encoder_body(record_name.clone(), custom_type) else { return; }; let json_type = self.printer.print_type(&Type::Named { publicity: Publicity::Public, package: JSON_PACKAGE_NAME.into(), module: JSON_MODULE.into(), name: "Json".into(), arguments: vec![], inferred_variant: None, }); let type_ = if custom_type.parameters.is_empty() { custom_type.name.clone() } else { let parameters = custom_type .parameters .iter() .map(|(_, name)| name) .join(", "); eco_format!("{}({})", custom_type.name, parameters) }; let function = format!( " fn {name}({record_name}: {type_}) -> {json_type} {{ {encoder} }}", ); self.edits.insert(custom_type.end_position, function); maybe_import(&mut self.edits, self.module, JSON_MODULE); CodeActionBuilder::new("Generate to-JSON function") .kind(CodeActionKind::REFACTOR) .preferred(false) .changes( self.params.text_document.uri.clone(), std::mem::take(&mut self.edits.edits), ) .push_to(self.actions); } } struct JsonEncoderPrinter<'a, 'b> { printer: &'a mut Printer<'b>, /// The name of the root type we are printing an encoder for type_name: EcoString, /// The module name of the root type we are printing an encoder for type_module: EcoString, } impl<'a, 'b> JsonEncoderPrinter<'a, 'b> { fn new(printer: &'a mut Printer<'b>, type_name: EcoString, type_module: EcoString) -> Self { Self { type_name, type_module, printer, } } fn encoder_for(&mut self, encoded_value: &str, type_: &Type, indent: usize) -> EcoString { let module_name = self.printer.print_module(JSON_MODULE); let is_capture = encoded_value == "_"; let maybe_capture = |mut function: EcoString| { if is_capture { function } else { function.push('('); function.push_str(encoded_value); function.push(')'); function } }; if type_.is_bool() { maybe_capture(eco_format!("{module_name}.bool")) } else if type_.is_float() { maybe_capture(eco_format!("{module_name}.float")) } else if type_.is_int() { maybe_capture(eco_format!("{module_name}.int")) } else if type_.is_string() { maybe_capture(eco_format!("{module_name}.string")) } else if type_.is_nil() { if is_capture { eco_format!("fn(_) {{ {module_name}.null() }}") } else { eco_format!("{module_name}.null()") } } else { match type_.tuple_types() { Some(types) => { let (tuple, new_indent) = if is_capture { ("value", indent + 4) } else { (encoded_value, indent + 2) }; // We need to iterate over all of the tuple's fields // to obtain an encoder for each one, so we reuse the // iteration to check whether this tuple can be ignored // in the encoder without calling `is_nil_like`. let mut encoders = Vec::new(); let all_values_are_nil = types.iter().enumerate().fold( true, |all_values_are_nil, (index, type_)| { encoders.push(self.encoder_for( &format!("{tuple}.{index}"), type_, new_indent, )); all_values_are_nil && is_nil_like(type_) }, ); if is_capture { eco_format!( "fn({value}) {{ {indent} {module_name}.preprocessed_array([ {indent} {encoders}, {indent} ]) {indent}}}", value = if all_values_are_nil { "_" } else { "value" }, indent = " ".repeat(indent), encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))), ) } else { eco_format!( "{module_name}.preprocessed_array([ {indent} {encoders}, {indent}])", indent = " ".repeat(indent), encoders = encoders.join(&format!(",\n{}", " ".repeat(new_indent))), ) } } _ => { let type_information = type_.named_type_information(); let type_information: Option<(&str, &str, &[Arc])> = type_information.as_ref().map(|(module, name, arguments)| { (module.as_str(), name.as_str(), arguments.as_slice()) }); match type_information { Some(("gleam", "List", [element])) => { eco_format!( "{module_name}.array({encoded_value}, {map_function})", map_function = self.encoder_for("_", element, indent) ) } Some(("gleam/option", "Option", [some])) => { eco_format!( "case {encoded_value} {{ {indent} {none} -> {module_name}.null() {indent} {some}({value}) -> {encoder} {indent}}}", indent = " ".repeat(indent), none = self .printer .print_constructor(&"gleam/option".into(), &"None".into()), some = self .printer .print_constructor(&"gleam/option".into(), &"Some".into()), value = if is_nil_like(some) { "_" } else { "value" }, encoder = self.encoder_for("value", some, indent + 2) ) } Some(("gleam/dict", "Dict", [key, value])) => { let stringify_function = match key .named_type_information() .as_ref() .map(|(module, name, arguments)| { (module.as_str(), name.as_str(), arguments.as_slice()) }) { Some(("gleam", "String", [])) => "fn(string) { string }", _ => &format!( r#"todo as "Function to stringify {}""#, self.printer.print_type(key) ), }; eco_format!( "{module_name}.dict({encoded_value}, {stringify_function}, {})", self.encoder_for("_", value, indent) ) } Some((module, name, _)) if module == self.type_module && name == self.type_name => { maybe_capture(eco_format!("{}_to_json", to_snake_case(name))) } _ => eco_format!( r#"todo as "Encoder for {}""#, self.printer.print_type(type_) ), } } } } } fn encode_field(&mut self, field: &RecordField<'_>, indent: usize) -> EcoString { let field_name = field.label.variable_name(); let encoder = self.encoder_for(&field_name, field.type_, indent); eco_format!( r#"{indent}#("{field_name}", {encoder})"#, indent = " ".repeat(indent), ) } } /// Builder for code action to pattern match on things like (anonymous) function /// arguments or variables. /// For example: /// /// ```gleam /// pub fn wibble(arg: #(key, value)) { /// // ^ [pattern match on argument] /// } /// /// // Generates /// /// pub fn wibble(arg: #(key, value)) { /// let #(value_0, value_1) = arg /// } /// ``` /// /// Another example with variables: /// /// ```gleam /// pub fn main() { /// let pair = #(1, 3) /// // ^ [pattern match on value] /// } /// /// // Generates /// /// pub fn main() { /// let pair = #(1, 3) /// let #(value_0, value_1) = pair /// } /// ``` /// pub struct PatternMatchOnValue<'a, A> { module: &'a Module, params: &'a CodeActionParams, compiler: &'a LspProjectCompiler, pattern_variable_under_cursor: Option<(&'a EcoString, PatternLocation, Arc)>, selected_value: Option>, edits: TextEdits<'a>, } /// A value we might want to pattern match on. /// Each variant will also contain all the info needed to know how to properly /// print and format the corresponding pattern matching code; that's why you'll /// see `Range`s and `SrcSpan` besides the type of the thing being matched. /// #[derive(Clone)] pub enum PatternMatchedValue<'a> { FunctionArgument { /// The argument being pattern matched on. /// arg: &'a TypedArg, /// The first statement inside the function body. Used to correctly /// position the inserted pattern matching. /// first_statement: &'a TypedStatement, /// The range of the entire function holding the argument. /// function_range: Range, }, LetVariable { variable_name: &'a EcoString, variable_type: Arc, /// The location of the entire let assignment the variable is part of, /// so that we can add the pattern matching _after_ it. /// assignment_location: SrcSpan, }, /// A variable that is bound in a case branch's pattern. For example: /// ```gleam /// case wibble { /// wobble -> 1 /// // ^^^^^ This! /// } /// ``` /// ClausePatternVariable { variable_type: Arc, variable_location: PatternLocation, clause_location: SrcSpan, /// All the names in the clause that are already taken by pattern variables. /// We need this to avoid generating invalid code were two pattern variables /// have the same name. /// /// For example: /// /// ```gleam /// case wibble { /// [first, ..rest] -> todo /// ^^^^^ When expanding `first` we can't add any variable pattern /// called `rest` as it would clash with the `rest` tail that is /// already there. /// } /// ``` /// bound_variables: Vec, }, UseVariable { variable_name: &'a EcoString, variable_type: Arc, /// The location of the entire use expression the variable is part of, /// so that we can add the pattern matching _after_ it. /// use_location: SrcSpan, }, } #[derive(Clone)] pub enum PatternLocation { /// Any pattern that doesn't need any special handling. /// Regular { location: SrcSpan }, /// List tails need some care to not generate invalid syntax when pattern /// matched on in case expressions. /// ListTail { /// This location covers the entire list tail pattern, including the `..` location: SrcSpan, }, } impl PatternLocation { fn regular(location: SrcSpan) -> Self { Self::Regular { location } } } impl<'a, IO> PatternMatchOnValue<'a, IO> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, compiler: &'a LspProjectCompiler, ) -> Self { Self { module, params, compiler, selected_value: None, pattern_variable_under_cursor: None, edits: TextEdits::new(line_numbers), } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); let action_title = match self.selected_value.clone() { Some(PatternMatchedValue::FunctionArgument { arg, first_statement: function_body, function_range, }) => { self.match_on_function_argument(arg, function_body, function_range); "Pattern match on argument" } Some( PatternMatchedValue::LetVariable { variable_name, variable_type, assignment_location: location, } | PatternMatchedValue::UseVariable { variable_name, variable_type, use_location: location, }, ) => { self.match_on_let_variable(variable_name, variable_type, location); "Pattern match on variable" } Some(PatternMatchedValue::ClausePatternVariable { variable_type, variable_location, clause_location, bound_variables, }) => { self.match_on_clause_variable( variable_type, variable_location, clause_location, &bound_variables, ); "Pattern match on variable" } None => return vec![], }; if self.edits.edits.is_empty() { return vec![]; } let mut action = Vec::with_capacity(1); CodeActionBuilder::new(action_title) .kind(CodeActionKind::REFACTOR_REWRITE) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(false) .push_to(&mut action); action } fn match_on_function_argument( &mut self, arg: &TypedArg, first_statement: &TypedStatement, function_range: Range, ) { let Some(arg_name) = arg.get_variable_name() else { return; }; let Some(patterns) = self.type_to_destructure_patterns(arg.type_.as_ref(), &mut NameGenerator::new()) else { return; }; let first_statement_location = first_statement.location(); let first_statement_range = self.edits.src_span_to_lsp_range(first_statement_location); // If we're trying to insert the pattern matching on the same // line as the one where the function is defined we will want to // put it on a new line instead. So in that case the nesting will // be the default 2 spaces. let needs_newline = function_range.start.line == first_statement_range.start.line; let nesting = if needs_newline { String::from(" ") } else { " ".repeat(first_statement_range.start.character as usize) }; let pattern_matching = if patterns.len() == 1 { let pattern = patterns.first(); format!("let {pattern} = {arg_name}") } else { let patterns = patterns .iter() .map(|p| format!(" {nesting}{p} -> todo")) .join("\n"); format!("case {arg_name} {{\n{patterns}\n{nesting}}}") }; let pattern_matching = if needs_newline { format!("\n{nesting}{pattern_matching}") } else { pattern_matching }; let has_empty_body = match first_statement { ast::Statement::Expression(TypedExpr::Todo { kind: TodoKind::EmptyFunction { .. }, .. }) => true, ast::Statement::Expression(_) | ast::Statement::Assignment(_) | ast::Statement::Use(_) | ast::Statement::Assert(_) => false, }; // If the pattern matching is added to a function with an empty // body then we do not add any nesting after it, or we would be // increasing the nesting of the closing `}`! let pattern_matching = if has_empty_body { format!("{pattern_matching}\n") } else { format!("{pattern_matching}\n{nesting}") }; self.edits .insert(first_statement_location.start, pattern_matching); } fn match_on_let_variable( &mut self, variable_name: &EcoString, variable_type: Arc, assignment_location: SrcSpan, ) { let Some(patterns) = self.type_to_destructure_patterns(variable_type.as_ref(), &mut NameGenerator::new()) else { return; }; let assignment_range = self.edits.src_span_to_lsp_range(assignment_location); let nesting = " ".repeat(assignment_range.start.character as usize); let pattern_matching = if patterns.len() == 1 { let pattern = patterns.first(); format!("let {pattern} = {variable_name}") } else { let patterns = patterns .iter() .map(|p| format!(" {nesting}{p} -> todo")) .join("\n"); format!("case {variable_name} {{\n{patterns}\n{nesting}}}") }; self.edits.insert( assignment_location.end, format!("\n{nesting}{pattern_matching}"), ); } fn match_on_clause_variable( &mut self, variable_type: Arc, variable_location: PatternLocation, clause_location: SrcSpan, bound_variables: &[BoundVariable], ) { let mut names = NameGenerator::new(); names.reserve_bound_variables(bound_variables); let patterns = if matches!(variable_location, PatternLocation::ListTail { .. }) { // Here we're dealing with a special case: if someone wants to expand the tail // of a list we can't just replace it with the usual list patterns `[]`, `[first, ..rest]`. // That would result in invalid syntax. So we have to generate list patterns // that have no square brackets. let first = names.rename_to_avoid_shadowing("first".into()); let rest = names.rename_to_avoid_shadowing("rest".into()); vec1!["".into(), eco_format!("{first}, ..{rest}")] } else if let Some(patterns) = self.type_to_destructure_patterns(variable_type.as_ref(), &mut names) { patterns } else { return; }; let clause_range = self.edits.src_span_to_lsp_range(clause_location); let nesting = " ".repeat(clause_range.start.character as usize); let variable_location = match variable_location { PatternLocation::Regular { location } => location, PatternLocation::ListTail { location } => location, }; let variable_start = (variable_location.start - clause_location.start) as usize; let variable_end = variable_start + variable_location.len(); let clause_code = code_at(self.module, clause_location); let patterns = patterns .iter() .map(|pattern| { let mut clause_code = clause_code.to_string(); // If we're replacing a variable that's using the shorthand // syntax we want to add a space to separate it from the // preceding `:`. let pattern = if variable_start == variable_end { &eco_format!(" {pattern}") } else { pattern }; clause_code.replace_range(variable_start..variable_end, pattern); clause_code }) .join(&format!("\n{nesting}")); self.edits.replace(clause_location, patterns); } /// Will produce a pattern that can be used on the left hand side of a let /// assignment to destructure a value of the given type. For example given /// this type: /// /// ```gleam /// pub type Wibble { /// Wobble(Int, label: String) /// } /// ``` /// /// The produced pattern will look like this: `Wobble(value_0, label:)`. /// The pattern will use the correct qualified/unqualified name for the /// constructor if it comes from another package. /// /// The function will only produce a list of patterns that can be used from /// the current module. So if the type comes from another module it must be /// public! Otherwise this function will return an empty vec. /// fn type_to_destructure_patterns( &mut self, type_: &Type, names: &mut NameGenerator, ) -> Option> { match type_ { Type::Fn { .. } => None, Type::Var { type_ } => self.type_var_to_destructure_patterns(&type_.borrow(), names), // We special case lists, they don't have "regular" constructors // like other types. Instead we always add the two clauses covering // the empty and non empty list. Type::Named { .. } if type_.is_list() => { let first = names.rename_to_avoid_shadowing("first".into()); let rest = names.rename_to_avoid_shadowing("rest".into()); Some(vec1![ EcoString::from("[]"), eco_format!("[{first}, ..{rest}]") ]) } Type::Named { module: type_module, name: type_name, .. } => { let mut patterns = vec![]; let constructors = get_type_constructors(self.compiler, &self.module.name, type_module, type_name); for constructor in constructors { let names_before = names.clone(); if let Some(pattern) = self.record_constructor_to_destructure_pattern(constructor, names) { patterns.push(pattern); } *names = names_before; } Vec1::try_from_vec(patterns).ok() } // We don't want to suggest this action for empty tuple as it // doesn't make a lot of sense to match on those. Type::Tuple { elements } if elements.is_empty() => None, Type::Tuple { elements } => { let elements = elements .iter() .map(|element| names.generate_name_from_type(element)) .join(", "); Some(vec1![eco_format!("#({elements})")]) } } } fn type_var_to_destructure_patterns( &mut self, type_var: &TypeVar, names: &mut NameGenerator, ) -> Option> { match type_var { TypeVar::Unbound { .. } | TypeVar::Generic { .. } => None, TypeVar::Link { type_ } => self.type_to_destructure_patterns(type_, names), } } /// Given the value constructor of a record, returns a string with the /// pattern used to match on that specific variant. /// /// Note how: /// - If the constructor is internal to another module or comes from another /// module, then this returns `None` since one cannot pattern match on it. /// - If the provided `ValueConstructor` is not a record constructor this /// will return `None`. /// fn record_constructor_to_destructure_pattern( &self, constructor: &ValueConstructor, names: &mut NameGenerator, ) -> Option { let type_::ValueConstructorVariant::Record { name: constructor_name, arity: constructor_arity, module: constructor_module, field_map, .. } = &constructor.variant else { // The constructor should always be a record, in case it's not // there's not much we can do and just fail. return None; }; // Since the constructor is a record constructor we know that its type // is either `Named` or a `Fn` type, in either case we have to get the // arguments types out of it. let Some(arguments_types) = constructor .type_ .fn_types() .map(|(arguments_types, _return)| arguments_types) .or_else(|| constructor.type_.constructor_types()) else { // This should never happen but just in case we don't want to unwrap // and panic. return None; }; let index_to_label = match field_map { None => HashMap::new(), Some(field_map) => { names.reserve_all_labels(field_map); field_map .fields .iter() .map(|(label, index)| (index, label)) .collect::>() } }; let mut pattern = pretty_constructor_name(self.module, constructor_module, constructor_name)?; if *constructor_arity == 0 { return Some(pattern); } pattern.push('('); let arguments = (0..*constructor_arity as u32) .map(|i| match index_to_label.get(&i) { Some(label) => eco_format!("{label}:"), None => match arguments_types.get(i as usize) { None => names.rename_to_avoid_shadowing(EcoString::from("value")), Some(type_) => names.generate_name_from_type(type_), }, }) .join(", "); pattern.push_str(&arguments); pattern.push(')'); Some(pattern) } } fn code_at(module: &Module, span: SrcSpan) -> &str { module .code .get(span.start as usize..span.end as usize) .expect("code location must be valid") } impl<'ast, IO> ast::visit::Visit<'ast> for PatternMatchOnValue<'ast, IO> { fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { // If we're not inside the function there's no point in exploring its // ast further. let function_span = SrcSpan { start: fun.location.start, end: fun.end_position, }; let function_range = self.edits.src_span_to_lsp_range(function_span); if !within(self.params.range, function_range) { return; } for arg in &fun.arguments { // If the cursor is placed on one of the arguments, then we can try // and generate code for that one. let arg_range = self.edits.src_span_to_lsp_range(arg.location); if within(self.params.range, arg_range) && let Some(first_statement) = fun.body.first() { self.selected_value = Some(PatternMatchedValue::FunctionArgument { arg, first_statement, function_range, }); return; } } // If the cursor is not on any of the function arguments then we keep // exploring the function body as we might want to destructure the // argument of an expression function! ast::visit::visit_typed_function(self, fun); } fn visit_typed_expr_fn( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, kind: &'ast FunctionLiteralKind, arguments: &'ast [TypedArg], body: &'ast Vec1, return_annotation: &'ast Option, ) { // If we're not inside the function there's no point in exploring its // ast further. let function_range = self.edits.src_span_to_lsp_range(*location); if !within(self.params.range, function_range) { return; } for argument in arguments { // If the cursor is placed on one of the arguments, then we can try // and generate code for that one. let arg_range = self.edits.src_span_to_lsp_range(argument.location); if within(self.params.range, arg_range) { self.selected_value = Some(PatternMatchedValue::FunctionArgument { arg: argument, first_statement: body.first(), function_range, }); return; } } // If the cursor is not on any of the function arguments then we keep // exploring the function body as we might want to destructure the // argument of an expression function! ast::visit::visit_typed_expr_fn( self, location, type_, kind, arguments, body, return_annotation, ); } fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { // If we're not inside the assignment there's no point in exploring its // ast further. let assignment_range = self.edits.src_span_to_lsp_range(assignment.location); if !within(self.params.range, assignment_range) { return; } ast::visit::visit_typed_assignment(self, assignment); if let Some((name, _, ref type_)) = self.pattern_variable_under_cursor { self.selected_value = Some(PatternMatchedValue::LetVariable { variable_name: name, variable_type: type_.clone(), assignment_location: assignment.location, }); } } fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) { // If we're not inside the clause there's no point in exploring its // ast further. let clause_range = self.edits.src_span_to_lsp_range(clause.location); if !within(self.params.range, clause_range) { return; } for pattern in clause.pattern.iter() { self.visit_typed_pattern(pattern); } for patterns in clause.alternative_patterns.iter() { for pattern in patterns { self.visit_typed_pattern(pattern); } } if let Some((_, variable_location, type_)) = self.pattern_variable_under_cursor.take() { self.selected_value = Some(PatternMatchedValue::ClausePatternVariable { variable_type: type_, variable_location, clause_location: clause.location(), bound_variables: clause.bound_variables().collect_vec(), }); } else { self.visit_typed_expr(&clause.then); } } fn visit_typed_use(&mut self, use_: &'ast TypedUse) { if let Some(assignments) = use_.callback_arguments() { for variable in assignments { let ast::Arg { names: ArgNames::Named { name, .. }, location: variable_location, type_, .. } = variable else { continue; }; // If we use a pattern in a use assignment, that will end up // being called `_use` something. We don't want to offer the // action when hovering a pattern so we ignore those. if name.starts_with("_use") { continue; } let variable_range = self.edits.src_span_to_lsp_range(*variable_location); if within(self.params.range, variable_range) { self.selected_value = Some(PatternMatchedValue::UseVariable { variable_name: name, variable_type: type_.clone(), use_location: use_.location, }); // If we've found the variable to pattern match on, there's no // point in keeping traversing the AST. return; } } } ast::visit::visit_typed_use(self, use_); } fn visit_typed_pattern_variable( &mut self, location: &'ast SrcSpan, name: &'ast EcoString, type_: &'ast Arc, _origin: &'ast VariableOrigin, ) { if within( self.params.range, self.edits.src_span_to_lsp_range(*location), ) { let location = PatternLocation::regular(*location); self.pattern_variable_under_cursor = Some((name, location, type_.clone())); } } fn visit_typed_pattern_call_arg(&mut self, arg: &'ast CallArg) { if let Some(name) = arg.label_shorthand_name() && within( self.params.range, self.edits.src_span_to_lsp_range(arg.location), ) { let location = PatternLocation::regular(SrcSpan { start: arg.location.end, end: arg.location.end, }); self.pattern_variable_under_cursor = Some((name, location, arg.value.type_())); return; } ast::visit::visit_typed_pattern_call_arg(self, arg); } fn visit_typed_pattern_string_prefix( &mut self, _location: &'ast SrcSpan, _left_location: &'ast SrcSpan, left_side_assignment: &'ast Option<(EcoString, SrcSpan)>, right_location: &'ast SrcSpan, _left_side_string: &'ast EcoString, right_side_assignment: &'ast AssignName, ) { if let Some((name, location)) = left_side_assignment && within( self.params.range, self.edits.src_span_to_lsp_range(*location), ) { let location = PatternLocation::regular(*location); self.pattern_variable_under_cursor = Some((name, location, type_::string())); } else if let AssignName::Variable(name) = right_side_assignment && within( self.params.range, self.edits.src_span_to_lsp_range(*right_location), ) { let location = PatternLocation::regular(*right_location); self.pattern_variable_under_cursor = Some((name, location, type_::string())); } } fn visit_typed_pattern_list( &mut self, location: &'ast SrcSpan, elements: &'ast Vec, tail: &'ast Option>, type_: &'ast Arc, ) { let (name, tail_location, tail_type) = if let Some(tail) = tail && let Pattern::Variable { name, type_, .. } = &tail.pattern { (name, tail.location, type_) } else { ast::visit::visit_typed_pattern_list(self, location, elements, tail, type_); return; }; let tail_range = self.edits.src_span_to_lsp_range(tail_location); if !within(self.params.range, tail_range) { ast::visit::visit_typed_pattern_list(self, location, elements, tail, type_); return; } let location = PatternLocation::ListTail { location: tail_location, }; self.pattern_variable_under_cursor = Some((name, location, tail_type.clone())) } } /// Given a type and its module, returns a list of its *importable* /// constructors. /// /// Since this focuses just on importable constructors, if either the module or /// the type are internal the returned array will be empty! /// fn get_type_constructors<'a, 'b, IO>( compiler: &'a LspProjectCompiler, current_module: &'b EcoString, type_module: &'b EcoString, type_name: &'b EcoString, ) -> Vec<&'a ValueConstructor> { let type_is_inside_current_module = current_module == type_module; let module_interface = if !type_is_inside_current_module { // If the type is outside of the module we're in, we can only pattern // match on it if the module can be imported. // The `get_module_interface` already takes care of making this check. compiler.get_module_interface(type_module) } else { // However, if the type is defined in the module we're in, we can always // pattern match on it. So we get the current module's interface. compiler .modules .get(current_module) .map(|module| &module.ast.type_info) }; let Some(module_interface) = module_interface else { return vec![]; }; // If the type is in an internal module that is not the current one, we // cannot use its constructors! if !type_is_inside_current_module && module_interface.is_internal { return vec![]; } let Some(constructors) = module_interface.types_value_constructors.get(type_name) else { return vec![]; }; constructors .variants .iter() .filter_map(|variant| { let constructor = module_interface.values.get(&variant.name)?; if type_is_inside_current_module || constructor.publicity.is_public() { Some(constructor) } else { None } }) .collect_vec() } /// Returns a pretty printed record constructor name, the way it would be used /// inside the given `module` (with the correct name and qualification). /// /// If the constructor cannot be used inside the module because it's not /// imported, then this function will return `None`. /// fn pretty_constructor_name( module: &Module, constructor_module: &EcoString, constructor_name: &EcoString, ) -> Option { match module .ast .names .named_constructor(constructor_module, constructor_name) { type_::printer::NameContextInformation::Unimported(_, _) => None, type_::printer::NameContextInformation::Unqualified(constructor_name) => { Some(eco_format!("{constructor_name}")) } type_::printer::NameContextInformation::Qualified(module_name, constructor_name) => { Some(eco_format!("{module_name}.{constructor_name}")) } } } /// Builder for the "generate function" code action. /// Whenever someone hovers an invalid expression that is inferred to have a /// function type the language server can generate a function definition for it. /// For example: /// /// ```gleam /// pub fn main() { /// wibble(1, 2, "hello") /// // ^ [generate function] /// } /// ``` /// /// Will generate the following definition: /// /// ```gleam /// pub fn wibble(arg_0: Int, arg_1: Int, arg_2: String) -> a { /// todo /// } /// ``` /// pub struct GenerateFunction<'a> { module: &'a Module, modules: &'a std::collections::HashMap, params: &'a CodeActionParams, edits: TextEdits<'a>, last_visited_definition_end: Option, function_to_generate: Option>, } struct FunctionToGenerate<'a> { module: Option<&'a str>, name: &'a str, arguments_types: Vec>, /// The arguments actually supplied as input to the function, if any. /// A function to generate might as well be just a name passed as an argument /// `list.map([1, 2, 3], to_generate)` so it's not guaranteed to actually /// have any actual arguments! given_arguments: Option<&'a [TypedCallArg]>, return_type: Arc, previous_definition_end: Option, } impl<'a> GenerateFunction<'a> { pub fn new( module: &'a Module, modules: &'a std::collections::HashMap, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, modules, params, edits: TextEdits::new(line_numbers), last_visited_definition_end: None, function_to_generate: None, } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); let Some( function_to_generate @ FunctionToGenerate { module, previous_definition_end: Some(insert_at), .. }, ) = self.function_to_generate.take() else { return vec![]; }; if let Some(module) = module { if let Some(module) = self.modules.get(module) { let insert_at = module.code.len() as u32; self.code_action_for_module( module, Publicity::Public, function_to_generate, insert_at, ) } else { Vec::new() } } else { let module = self.module; self.code_action_for_module(module, Publicity::Private, function_to_generate, insert_at) } } fn code_action_for_module( mut self, module: &'a Module, publicity: Publicity, function_to_generate: FunctionToGenerate<'a>, insert_at: u32, ) -> Vec { let FunctionToGenerate { name, arguments_types, given_arguments, return_type, .. } = function_to_generate; // This might be triggered on variants as well, in that case we don't // want to offer this action. The "generate variant" action will be // offered instead. if !is_valid_lowercase_name(name) { return vec![]; } // Labels do not share the same namespace as argument so we use two // separate generators to avoid renaming a label in case it shares a // name with an argument. let mut label_names = NameGenerator::new(); let mut argument_names = NameGenerator::new(); // Since we are generating a new function, type variables from other // functions and constants are irrelevant to the types we print. let mut printer = Printer::new_without_type_variables(&module.ast.names); let arguments = arguments_types .iter() .enumerate() .map(|(index, argument_type)| { let call_argument = given_arguments.and_then(|arguments| arguments.get(index)); let (label, name) = argument_names.generate_label_and_name(call_argument, argument_type); let pretty_type = printer.print_type(argument_type); if let Some(label) = label { let label = label_names.rename_to_avoid_shadowing(label.clone()); format!("{label} {name}: {pretty_type}") } else { format!("{name}: {pretty_type}") } }) .join(", "); let return_type = printer.print_type(&return_type); let publicity = if publicity.is_public() { "pub " } else { "" }; // Make sure we use the line number information of the module we are // editing, which might not be the module where the code action is // triggered. self.edits.line_numbers = &module.ast.type_info.line_numbers; self.edits.insert( insert_at, format!("\n\n{publicity}fn {name}({arguments}) -> {return_type} {{\n todo\n}}"), ); let Some(uri) = url_from_path(module.input_path.as_str()) else { return Vec::new(); }; let mut action = Vec::with_capacity(1); CodeActionBuilder::new("Generate function") .kind(CodeActionKind::QUICKFIX) .changes(uri, self.edits.edits) .preferred(true) .push_to(&mut action); action } fn try_save_function_to_generate( &mut self, name: &'a EcoString, function_type: &Arc, given_arguments: Option<&'a [TypedCallArg]>, ) { match function_type.fn_types() { None => {} Some((arguments_types, return_type)) => { self.function_to_generate = Some(FunctionToGenerate { name, arguments_types, given_arguments, return_type, previous_definition_end: self.last_visited_definition_end, module: None, }) } } } fn try_save_function_from_other_module( &mut self, module: &'a str, name: &'a str, function_type: &Arc, given_arguments: Option<&'a [TypedCallArg]>, ) { if let Some((arguments_types, return_type)) = function_type.fn_types() && is_valid_lowercase_name(name) { self.function_to_generate = Some(FunctionToGenerate { name, arguments_types, given_arguments, return_type, previous_definition_end: self.last_visited_definition_end, module: Some(module), }) } } } impl<'ast> ast::visit::Visit<'ast> for GenerateFunction<'ast> { fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { self.last_visited_definition_end = Some(fun.end_position); ast::visit::visit_typed_function(self, fun); } fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) { self.last_visited_definition_end = Some(constant.value.location().end); ast::visit::visit_typed_module_constant(self, constant); } fn visit_typed_expr_invalid( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, extra_information: &'ast Option, ) { let invalid_range = self.edits.src_span_to_lsp_range(*location); if within(self.params.range, invalid_range) { match extra_information { Some(InvalidExpression::ModuleSelect { module_name, label }) => { self.try_save_function_from_other_module(module_name, label, type_, None) } Some(InvalidExpression::UnknownVariable { name }) => { self.try_save_function_to_generate(name, type_, None) } None => {} } } ast::visit::visit_typed_expr_invalid(self, location, type_, extra_information); } fn visit_typed_constant_invalid( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, extra_information: &'ast Option, ) { let constant_range = self.edits.src_span_to_lsp_range(*location); if let Some(extra_information) = extra_information && within(self.params.range, constant_range) { match extra_information { InvalidExpression::ModuleSelect { module_name, label } => { self.try_save_function_from_other_module(module_name, label, type_, None) } InvalidExpression::UnknownVariable { name } => { self.try_save_function_to_generate(name, type_, None) } } } } fn visit_typed_expr_call( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, fun: &'ast TypedExpr, arguments: &'ast [TypedCallArg], ) { // If the function being called is invalid we need to generate a // function that has the proper labels. let fun_range = self.edits.src_span_to_lsp_range(fun.location()); if within(self.params.range, fun_range) { if !labels_are_correct(arguments) { return; } match fun { TypedExpr::Invalid { type_, extra_information: Some(InvalidExpression::ModuleSelect { module_name, label }), location: _, } => { return self.try_save_function_from_other_module( module_name, label, type_, Some(arguments), ); } TypedExpr::Invalid { type_, extra_information: Some(InvalidExpression::UnknownVariable { name }), location: _, } => { return self.try_save_function_to_generate(name, type_, Some(arguments)); } TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => {} } } ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments); } } /// Builder for the "generate variant" code action. This will generate a variant /// for a type if it can tell the type it should come from. It will work with /// non-existing variants both used as expressions /// /// ```gleam /// let a = IDoNotExist(1) /// // ^^^^^^^^^^^ It would generate this variant here /// ``` /// /// And as patterns: /// /// ```gleam /// let assert IDoNotExist(1) = todo /// ^^^^^^^^^^^ It would generate this variant here /// ``` /// pub struct GenerateVariant<'a, IO> { module: &'a Module, compiler: &'a LspProjectCompiler>, params: &'a CodeActionParams, line_numbers: &'a LineNumbers, variant_to_generate: Option>, } struct VariantToGenerate<'a> { name: &'a str, end_position: u32, arguments_types: Vec>, /// Wether the type we're adding the variant to is written with braces or /// not. We need this information to add braces when missing. /// type_braces: TypeBraces, /// The module this variant will be added to. /// module_name: EcoString, /// The arguments actually supplied as input to the variant, if any. /// A variant to generate might as well be just a name passed as an argument /// `list.map([1, 2, 3], ToGenerate)` so it's not guaranteed to actually /// have any actual arguments! /// given_arguments: Option>, } #[derive(Debug, Clone, Copy)] enum TypeBraces { /// If the type is written like this: `pub type Wibble` HasBraces, /// If the type is written like this: `pub type Wibble {}` NoBraces, } /// The arguments to an invalid call or pattern we can use to generate a variant. /// enum Arguments<'a> { /// These are the arguments provided to the invalid variant constructor /// when it's used as a function: `let a = Wibble(1, 2)`. /// Expressions(&'a [TypedCallArg]), /// These are the arguments provided to the invalid variant constructor when /// it's used in a pattern: `let assert Wibble(1, 2) = a` /// Patterns(&'a [CallArg]), } /// An invalid variant might be used both as a pattern in a case expression or /// as a regular value in an expression. We want to generate the variant in both /// cases, so we use this enum to tell apart the two cases and be able to reuse /// most of the code for both as they are very similar. /// enum Argument<'a> { Expression(&'a TypedCallArg), Pattern(&'a CallArg), } impl<'a> Arguments<'a> { fn get(&self, index: usize) -> Option> { match self { Arguments::Patterns(call_arguments) => call_arguments.get(index).map(Argument::Pattern), Arguments::Expressions(call_arguments) => { call_arguments.get(index).map(Argument::Expression) } } } fn types(&self) -> Vec> { match self { Arguments::Expressions(call_arguments) => call_arguments .iter() .map(|argument| argument.value.type_()) .collect_vec(), Arguments::Patterns(call_arguments) => call_arguments .iter() .map(|argument| argument.value.type_()) .collect_vec(), } } } impl Argument<'_> { fn label(&self) -> Option { match self { Argument::Expression(call_arg) => call_arg.label.clone(), Argument::Pattern(call_arg) => call_arg.label.clone(), } } } impl<'a, IO> GenerateVariant<'a, IO> { pub fn new( module: &'a Module, compiler: &'a LspProjectCompiler>, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, compiler, line_numbers, variant_to_generate: None, } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); let Some(VariantToGenerate { name, arguments_types, given_arguments, module_name, end_position, type_braces, }) = &self.variant_to_generate else { return vec![]; }; let Some((variant_module, variant_edits)) = self.edits_to_create_variant( name, arguments_types, given_arguments, module_name, *end_position, *type_braces, ) else { return vec![]; }; let mut action = Vec::with_capacity(1); CodeActionBuilder::new("Generate variant") .kind(CodeActionKind::QUICKFIX) .changes(variant_module, variant_edits) .preferred(true) .push_to(&mut action); action } /// Returns the edits needed to add this new variant to the given module. /// It also returns the uri of the module the edits should be applied to. /// fn edits_to_create_variant( &self, variant_name: &str, arguments_types: &[Arc], given_arguments: &Option>, module_name: &EcoString, end_position: u32, type_braces: TypeBraces, ) -> Option<(Url, Vec)> { let mut label_names = NameGenerator::new(); let mut printer = Printer::new(&self.module.ast.names); let arguments = arguments_types .iter() .enumerate() .map(|(index, argument_type)| { let label = given_arguments .as_ref() .and_then(|arguments| arguments.get(index)?.label()) .map(|label| label_names.rename_to_avoid_shadowing(label)); let pretty_type = printer.print_type(argument_type); if let Some(arg_label) = label { format!("{arg_label}: {pretty_type}") } else { format!("{pretty_type}") } }) .join(", "); let variant = if arguments.is_empty() { variant_name.to_string() } else { format!("{variant_name}({arguments})") }; let (new_text, insert_at) = match type_braces { TypeBraces::HasBraces => (format!(" {variant}\n"), end_position - 1), TypeBraces::NoBraces => (format!(" {{\n {variant}\n}}"), end_position), }; if *module_name == self.module.name { // If we're editing the current module we can use the line numbers that // were already computed before-hand without wasting any time to add the // new edit. let mut edits = TextEdits::new(self.line_numbers); edits.insert(insert_at, new_text); Some((self.params.text_document.uri.clone(), edits.edits)) } else { // Otherwise we're changing a different module and we need to get its // code and line numbers to properly apply the new edit. let module = self .compiler .modules .get(module_name) .expect("module to exist"); let line_numbers = LineNumbers::new(&module.code); let mut edits = TextEdits::new(&line_numbers); edits.insert(insert_at, new_text); Some((url_from_path(module.input_path.as_str())?, edits.edits)) } } fn try_save_variant_to_generate( &mut self, function_name_location: SrcSpan, function_type: &Arc, given_arguments: Option>, ) { let variant_to_generate = self.variant_to_generate(function_name_location, function_type, given_arguments); if variant_to_generate.is_some() { self.variant_to_generate = variant_to_generate; } } fn variant_to_generate( &mut self, function_name_location: SrcSpan, type_: &Arc, given_arguments: Option>, ) -> Option> { let name = code_at(self.module, function_name_location); if !is_valid_uppercase_name(name) { return None; } let (arguments_types, custom_type) = match (type_.fn_types(), &given_arguments) { (Some(result), _) => result, (None, Some(arguments)) => (arguments.types(), type_.clone()), (None, None) => (vec![], type_.clone()), }; let (module_name, type_name, _) = custom_type.named_type_information()?; let module = self.compiler.modules.get(&module_name)?; let (end_position, type_braces) = (module.ast.definitions.custom_types.iter()) .filter(|custom_type| custom_type.name == type_name) .find_map(|custom_type| { // If there's already a variant with this name then we definitely // don't want to generate a new variant with the same name! let variant_with_this_name_already_exists = custom_type .constructors .iter() .map(|constructor| &constructor.name) .any(|existing_constructor_name| existing_constructor_name == name); if variant_with_this_name_already_exists { return None; } let type_braces = if custom_type.end_position == custom_type.location.end { TypeBraces::NoBraces } else { TypeBraces::HasBraces }; Some((custom_type.end_position, type_braces)) })?; Some(VariantToGenerate { name, arguments_types, given_arguments, module_name, end_position, type_braces, }) } } impl<'ast, IO> ast::visit::Visit<'ast> for GenerateVariant<'ast, IO> { fn visit_typed_expr_invalid( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, extra_information: &'ast Option, ) { let invalid_range = src_span_to_lsp_range(*location, self.line_numbers); if within(self.params.range, invalid_range) { self.try_save_variant_to_generate(*location, type_, None); } ast::visit::visit_typed_expr_invalid(self, location, type_, extra_information); } fn visit_typed_expr_call( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, fun: &'ast TypedExpr, arguments: &'ast [TypedCallArg], ) { // If the function being called is invalid we need to generate a // function that has the proper labels. let fun_range = src_span_to_lsp_range(fun.location(), self.line_numbers); if within(self.params.range, fun_range) && fun.is_invalid() { if labels_are_correct(arguments) { self.try_save_variant_to_generate( fun.location(), &fun.type_(), Some(Arguments::Expressions(arguments)), ); } } else { ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments); } } fn visit_typed_pattern_invalid(&mut self, location: &'ast SrcSpan, type_: &'ast Arc) { let invalid_range = src_span_to_lsp_range(*location, self.line_numbers); if within(self.params.range, invalid_range) { self.try_save_variant_to_generate(*location, type_, None); } ast::visit::visit_typed_pattern_invalid(self, location, type_); } fn visit_typed_pattern_constructor( &mut self, location: &'ast SrcSpan, name_location: &'ast SrcSpan, name: &'ast EcoString, arguments: &'ast Vec>, module: &'ast Option<(EcoString, SrcSpan)>, constructor: &'ast Inferred, spread: &'ast Option, type_: &'ast Arc, ) { let pattern_range = src_span_to_lsp_range(*location, self.line_numbers); if within(self.params.range, pattern_range) { if labels_are_correct(arguments) { self.try_save_variant_to_generate( *name_location, type_, Some(Arguments::Patterns(arguments)), ); } } else { ast::visit::visit_typed_pattern_constructor( self, location, name_location, name, arguments, module, constructor, spread, type_, ); } } } #[must_use] /// Checks the labels in the given arguments are correct: that is there's no /// duplicate labels and all labelled arguments come after the unlabelled ones. fn labels_are_correct(arguments: &[CallArg]) -> bool { let mut labelled_arg_found = false; let mut used_labels = HashSet::new(); for argument in arguments { match &argument.label { // Labels are invalid if there's duplicate ones or if an unlabelled // argument comes after a labelled one. Some(label) if used_labels.contains(label) => return false, None if labelled_arg_found => return false, // Otherwise we just add the label to the used ones. Some(label) => { labelled_arg_found = true; let _ = used_labels.insert(label); } None => {} } } true } #[derive(Clone)] struct NameGenerator { used_names: im::HashSet, } impl NameGenerator { pub fn new() -> Self { NameGenerator { used_names: im::HashSet::new(), } } pub fn rename_to_avoid_shadowing(&mut self, base: EcoString) -> EcoString { let mut i = 1; let mut candidate_name = base.clone(); loop { if self.used_names.contains(&candidate_name) { i += 1; candidate_name = eco_format!("{base}_{i}"); } else { let _ = self.used_names.insert(candidate_name.clone()); return candidate_name; } } } /// Given an argument type and the actual call argument (if any), comes up /// with a label and a name to use for that argument when generating a /// function. /// pub fn generate_label_and_name( &mut self, call_argument: Option<&CallArg>, argument_type: &Arc, ) -> (Option, EcoString) { let label = call_argument.and_then(|argument| argument.label.clone()); let argument_name = call_argument // We always favour a name derived from the expression (for example if // the argument is a variable) .and_then(|argument| self.generate_name_from_expression(&argument.value)) // If we don't have such a name and there's a label we use that name. .or_else(|| Some(self.rename_to_avoid_shadowing(label.clone()?))) // If all else fails we fallback to using a name derived from the // argument's type. .unwrap_or_else(|| self.generate_name_from_type(argument_type)); (label, argument_name) } pub fn generate_name_from_type(&mut self, type_: &Arc) -> EcoString { let type_to_base_name = |type_: &Arc| { type_ .named_type_name() .map(|(_type_module, type_name)| to_snake_case(&type_name)) .filter(|name| is_valid_lowercase_name(name)) .unwrap_or(EcoString::from("value")) }; let base_name = match type_.list_type() { None => type_to_base_name(type_), // If we're coming up with a name for a list we want to use the // plural form for the name of the inner type. For example: // `List(Pokemon)` should generate `pokemons`. Some(inner_type) => { let base_name = type_to_base_name(&inner_type); // If the inner type name already ends in "s" we leave it as it // is, or it would look funny. if base_name.ends_with('s') { base_name } else { eco_format!("{base_name}s") } } }; self.rename_to_avoid_shadowing(base_name) } fn generate_name_from_expression(&mut self, expression: &TypedExpr) -> Option { match expression { // If the argument is a record, we can't use it as an argument name. // Similarly, we don't want to base the variable name off a // compiler-generated variable like `_pipe`. TypedExpr::Var { name, constructor, .. } if !constructor.variant.is_record() && !constructor.variant.is_generated_variable() => { Some(self.rename_to_avoid_shadowing(name.clone())) } // If the argument is a record access, we generate a name from the // label used. // For example if we have `wibble.id` we would end up picking `id`. TypedExpr::RecordAccess { label, .. } => { Some(self.rename_to_avoid_shadowing(label.clone())) } TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => None, } } /// Given some typed definitions this reserves all the value names defined /// by all the top level definitions. That is: all function names, constant /// names, and imported modules names. pub fn reserve_module_value_names(&mut self, definitions: &TypedDefinitions) { for constant in &definitions.constants { self.add_used_name(constant.name.clone()); } for function in &definitions.functions { if let Some((_, name)) = &function.name { self.add_used_name(name.clone()); } } for import in &definitions.imports { let module_name = match &import.used_name() { Some(used_name) => used_name.clone(), None => import.module.clone(), }; self.add_used_name(module_name); } } pub fn add_used_name(&mut self, name: EcoString) { let _ = self.used_names.insert(name); } pub fn reserve_all_labels(&mut self, field_map: &FieldMap) { field_map .fields .iter() .for_each(|(label, _)| self.add_used_name(label.clone())); } pub fn reserve_variable_names(&mut self, variable_names: VariablesNames) { variable_names .names .iter() .for_each(|name| self.add_used_name(name.clone())); } fn reserve_bound_variables(&mut self, bound_variables: &[BoundVariable]) { for variable in bound_variables { self.add_used_name(variable.name()); } } } #[must_use] fn is_valid_lowercase_name(name: &str) -> bool { if !name.starts_with(|char: char| char.is_ascii_lowercase()) { return false; } for char in name.chars() { let is_valid_char = char.is_ascii_digit() || char.is_ascii_lowercase() || char == '_'; if !is_valid_char { return false; } } str_to_keyword(name).is_none() } #[must_use] fn is_valid_uppercase_name(name: &str) -> bool { if !name.starts_with(|char: char| char.is_ascii_uppercase()) { return false; } for char in name.chars() { if !char.is_ascii_alphanumeric() { return false; } } true } /// Code action to rewrite a single-step pipeline into a regular function call. /// For example: `a |> b(c, _)` would be rewritten as `b(c, a)`. /// pub struct ConvertToFunctionCall<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, locations: Option, } /// All the different locations the "Convert to function call" code action needs /// to properly rewrite a pipeline into a function call. /// struct ConvertToFunctionCallLocations { /// This is the location of the value being piped into a call. /// /// ```gleam /// [1, 2, 3] |> list.length /// // ^^^^^^^^^ This one here /// ``` /// first_value: SrcSpan, /// This is the location of the call the value is being piped into. /// /// ```gleam /// [1, 2, 3] |> list.length /// // ^^^^^^^^^^^ This one here /// ``` /// call: SrcSpan, /// This is the kind of desugaring that is taking place when piping /// `first_value` into `call`. /// call_kind: PipelineAssignmentKind, } impl<'a> ConvertToFunctionCall<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), locations: None, } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); // If we couldn't find a pipeline to rewrite we don't return any action. let Some(ConvertToFunctionCallLocations { first_value, call, call_kind, }) = self.locations else { return vec![]; }; // We first delete the first value of the pipeline as it's going to be // inlined as a function call argument. self.edits.delete(SrcSpan { start: first_value.start, end: call.start, }); // Then we have to insert the piped value in the appropriate position. // This will change based on how the pipeline is being desugared, we // know this thanks to the `call_kind` let first_value_text = self .module .code .get(first_value.start as usize..first_value.end as usize) .expect("invalid code span") .to_string(); match call_kind { // When piping into a `_` we replace the hole with the piped value: // `[1, 2] |> map(_, todo)` becomes `map([1, 2], todo)`. PipelineAssignmentKind::Hole { hole } => self.edits.replace(hole, first_value_text), // When piping is desguared as a function call we need to add the // missing parentheses: // `[1, 2] |> length` becomes `length([1, 2])` PipelineAssignmentKind::FunctionCall => { self.edits.insert(call.end, format!("({first_value_text})")) } // When the piped value is inserted as the first argument there's two // possible scenarios: // - there's a second argument as well: in that case we insert it // before the second arg and add a comma // - there's no other argument: `[1, 2] |> length()` becomes // `length([1, 2])`, we insert the value between the empty // parentheses PipelineAssignmentKind::FirstArgument { second_argument: Some(SrcSpan { start, .. }), } => self.edits.insert(start, format!("{first_value_text}, ")), PipelineAssignmentKind::FirstArgument { second_argument: None, } => self.edits.insert(call.end - 1, first_value_text), // When the value is piped into an echo, to rewrite the pipeline we // have to insert the value after the `echo` with no parentheses: // `a |> echo` is rewritten as `echo a`. PipelineAssignmentKind::Echo => { self.edits.insert(call.end, format!(" {first_value_text}")) } } let mut action = Vec::with_capacity(1); CodeActionBuilder::new("Convert to function call") .kind(CodeActionKind::REFACTOR_REWRITE) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(false) .push_to(&mut action); action } } impl<'ast> ast::visit::Visit<'ast> for ConvertToFunctionCall<'ast> { fn visit_typed_expr_pipeline( &mut self, location: &'ast SrcSpan, first_value: &'ast TypedPipelineAssignment, assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], finally: &'ast TypedExpr, finally_kind: &'ast PipelineAssignmentKind, ) { let pipeline_range = self.edits.src_span_to_lsp_range(*location); if within(self.params.range, pipeline_range) { // We will always desugar the pipeline's first step. If there's no // intermediate assignment it means we're dealing with a single step // pipeline and the call is `finally`. let (call, call_kind) = assignments .first() .map(|(call, kind)| (call.location, *kind)) .unwrap_or_else(|| (finally.location(), *finally_kind)); self.locations = Some(ConvertToFunctionCallLocations { first_value: first_value.location, call, call_kind, }); ast::visit::visit_typed_expr_pipeline( self, location, first_value, assignments, finally, finally_kind, ); } } } /// Builder for code action to inline a variable. /// pub struct InlineVariable<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, actions: Vec, } impl<'a> InlineVariable<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), actions: Vec::new(), } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); self.actions } fn maybe_inline(&mut self, location: SrcSpan, name: EcoString) { let references = FindVariableReferences::new(location, name).find_in_module(&self.module.ast); let reference = if references.len() == 1 { references .into_iter() .next() .expect("References has length 1") } else { return; }; let Some(ast::Statement::Assignment(assignment)) = self.module.ast.find_statement(location.start) else { return; }; // If the assignment does not simple bind a variable, for example: // ```gleam // let #(first, second, third) // io.println(first) // // ^ Inline here // ``` // We can't inline it. if !matches!(assignment.pattern, Pattern::Variable { .. }) { return; } // If the assignment was generated by the compiler, it doesn't have a // syntactical representation, so we can't inline it. if matches!(assignment.kind, AssignmentKind::Generated) { return; } let value_location = assignment.value.location(); let value = self .module .code .get(value_location.start as usize..value_location.end as usize) .expect("Span is valid"); match reference.kind { VariableReferenceKind::Variable => { self.edits.replace(reference.location, value.into()); } VariableReferenceKind::LabelShorthand => { self.edits .insert(reference.location.end, format!(" {value}")); } } let mut location = assignment.location; let mut chars = self.module.code[location.end as usize..].chars(); // Delete any whitespace after the removed statement while chars.next().is_some_and(char::is_whitespace) { location.end += 1; } self.edits.delete(location); CodeActionBuilder::new("Inline variable") .kind(CodeActionKind::REFACTOR_INLINE) .changes( self.params.text_document.uri.clone(), std::mem::take(&mut self.edits.edits), ) .preferred(false) .push_to(&mut self.actions); } } impl<'ast> ast::visit::Visit<'ast> for InlineVariable<'ast> { fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { let TypedPattern::Variable { location, name, .. } = &assignment.pattern else { ast::visit::visit_typed_assignment(self, assignment); return; }; // We special case assignment variables because we want to trigger the // code action also if we're over the let keyword: // // ```gleam // let wibble = 11 // // ^^^^^^^^^^ Here! // ``` // let assignment_range = self .edits .src_span_to_lsp_range(SrcSpan::new(assignment.location.start, location.end)); if !within(self.params.range, assignment_range) { ast::visit::visit_typed_assignment(self, assignment); return; } self.maybe_inline(*location, name.clone()); } fn visit_typed_expr_var( &mut self, location: &'ast SrcSpan, constructor: &'ast ValueConstructor, name: &'ast EcoString, ) { let range = self.edits.src_span_to_lsp_range(*location); if !within(self.params.range, range) { return; } let type_::ValueConstructorVariant::LocalVariable { location, origin } = &constructor.variant else { return; }; // We can only inline variables assigned by `let` statements, as it //doesn't make sense to do so with any other kind of variable. match origin.declaration { VariableDeclaration::LetPattern => {} VariableDeclaration::UsePattern | VariableDeclaration::ClausePattern | VariableDeclaration::FunctionParameter { .. } | VariableDeclaration::Generated => return, } self.maybe_inline(*location, name.clone()); } fn visit_typed_pattern_variable( &mut self, location: &'ast SrcSpan, name: &'ast EcoString, _type: &'ast Arc, origin: &'ast VariableOrigin, ) { // We can only inline variables assigned by `let` statements, as it //doesn't make sense to do so with any other kind of variable. match origin.declaration { VariableDeclaration::LetPattern => {} VariableDeclaration::UsePattern | VariableDeclaration::ClausePattern | VariableDeclaration::FunctionParameter { .. } | VariableDeclaration::Generated => return, } let range = self.edits.src_span_to_lsp_range(*location); if !within(self.params.range, range) { return; } self.maybe_inline(*location, name.clone()); } } /// Builder for the "convert to pipe" code action. /// /// ```gleam /// pub fn main() { /// wibble(wobble, woo) /// // ^ [convert to pipe] /// } /// ``` /// /// Will turn the code into the following pipeline: /// /// ```gleam /// pub fn main() { /// wobble |> wibble(woo) /// } /// ``` /// pub struct ConvertToPipe<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, argument_to_pipe: Option>, visited_item: VisitedItem, } pub enum VisitedItem { RegularExpression, UseRightHandSide, PipelineFinalStep, } /// Holds all the data needed by the "convert to pipe" code action to properly /// rewrite a call into a pipe. Here's what each span means: /// /// ```gleam /// wibble(wobb|le, woo) /// // ^^^^^^^^^^^^^^^^^^^^ call /// // ^^^^^^ called /// // ^^^^^^^ arg /// // ^^^ next arg /// ``` /// /// In this example `position` is 0, since the cursor is over the first /// argument. /// pub struct ConvertToPipeArg<'a> { /// The span of the called function. called: SrcSpan, /// The span of the entire function call. call: SrcSpan, /// The position (0-based) of the argument. position: usize, /// The argument we have to pipe. arg: &'a TypedCallArg, /// The span of the argument following the one we have to pipe, if there's /// any. next_arg: Option, } impl<'a> ConvertToPipe<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), visited_item: VisitedItem::RegularExpression, argument_to_pipe: None, } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); let Some(ConvertToPipeArg { called, call, position, arg, next_arg, }) = self.argument_to_pipe else { return vec![]; }; let arg_location = if arg.uses_label_shorthand() { SrcSpan { start: arg.location.start, end: arg.location.end - 1, } } else if arg.label.is_some() { arg.value.location() } else { arg.location }; let arg_text = code_at(self.module, arg_location); // If the expression being piped is a binary operation with // precedence lower than pipes then we have to wrap it in curly // braces to not mess with the order of operations. let arg_text = if let TypedExpr::BinOp { name, .. } = arg.value && name.precedence() < PIPE_PRECEDENCE { &format!("{{ {arg_text} }}") } else { arg_text }; match next_arg { // When extracting an argument we never want to remove any explicit // label that was written down, so in case it is labelled (be it a // shorthand or not) we'll always replace the value with a `_` _ if arg.uses_label_shorthand() => self.edits.insert(arg.location.end, " _".into()), _ if arg.label.is_some() => self.edits.replace(arg.value.location(), "_".into()), // Now we can deal with unlabelled arguments: // If we're removing the first argument and there's other arguments // after it, we need to delete the comma that was separating the // two. Some(next_arg) if position == 0 => self.edits.delete(SrcSpan { start: arg.location.start, end: next_arg.start, }), // Otherwise, if we're deleting the first argument and there's // no other arguments following it, we remove the call's // parentheses. None if position == 0 => self.edits.delete(SrcSpan { start: called.end, end: call.end, }), // In all other cases we're piping something that is not the first // argument so we just replace it with an `_`. _ => self.edits.replace(arg.location, "_".into()), }; // Finally we can add the argument that was removed as the first step // of the newly defined pipeline. self.edits.insert(call.start, format!("{arg_text} |> ")); let mut action = Vec::with_capacity(1); CodeActionBuilder::new("Convert to pipe") .kind(CodeActionKind::REFACTOR_REWRITE) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(false) .push_to(&mut action); action } } impl<'ast> ast::visit::Visit<'ast> for ConvertToPipe<'ast> { fn visit_typed_expr_call( &mut self, location: &'ast SrcSpan, _type_: &'ast Arc, fun: &'ast TypedExpr, arguments: &'ast [TypedCallArg], ) { if arguments.iter().any(|arg| arg.is_capture_hole()) { return; } // If we're visiting the typed function produced by typing a use, we // skip the thing itself and only visit its arguments and called // function, that is the body of the use. match self.visited_item { VisitedItem::RegularExpression => (), VisitedItem::UseRightHandSide | VisitedItem::PipelineFinalStep => { self.visited_item = VisitedItem::RegularExpression; ast::visit::visit_typed_expr(self, fun); arguments .iter() .for_each(|arg| ast::visit::visit_typed_call_arg(self, arg)); return; } } // We only visit a call if the cursor is somewhere within its location, // otherwise we skip it entirely. let call_range = self.edits.src_span_to_lsp_range(*location); if !within(self.params.range, call_range) { return; } // If the cursor is over any of the arguments then we'll use that as // the one to extract. // Otherwise the cursor must be over the called function, in that case // we extract the first argument (if there's one): // // ```gleam // wibble(wobble, woo) // // ^^^^^^^^^^^^^ pipe the first argument if I'm here // // ^^^ pipe the second argument if I'm here // ``` let argument_to_pipe = arguments .iter() .enumerate() .find_map(|(position, arg)| { let arg_range = self.edits.src_span_to_lsp_range(arg.location); if within(self.params.range, arg_range) { Some((position, arg)) } else { None } }) .or_else(|| arguments.first().map(|argument| (0, argument))); // If we're not hovering over any of the arguments _or_ there's no // argument to extract at all we just return, there's nothing we can do // on this call or any of its arguments (since we've determined the // cursor is not over any of those). let Some((position, arg)) = argument_to_pipe else { return; }; self.argument_to_pipe = Some(ConvertToPipeArg { called: fun.location(), call: *location, position, arg, next_arg: arguments .get(position + 1) .map(|argument| argument.location), }); // We still want to visit the arguments so that if we're hovering a // nested pipeline, that's going to be the one we transform: // // ```gleam // wibble(Wobble( // field: call(other(last(1))) // // ^^^^ We want to convert this one if we hover over it, // // not the outer `wibble(Wobble(...))` call // )) // ``` // for argument in arguments { ast::visit::visit_typed_call_arg(self, argument); } } fn visit_typed_expr_pipeline( &mut self, _location: &'ast SrcSpan, first_value: &'ast TypedPipelineAssignment, _assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], finally: &'ast TypedExpr, _finally_kind: &'ast PipelineAssignmentKind, ) { // We can only apply the action on the first step of a pipeline, so we // visit just that one and skip all the others. ast::visit::visit_typed_pipeline_assignment(self, first_value); self.visited_item = VisitedItem::PipelineFinalStep; ast::visit::visit_typed_expr(self, finally); } fn visit_typed_use(&mut self, use_: &'ast TypedUse) { self.visited_item = VisitedItem::UseRightHandSide; ast::visit::visit_typed_use(self, use_); } } /// Code action to interpolate a string. If the cursor is inside the string /// (not selecting anything) the language server will offer to split it: /// /// ```gleam /// "wibble | wobble" /// // ^ [Split string] /// // Will produce the following /// "wibble " <> todo <> " wobble" /// ``` /// /// If the cursor is selecting an entire valid gleam name, then the language /// server will offer to interpolate it as a variable: /// /// ```gleam /// "wibble wobble woo" /// // ^^^^^^ [Interpolate variable] /// // Will produce the following /// "wibble " <> wobble <> " woo" /// ``` /// /// > Note: the cursor won't end up right after the inserted variable/todo. /// > that's a bit annoying, but in a future LSP version we will be able to /// > isnert tab stops to allow one to jump to the newly added variable/todo. /// pub struct InterpolateString<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, string_interpolation: Option<(SrcSpan, StringInterpolation)>, string_literal_position: StringLiteralPosition, } #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum StringLiteralPosition { FirstPipelineStep, Other, } #[derive(Clone, Copy)] enum StringInterpolation { InterpolateValue { value_location: SrcSpan }, SplitString { split_at: u32 }, } impl<'a> InterpolateString<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), string_interpolation: None, string_literal_position: StringLiteralPosition::Other, } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); let Some((string_location, interpolation)) = self.string_interpolation else { return vec![]; }; if self.string_literal_position == StringLiteralPosition::FirstPipelineStep { self.edits.insert(string_location.start, "{ ".into()); } match interpolation { StringInterpolation::InterpolateValue { value_location } => { let name = self .module .code .get(value_location.start as usize..value_location.end as usize) .expect("invalid value range"); // We trust that the programmer has correctly selected the part // of the string they want to interpolate and simply "cut it out" // for them. In future, we could try and parse their selection to // see if it is a valid expression in Gleam. if value_location.start == string_location.start + 1 { self.edits .insert(string_location.start, format!("{name} <> ")); } else if value_location.end == string_location.end - 1 { self.edits .insert(string_location.end, format!(" <> {name}")); } else { self.edits .insert(value_location.start, format!("\" <> {name} <> \"")); } self.edits.delete(value_location); } StringInterpolation::SplitString { split_at } if self.can_split_string_at(split_at) => { self.edits.insert(split_at, "\" <> todo <> \"".into()); } StringInterpolation::SplitString { .. } => return vec![], }; if self.string_literal_position == StringLiteralPosition::FirstPipelineStep { self.edits.insert(string_location.end, " }".into()); } let mut action = Vec::with_capacity(1); CodeActionBuilder::new("Interpolate string") .kind(CodeActionKind::REFACTOR_REWRITE) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(false) .push_to(&mut action); action } fn can_split_string_at(&self, at: u32) -> bool { self.string_interpolation .is_some_and(|(string_location, _)| { !(at <= string_location.start + 1 || at >= string_location.end - 1) }) } fn visit_literal_string( &mut self, string_location: SrcSpan, string_position: StringLiteralPosition, ) { // We can only interpolate/split a string if the cursor is somewhere // within its location, otherwise we skip it. let string_range = self.edits.src_span_to_lsp_range(string_location); if !within(self.params.range, string_range) { return; } let selection @ SrcSpan { start, end } = self.edits.lsp_range_to_src_span(self.params.range); // We can't interpolate/split if the double quotes delimiting the // string have been selected. if start == string_location.start || end == string_location.end { return; } let name = self .module .code .get(start as usize..end as usize) .expect("invalid value range"); // TUI editors like Helix and Kakoune that use the selection-action edit // model are always in the equivalent of Vim's VISUAL mode, i.e. they always // have something selected. For programmers using these editors, the // smallest selection possible is a 1-character selection. The best we can do // to provide parity with other editors is to consider a single-character SPACE // selection as an empty selection, as they most likely want to split the // string instead of interpolating a variable. let interpolation = if start == end || (end - start == 1 && name == " ") { StringInterpolation::SplitString { split_at: start } } else { StringInterpolation::InterpolateValue { value_location: selection, } }; self.string_interpolation = Some((string_location, interpolation)); self.string_literal_position = string_position; } } impl<'ast> ast::visit::Visit<'ast> for InterpolateString<'ast> { fn visit_typed_expr_string( &mut self, location: &'ast SrcSpan, _type_: &'ast Arc, _value: &'ast EcoString, ) { self.visit_literal_string(*location, StringLiteralPosition::Other); } fn visit_typed_expr_pipeline( &mut self, _location: &'ast SrcSpan, first_value: &'ast TypedPipelineAssignment, assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)], finally: &'ast TypedExpr, _finally_kind: &'ast PipelineAssignmentKind, ) { if first_value.value.is_literal_string() { self.visit_literal_string( first_value.location, StringLiteralPosition::FirstPipelineStep, ); } else { ast::visit::visit_typed_pipeline_assignment(self, first_value); } assignments .iter() .for_each(|(a, _)| ast::visit::visit_typed_pipeline_assignment(self, a)); self.visit_typed_expr(finally); } } /// Code action to replace a `..` in a pattern with all the missing fields that /// have not been explicitly provided; labelled ones are introduced with the /// shorthand syntax. /// /// ```gleam /// pub type Pokemon { /// Pokemon(Int, name: String, moves: List(String)) /// } /// /// pub fn main() { /// let Pokemon(..) = todo /// // ^^ Cursor over the spread /// } /// ``` /// Would become /// ```gleam /// pub fn main() { /// let Pokemon(int, name:, moves:) = todo /// } /// pub struct FillUnusedFields<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, data: Option, } pub struct FillUnusedFieldsData { /// All the missing positional and labelled fields. positional: Vec>, labelled: Vec<(EcoString, Arc)>, /// We need this in order to tell where the missing positional arguments /// should be inserted. first_labelled_argument_start: Option, /// The end of the final argument before the spread, if there's any. /// We'll use this to delete everything that comes after the final argument, /// after adding all the ignored fields. last_argument_end: Option, spread_location: SrcSpan, } impl<'a> FillUnusedFields<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), data: None, } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); let Some(FillUnusedFieldsData { positional, labelled, first_labelled_argument_start, last_argument_end, spread_location, }) = self.data else { return vec![]; }; // Do not suggest this code action if there's no ignored fields at all. if positional.is_empty() && labelled.is_empty() { return vec![]; }; // We add all the missing positional arguments before the first // labelled one (and so after all the already existing positional ones). if !positional.is_empty() { // We want to make sure that all positional args will have a name // that's different from any label. So we add those as already used // names. let mut names = NameGenerator::new(); for (label, _) in labelled.iter() { names.add_used_name(label.clone()); } let positional_arguments = positional .iter() .map(|type_| names.generate_name_from_type(type_)) .join(", "); let insert_at = first_labelled_argument_start.unwrap_or(spread_location.start); // The positional arguments are going to be followed by some other // arguments if there's some already existing labelled args // (`last_argument_end.is_some`), of if we're adding those labelled args // ourselves (`!labelled.is_empty()`). So we need to put a comma after the // final positional argument we're adding to separate it from the ones that // are going to come after. let has_arguments_after = last_argument_end.is_some() || !labelled.is_empty(); let positional_arguments = if has_arguments_after { format!("{positional_arguments}, ") } else { positional_arguments }; self.edits.insert(insert_at, positional_arguments); } if !labelled.is_empty() { // If there's labelled arguments to add, we replace the existing spread // with the arguments to be added. This way commas and all should already // be correct. let labelled_arguments = labelled .iter() .map(|(label, _)| format!("{label}:")) .join(", "); self.edits.replace(spread_location, labelled_arguments); } else if let Some(delete_start) = last_argument_end { // However, if there's no labelled arguments to insert we still need // to delete the entire spread: we start deleting from the end of the // final argument, if there's one. // This way we also get rid of any comma separating the last argument // and the spread to be removed. self.edits .delete(SrcSpan::new(delete_start, spread_location.end)) } else { // Otherwise we just delete the spread. self.edits.delete(spread_location) } let mut action = Vec::with_capacity(1); CodeActionBuilder::new("Fill unused fields") .kind(CodeActionKind::REFACTOR_REWRITE) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(false) .push_to(&mut action); action } } impl<'ast> ast::visit::Visit<'ast> for FillUnusedFields<'ast> { fn visit_typed_pattern(&mut self, pattern: &'ast TypedPattern) { // We can only interpolate/split a string if the cursor is somewhere // within its location, otherwise we skip it. let pattern_range = self.edits.src_span_to_lsp_range(pattern.location()); if !within(self.params.range, pattern_range) { return; } if let TypedPattern::Constructor { arguments, spread: Some(spread_location), .. } = pattern && let Some(PatternUnusedArguments { positional, labelled, }) = pattern.unused_arguments() { // If there's any unused argument that's being ignored we want to // suggest the code action. let first_labelled_argument_start = arguments .iter() .find(|arg| !arg.is_implicit() && arg.label.is_some()) .map(|arg| arg.location.start); let last_argument_end = arguments .iter() .rfind(|arg| !arg.is_implicit()) .map(|arg| arg.location.end); self.data = Some(FillUnusedFieldsData { positional, labelled, first_labelled_argument_start, last_argument_end, spread_location: *spread_location, }); }; ast::visit::visit_typed_pattern(self, pattern); } } /// Code action to remove an echo. /// pub struct RemoveEchos<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, is_hovering_echo: bool, echo_spans_to_delete: Vec, // We need to keep a reference to the two latest pipeline assignments we // run into to properly delete an echo that's inside a pipeline. latest_pipe_step: Option, second_to_latest_pipe_step: Option, } impl<'a> RemoveEchos<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), is_hovering_echo: false, echo_spans_to_delete: vec![], latest_pipe_step: None, second_to_latest_pipe_step: None, } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); // We only want to trigger the action if we're over one of the echos in // the module if !self.is_hovering_echo { return vec![]; }; for span in self.echo_spans_to_delete { self.edits.delete(span); } let mut action = Vec::with_capacity(1); CodeActionBuilder::new("Remove all `echo`s from this module") .kind(CodeActionKind::REFACTOR_REWRITE) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(false) .push_to(&mut action); action } fn visit_function_statements(&mut self, statements: &'a [TypedStatement]) { for i in 0..statements.len() { let statement = statements .get(i) .expect("Statement must exist in iteration"); let next_statement = statements.get(i + 1); let is_last = i == statements.len() - 1; match statement { // We remove any echo that is used as a standalone statement used // to print a literal value. // // ```gleam // pub fn main() { // echo "I'm here" // do_something() // echo "Safe!" // do_something_else() // } // ``` // // Here we want to remove not just the echo but also the literal // strings they're printing. // // It's safe to do this only if echo is not the last expression // in a function's block (otherwise we might change the function's // return type by removing the entire line) and the value being // printed is a literal expression. // ast::Statement::Expression(TypedExpr::Echo { location, expression, .. }) if !is_last && expression.as_ref().is_some_and(|expression| { expression.is_literal() || expression.is_var() }) => { let echo_range = self.edits.src_span_to_lsp_range(*location); if within(self.params.range, echo_range) { self.is_hovering_echo = true; } let end = next_statement .map(|next| { let echo_end = location.end; let next_start = next.location().start; // We want to remove everything until the start of the // following statement. However, we have to be careful not to // delete any comments. So if there's any comment between the // echo to remove and the next statement, we just delete until // the comment's start. self.module .extra .first_comment_between(echo_end, next_start) // For comments we record the start of their content, not of the `//` // so we're subtracting 2 here to not delete the `//` as well .map(|comment| comment.start - 2) .unwrap_or(next_start) }) .unwrap_or(location.end); self.echo_spans_to_delete.push(SrcSpan { start: location.start, end, }); } // Otherwise we visit the statement as usual. ast::Statement::Expression(_) | ast::Statement::Assignment(_) | ast::Statement::Use(_) | ast::Statement::Assert(_) => ast::visit::visit_typed_statement(self, statement), } } } } impl<'ast> ast::visit::Visit<'ast> for RemoveEchos<'ast> { fn visit_typed_function(&mut self, fun: &'ast TypedFunction) { self.visit_function_statements(&fun.body); } fn visit_typed_expr_fn( &mut self, _location: &'ast SrcSpan, _type_: &'ast Arc, _kind: &'ast FunctionLiteralKind, _arguments: &'ast [TypedArg], body: &'ast Vec1, _return_annotation: &'ast Option, ) { self.visit_function_statements(body); } fn visit_typed_expr_echo( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, expression: &'ast Option>, message: &'ast Option>, ) { // We also want to trigger the action if we're hovering over the expression // being printed. So we create a unique span starting from the start of echo // end ending at the end of the expression. // // ``` // echo 1 + 2 // ^^^^^^^^^^ This is `location`, we want to trigger the action if we're // inside it, not just the keyword // ``` // let echo_range = self.edits.src_span_to_lsp_range(*location); if within(self.params.range, echo_range) { self.is_hovering_echo = true; } // We also want to remove the echo message! if message.is_some() { let start = expression .as_ref() .map(|expression| expression.location().end) .unwrap_or(location.start + 4); self.echo_spans_to_delete .push(SrcSpan::new(start, location.end)); } if let Some(expression) = expression { // If there's an expression we delete everything we find until its // start (excluded). let span_to_delete = SrcSpan::new(location.start, expression.location().start); self.echo_spans_to_delete.push(span_to_delete); } else { // Othwerise we know we're inside a pipeline, we take the closest step // that is not echo itself and delete everything from its end until the // end of the echo keyword: // // ```txt // wibble |> echo |> wobble // ^^^^^^^^ This span right here // ``` let step_preceding_echo = self .latest_pipe_step .filter(|l| l != location) .or(self.second_to_latest_pipe_step); if let Some(step_preceding_echo) = step_preceding_echo { let span_to_delete = SrcSpan::new(step_preceding_echo.end, location.start + 4); self.echo_spans_to_delete.push(span_to_delete); } } ast::visit::visit_typed_expr_echo(self, location, type_, expression, message); } fn visit_typed_pipeline_assignment(&mut self, assignment: &'ast TypedPipelineAssignment) { if self.latest_pipe_step.is_some() { self.second_to_latest_pipe_step = self.latest_pipe_step; } self.latest_pipe_step = Some(assignment.location); ast::visit::visit_typed_pipeline_assignment(self, assignment); } } /// Code action to wrap assignment and case clause values in a block. /// /// ```gleam /// pub type PokemonType { /// Fire /// Water /// } /// /// pub fn main() { /// let pokemon_type: PokemonType = todo /// case pokemon_type { /// Water -> soak() /// ^^^^^^ Cursor over the spread /// Fire -> burn() /// } /// } /// ``` /// Becomes /// ```gleam /// pub type PokemonType { /// Fire /// Water /// } /// /// pub fn main() { /// let pokemon_type: PokemonType = todo /// case pokemon_type { /// Water -> { /// soak() /// } /// Fire -> burn() /// } /// } /// ``` /// pub struct WrapInBlock<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, selected_expression: Option, } impl<'a> WrapInBlock<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), selected_expression: None, } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); let Some(expr_span) = self.selected_expression else { return vec![]; }; let Some(expr_string) = self .module .code .get(expr_span.start as usize..(expr_span.end as usize + 1)) else { return vec![]; }; let range = self .edits .src_span_to_lsp_range(self.selected_expression.expect("Real range value")); let indent_size = count_indentation(&self.module.code, self.edits.line_numbers, range.start.line); let expr_indent_size = indent_size + 2; let indent = " ".repeat(indent_size); let inner_indent = " ".repeat(expr_indent_size); self.edits.replace( expr_span, format!("{{\n{inner_indent}{expr_string}{indent}}}"), ); let mut action = Vec::with_capacity(1); CodeActionBuilder::new("Wrap in block") .kind(CodeActionKind::REFACTOR_EXTRACT) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(false) .push_to(&mut action); action } } impl<'ast> ast::visit::Visit<'ast> for WrapInBlock<'ast> { fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) { ast::visit::visit_typed_expr(self, &assignment.value); if !within( self.params.range, self.edits .src_span_to_lsp_range(assignment.value.location()), ) { return; } match &assignment.value { // To avoid wrapping the same expression in multiple, nested blocks. TypedExpr::Block { .. } => {} TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => { self.selected_expression = Some(assignment.value.location()); } }; ast::visit::visit_typed_assignment(self, assignment); } fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) { ast::visit::visit_typed_clause(self, clause); if !within( self.params.range, self.edits.src_span_to_lsp_range(clause.then.location()), ) { return; } // To avoid wrapping the same expression in multiple, nested blocks. if !matches!(clause.then, TypedExpr::Block { .. }) { self.selected_expression = Some(clause.then.location()); }; ast::visit::visit_typed_clause(self, clause); } } /// Code action to fix wrong binary operators when the compiler can easily tell /// what the correct alternative is. /// /// ```gleam /// 1 +. 2 // becomes 1 + 2 /// 1.0 + 2.3 // becomes 1.0 +. 2.3 /// ``` /// pub struct FixBinaryOperation<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, fix: Option<(SrcSpan, ast::BinOp)>, } impl<'a> FixBinaryOperation<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), fix: None, } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); let Some((location, replacement)) = self.fix else { return vec![]; }; self.edits.replace(location, replacement.name().into()); let mut action = Vec::with_capacity(1); CodeActionBuilder::new(&format!("Use `{}`", replacement.name())) .kind(CodeActionKind::REFACTOR_REWRITE) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(true) .push_to(&mut action); action } } impl<'ast> ast::visit::Visit<'ast> for FixBinaryOperation<'ast> { fn visit_typed_expr_bin_op( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, name: &'ast ast::BinOp, name_location: &'ast SrcSpan, left: &'ast TypedExpr, right: &'ast TypedExpr, ) { let binop_range = self.edits.src_span_to_lsp_range(*location); if !within(self.params.range, binop_range) { return; } if name.is_int_operator() && left.type_().is_float() && right.type_().is_float() { self.fix = name.float_equivalent().map(|fix| (*name_location, fix)); } else if name.is_float_operator() && left.type_().is_int() && right.type_().is_int() { self.fix = name.int_equivalent().map(|fix| (*name_location, fix)) } else if *name == ast::BinOp::AddInt && left.type_().is_string() && right.type_().is_string() { self.fix = Some((*name_location, ast::BinOp::Concatenate)) } ast::visit::visit_typed_expr_bin_op( self, location, type_, name, name_location, left, right, ); } } /// Code action builder to automatically fix segments that have a value that's /// guaranteed to overflow. /// pub struct FixTruncatedBitArraySegment<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, truncation: Option, } impl<'a> FixTruncatedBitArraySegment<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), truncation: None, } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); let Some(truncation) = self.truncation else { return vec![]; }; let replacement = truncation.truncated_into.to_string(); self.edits .replace(truncation.value_location, replacement.clone()); let mut action = Vec::with_capacity(1); CodeActionBuilder::new(&format!("Replace with `{replacement}`")) .kind(CodeActionKind::REFACTOR_REWRITE) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(true) .push_to(&mut action); action } } impl<'ast> ast::visit::Visit<'ast> for FixTruncatedBitArraySegment<'ast> { fn visit_typed_expr_bit_array_segment(&mut self, segment: &'ast ast::TypedExprBitArraySegment) { let segment_range = self.edits.src_span_to_lsp_range(segment.location); if !within(self.params.range, segment_range) { return; } if let Some(truncation) = segment.check_for_truncated_value() { self.truncation = Some(truncation); } ast::visit::visit_typed_expr_bit_array_segment(self, segment); } } /// Code action builder to remove unused imports and values. /// pub struct RemoveUnusedImports<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, } #[derive(Debug)] enum UnusedImport { ValueOrType(SrcSpan), Module(SrcSpan), ModuleAlias(SrcSpan), } impl UnusedImport { fn location(&self) -> SrcSpan { match self { UnusedImport::ValueOrType(location) | UnusedImport::Module(location) | UnusedImport::ModuleAlias(location) => *location, } } } impl<'a> RemoveUnusedImports<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), } } /// Given an import location, returns a list of the spans of all the /// unqualified values it's importing. Sorted by SrcSpan location. /// fn imported_values(&self, import_location: SrcSpan) -> Vec { self.module .ast .definitions .imports .iter() .find(|import| import.location.contains(import_location.start)) .map(|import| { let types = import.unqualified_types.iter().map(|type_| type_.location); let values = import.unqualified_values.iter().map(|value| value.location); types .chain(values) .sorted_by_key(|location| location.start) .collect_vec() }) .unwrap_or_default() } pub fn code_actions(mut self) -> Vec { // If there's no import in the module then there can't be any unused // import to remove. if self.module.ast.definitions.imports.is_empty() { return vec![]; } let unused_imports = self .module .ast .type_info .warnings .iter() .filter_map(|warning| match warning { type_::Warning::UnusedImportedValue { location, .. } => { Some(UnusedImport::ValueOrType(*location)) } type_::Warning::UnusedType { location, imported: true, .. } => Some(UnusedImport::ValueOrType(*location)), type_::Warning::UnusedImportedModule { location, .. } => { Some(UnusedImport::Module(*location)) } type_::Warning::UnusedImportedModuleAlias { location, .. } => { Some(UnusedImport::ModuleAlias(*location)) } type_::Warning::Todo { .. } | type_::Warning::ImplicitlyDiscardedResult { .. } | type_::Warning::UnusedLiteral { .. } | type_::Warning::UnusedValue { .. } | type_::Warning::NoFieldsRecordUpdate { .. } | type_::Warning::AllFieldsRecordUpdate { .. } | type_::Warning::UnusedType { .. } | type_::Warning::UnusedConstructor { .. } | type_::Warning::UnusedPrivateModuleConstant { .. } | type_::Warning::UnusedPrivateFunction { .. } | type_::Warning::UnusedVariable { .. } | type_::Warning::UnnecessaryDoubleIntNegation { .. } | type_::Warning::UnnecessaryDoubleBoolNegation { .. } | type_::Warning::InefficientEmptyListCheck { .. } | type_::Warning::TransitiveDependencyImported { .. } | type_::Warning::DeprecatedItem { .. } | type_::Warning::UnreachableCasePattern { .. } | type_::Warning::UnusedDiscardPattern { .. } | type_::Warning::CaseMatchOnLiteralCollection { .. } | type_::Warning::CaseMatchOnLiteralValue { .. } | type_::Warning::OpaqueExternalType { .. } | type_::Warning::InternalTypeLeak { .. } | type_::Warning::RedundantAssertAssignment { .. } | type_::Warning::AssertAssignmentOnImpossiblePattern { .. } | type_::Warning::TodoOrPanicUsedAsFunction { .. } | type_::Warning::UnreachableCodeAfterPanic { .. } | type_::Warning::RedundantPipeFunctionCapture { .. } | type_::Warning::FeatureRequiresHigherGleamVersion { .. } | type_::Warning::JavaScriptIntUnsafe { .. } | type_::Warning::AssertLiteralBool { .. } | type_::Warning::BitArraySegmentTruncatedValue { .. } | type_::Warning::ModuleImportedTwice { .. } | type_::Warning::TopLevelDefinitionShadowsImport { .. } | type_::Warning::RedundantComparison { .. } | type_::Warning::UnusedRecursiveArgument { .. } => None, }) .sorted_by_key(|import| import.location()) .collect_vec(); // If the cursor is not over any of the unused imports then we don't offer // the code action. let hovering_unused_import = unused_imports.iter().any(|import| { let unused_range = self.edits.src_span_to_lsp_range(import.location()); overlaps(self.params.range, unused_range) }); if !hovering_unused_import { return vec![]; } // Otherwise we start removing all unused imports: for import in &unused_imports { match import { // When an entire module is unused we can delete its entire location // in the source code. UnusedImport::Module(location) | UnusedImport::ModuleAlias(location) => { if self.edits.line_numbers.spans_entire_line(location) { // If the unused module spans over the entire line then // we also take care of removing the following newline // characther! self.edits.delete(SrcSpan { start: location.start, end: location.end + 1, }) } else { self.edits.delete(*location) } } // When removing unused imported values we have to be a bit more // careful: an unused value might be followed or preceded by a // comma that we also need to remove! UnusedImport::ValueOrType(location) => { let imported = self.imported_values(*location); let unused_index = imported.binary_search(location); let is_last = unused_index.is_ok_and(|index| index == imported.len() - 1); let next_value = unused_index .ok() .and_then(|value_index| imported.get(value_index + 1)); let previous_value = unused_index.ok().and_then(|value_index| { value_index .checked_sub(1) .and_then(|previous_index| imported.get(previous_index)) }); let previous_is_unused = previous_value.is_some_and(|previous| { unused_imports .as_slice() .binary_search_by_key(previous, |import| import.location()) .is_ok() }); match (previous_value, next_value) { // If there's a value following the unused import we need // to remove all characters until its start! // // ```gleam // import wibble.{unused, used} // // ^^^^^^^^^^^ We need to remove all of this! // ``` // (_, Some(next_value)) => self.edits.delete(SrcSpan { start: location.start, end: next_value.start, }), // If this unused import is the last of the unuqualified // list and is preceded by another used value then we // need to do some additional cleanup and remove all // characters starting from its end. // (If the previous one is unused as well it will take // care of removing all the extra space) // // ```gleam // import wibble.{used, unused} // // ^^^^^^^^^^^^ We need to remove all of this! // ``` // (Some(previous_value), _) if is_last && !previous_is_unused => { self.edits.delete(SrcSpan { start: previous_value.end, end: location.end, }) } // In all other cases it means that this is the only // item in the import list. We can just remove it. // // ```gleam // import wibble.{unused} // // ^^^^^^ We remove this import, the formatter will already // // take care of removing the empty curly braces // ``` // (_, _) => self.edits.delete(*location), } } } } let mut action = Vec::with_capacity(1); CodeActionBuilder::new("Remove unused imports") .kind(CodeActionKind::REFACTOR_REWRITE) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(true) .push_to(&mut action); action } } /// Code action to remove a block wrapping a single expression. /// pub struct RemoveBlock<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, block_span: Option, position: RemoveBlockPosition, } #[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd)] enum RemoveBlockPosition { InsideBinOp, OutsideBinOp, } impl<'a> RemoveBlock<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), block_span: None, position: RemoveBlockPosition::OutsideBinOp, } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); let Some(SrcSpan { start, end }) = self.block_span else { return vec![]; }; self.edits.delete(SrcSpan::new(start, start + 1)); self.edits.delete(SrcSpan::new(end - 1, end)); let mut action = Vec::with_capacity(1); CodeActionBuilder::new("Remove block") .kind(CodeActionKind::REFACTOR_REWRITE) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(true) .push_to(&mut action); action } } impl<'ast> ast::visit::Visit<'ast> for RemoveBlock<'ast> { fn visit_typed_expr_bin_op( &mut self, _location: &'ast SrcSpan, _type_: &'ast Arc, _name: &'ast ast::BinOp, _name_location: &'ast SrcSpan, left: &'ast TypedExpr, right: &'ast TypedExpr, ) { let old_position = self.position; self.position = RemoveBlockPosition::InsideBinOp; ast::visit::visit_typed_expr(self, left); self.position = RemoveBlockPosition::InsideBinOp; ast::visit::visit_typed_expr(self, right); self.position = old_position; } fn visit_typed_expr_block( &mut self, location: &'ast SrcSpan, statements: &'ast [TypedStatement], ) { let block_range = self.edits.src_span_to_lsp_range(*location); if !within(self.params.range, block_range) { return; } match statements { [] | [_, _, ..] => (), [value] => match value { ast::Statement::Use(_) | ast::Statement::Assert(_) | ast::Statement::Assignment(_) => { ast::visit::visit_typed_expr_block(self, location, statements) } ast::Statement::Expression(expr) => match expr { TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => { self.block_span = Some(*location); } TypedExpr::BinOp { .. } | TypedExpr::Pipeline { .. } => { if self.position == RemoveBlockPosition::OutsideBinOp { self.block_span = Some(*location); } } }, }, } ast::visit::visit_typed_expr_block(self, location, statements); } } /// Code action to remove `opaque` from a private type. /// pub struct RemovePrivateOpaque<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, opaque_span: Option, } impl<'a> RemovePrivateOpaque<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), opaque_span: None, } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); let Some(opaque_span) = self.opaque_span else { return vec![]; }; self.edits.delete(opaque_span); let mut action = Vec::with_capacity(1); CodeActionBuilder::new("Remove opaque from private type") .kind(CodeActionKind::QUICKFIX) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(true) .push_to(&mut action); action } } impl<'ast> ast::visit::Visit<'ast> for RemovePrivateOpaque<'ast> { fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) { let custom_type_range = self.edits.src_span_to_lsp_range(custom_type.location); if !within(self.params.range, custom_type_range) { return; } if custom_type.opaque && custom_type.publicity.is_private() { self.opaque_span = Some(SrcSpan { start: custom_type.location.start, end: custom_type.location.start + 7, }) } } } /// Code action to rewrite a case expression as part of an outer case expression /// branch. For example: /// /// ```gleam /// case wibble { /// Ok(a) -> case a { /// 1 -> todo /// _ -> todo /// } /// Error(_) -> todo /// } /// ``` /// /// Would become: /// /// ```gleam /// case wibble { /// Ok(1) -> todo /// Ok(_) -> todo /// Error(_) -> todo /// } /// ``` /// pub struct CollapseNestedCase<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, collapsed: Option>, } /// This holds all the needed data about the pattern to collapse. /// We'll use this piece of code as an example: /// ```gleam /// case something { /// User(username: _, NotAdmin) -> "Stranger!!" /// User(username:, Admin) if wibble -> /// case username { // <- We're collapsing this nested case /// "Joe" -> "Hello, Joe!" /// _ -> "I don't know you, " <> username /// } /// } /// ``` /// struct Collapsed<'a> { /// This is the span covering the entire clause being collapsed: /// /// ```gleam /// case something { /// User(username: _, NotAdmin) -> "Stranger!!" /// User(username:, Admin) if wibble -> /// ┬ It goes all the way from here... /// ╭─╯ /// │ case username { /// │ "Joe" -> "Hello, Joe!" /// │ _ -> "I don't know you, " <> username /// │ } /// │ ┬ ...to here! /// ╰───╯ /// } /// ``` /// outer_clause_span: SrcSpan, /// The (optional) guard of the outer branch. In this exmaple it's this one: /// /// ```gleam /// case something { /// User(username: _, NotAdmin) -> "Stranger!!" /// User(username:, Admin) if wibble -> /// ┬──────── /// ╰─ `outer_guard` /// case username { /// "Joe" -> "Hello, Joe!" /// _ -> "I don't know you, " <> username /// } /// } /// ``` /// outer_guard: &'a Option, /// The pattern variable being matched on: /// /// ```gleam /// case something { /// User(username: _, NotAdmin) -> "Stranger!!" /// User(username:, Admin) if wibble -> /// ┬─────── /// ╰─ `matched_variable` /// case username { /// "Joe" -> "Hello, Joe!" /// _ -> "I don't know you, " <> username /// } /// } /// ``` /// matched_variable: BoundVariable, /// The span covering the entire pattern that is bringing the matched /// variable in scope: /// /// ```gleam /// case something { /// User(username: _, NotAdmin) -> "Stranger!!" /// User(username:, Admin) if wibble -> /// ┬───────────────────── /// ╰─ `matched_pattern_span` /// case username { /// "Joe" -> "Hello, Joe!" /// _ -> "I don't know you, " <> username /// } /// } /// ``` /// matched_pattern_span: SrcSpan, /// The clauses matching on the `username` variable. In this case they are: /// ```gleam /// "Joe" -> "Hello, Joe!" /// _ -> "I don't know you, " <> username /// ``` /// inner_clauses: &'a Vec, } impl<'a> CollapseNestedCase<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), collapsed: None, } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); let Some(Collapsed { outer_clause_span, outer_guard, ref matched_variable, matched_pattern_span, inner_clauses, }) = self.collapsed else { return vec![]; }; // Now comes the tricky part: we need to replace the current pattern // that is bringing the variable into scope with many new patterns, one // for each of the inner clauses. // // Each time we will have to replace the matched variable with the // pattern used in the inner clause. Let's look at an example: // // ```gleam // Ok(a) -> case a { // 1 -> wibble // 2 | 3 -> wobble // _ -> woo // } // ``` // // Here we will replace `a` in the `Ok(a)` outer pattern with `1`, then // with `2` and `3`, and finally with `_`. Obtaining something like // this: // // ```gleam // Ok(1) -> wibble // Ok(2) | Ok(3) -> wobble // Ok(_) -> woo // ``` // // Notice one key detail: since alternative patterns can't be nested we // can't simply write `Ok(2 | 3)` but we have to write `Ok(2) | Ok(3)`! let pattern_text: String = code_at(self.module, matched_pattern_span).into(); let matched_variable_span = matched_variable.location; let pattern_with_variable = |mut new_content: String| { let mut new_pattern = pattern_text.clone(); match matched_variable { BoundVariable { name: BoundVariableName::Regular { .. } | BoundVariableName::ListTail { .. }, .. } => { let trimmed_contents = new_content.trim(); let pattern_is_literal_list = trimmed_contents.starts_with("[") && trimmed_contents.ends_with("]"); let pattern_is_discard = trimmed_contents == "_"; let span_to_replace = match ( &matched_variable.name, // We verify whether the pattern is compatible with the list prefix `..`. // For example, `..var` is valid syntax, but `..[]` and `.._` are not. pattern_is_literal_list || pattern_is_discard, ) { // We normally replace the selected variable with the pattern. (BoundVariableName::Regular { .. }, _) => matched_variable_span, // If the selected pattern is not a list, we also replace it normally. (BoundVariableName::ListTail { .. }, false) => matched_variable_span, // If the pattern is a list to also remove the list tail prefix. (BoundVariableName::ListTail { tail_location, .. }, true) => { // When it's a list literal, we remove the surrounding brackets. let len = trimmed_contents.len(); if let Some(slice) = new_content.trim().get(1..(len - 1)) { new_content = slice.to_string() }; *tail_location } (BoundVariableName::ShorthandLabel { .. }, _) => unreachable!(), }; let start_of_pattern = (span_to_replace.start - matched_pattern_span.start) as usize; let pattern_length = span_to_replace.len(); let end_of_pattern = start_of_pattern + pattern_length; let replaced_range = start_of_pattern..end_of_pattern; new_pattern.replace_range(replaced_range, &new_content); } BoundVariable { name: BoundVariableName::ShorthandLabel { .. }, .. } => { // But if it's introduced using the shorthand syntax we can't // just replace it's location with the new pattern: we would be // removing the label!! // So we instead insert the pattern right after the label. new_pattern.insert_str( (matched_variable_span.end - matched_pattern_span.start) as usize, &format!(" {new_content}"), ); } } new_pattern }; let mut new_clauses = vec![]; for clause in inner_clauses { // Here we take care of unrolling any alterantive patterns: for each // of the alternatives we build a new pattern and then join // everything together with ` | `. let references_to_matched_variable = FindVariableReferences::new(matched_variable_span, matched_variable.name()) .find(&clause.then); let new_patterns = iter::once(&clause.pattern) .chain(&clause.alternative_patterns) .map(|patterns| { // If we've reached this point we've already made in the // traversal that the inner clause is matching on a single // subject. So this should be safe to expect! let pattern_location = patterns.first().expect("must have a pattern").location(); let mut pattern_code = code_at(self.module, pattern_location).to_string(); if !references_to_matched_variable.is_empty() { pattern_code = format!("{pattern_code} as {}", matched_variable.name()); }; pattern_with_variable(pattern_code) }) .join(" | "); let clause_code = code_at(self.module, clause.then.location()); let guard_code = match (outer_guard, &clause.guard) { (Some(outer), Some(inner)) => { let mut outer_code = code_at(self.module, outer.location()).to_string(); let mut inner_code = code_at(self.module, inner.location()).to_string(); if ast::BinOp::And.precedence() > outer.precedence() { outer_code = format!("{{ {outer_code} }}") } if ast::BinOp::And.precedence() > inner.precedence() { inner_code = format!("{{ {inner_code} }}") } format!(" if {outer_code} && {inner_code}") } (None, Some(guard)) | (Some(guard), None) => { format!(" if {}", code_at(self.module, guard.location())) } (None, None) => "".into(), }; new_clauses.push(format!("{new_patterns}{guard_code} -> {clause_code}")); } let pattern_nesting = self .edits .src_span_to_lsp_range(outer_clause_span) .start .character; let indentation = " ".repeat(pattern_nesting as usize); self.edits.replace( outer_clause_span, new_clauses.join(&format!("\n{indentation}")), ); let mut action = Vec::with_capacity(1); CodeActionBuilder::new("Collapse nested case") .kind(CodeActionKind::REFACTOR_REWRITE) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(false) .push_to(&mut action); action } /// If the clause can be flattened because it's matching on a single variable /// defined in it, this function will return the info needed by the language /// server to flatten that case. /// /// We can only flatten a case expression in a very specific case: /// - This pattern may be introducing multiple variables, /// - The expression following this branch must be a case, and /// - It must be matching on one of those variables /// /// For example: /// /// ```gleam /// Wibble(a, b, 1) -> case a { ... } /// Wibble(a, b, 1) -> case b { ... } /// ``` /// fn flatten_clause(&self, clause: &'a ast::TypedClause) -> Option> { let ast::TypedClause { pattern, alternative_patterns, then, location, guard, } = clause; if !alternative_patterns.is_empty() { return None; } // The `then` clause must be a single case expression matching on a // single variable. let Some(TypedExpr::Case { subjects, clauses, .. }) = single_expression(then) else { return None; }; let [TypedExpr::Var { name, .. }] = subjects.as_slice() else { return None; }; // That variable must be one the variables we brought into scope in this // branch. let variable = pattern .iter() .flat_map(|pattern| pattern.bound_variables()) .find(|variable| variable.name() == *name)?; // There's one last condition to trigger the code action: we must // actually be with the cursor over the pattern or the nested case // expression! // // ```gleam // case wibble { // Ok(a) -> case a { // //^^^^^^^^^^^^^^^ Anywhere over here! // } // } // ``` // let first_pattern = pattern.first().expect("at least one pattern"); let last_pattern = pattern.last().expect("at least one pattern"); let pattern_location = first_pattern.location().merge(&last_pattern.location()); let last_inner_subject = subjects.last().expect("at least one subject"); let trigger_location = pattern_location.merge(&last_inner_subject.location()); let trigger_range = self.edits.src_span_to_lsp_range(trigger_location); if within(self.params.range, trigger_range) { Some(Collapsed { outer_clause_span: *location, outer_guard: guard, matched_variable: variable, matched_pattern_span: pattern_location, inner_clauses: clauses, }) } else { None } } } impl<'ast> ast::visit::Visit<'ast> for CollapseNestedCase<'ast> { fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) { if let Some(collapsed) = self.flatten_clause(clause) { self.collapsed = Some(collapsed); // We're done, there's no need to keep exploring as we know the // cursor is over this pattern and it can't be over any other one! return; }; ast::visit::visit_typed_clause(self, clause); } } /// If the expression is a single expression, or a block containing a single /// expression, this function will return it. /// But if the expression is a block with multiple statements, an assignment /// of a use, this will return None. /// fn single_expression(expression: &TypedExpr) -> Option<&TypedExpr> { match expression { // If a block has a single statement, we can flatten it into a // single expression if that one statement is an expression. TypedExpr::Block { statements, .. } if statements.len() == 1 => match statements.first() { ast::Statement::Expression(expression) => single_expression(expression), ast::Statement::Assignment(_) | ast::Statement::Use(_) | ast::Statement::Assert(_) => { None } }, // If a block has multiple statements then it can't be flattened // into a single expression. TypedExpr::Block { .. } => None, TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::ModuleSelect { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => Some(expression), } } /// Code action to remove unreachable clauses from a case expression. /// pub struct RemoveUnreachableCaseClauses<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, /// The source location of the patterns of all the unreachable clauses in /// the current module. /// unreachable_clauses: HashSet, clauses_to_delete: Vec, } impl<'a> RemoveUnreachableCaseClauses<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { let unreachable_clauses = module .ast .type_info .warnings .iter() .filter_map(|warning| { if let type_::Warning::UnreachableCasePattern { location, .. } = warning { Some(*location) } else { None } }) .collect(); Self { unreachable_clauses, module, params, edits: TextEdits::new(line_numbers), clauses_to_delete: vec![], } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); if self.clauses_to_delete.is_empty() { return vec![]; } for branch in self.clauses_to_delete { self.edits.delete(branch); } let mut action = Vec::with_capacity(1); CodeActionBuilder::new("Remove unreachable clauses") .kind(CodeActionKind::QUICKFIX) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(true) .push_to(&mut action); action } } impl<'ast> ast::visit::Visit<'ast> for RemoveUnreachableCaseClauses<'ast> { fn visit_typed_expr_case( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, subjects: &'ast [TypedExpr], clauses: &'ast [ast::TypedClause], compiled_case: &'ast CompiledCase, ) { // We're showing the code action only if we're within one of the // unreachable patterns. And the code action is going to remove all the // unreachable patterns for this case. let is_hovering_clause = clauses.iter().any(|clause| { let pattern_range = self.edits.src_span_to_lsp_range(clause.pattern_location()); within(self.params.range, pattern_range) }); if is_hovering_clause { self.clauses_to_delete = clauses .iter() .filter(|clause| { self.unreachable_clauses .contains(&clause.pattern_location()) }) .map(|clause| clause.location()) .collect_vec(); return; } // If we're not hovering any of the clauses then we want to // keep visiting the case expression as the unreachable branch might be // in one of the nested cases. ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case); } } /// Code action to add labels to a constructor/call where all the labels where /// omitted. /// pub struct AddOmittedLabels<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, arguments_and_omitted_labels: Option>, } struct CallArgumentWithOmittedLabel { location: SrcSpan, /// If the argument has a label this will be the label we can use for it. /// omitted_label: Option, /// If the argument is a variable that has the same name as the omitted label /// and could use the shorthand syntax. /// can_use_shorthand_syntax: bool, } impl<'a> AddOmittedLabels<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), arguments_and_omitted_labels: None, } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); let Some(call_arguments) = self.arguments_and_omitted_labels else { return vec![]; }; for call_argument in call_arguments { let Some(label) = call_argument.omitted_label else { continue; }; if call_argument.can_use_shorthand_syntax { self.edits.insert(call_argument.location.end, ":".into()); } else { self.edits .insert(call_argument.location.start, format!("{label}: ")) } } let mut action = Vec::with_capacity(1); CodeActionBuilder::new("Add omitted labels") .kind(CodeActionKind::REFACTOR_REWRITE) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(false) .push_to(&mut action); action } } impl<'ast> ast::visit::Visit<'ast> for AddOmittedLabels<'ast> { fn visit_typed_expr_call( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, fun: &'ast TypedExpr, arguments: &'ast [TypedCallArg], ) { let called_function_range = self.edits.src_span_to_lsp_range(fun.location()); if !within(self.params.range, called_function_range) { ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments); return; } let Some(field_map) = fun.field_map() else { ast::visit::visit_typed_expr_call(self, location, type_, fun, arguments); return; }; let argument_index_to_label = field_map.indices_to_labels(); let mut omitted_labels = Vec::with_capacity(arguments.len()); for (index, argument) in arguments.iter().enumerate() { // If the argument already has a label we don't want to add a label // for it, so we skip it. if let Some(label) = &argument.label { // Though, before skipping, we want to make sure that the label // is actually right for the function call. If it's not then we // give up on adding labels because there wouldn't be no way of // knowing which label to add. if !field_map.fields.contains_key(label) { return; } else { continue; } } // No labels for pipes, uses, etc! if argument.is_implicit() { continue; } let label = argument_index_to_label .get(&(index as u32)) .cloned() .cloned(); let can_use_shorthand_syntax = match (&label, &argument.value) { (Some(label), TypedExpr::Var { name, .. }) => name == label, (Some(_) | None, _) => false, }; omitted_labels.push(CallArgumentWithOmittedLabel { location: argument.location, omitted_label: label, can_use_shorthand_syntax, }) } self.arguments_and_omitted_labels = Some(omitted_labels); } } /// Code action to extract selected code into a separate function. /// If a user selected a portion of code in a function, we offer a code action /// to extract it into a new one. This can either be a single expression, such /// as in the following example: /// /// ```gleam /// pub fn main() { /// let value = { /// // ^ User selects from here /// ... /// } /// //^ Until here /// } /// ``` /// /// Here, we would extract the selected block expression. It could also be a /// series of statements. For example: /// /// ```gleam /// pub fn main() { /// let a = 1 /// //^ User selects from here /// let b = 2 /// let c = a + b /// // ^ Until here /// /// do_more_things(c) /// } /// ``` /// /// Here, we want to extract the statements inside the user's selection. /// pub struct ExtractFunction<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, function: Option>, function_end_position: Option, /// Since the `visit_typed_statement` visitor function doesn't tell us when /// a statement is the last in a block or function, we need to track that /// manually. last_statement_location: Option, } /// Information about a section of code we are extracting as a function. struct ExtractedFunction<'a> { /// A list of parameters which need to be passed to the extracted function. /// These are any variables used in the extracted code, which are defined /// outside of the extracted code. parameters: Vec<(EcoString, Arc)>, /// A list of values which need to be returned from the extracted function. /// These are the variables defined in the extracted code which are used /// outside of the extracted section. returned_variables: Vec<(EcoString, Arc)>, /// The piece of code to be extracted. This is either a single expression or /// a list of statements, as explained in the documentation of `ExtractFunction` value: ExtractedValue<'a>, } impl<'a> ExtractedFunction<'a> { fn new(value: ExtractedValue<'a>) -> Self { Self { value, parameters: Vec::new(), returned_variables: Vec::new(), } } fn location(&self) -> SrcSpan { match &self.value { ExtractedValue::Expression(expression) => expression.location(), ExtractedValue::Statements { location, .. } => *location, } } } #[derive(Debug)] enum ExtractedValue<'a> { Expression(&'a TypedExpr), Statements { location: SrcSpan, position: StatementPosition, }, } /// When we are extracting multiple statements, there are two possible cases: /// The first is if we are extracting statements in the middle of a function. /// In this case, we will need to return some number of arguments, or `Nil`. /// For example: /// /// ```gleam /// pub fn main() { /// let message = "Hello!" /// let log_message = "[INFO] " <> message /// //^ Select from here /// io.println(log_message) /// // ^ Until here /// /// do_some_more_things() /// } /// ``` /// /// Here, the extracted function doesn't bind any variables which we need /// afterwards, it purely performs side effects. In this case we can just return /// `Nil` from the new function. /// /// However, consider the following: /// /// ```gleam /// pub fn main() { /// let a = 1 /// let b = 2 /// //^ Select from here /// a + b /// // ^ Until here /// } /// ``` /// /// Here, despite us not needing any variables from the extracted code, there /// is one key difference: the `a + b` expression is at the end of the function, /// and so its value is returned from the entire function. This is known as the /// "tail" position. In that case, we can't return `Nil` as that would make the /// `main` function return `Nil` instead of the result of the addition. If we /// extract the tail-position statement, we need to return that last value rather /// than `Nil`. /// #[derive(Debug)] enum StatementPosition { Tail { type_: Arc }, NotTail, } impl<'a> ExtractFunction<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), function: None, function_end_position: None, last_statement_location: None, } } pub fn code_actions(mut self) -> Vec { // If no code is selected, then there is no function to extract and we // can return no code actions. if self.params.range.start == self.params.range.end { return Vec::new(); } self.visit_typed_module(&self.module.ast); let Some(end) = self.function_end_position else { return Vec::new(); }; // If nothing was found in the selected range, there is no code action. let Some(extracted) = self.function.take() else { return Vec::new(); }; match extracted.value { // If we extract a block, it isn't very helpful to have the body of the // extracted function just be a single block expression, so instead we // extract the statements inside the block. For example, the following // code: // // ```gleam // pub fn main() { // let x = { // // ^ Select from here // let a = 1 // let b = 2 // a + b // } // //^ Until here // x // } // ``` // // Would produce the following extracted function: // // ```gleam // fn function() { // let a = 1 // let b = 2 // a + b // } // ``` // // Rather than: // // ```gleam // fn function() { // { // let a = 1 // let b = 2 // a + b // } // } // ``` // ExtractedValue::Expression(TypedExpr::Block { statements, location: full_location, }) => { let location = statements .first() .location() .merge(&statements.last().location()); self.extract_code_in_tail_position( *full_location, location, statements.last().type_(), extracted.parameters, end, ) } ExtractedValue::Expression(TypedExpr::Fn { type_, location: full_location, kind: FunctionLiteralKind::Anonymous { .. }, arguments, body, .. }) => { let location = body.first().location().merge(&body.last().location()); let return_type = type_.return_type().expect("Fn should have a return type"); if extracted.parameters.is_empty() { self.extract_anonymous_function( *full_location, location, arguments, return_type, end, ) } else if arguments.len() == 1 { self.extract_anonymous_function_with_capture_hole( *full_location, location, arguments.first().expect("There is exactly one argument"), extracted.parameters, return_type, end, ) } else { self.extract_anonymous_function_body( location, arguments, extracted.parameters, return_type, end, ) } } ExtractedValue::Expression(expression) => { let expression_type = if let TypedExpr::Fn { type_, kind: FunctionLiteralKind::Use { .. }, .. } = expression { type_.fn_types().expect("use callback to be a function").1 } else { expression.type_() }; self.extract_code_in_tail_position( expression.location(), expression.location(), expression_type, extracted.parameters, end, ) } ExtractedValue::Statements { location, position: StatementPosition::NotTail, } => self.extract_statements( location, extracted.parameters, extracted.returned_variables, end, ), ExtractedValue::Statements { location, position: StatementPosition::Tail { type_ }, } => self.extract_code_in_tail_position( location, location, type_, extracted.parameters, end, ), } let mut action = Vec::with_capacity(1); CodeActionBuilder::new("Extract function") .kind(CodeActionKind::REFACTOR_EXTRACT) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(false) .push_to(&mut action); action } /// Choose a suitable name for an extracted function to make sure it doesn't /// clash with existing functions defined in the module and cause an error. fn function_name(&self) -> EcoString { if !self.module.ast.type_info.values.contains_key("function") { return "function".into(); } let mut number = 2; loop { let name = eco_format!("function_{number}"); if !self.module.ast.type_info.values.contains_key(&name) { return name; } number += 1; } } /// For anonymous functions that do not capture any variables from an outer scope. /// Moves the function so it is defined at the module top-level instead, /// replacing the original literal with a reference to the new function. fn extract_anonymous_function( &mut self, location: SrcSpan, code_location: SrcSpan, arguments: &[TypedArg], return_type: Arc, function_end: u32, ) { // --- BEFORE // ```gleam // pub fn main() { // list.each([1, 2, 3], fn(x) { io.println(int.to_string(x)) }) // ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ // } // ``` // // --- AFTER // ```gleam // pub fn main() { // list.each([1, 2, 3], function) // } // // fn function(x: Int) -> Nil { // io.println(int.to_string(x)) // } // ``` let name = self.function_name(); self.edits.replace(location, name.to_string()); let mut printer = Printer::new(&self.module.ast.names); let return_type = printer.print_type(&return_type); let function_body = code_at(self.module, code_location); let mut name_generator = NameGenerator::new(); let arguments = arguments .iter() .map(|arg| { if let Some(name) = arg.get_variable_name() { eco_format!("{name}: {}", printer.print_type(&arg.type_)) } else { let name = name_generator.generate_name_from_type(&arg.type_); eco_format!("_{name}: {}", printer.print_type(&arg.type_)) } }) .join(", "); let function = format!( "\n\nfn {name}({arguments}) -> {return_type} {{ {function_body} }}" ); self.edits.insert(function_end, function); } /// For anonymous functions that capture variables from an external scope /// but only expect a single argument. /// Uses function caputre syntax to provide a more concise refactoring than /// `extract_anonymous_function_body`. fn extract_anonymous_function_with_capture_hole( &mut self, location: SrcSpan, code_location: SrcSpan, argument: &TypedArg, extra_parameters: Vec<(EcoString, Arc)>, return_type: Arc, function_end: u32, ) { let name = self.function_name(); // --- BEFORE // ```gleam // pub fn main() { // let needle = 42 // let haystack = [25, 81, 74, 42, 33] // list.filter(haystack, fn(x) { x == needle }) // ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ // } // ``` // // --- AFTER // ```gleam // pub fn main() { // let needle = 42 // let haystack = [25, 81, 74, 42, 33] // list.filter(haystack, function(_, needle)) // } // // fn function(x: Int, needle: Int) -> Bool { // x == needle // } // ``` let call = format!( "{name}(_, {})", extra_parameters.iter().map(|(name, _)| name).join(", ") ); self.edits.replace(location, call); let mut printer = Printer::new(&self.module.ast.names); // build up the code for the newly generated function let return_type = printer.print_type(&return_type); let function_body = code_at(self.module, code_location); let argument = if let Some(name) = argument.get_variable_name() { eco_format!("{name}: {}", printer.print_type(&argument.type_)) } else { let name = NameGenerator::new().generate_name_from_type(&argument.type_); eco_format!("_{name}: {}", printer.print_type(&argument.type_)) }; let extra_parameters = extra_parameters .iter() .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_))) .join(", "); let function = format!( "\n\nfn {name}({argument}, {extra_parameters}) -> {return_type} {{ {function_body} }}" ); self.edits.insert(function_end, function); } /// For non-unary anonymous functions that capture variables from an external scope. /// Replaces just the _function body_ with a call to the newly generated function. fn extract_anonymous_function_body( &mut self, location: SrcSpan, arguments: &[TypedArg], extra_parameters: Vec<(EcoString, Arc)>, return_type: Arc, function_end: u32, ) { let name = self.function_name(); // --- BEFORE // ```gleam // pub fn main() { // let factor = 2 // list.fold([], 0, fn(acc, value) { acc + value * factor }) // ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ // } // ``` // // --- AFTER // ```gleam // pub fn main() { // let factor = 2 // list.fold([], 0, fn(acc, value) { function(acc, value, factor) }) // } // // fn function(acc: Int, value: Int, factor: Int) -> Int { // acc + value * factor // } // ``` // if the programmer has ignored an argument, the generated function // cannot take it as an parameter let arguments = arguments .iter() .filter_map(|arg| arg.get_variable_name().map(|name| (name, &arg.type_))) .chain(extra_parameters.iter().map(|(name, type_)| (name, type_))) .collect::)>>(); let call = format!( "{name}({})", arguments.iter().map(|(name, _)| name).join(", ") ); self.edits.replace(location, call); let mut printer = Printer::new(&self.module.ast.names); let return_type = printer.print_type(&return_type); let function_body = code_at(self.module, location); let arguments = arguments .iter() .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_))) .join(", "); let function = format!( "\n\nfn {name}({arguments}) -> {return_type} {{ {function_body} }}" ); self.edits.insert(function_end, function); } /// Extracts code from the end of a function or block. This could either be /// a single expression, or multiple statements followed by a final expression. fn extract_code_in_tail_position( &mut self, location: SrcSpan, code_location: SrcSpan, type_: Arc, parameters: Vec<(EcoString, Arc)>, function_end: u32, ) { let expression_code = code_at(self.module, code_location); let name = self.function_name(); let arguments = parameters.iter().map(|(name, _)| name).join(", "); let call = format!("{name}({arguments})"); // Since we are only extracting a single expression, we can just replace // it with the call and preserve all other semantics; only one value can // be returned from the expression, unlike when extracting multiple // statements. self.edits.replace(location, call); let mut printer = Printer::new(&self.module.ast.names); let parameters = parameters .iter() .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_))) .join(", "); let return_type = printer.print_type(&type_); let function = format!( "\n\nfn {name}({parameters}) -> {return_type} {{ {expression_code} }}" ); self.edits.insert(function_end, function); } fn extract_statements( &mut self, location: SrcSpan, parameters: Vec<(EcoString, Arc)>, returned_variables: Vec<(EcoString, Arc)>, function_end: u32, ) { let code = code_at(self.module, location); let returns_anything = !returned_variables.is_empty(); // Here, we decide what value to return from the function. There are // three cases: // The first is when the extracted code is purely for side-effects, and // does not produce any values which are needed outside of the extracted // code. For example: // // ```gleam // pub fn main() { // let message = "Something important" // //^ Select from here // io.println("Something important") // io.println("Something else which is repeated") // // ^ Until here // // do_final_thing() // } // ``` // // It doesn't make sense to return any values from this function, since // no values from the extract code are used afterwards, so we simply // return `Nil`. // // The next is when we need just a single value defined in the extracted // function, such as in this piece of code: // // ```gleam // pub fn main() { // let a = 10 // //^ Select from here // let b = 20 // let c = a + b // // ^ Until here // // echo c // } // ``` // // Here, we can just return the single value, `c`. // // The last situation is when we need multiple defined values, such as // in the following code: // // ```gleam // pub fn main() { // let a = 10 // //^ Select from here // let b = 20 // let c = a + b // // ^ Until here // // echo a // echo b // echo c // } // ``` // // In this case, we must return a tuple containing `a`, `b` and `c` in // order for the calling function to have access to the correct values. let (return_type, return_value) = match returned_variables.as_slice() { [] => (type_::nil(), "Nil".into()), [(name, type_)] => (type_.clone(), name.clone()), _ => { let values = returned_variables.iter().map(|(name, _)| name).join(", "); let type_ = type_::tuple( returned_variables .into_iter() .map(|(_, type_)| type_) .collect(), ); (type_, eco_format!("#({values})")) } }; let name = self.function_name(); let arguments = parameters.iter().map(|(name, _)| name).join(", "); // If any values are returned from the extracted function, we need to // bind them so that they are accessible in the current scope. let call = if returns_anything { format!("let {return_value} = {name}({arguments})") } else { format!("{name}({arguments})") }; self.edits.replace(location, call); let mut printer = Printer::new(&self.module.ast.names); let parameters = parameters .iter() .map(|(name, type_)| eco_format!("{name}: {}", printer.print_type(type_))) .join(", "); let return_type = printer.print_type(&return_type); let function = format!( "\n\nfn {name}({parameters}) -> {return_type} {{ {code} {return_value} }}" ); self.edits.insert(function_end, function); } /// When a variable is referenced, we need to decide if we need to do anything /// to ensure that the reference is still valid after extracting a function. /// If the variable is defined outside the extracted function, but used inside /// it, then we need to add it as a parameter of the function. Similarly, if /// a variable is defined inside the extracted code, but used outside of it, /// we need to ensure that value is returned from the function so that it is /// accessible. fn register_referenced_variable( &mut self, name: &EcoString, type_: &Arc, location: SrcSpan, definition_location: SrcSpan, ) { let Some(extracted) = &mut self.function else { return; }; let extracted_location = extracted.location(); // If a variable defined outside the extracted code is referenced inside // it, we need to add it to the list of parameters. let variables = if extracted_location.contains_span(location) && !extracted_location.contains_span(definition_location) { &mut extracted.parameters // If a variable defined inside the extracted code is referenced outside // it, then we need to ensure that it is returned from the function. } else if extracted_location.contains_span(definition_location) && !extracted_location.contains_span(location) { &mut extracted.returned_variables } else { return; }; // If the variable has already been tracked, no need to register it again. // We use a `Vec` here rather than a `HashMap` because we want to ensure // the order of arguments is consistent; in this case it will be determined // by the order the variables are used. This isn't always desired, but it's // better than random order, and makes it easier to write tests too. // The cost of iterating the list here is minimal; it is unlikely that // a given function will ever have more than 10 or so parameters. if variables.iter().any(|(variable, _)| variable == name) { return; } variables.push((name.clone(), type_.clone())); } fn can_extract(&self, location: SrcSpan) -> bool { let expression_range = self.edits.src_span_to_lsp_range(location); let selected_range = self.params.range; // If the selected range doesn't touch the expression at all, then there // is no reason to extract it. if !overlaps(expression_range, selected_range) { return false; } // Determine whether the selected range falls completely within the // expression. For example: // ```gleam // pub fn main() { // let something = { // let a = 1 // let b = 2 // let c = a + b // //^ The user has selected from here // let d = a * b // c / d // // ^ Until here // } // } // ``` // // Here, the selected range does overlap with the `let something` // statement; but we don't want to extract that whole statement! The // user only wanted to extract the statements inside the block. So if // the selected range falls completely within the expression, we ignore // it and traverse the tree further until we find exactly what the user // selected. // let selected_within_expression = selected_range.start > expression_range.start && selected_range.start < expression_range.end && selected_range.end > expression_range.start && selected_range.end < expression_range.end; // If the selected range is completely within the expression, we don't // want to extract it. !selected_within_expression } } impl<'ast> ast::visit::Visit<'ast> for ExtractFunction<'ast> { fn visit_typed_function(&mut self, function: &'ast TypedFunction) { let range = self.edits.src_span_to_lsp_range(function.full_location()); if within(self.params.range, range) { self.function_end_position = Some(function.end_position); self.last_statement_location = function.body.last().map(|last| last.location()); ast::visit::visit_typed_function(self, function); } } fn visit_typed_expr_block( &mut self, location: &'ast SrcSpan, statements: &'ast [TypedStatement], ) { let last_statement_location = self.last_statement_location; self.last_statement_location = statements.last().map(|last| last.location()); ast::visit::visit_typed_expr_block(self, location, statements); self.last_statement_location = last_statement_location; } fn visit_typed_expr(&mut self, expression: &'ast TypedExpr) { // If we have already determined what code we want to extract, we don't // want to extract this instead. This expression would be inside the // piece of code we already are going to extract, leading to us // extracting just a single literal in any selection, which is of course // not desired. if self.function.is_none() { // If this expression is fully selected, we mark it as being extracted. if self.can_extract(expression.location()) { self.function = Some(ExtractedFunction::new(ExtractedValue::Expression( expression, ))); } } ast::visit::visit_typed_expr(self, expression); } fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) { let statement_location = statement.location(); if self.can_extract(statement_location) { let is_in_tail_position = self.last_statement_location .is_some_and(|last_statement_location| { last_statement_location == statement_location }); // A use is always eating up the entire block, if we're extracting it, // it will be in tail position there and the extracted function should // return its returned value. let position = if statement.is_use() || is_in_tail_position { StatementPosition::Tail { type_: statement.type_(), } } else { StatementPosition::NotTail }; match &mut self.function { None => { self.function = Some(ExtractedFunction::new(ExtractedValue::Statements { location: statement_location, position, })); } // If we have already chosen an expression to extract, that means // that this statement is within the already extracted expression, // so we don't want to extract this instead. Some(ExtractedFunction { value: ExtractedValue::Expression(_), .. }) => {} // If we are selecting multiple statements, this statement should // be included within list, so we merge the spans to ensure it // is included. Some(ExtractedFunction { value: ExtractedValue::Statements { location, position: extracted_position, }, .. }) => { *location = location.merge(&statement_location); *extracted_position = position; } } } ast::visit::visit_typed_statement(self, statement); } fn visit_typed_expr_var( &mut self, location: &'ast SrcSpan, constructor: &'ast ValueConstructor, name: &'ast EcoString, ) { if let type_::ValueConstructorVariant::LocalVariable { location: definition_location, .. } = &constructor.variant { self.register_referenced_variable( name, &constructor.type_, *location, *definition_location, ); } } fn visit_typed_clause_guard_var( &mut self, location: &'ast SrcSpan, name: &'ast EcoString, type_: &'ast Arc, definition_location: &'ast SrcSpan, _origin: &'ast VariableOrigin, ) { self.register_referenced_variable(name, type_, *location, *definition_location); } fn visit_typed_bit_array_size_variable( &mut self, location: &'ast SrcSpan, name: &'ast EcoString, constructor: &'ast Option>, type_: &'ast Arc, ) { let variant = match constructor { Some(constructor) => &constructor.variant, None => return, }; if let type_::ValueConstructorVariant::LocalVariable { location: definition_location, .. } = variant { self.register_referenced_variable(name, type_, *location, *definition_location); } } } /// Code action to merge two identical branches together. /// pub struct MergeCaseBranches<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, /// These are the positions of the patterns of all the consecutive branches /// we've determined can be merged, for example if we're mergin the first /// two branches here: /// /// ```gleam /// case wibble { /// 1 -> todo /// // ^ this location here /// 20 -> todo /// // ^^ and this location here /// _ -> todo /// } /// ``` /// /// We need those to delete all the space between each consecutive pattern, /// replacing it with the `|` for alternatives /// patterns_to_merge: Option, } struct MergeableBranches { /// The span of the body to keep when merging multiple branches. For /// example: /// /// ```gleam /// case n { /// // Imagine we're merging the first three branches together... /// 1 -> todo /// 2 -> n * 2 /// // ^^^^^ This would be the location of the one body to keep /// 3 -> todo /// _ -> todo /// } /// ``` /// body_to_keep: SrcSpan, /// The location body of the last of the branches that are going to be /// merged; that is where we're going to place the code of the body to keep /// once the action is done. For example: /// /// ```gleam /// case n { /// // Imagine we're merging the first three branches together... /// 1 -> todo /// 2 -> n * 2 /// 3 -> todo /// // ^^^^ This would be the location of the final body /// _ -> todo /// } /// ``` /// final_body: SrcSpan, /// The span of the patterns whose branches are going to be merged. For /// example: /// /// ```gleam /// case n { /// // Imagine we're merging the first three branches together... /// 1 -> todo /// // ^ /// 2 -> n * 2 /// // ^ /// 3 -> todo /// // ^ These would be the locations of the patterns /// _ -> todo /// } /// ``` /// patterns_to_merge: Vec, } impl<'a> MergeCaseBranches<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), patterns_to_merge: None, } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); let Some(mergeable_branches) = self.patterns_to_merge else { return vec![]; }; for (one, next) in mergeable_branches.patterns_to_merge.iter().tuple_windows() { self.edits .replace(SrcSpan::new(one.end, next.start), " | ".into()); } self.edits.replace( mergeable_branches.final_body, code_at(self.module, mergeable_branches.body_to_keep).into(), ); let mut action = Vec::with_capacity(1); CodeActionBuilder::new("Merge case branches") .kind(CodeActionKind::REFACTOR_REWRITE) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(false) .push_to(&mut action); action } fn select_mergeable_branches( &self, clauses: &'a [ast::TypedClause], ) -> Option { let mut clauses = clauses .iter() // We want to skip all the branches at the beginning of the case // expression that the cursor is not hovering over. For example: // // ```gleam // case wibble { // a -> 1 <- we want to skip this one here that is not selected // b -> 2 // ^^^^ this is the selection // _ -> 3 // ^^ // } // ``` .skip_while(|clause| { let clause_range = self.edits.src_span_to_lsp_range(clause.location); !overlaps(self.params.range, clause_range) }) // Then we only want to take the clauses that we're hovering over // with our selection (even partially!) // In the provious example they would be `b -> 2` and `_ -> 3`. .take_while(|clause| { let clause_range = self.edits.src_span_to_lsp_range(clause.location); overlaps(self.params.range, clause_range) }); let first_hovered_clause = clauses.next()?; // This is the clause we're comparing all the others with. We need to // make sure that all the clauses we're going to join can be merged with // this one. let mut reference_clause = first_hovered_clause; let mut clause_patterns_to_merge = vec![reference_clause.pattern_location()]; let mut final_body = first_hovered_clause.then.location(); for clause in clauses { // As soon as we find a clause that can't be merged with the current // reference we know we're done looking for consecutive clauses to // merge. if !clauses_can_be_merged(reference_clause, clause) { break; } clause_patterns_to_merge.push(clause.pattern_location()); final_body = clause.then.location(); // If the current reference is a `todo` expression, we want to use // the newly found mergeable clause as the next reference. The // reference clause is the one whose body will be kept around, so if // we can we avoid keeping `todo`s if reference_clause.then.is_todo_with_no_message() { reference_clause = clause; } } // We only offer the code action if we have found two or more clauses // to merge. if clause_patterns_to_merge.len() >= 2 { Some(MergeableBranches { final_body, body_to_keep: reference_clause.then.location(), patterns_to_merge: clause_patterns_to_merge, }) } else { None } } } fn clauses_can_be_merged(one: &ast::TypedClause, other: &ast::TypedClause) -> bool { // Two clauses cannot be merged if any of those has an if guard if one.guard.is_some() || other.guard.is_some() { return false; } // Two clauses can only be merged if they define the same variables, // otherwise joining them would result in invalid code. let variables_one = one .bound_variables() .map(|variable| (variable.name(), variable.type_)) .collect::>(); let variables_other = other .bound_variables() .map(|variable| (variable.name(), variable.type_)) .collect::>(); for (name, type_) in variables_one.iter() { if let Some(type_other) = variables_other.get(name) && type_other.same_as(type_) { continue; } // There's a variable that is not defined in the second branch but // is defined in the first one, or it's defined in the second branch // but it has an incompatible type. return false; } for (name, _) in variables_other.iter() { if !variables_one.contains_key(name) { // There's some variables defined in the second branch that are not // defined in the first one, so they can't be merged! return false; } } // Anything can be merged with a simple todo, or the two bodies must be // syntactically equal. one.then.is_todo_with_no_message() || other.then.is_todo_with_no_message() || one.then.syntactically_eq(&other.then) } impl<'ast> ast::visit::Visit<'ast> for MergeCaseBranches<'ast> { fn visit_typed_expr_case( &mut self, location: &'ast SrcSpan, type_: &'ast Arc, subjects: &'ast [TypedExpr], clauses: &'ast [ast::TypedClause], compiled_case: &'ast CompiledCase, ) { // We only trigger the code action if we are within a case expression, // otherwise there's no point in exploring the expression any further. let case_range = self.edits.src_span_to_lsp_range(*location); if !within(self.params.range, case_range) { return; } if let result @ Some(_) = self.select_mergeable_branches(clauses) { self.patterns_to_merge = result } // We still need to visit the case expression in case we want to apply // the code action to some case expression that is nested in one of its // branches! ast::visit::visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case); } } /// Code action to add a missing type parameter to custom types. /// If a custom type is missing a type parameter, as it is the case /// in the following example, this action will offer to add the /// type parameter to the type definition. /// /// Before: /// ```gleam /// type Wibble { /// Wibble(field: t) /// } /// ``` /// /// After: /// ```gleam /// type Wibble(t) { /// Wibble(field: t) /// } /// ``` /// pub struct AddMissingTypeParameter<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, /// The source location where the parameters should be defined. /// This might be a zero-length span if there are no parameters yet, /// or it might cover the already existing type parameter definitions. parameters_location: Option, /// If the type definition already had existing parameters before. has_existing_parameters: bool, /// The set of all type parameter names in the different variants of the type /// that are not already part of the type parameter definition on the type. missing_parameters: HashSet, } impl<'a> AddMissingTypeParameter<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), parameters_location: None, has_existing_parameters: false, missing_parameters: HashSet::new(), } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); let Some(type_parameters_location) = self.parameters_location else { return vec![]; }; if self.missing_parameters.is_empty() { return vec![]; } let mut new_parameters = self.missing_parameters.iter().sorted().join(", "); if self.has_existing_parameters { let has_trailing_comma = self .module .extra .trailing_commas .iter() .any(|&trailing_comma| type_parameters_location.contains(trailing_comma)); if !has_trailing_comma { new_parameters.insert_str(0, ", "); } self.edits .insert(type_parameters_location.end - 1, new_parameters); } else { self.edits .insert(type_parameters_location.end, format!("({new_parameters})")); } let mut action = Vec::with_capacity(1); CodeActionBuilder::new("Add missing type parameter") .kind(CodeActionKind::QUICKFIX) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(true) .push_to(&mut action); action } } impl<'ast> ast::visit::Visit<'ast> for AddMissingTypeParameter<'ast> { fn visit_typed_custom_type(&mut self, custom_type: &'ast ast::TypedCustomType) { let custom_type_range = self .edits .src_span_to_lsp_range(custom_type.full_location()); // Only continue, if the action was selected anywhere within the custom type definition. if !overlaps(self.params.range, custom_type_range) { return; } self.parameters_location = Some(SrcSpan::new( custom_type.name_location.end, custom_type.location.end, )); self.has_existing_parameters = !custom_type.typed_parameters.is_empty(); let existing_names: HashSet<_> = custom_type .parameters .iter() .map(|(_, name)| name) .collect(); // Collect the remaining type parameters from the variant constructors. for record in &custom_type.constructors { for argument in &record.arguments { if let ast::TypeAst::Var(ast::TypeAstVar { name, .. }) = &argument.ast && !existing_names.contains(name) { let _ = self.missing_parameters.insert(name.clone()); } } } } } /// Code action to replace a `_` with its actual type in an annotation. /// /// Before: /// ```gleam /// fn wibble() -> Ok(_) { Ok(1) } /// // ^ Trigger it here /// ``` /// /// After: /// ```gleam /// fn wibble() -> Ok(Int) { Ok(1) } /// ``` /// pub struct ReplaceUnderscoreWithType<'a> { module: &'a Module, params: &'a CodeActionParams, edits: TextEdits<'a>, hovered_hole: Option, } struct HoveredHole { type_: Arc, location: SrcSpan, } impl<'a> ReplaceUnderscoreWithType<'a> { pub fn new( module: &'a Module, line_numbers: &'a LineNumbers, params: &'a CodeActionParams, ) -> Self { Self { module, params, edits: TextEdits::new(line_numbers), hovered_hole: None, } } pub fn code_actions(mut self) -> Vec { self.visit_typed_module(&self.module.ast); let mut action = Vec::with_capacity(1); let Some(HoveredHole { type_, location }) = self.hovered_hole else { return vec![]; }; let mut printer = Printer::new(&self.module.ast.names); self.edits .replace(location, format!("{}", printer.print_type(&type_))); CodeActionBuilder::new("Replace `_` with type") .kind(CodeActionKind::QUICKFIX) .changes(self.params.text_document.uri.clone(), self.edits.edits) .preferred(true) .push_to(&mut action); action } } impl<'ast> ast::visit::Visit<'ast> for ReplaceUnderscoreWithType<'ast> { fn visit_type_ast(&mut self, node: &'ast ast::TypeAst, inferred_type: Option>) { // We never traverse a type annotation we're not hovering let node_location = self.edits.src_span_to_lsp_range(node.location()); if !within(self.params.range, node_location) { return; } ast::visit::visit_type_ast(self, node, inferred_type); } fn visit_type_ast_hole( &mut self, location: &'ast SrcSpan, _name: &'ast EcoString, type_: Option>, ) { if let Some(type_) = type_ { self.hovered_hole = Some(HoveredHole { type_, location: *location, }) } } } ================================================ FILE: language-server/src/compiler.rs ================================================ use debug_ignore::DebugIgnore; use ecow::EcoString; use itertools::Itertools; use gleam_core::{ Error, Result, Warning, analyse::TargetSupport, build::{self, Mode, Module, NullTelemetry, Outcome, ProjectCompiler}, config::PackageConfig, io::{BeamCompiler, CommandExecutor, FileSystemReader, FileSystemWriter, Stdio}, line_numbers::LineNumbers, manifest::Manifest, paths::ProjectPaths, type_::ModuleInterface, warning::VectorWarningEmitterIO, }; use std::{collections::HashMap, rc::Rc}; use camino::Utf8PathBuf; use super::{LockGuard, Locker}; /// A wrapper around the project compiler which makes it possible to repeatedly /// recompile the top level package, reusing the information about the already /// compiled dependency packages. /// #[derive(Debug)] pub struct LspProjectCompiler { pub project_compiler: ProjectCompiler, /// Information on compiled modules. pub modules: HashMap, pub sources: HashMap, /// The storage for the warning emitter. pub warnings: Rc, /// A lock to ensure that multiple instances of the LSP don't try and use /// build directory at the same time. pub locker: DebugIgnore>, } impl LspProjectCompiler where IO: CommandExecutor + FileSystemWriter + FileSystemReader + BeamCompiler + Clone, { pub fn new( manifest: Manifest, config: PackageConfig, paths: ProjectPaths, io: IO, locker: Box, ) -> Result { let target = config.target; let name = config.name.clone(); let warnings = Rc::new(VectorWarningEmitterIO::default()); // The build caches do not contain all the information we need in the // LSP (e.g. the typed AST) so delete the caches for the top level // package before we run for the first time. { let _guard: LockGuard = locker.lock_for_build()?; let path = paths.build_directory_for_package(Mode::Lsp, target, &name); io.delete_directory(&path)?; } let options = build::Options { warnings_as_errors: false, mode: Mode::Lsp, target: None, codegen: build::Codegen::None, compile: build::Compile::All, root_target_support: TargetSupport::Enforced, no_print_progress: false, }; let mut project_compiler = ProjectCompiler::new( config, options, manifest.packages, &NullTelemetry, warnings.clone(), paths, io, ); // To avoid the Erlang compiler printing to stdout (and thus // violating LSP which is currently using stdout) we silence it. project_compiler.subprocess_stdio = Stdio::Null; Ok(Self { locker: locker.into(), warnings, project_compiler, modules: HashMap::new(), sources: HashMap::new(), }) } pub fn compile(&mut self) -> Outcome, Error> { // Lock the build directory to ensure to ensure we are the only one compiling let _lock_guard: LockGuard = match self.locker.lock_for_build() { Ok(it) => it, Err(err) => return err.into(), }; // Verify that the build directory was created using the same version of // Gleam as we are running. If it is not then we discard the build // directory as the cache files may be in a different format. if let Err(e) = self.project_compiler.check_gleam_version() { return e.into(); } self.project_compiler.reset_state_for_new_compile_run(); let compiled_dependencies = match self.project_compiler.compile_dependencies() { Ok(it) => it, Err(err) => return err.into(), }; // Store the compiled dependency module information for module in &compiled_dependencies { let path = module.input_path.as_os_str().to_string_lossy().to_string(); // strip canonicalised windows prefix #[cfg(target_family = "windows")] let path = path .strip_prefix(r"\\?\") .map(|s| s.to_string()) .unwrap_or(path); let line_numbers = LineNumbers::new(&module.code); let source = ModuleSourceInformation { path, line_numbers }; _ = self.sources.insert(module.name.clone(), source); } // Since cached modules are not recompiled we need to manually add them for (name, module) in self.project_compiler.get_importable_modules() { // It we already have the source for an importable module it means // that we already have all the information we are adding here, so // we can skip past to to avoid doing extra work for no gain. if self.sources.contains_key(name) || name == "gleam" { continue; } // Create the source information let path = module.src_path.to_string(); // strip canonicalised windows prefix #[cfg(target_family = "windows")] let path = path .strip_prefix(r"\\?\") .map(|s| s.to_string()) .unwrap_or(path); let line_numbers = module.line_numbers.clone(); let source = ModuleSourceInformation { path, line_numbers }; _ = self.sources.insert(name.clone(), source); } // Warnings from dependencies are not fixable by the programmer so // we don't bother them with diagnostics for them. let _ = self.take_warnings(); // Compile the root package, that is, the one that the programmer is // working in. let (modules, error) = match self.project_compiler.compile_root_package() { Outcome::Ok(package) => (package.modules, None), Outcome::PartialFailure(package, error) => (package.modules, Some(error)), Outcome::TotalFailure(error) => (vec![], Some(error)), }; // Record the compiled dependency modules let mut compiled_modules = compiled_dependencies .into_iter() .map(|m| m.input_path) .collect_vec(); // Store the compiled module information for module in modules { let path = module.input_path.as_os_str().to_string_lossy().to_string(); let line_numbers = LineNumbers::new(&module.code); let source = ModuleSourceInformation { path, line_numbers }; // Record that this one has been compiled. This is returned by this // function and is used to determine what diagnostics to reset. compiled_modules.push(module.input_path.clone()); // Register information for the LS to use _ = self.sources.insert(module.name.clone(), source); _ = self.modules.insert(module.name.clone(), module); } match error { None => Outcome::Ok(compiled_modules), Some(error) => Outcome::PartialFailure(compiled_modules, error), } } } impl LspProjectCompiler { pub fn take_warnings(&mut self) -> Vec { self.warnings.take() } pub fn get_source(&self, module: &str) -> Option<&ModuleSourceInformation> { self.sources.get(module) } pub fn get_module_interface(&self, name: &str) -> Option<&ModuleInterface> { self.project_compiler.get_importable_modules().get(name) } } #[derive(Debug)] pub struct ModuleSourceInformation { /// The path to the source file from within the project root pub path: String, /// Useful for converting from Gleam's byte index offsets to the LSP line /// and column number positions. pub line_numbers: LineNumbers, } ================================================ FILE: language-server/src/completer.rs ================================================ use std::{collections::HashMap, sync::Arc}; use ecow::{EcoString, eco_format}; use itertools::Itertools; use lsp_types::{ CompletionItem, CompletionItemKind, CompletionItemLabelDetails, CompletionTextEdit, Documentation, MarkupContent, MarkupKind, Position, Range, TextDocumentPositionParams, TextEdit, }; use strum::IntoEnumIterator; use vec1::Vec1; use gleam_core::{ Result, ast::{ self, Arg, CallArg, Function, FunctionLiteralKind, Pattern, Publicity, TypedExpr, visit::Visit, }, build::{Module, Origin}, line_numbers::LineNumbers, parse::{LiteralFloatValue, parse_int_value}, type_::{ self, FieldMap, ModuleInterface, PRELUDE_MODULE_NAME, PreludeType, RecordAccessor, Type, TypeConstructor, ValueConstructorVariant, collapse_links, error::VariableOrigin, pretty::Printer, }, }; use super::{ compiler::LspProjectCompiler, edits::{ Newlines, add_newlines_after_import, get_import_edit, position_of_first_definition_if_import, }, files::FileSystemProxy, }; // Represents the kind/specificity of completion that is being requested. #[derive(Copy, Clone)] enum CompletionKind { // A language keyword that we can offer completions for, like `todo`, // `panic`, or `echo` Keyword, // A label for a function or type definition Label, // A field of a record FieldAccessor, // Values or types defined in the current module LocallyDefined, // Values or types defined in an already imported module ImportedModule, // Types or values defined in the prelude Prelude, // Types defined in a module that has not been imported ImportableModule, } #[derive(Copy, Clone)] enum TypeMatch { Matching, Incompatible, Unknown, } // Gives the sort text for a completion item based on the kind and label. // This ensures that more specific kinds of completions are placed before // less specific ones.. fn sort_text(kind: CompletionKind, label: &str, type_match: TypeMatch) -> String { let priority: u8 = match kind { CompletionKind::Keyword => 0, CompletionKind::Label => 1, CompletionKind::FieldAccessor => 2, CompletionKind::LocallyDefined => 3, CompletionKind::ImportedModule => 4, CompletionKind::Prelude => 5, CompletionKind::ImportableModule => 6, }; match type_match { // We want to prioritise type which match what is expected in the completion // as those are more likely to be what the user wants. TypeMatch::Matching => format!("0{priority}_{label}"), TypeMatch::Incompatible | TypeMatch::Unknown => format!("{priority}_{label}"), } } /// The form in which a type completion is needed in context. #[derive(Debug)] enum TypeCompletionContext { /// The type completion is for an unqualified import that doesn't have an /// import list yet. So adding the type will also require adding braces: /// /// ```gleam /// import wibble.Wib| /// // ^^^^^ We're typing this... /// import wibble.{type Wibble} /// // ^^^^^^^^^^^^^^ ...so this will be the completion /// ``` /// UnqualifiedImport, /// The type completion is for an unqualified import within already existing /// braces. /// /// ```gleam /// import wibble.{type AlreadyImported, Wibb|} /// // ^^^^^ We're typing this... /// import wibble.{type AlreadyImported, type Wibble} /// // ^^^^^^^^^^^ ...so this will be the completion /// ``` /// UnqualifiedImportWithinBraces, /// The type completion is for an unqualified type (for example something /// coming from the prelude, or a type that was already imported in a /// unqualified way). /// /// ```gleam /// import wobble.{type Wobble} /// /// pub fn new_wobble() -> Wob| /// // ^^^^ We're typing this... /// pub fn new_wobble() -> Wobble /// // ^^^^^^ ... so this will be the completion /// ``` /// UnqualifiedType, /// The type completion is for a qualified type. /// /// ```gleam /// import wobble /// /// pub fn new_wobble() -> wobble.W| /// // ^^^^^^^^ We're typing this... /// pub fn new_wobble() -> wobble.Wobble /// // ^^^^^^^^^^^^^ ...so this will be the completion /// ``` /// QualifiedType, } /// Represents the surroundings of the cursor when trying to figure out a /// completion. /// /// ```gleam /// wibble.w|ob /// // ^ cursor here /// ``` struct CursorSurroundings { /// The text surrounding the cursor. For example: /// /// ```gleam /// wibble.w|ob /// // ^ cursor here /// // The surrounding text is: "wibble.wob" /// ``` /// surrounding_text: EcoString, surrounding_text_range: Range, /// The text that comes immediately before the cursor. /// For example: /// /// ```gleam /// wibble.w|ob /// // ^ cursor here /// // The text before is: "wibble.w" /// ``` /// text_before_cursor: EcoString, /// The range of the text that comes immediately before the cursor. /// For example: /// /// ```gleam /// wibble.w|ob /// // ^ cursor here /// // ^^^^^^^^ This is the range /// ``` /// /// This is what is usually replaced with a completion. /// text_before_cursor_range: Range, /// The text that comes immediately after the cursor. /// For example: /// /// ```gleam /// wibble.w|ob /// // ^ cursor here /// // The text before is: "ob" /// ``` /// text_after_cursor: EcoString, } impl CursorSurroundings { fn selected_module(&self) -> Option { self.surrounding_text .split_once('.') .map(|(selected_module, _)| EcoString::from(selected_module)) } /// Given a proposed completion, this returns the text edit to obtain that /// completion. /// /// > This could also return `None` as sometimes no further edit is actually /// > needed! /// fn to_text_edit(&self, new_text: String) -> Option { // We need to check if the new text we're adding could actually be // a simple addition in the middle of something that is already being // typed. // Say we're editing our code to actually make the `Json` type // qualified: // // ```gleam // jso|Json // ^ cursor here // ``` // // Halfway through writing the module name we might just want to accept // the completion for: `json.Json`. // What one would normally expect is for the final code to be: // // ```gleam // json.Json| // // after accepting completion // // // and not something like this! // // json.JsonJson // ``` // // This only makes sense if the completion we're adding has a prefix and // suffix in common with the surrounding text: let remaining_label = new_text .strip_prefix(self.text_before_cursor.as_str()) .and_then(|rest| rest.strip_suffix(self.text_after_cursor.as_str())); match remaining_label { // The entire existing text is already the same as the new text we // might want to add. // In that case there's no need to perform any edit at all! Some("") => None, // This is one of the cases (like the one in the example above) were // the label is a more complete version of the text surrounding the // cursor. So we replace the entire range. Some(_) => Some(CompletionTextEdit::Edit(TextEdit { range: self.surrounding_text_range, new_text, })), // In all other cases the completion is never meant to replace text // that comes after the cursor. // We only replace what comes before it with the new text. None => Some(CompletionTextEdit::Edit(TextEdit { range: self.text_before_cursor_range, new_text, })), } } } pub struct Completer<'a, IO> { /// The direct buffer source code src: &'a EcoString, /// The line number information of the buffer source code pub src_line_numbers: LineNumbers, /// The current cursor position within the buffer source code cursor_position: &'a Position, /// A reference to the lsp compiler for getting module information compiler: &'a LspProjectCompiler>, /// A reference to the current module the completion is for module: &'a Module, /// The line number information of the latest compiled module. /// This is not necessarily the same as src_line_numbers if the module /// is in a non-compiling state pub module_line_numbers: LineNumbers, /// The expected type of the value we are completing. `None` if we are /// completing a type annotation or label, where this information is not /// applicable. pub expected_type: Option>, } impl<'a, IO> Completer<'a, IO> { pub fn new( src: &'a EcoString, params: &'a TextDocumentPositionParams, compiler: &'a LspProjectCompiler>, module: &'a Module, ) -> Self { Completer { src, src_line_numbers: LineNumbers::new(src.as_str()), cursor_position: ¶ms.position, compiler, module, module_line_numbers: LineNumbers::new(&module.code), expected_type: None, } } // Gets the current range around the cursor to place a completion // and the phrase surrounding the cursor to use for completion. // This method takes in a helper to determine what qualifies as // a phrase depending on context. fn get_phrase_surrounding_for_completion( &'a self, valid_phrase_char: &impl Fn(char) -> bool, ) -> CursorSurroundings { let cursor = self.src_line_numbers.byte_index(*self.cursor_position); // Get part of phrase prior to cursor let before = self .src .get(..cursor as usize) .and_then(|line| line.rsplit_once(valid_phrase_char).map(|r| r.1)) .unwrap_or(""); // Get part of phrase following cursor let after = self .src .get(cursor as usize..) .and_then(|line| line.split_once(valid_phrase_char).map(|r| r.0)) .unwrap_or(""); let text_before_cursor_range = Range { start: Position { line: self.cursor_position.line, character: self.cursor_position.character - before.len() as u32, }, end: Position { line: self.cursor_position.line, character: self.cursor_position.character, }, }; let surrounding_text_range = Range { start: text_before_cursor_range.start, end: Position { line: self.cursor_position.line, character: self.cursor_position.character + after.len() as u32, }, }; CursorSurroundings { surrounding_text: eco_format!("{before}{after}"), surrounding_text_range, text_before_cursor: EcoString::from(before), text_before_cursor_range, text_after_cursor: EcoString::from(after), } } // Gets the current range around the cursor to place a completion // and any part of the phrase preceeding a dot if a module is being selected from. // A continuous phrase in this case is a name or typename that may have a dot in it. // This is used to match the exact location to fill in the completion. fn get_phrase_surrounding_completion(&'a self) -> CursorSurroundings { self.get_phrase_surrounding_for_completion(&|c: char| { // Checks if a character is not a valid name/upname character or a dot. !c.is_ascii_alphanumeric() && c != '.' && c != '_' }) } // Gets the current range around the cursor to place a completion. // For unqualified imports we special case the word being completed to allow for whitespace but not dots. // This is to allow `type MyType` to be treated as 1 "phrase" for the sake of completion. fn get_phrase_surrounding_completion_for_import(&'a self) -> CursorSurroundings { self.get_phrase_surrounding_for_completion(&|c: char| { // Checks if a character is not a valid name/upname character or whitespace. // The newline character is not included as well. !c.is_ascii_alphanumeric() && c != '_' && c != ' ' && c != '\t' }) } /// Checks if the line being editted is an import line and provides completions if it is. /// If the line includes a dot then it provides unqualified import completions. /// Otherwise it provides direct module import completions. pub fn import_completions(&'a self) -> Option>>> { let start_of_line = self.src_line_numbers.byte_index(Position { line: self.cursor_position.line, character: 0, }); let end_of_line = self.src_line_numbers.byte_index(Position { line: self.cursor_position.line + 1, character: 0, }); // Drop all lines except the line the cursor is on let src = self.src.get(start_of_line as usize..end_of_line as usize)?; // If this isn't an import line then we don't offer import completions if !src.trim_start().starts_with("import") { return None; } // Check if we are completing an unqualified import if let Some(dot_index) = src.find('.') { // Find the module that is being imported from let importing_module_name = src.get(6..dot_index)?.trim(); let importing_module: &ModuleInterface = self.compiler.get_module_interface(importing_module_name)?; let within_braces = match src.get(dot_index + 1..) { Some(x) => x.trim_start().starts_with('{'), None => false, }; Some(Ok(Some(self.unqualified_completions_from_module( importing_module, within_braces, )))) } else { // Find where to start and end the import completion let start = self.src_line_numbers.line_and_column_number(start_of_line); let end = self .src_line_numbers .line_and_column_number(end_of_line - 1); let start = Position::new(start.line - 1, start.column + 6); let end = Position::new(end.line - 1, end.column - 1); let completions = self.complete_modules_for_import(start, end); Some(Ok(Some(completions))) } } /// Gets the completes for unqualified imports from a module. pub fn unqualified_completions_from_module( &'a self, module_being_imported_from: &'a ModuleInterface, within_braces: bool, ) -> Vec { let cursor_surroundings = self.get_phrase_surrounding_completion_for_import(); let mut completions = vec![]; // Find values and type that have already previously been imported let mut already_imported_types = std::collections::HashSet::new(); let mut already_imported_values = std::collections::HashSet::new(); // Search the ast for import statements for import in &self.module.ast.definitions.imports { // Find the import that matches the module being imported from if import.module == module_being_imported_from.name { // Add the values and types that have already been imported for unqualified in &import.unqualified_types { let _ = already_imported_types.insert(&unqualified.name); } for unqualified in &import.unqualified_values { let _ = already_imported_values.insert(&unqualified.name); } } } // Get completable types for (name, type_) in &module_being_imported_from.types { // Skip types that should not be suggested if !self.is_suggestable_import( &type_.publicity, module_being_imported_from.package.as_str(), ) { continue; } // Skip type that are already imported if already_imported_types.contains(name) { continue; } completions.push(type_completion( None, name, type_, &cursor_surroundings, if within_braces { TypeCompletionContext::UnqualifiedImportWithinBraces } else { TypeCompletionContext::UnqualifiedImport }, CompletionKind::ImportedModule, )); } // Get completable values for (name, value) in &module_being_imported_from.values { // Skip values that should not be suggested if !self.is_suggestable_import( &value.publicity, module_being_imported_from.package.as_str(), ) { continue; } // Skip values that are already imported if already_imported_values.contains(name) { continue; } completions.push(self.value_completion( None, &module_being_imported_from.name, name, value, &cursor_surroundings, CompletionKind::ImportedModule, )); } completions } // Get all the modules that can be imported that have not already been imported. fn completable_modules_for_import(&self) -> Vec<(&EcoString, &ModuleInterface)> { let mut direct_dep_packages: std::collections::HashSet<&EcoString> = std::collections::HashSet::from_iter( self.compiler.project_compiler.config.dependencies.keys(), ); if !self.module.origin.is_src() { // In tests we can import direct dev dependencies direct_dep_packages.extend( self.compiler .project_compiler .config .dev_dependencies .keys(), ) } let already_imported: std::collections::HashSet = std::collections::HashSet::from_iter( self.module.dependencies.iter().map(|d| d.0.clone()), ); self.compiler .project_compiler .get_importable_modules() .iter() // // You cannot import yourself .filter(|(name, _)| *name != &self.module.name) // // Different origin directories will get different import completions .filter(|(_, module)| match self.module.origin { // src/ can import from src/ Origin::Src => module.origin.is_src(), // dev/ can import from src/ or dev/ Origin::Dev => !module.origin.is_test(), // Test can import from anywhere Origin::Test => true, }) // // It is possible to import internal modules from other packages, // but it's not recommended so we don't include them in completions .filter(|(_, module)| module.package == self.root_package_name() || !module.is_internal) // // You cannot import a module twice .filter(|(name, _)| !already_imported.contains(*name)) // // It is possible to import modules from dependencies of dependencies // but it's not recommended so we don't include them in completions .filter(|(_, module)| { let is_root_or_prelude = module.package == self.root_package_name() || module.package.is_empty(); is_root_or_prelude || direct_dep_packages.contains(&module.package) }) .collect() } // Get all the completions for modules that can be imported fn complete_modules_for_import( &'a self, start: Position, end: Position, ) -> Vec { self.completable_modules_for_import() .iter() .map(|(name, _)| CompletionItem { label: name.to_string(), kind: Some(CompletionItemKind::MODULE), text_edit: Some(CompletionTextEdit::Edit(TextEdit { range: Range { start, end }, new_text: name.to_string(), })), ..Default::default() }) .collect() } // NOTE: completion_types and completion_values are really similar // but just different enough that an abstraction would // be really hard to understand or use a lot of trait magic. // For now I've left it as is but might be worth revisiting. /// Provides completions for when the context being editted is a type. pub fn completion_types(&'a self) -> Vec { let cursor_surroundings = self.get_phrase_surrounding_completion(); let selected_module = cursor_surroundings.selected_module(); let mut completions = vec![]; // Module and prelude types // Do not complete direct module types if the user has already started // typing a module select. That is, when the user has already typed // `mymodule.|` we know local module types and prelude types are no // longer relevant and needed for completions. if selected_module.is_none() { for (name, type_) in &self.module.ast.type_info.types { completions.push(type_completion( None, name, type_, &cursor_surroundings, TypeCompletionContext::UnqualifiedType, CompletionKind::LocallyDefined, )); } for type_ in PreludeType::iter() { let label: String = type_.name().into(); let sort_text = Some(sort_text( CompletionKind::Prelude, &label, TypeMatch::Unknown, )); completions.push(CompletionItem { label, detail: Some("Type".into()), kind: Some(CompletionItemKind::CLASS), sort_text, ..Default::default() }); } } // Qualified types for import in &self.module.ast.definitions.imports { // The module may not be known of yet if it has not previously // compiled yet in this editor session. let Some(module) = self.compiler.get_module_interface(&import.module) else { continue; }; let Some(module_name) = &import.used_name() else { continue; }; for (name, type_) in &module.types { if !self.is_suggestable_import(&type_.publicity, module.package.as_str()) { continue; } // If the user has already started typing a module select // then don't show irrelevant modules. // For example: when the user has typed `mymodule.|` we // should only show items from `mymodule`. if let Some(typed_module) = &selected_module && module_name != typed_module { continue; } completions.push(type_completion( Some(module_name), name, type_, &cursor_surroundings, TypeCompletionContext::QualifiedType, CompletionKind::ImportedModule, )); } // Unqualified types // Do not complete unqualified types if the user has already started // typing a module select. // For example, when the user has already typed `mymodule.|` we know // unqualified module types are no longer relevant. if selected_module.is_none() { for unqualified in &import.unqualified_types { if let Some(type_) = module.get_public_type(&unqualified.name) { completions.push(type_completion( None, unqualified.used_name(), type_, &cursor_surroundings, TypeCompletionContext::UnqualifiedType, CompletionKind::ImportedModule, )) } } } } // Importable modules let first_import_pos = position_of_first_definition_if_import(self.module, &self.src_line_numbers); let first_is_import = first_import_pos.is_some(); let import_location = first_import_pos.unwrap_or_default(); let after_import_newlines = add_newlines_after_import( import_location, first_is_import, &self.src_line_numbers, self.src, ); for (module_full_name, module) in self.completable_modules_for_import() { // Do not try to import the prelude. if module_full_name == "gleam" { continue; } let qualifier = module_full_name .split('/') .next_back() .unwrap_or(module_full_name); // If the user has already started a module select then don't show irrelevant modules. // e.x. when the user has typed mymodule.| we should only show items from mymodule. if let Some(selected_module) = &selected_module && qualifier != selected_module { continue; } // Qualified types for (name, type_) in &module.types { if !self.is_suggestable_import(&type_.publicity, module.package.as_str()) { continue; } let mut completion = type_completion( Some(qualifier), name, type_, &cursor_surroundings, TypeCompletionContext::QualifiedType, CompletionKind::ImportableModule, ); add_import_to_completion( &mut completion, import_location, module_full_name, &after_import_newlines, ); completions.push(completion); } } completions } /// Provides completions for when the context being editted is a value. pub fn completion_values(&'a self) -> Vec { let cursor_surroundings = self.get_phrase_surrounding_completion(); let selected_module = cursor_surroundings.selected_module(); let mut completions = vec![]; let mod_name = self.module.name.as_str(); let cursor = self.src_line_numbers.byte_index(*self.cursor_position); // If the value for which we've been asked to give completions is a regular // number it doesn't make sense to provide any completion! // // ```gleam // // imagine you're typing a number... // 2 // // ^ it would be quite annoying if suggestions popped up: // // [list.window_by_2] // // [int.to_base32] // // ... // ``` // // This usually happens in IDEs like Zed that still ask for completions // even if the programmer is typing in a number. We can't control when // an IDE asks for completions, but we can avoid replying nonsense in // this context. if parse_int_value(&cursor_surroundings.surrounding_text).is_some() || LiteralFloatValue::parse(&cursor_surroundings.surrounding_text).is_some() { return vec![]; } // Keyword completions if !cursor_surroundings.surrounding_text.is_empty() { for keyword in ["panic", "todo", "echo"] { if keyword.starts_with(cursor_surroundings.surrounding_text.as_str()) { completions.push(self.keyword_completion(keyword, &cursor_surroundings)) } } } // Module and prelude values // Do not complete direct module values if the user has already started typing a module select. // e.x. when the user has typed mymodule.| we know local module and prelude values are no longer // relevant. if selected_module.is_none() { // Find the function that the cursor is in and push completions for // its arguments and local variables. if let Some(function) = self .module .ast .definitions .functions .iter() .filter(|function| function.full_location().contains(cursor)) .peekable() .peek() { completions.extend( LocalCompletion::new( mod_name, cursor_surroundings.surrounding_text_range, cursor, self.expected_type.clone(), ) .fn_completions(function), ); } for (name, value) in &self.module.ast.type_info.values { // Here we do not check for the internal attribute: we always want // to show autocompletions for values defined in the same module, // even if those are internal. completions.push(self.value_completion( None, mod_name, name, value, &cursor_surroundings, CompletionKind::LocallyDefined, )); } let mut push_prelude_completion = |label: &str, kind, type_: Arc| { let label = label.to_string(); let sort_text = Some(sort_text( CompletionKind::Prelude, &label, match_type(&self.expected_type, &type_), )); completions.push(CompletionItem { label, detail: Some(PRELUDE_MODULE_NAME.into()), kind: Some(kind), sort_text, ..Default::default() }); }; for type_ in PreludeType::iter() { match type_ { PreludeType::Bool => { push_prelude_completion( "True", CompletionItemKind::ENUM_MEMBER, type_::bool(), ); push_prelude_completion( "False", CompletionItemKind::ENUM_MEMBER, type_::bool(), ); } PreludeType::Nil => { push_prelude_completion( "Nil", CompletionItemKind::ENUM_MEMBER, type_::nil(), ); } PreludeType::Result => { push_prelude_completion( "Ok", CompletionItemKind::CONSTRUCTOR, type_::result(type_::unbound_var(0), type_::unbound_var(0)), ); push_prelude_completion( "Error", CompletionItemKind::CONSTRUCTOR, type_::result(type_::unbound_var(0), type_::unbound_var(0)), ); } PreludeType::BitArray | PreludeType::Float | PreludeType::Int | PreludeType::List | PreludeType::String | PreludeType::UtfCodepoint => {} } } } // Imported modules for import in &self.module.ast.definitions.imports { // The module may not be known of yet if it has not previously // compiled yet in this editor session. let Some(module) = self.compiler.get_module_interface(&import.module) else { continue; }; // Qualified values for (name, value) in &module.values { if !self.is_suggestable_import(&value.publicity, module.package.as_str()) { continue; } if let Some(module) = import.used_name() { // If the user has already started a module select then don't show irrelevant modules. // e.x. when the user has typed mymodule.| we should only show items from mymodule. if let Some(input_mod_name) = &selected_module && &module != input_mod_name { continue; } completions.push(self.value_completion( Some(&module), mod_name, name, value, &cursor_surroundings, CompletionKind::ImportedModule, )); } } // Unqualified values // Do not complete unqualified values if the user has already started typing a module select. // e.x. when the user has typed mymodule.| we know unqualified module values are no longer relevant. if selected_module.is_none() { for unqualified in &import.unqualified_values { if let Some(value) = module.get_public_value(&unqualified.name) { let name = unqualified.used_name(); completions.push(self.value_completion( None, mod_name, name, value, &cursor_surroundings, CompletionKind::ImportedModule, )) } } } } // Importable modules let first_import_pos = position_of_first_definition_if_import(self.module, &self.src_line_numbers); let first_is_import = first_import_pos.is_some(); let import_location = first_import_pos.unwrap_or_default(); let after_import_newlines = add_newlines_after_import( import_location, first_is_import, &self.src_line_numbers, self.src, ); for (module_full_name, module) in self.completable_modules_for_import() { // Do not try to import the prelude. if module_full_name == "gleam" { continue; } let qualifier = module_full_name .split('/') .next_back() .unwrap_or(module_full_name); // If the user has already started a module select then don't show irrelevant modules. // e.x. when the user has typed mymodule.| we should only show items from mymodule. if let Some(selected_module) = &selected_module && qualifier != selected_module { continue; } // Qualified values for (name, value) in &module.values { if !self.is_suggestable_import(&value.publicity, module.package.as_str()) { continue; } let mut completion = self.value_completion( Some(qualifier), module_full_name, name, value, &cursor_surroundings, CompletionKind::ImportableModule, ); add_import_to_completion( &mut completion, import_location, module_full_name, &after_import_newlines, ); completions.push(completion); } } completions } // Looks up the type accessors for the given type fn type_accessors_from_modules( &'a self, importable_modules: &'a im::HashMap, type_: Arc, ) -> Option<&'a HashMap> { let type_ = collapse_links(type_); match type_.as_ref() { Type::Named { name, module, inferred_variant, .. } => importable_modules .get(module) .and_then(|i| i.accessors.get(name)) .filter(|a| a.publicity.is_importable() || module == &self.module.name) .map(|a| a.accessors_for_variant(*inferred_variant)), Type::Fn { .. } | Type::Var { .. } | Type::Tuple { .. } => None, } } /// Provides completions for field accessors when the context being editted /// is a custom type instance pub fn completion_field_accessors(&'a self, type_: Arc) -> Vec { if let Type::Named { publicity, module: type_module, .. } = type_.as_ref() && publicity.is_internal() && *type_module != self.module.name { // If we're asking for field completions for an internal type that // is not defined in the current module we don't want to show // anything. This makes it a lot harder to inadvertently rely on // internal implementation details without noticing. return vec![]; } self.type_accessors_from_modules( self.compiler.project_compiler.get_importable_modules(), type_, ) .map(|accessors| { accessors .values() .map(|accessor| self.field_completion(&accessor.label, accessor.type_.clone())) .collect_vec() }) .unwrap_or_default() } fn callable_field_map( &'a self, expr: &'a TypedExpr, importable_modules: &'a im::HashMap, ) -> Option<&'a FieldMap> { match expr { TypedExpr::Var { constructor, .. } => constructor.field_map(), TypedExpr::ModuleSelect { module_name, label, .. } => importable_modules .get(module_name) .and_then(|i| i.values.get(label)) .and_then(|a| a.field_map()), TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => None, } } /// Provides completions for labels when the context being editted is a call /// that has labelled arguments that can be passed pub fn completion_labels( &'a self, fun: &TypedExpr, existing_arguments: &[CallArg], ) -> Vec { let fun_type = fun.type_().fn_types().map(|(arguments, _)| arguments); let already_included_labels = existing_arguments .iter() .filter_map(|argument| { // Record updates can have implicit arguments added as placeholders // by the compiler. Those are still arguments that could be typed // by the developer and used, so we don't want to include those // in the ones that have already been included and won't be recommended. if argument.is_implicit() && !argument.is_use_implicit_callback() { None } else { argument.label.clone() } }) .collect_vec(); let Some(field_map) = self.callable_field_map(fun, self.compiler.project_compiler.get_importable_modules()) else { return vec![]; }; field_map .fields .iter() .filter(|field| !already_included_labels.contains(field.0)) .map(|(label, arg_index)| { let detail = fun_type.as_ref().and_then(|arguments| { arguments .get(*arg_index as usize) .map(|argument| Printer::new().pretty_print(argument, 0)) }); let label = format!("{label}:"); let sort_text = Some(sort_text(CompletionKind::Label, &label, TypeMatch::Unknown)); CompletionItem { label, detail, kind: Some(CompletionItemKind::FIELD), sort_text, ..Default::default() } }) .collect() } fn root_package_name(&self) -> &str { self.compiler.project_compiler.config.name.as_str() } // checks based on the publicity if something should be suggested for import from root package fn is_suggestable_import(&self, publicity: &Publicity, package: &str) -> bool { match publicity { // We skip private types as we never want those to appear in // completions. Publicity::Private => false, // We only skip internal types if those are not defined in // the root package. Publicity::Internal { .. } if package != self.root_package_name() => false, Publicity::Internal { .. } => true, // We never skip public types. Publicity::Public => true, } } fn keyword_completion( &self, keyword: &str, cursor_surrounding: &CursorSurroundings, ) -> CompletionItem { let label = keyword.to_string(); CompletionItem { label: label.clone(), kind: Some(CompletionItemKind::KEYWORD), detail: None, label_details: None, documentation: None, sort_text: Some(sort_text( CompletionKind::Keyword, &label, TypeMatch::Matching, )), text_edit: cursor_surrounding.to_text_edit(label), ..Default::default() } } fn value_completion( &self, module_qualifier: Option<&str>, module_name: &str, name: &str, value: &type_::ValueConstructor, cursor_surrounding: &CursorSurroundings, priority: CompletionKind, ) -> CompletionItem { let type_match = match_type(&self.expected_type, &value.type_); let label = match module_qualifier { Some(module) => format!("{module}.{name}"), None => name.to_string(), }; let type_ = Printer::new().pretty_print(&value.type_, 0); let kind = Some(match value.variant { ValueConstructorVariant::LocalVariable { .. } => CompletionItemKind::VARIABLE, ValueConstructorVariant::ModuleConstant { .. } => CompletionItemKind::CONSTANT, ValueConstructorVariant::ModuleFn { .. } => CompletionItemKind::FUNCTION, ValueConstructorVariant::Record { arity: 0, .. } => CompletionItemKind::ENUM_MEMBER, ValueConstructorVariant::Record { .. } => CompletionItemKind::CONSTRUCTOR, }); let documentation = value.get_documentation().map(|documentation| { Documentation::MarkupContent(MarkupContent { kind: MarkupKind::Markdown, value: documentation.into(), }) }); CompletionItem { label: label.clone(), kind, detail: Some(type_), label_details: Some(CompletionItemLabelDetails { detail: None, description: Some(module_name.into()), }), documentation, sort_text: Some(sort_text(priority, &label, type_match)), text_edit: cursor_surrounding.to_text_edit(label), ..Default::default() } } fn field_completion(&self, label: &str, type_: Arc) -> CompletionItem { let type_match = match_type(&self.expected_type, &type_); let type_ = Printer::new().pretty_print(&type_, 0); CompletionItem { label: label.into(), kind: Some(CompletionItemKind::FIELD), detail: Some(type_), sort_text: Some(sort_text(CompletionKind::FieldAccessor, label, type_match)), ..Default::default() } } } fn add_import_to_completion( item: &mut CompletionItem, import_location: Position, module_full_name: &EcoString, insert_newlines: &Newlines, ) { item.additional_text_edits = Some(vec![get_import_edit( import_location, module_full_name, insert_newlines, )]); } fn type_completion( module: Option<&str>, name: &str, type_: &TypeConstructor, cursor_surrounding: &CursorSurroundings, type_completion_context: TypeCompletionContext, priority: CompletionKind, ) -> CompletionItem { let label = match module { Some(module) => format!("{module}.{name}"), None => name.to_string(), }; let kind = Some(if type_.type_.is_variable() { CompletionItemKind::VARIABLE } else { CompletionItemKind::CLASS }); let completion_text = match type_completion_context { TypeCompletionContext::UnqualifiedImport => format!("{{type {label}}}"), TypeCompletionContext::UnqualifiedImportWithinBraces => format!("type {label}"), TypeCompletionContext::QualifiedType | TypeCompletionContext::UnqualifiedType => { label.clone() } }; CompletionItem { label: label.clone(), kind, detail: Some("Type".into()), sort_text: Some(sort_text(priority, &label, TypeMatch::Unknown)), text_edit: cursor_surrounding.to_text_edit(completion_text), ..Default::default() } } fn match_type(expected_type: &Option>, type_: &Type) -> TypeMatch { if let Some(expected_type) = expected_type { // If the type of the value we are completing is unbound, that // technically means that all types match, which doesn't give us // any useful information so we treat it as not knowing what the // type is, which generally is the case. if expected_type.is_unbound() { TypeMatch::Unknown } // We also want to prioritise functions which return the desired type, // as often the user's intention will be to write a function call. else if let Some((_, return_)) = type_.fn_types() && expected_type.same_as(&return_) { TypeMatch::Matching } else if expected_type.same_as(type_) { TypeMatch::Matching } else { TypeMatch::Incompatible } } else { TypeMatch::Unknown } } pub struct LocalCompletion<'a> { mod_name: &'a str, insert_range: Range, cursor: u32, completions: HashMap, expected_type: Option>, } impl<'a> LocalCompletion<'a> { pub fn new( mod_name: &'a str, insert_range: Range, cursor: u32, expected_type: Option>, ) -> Self { Self { mod_name, insert_range, cursor, completions: HashMap::new(), expected_type, } } /// Generates completion items for a given function, including its arguments /// and local variables. pub fn fn_completions( mut self, fun: &'a Function, TypedExpr>, ) -> Vec { // Add function arguments to completions self.visit_fn_arguments(&fun.arguments); // Visit the function body statements for statement in &fun.body { // Visit the statement to find local variables self.visit_typed_statement(statement); } self.completions.into_values().collect_vec() } fn visit_fn_arguments(&mut self, arguments: &[Arg>]) { for argument in arguments { if let Some(name) = argument.get_variable_name() { self.push_completion(name, argument.type_.clone()); } } } fn push_completion(&mut self, name: &EcoString, type_: Arc) { if name.is_empty() || name.starts_with('_') { return; } _ = self.completions.insert( name.clone(), self.local_value_completion(self.mod_name, name, type_, self.insert_range), ); } fn local_value_completion( &self, module_name: &str, name: &str, type_: Arc, insert_range: Range, ) -> CompletionItem { let type_match = match_type(&self.expected_type, &type_); let label = name.to_string(); let type_ = Printer::new().pretty_print(&type_, 0); let documentation = Documentation::MarkupContent(MarkupContent { kind: MarkupKind::Markdown, value: String::from("A locally defined variable."), }); CompletionItem { label: label.clone(), kind: Some(CompletionItemKind::VARIABLE), detail: Some(type_), label_details: Some(CompletionItemLabelDetails { detail: None, description: Some(module_name.into()), }), documentation: Some(documentation), sort_text: Some(sort_text( CompletionKind::LocallyDefined, &label, type_match, )), text_edit: Some(CompletionTextEdit::Edit(TextEdit { range: insert_range, new_text: label.clone(), })), ..Default::default() } } } impl<'ast> Visit<'ast> for LocalCompletion<'_> { fn visit_typed_statement(&mut self, statement: &'ast ast::TypedStatement) { // We only want to suggest local variables that are defined before // the cursor if statement.location().start >= self.cursor { return; } ast::visit::visit_typed_statement(self, statement); } /// Visits a typed assignment, selectively processing either the value or the pattern /// based on the cursor position. /// - If the cursor is within the assignment It visits only the value expression. /// This avoids suggesting variables that are being defined in the assignment itself. /// - If the cursor is outside the assignment It visits only the pattern. /// This prevents suggesting variables that might be out of scope. fn visit_typed_assignment(&mut self, assignment: &'ast ast::TypedAssignment) { if assignment.location.contains(self.cursor) { self.visit_typed_expr(&assignment.value); } else { self.visit_typed_pattern(&assignment.pattern); } } fn visit_typed_expr_fn( &mut self, location: &'ast ast::SrcSpan, _: &'ast Arc, _: &'ast FunctionLiteralKind, arguments: &'ast [ast::TypedArg], body: &'ast Vec1, _: &'ast Option, ) { // If we are completing after the function body, any locally defined // variables are now out of scope so we don't register any. if self.cursor >= location.end { return; } self.visit_fn_arguments(arguments); for statement in body { self.visit_typed_statement(statement); } } fn visit_typed_expr_block( &mut self, location: &'ast ast::SrcSpan, statements: &'ast [ast::TypedStatement], ) { // If we are completing after the block, any locally defined variables // are now out of scope so we don't register any. if self.cursor >= location.end { return; } ast::visit::visit_typed_expr_block(self, location, statements); } fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) { // Any code which comes before or after a case clause cannot access any // of the variables defined within it, so we ignore this clause if so. if self.cursor < clause.location.start || self.cursor > clause.location.end { return; } ast::visit::visit_typed_clause(self, clause); } fn visit_typed_pattern_variable( &mut self, _: &'ast ast::SrcSpan, name: &'ast EcoString, type_: &'ast Arc, _origin: &'ast VariableOrigin, ) { self.push_completion(name, type_.clone()); } fn visit_typed_pattern_discard( &mut self, _: &'ast ast::SrcSpan, name: &'ast EcoString, type_: &'ast Arc, ) { self.push_completion(name, type_.clone()); } fn visit_typed_pattern_string_prefix( &mut self, _: &'ast ast::SrcSpan, _: &'ast ast::SrcSpan, _: &'ast Option<(EcoString, ast::SrcSpan)>, _: &'ast ast::SrcSpan, _: &'ast EcoString, right_side_assignment: &'ast ast::AssignName, ) { self.push_completion(right_side_assignment.name(), type_::string()); } fn visit_typed_pattern_assign( &mut self, _: &'ast ast::SrcSpan, name: &'ast EcoString, pattern: &'ast Pattern>, ) { self.visit_typed_pattern(pattern); self.push_completion(name, pattern.type_().clone()); } } ================================================ FILE: language-server/src/edits.rs ================================================ use ecow::EcoString; use lsp_types::{Position, Range, TextEdit}; use gleam_core::{ ast::{Import, SrcSpan, TypedDefinitions}, build::Module, line_numbers::LineNumbers, }; use super::src_span_to_lsp_range; // Gets the position of the import statement if it's the first definition in the module. pub fn position_of_first_definition_if_import( module: &Module, line_numbers: &LineNumbers, ) -> Option { let TypedDefinitions { imports, constants, custom_types, type_aliases, functions, } = &module.ast.definitions; // We first find the firts import by position let first_import = imports.iter().min_by_key(|import| import.location)?; // Then we need to make sure it actually comes before any other definition. let import_is_first_definition = constants .iter() .map(|constant| constant.location) .chain(custom_types.iter().map(|custom_type| custom_type.location)) .chain(type_aliases.iter().map(|type_alias| type_alias.location)) .chain(functions.iter().map(|function| function.location)) .all(|location| location >= first_import.location); if import_is_first_definition { Some(src_span_to_lsp_range(first_import.location, line_numbers).start) } else { None } } pub enum Newlines { Single, Double, } // Returns how many newlines should be added after an import statement. By default `Newlines::Single`, // but if there's not any import statement, it returns `Newlines::Double`. // // * ``import_location`` - The position of the first import statement in the source code. pub fn add_newlines_after_import( import_location: Position, has_imports: bool, line_numbers: &LineNumbers, src: &str, ) -> Newlines { let import_start_cursor = line_numbers.byte_index(import_location); let is_new_line = src .chars() .nth(import_start_cursor as usize) .unwrap_or_default() == '\n'; match !has_imports && !is_new_line { true => Newlines::Double, false => Newlines::Single, } } pub fn get_import_edit( import_location: Position, module_full_name: &str, insert_newlines: &Newlines, ) -> TextEdit { let new_lines = match insert_newlines { Newlines::Single => "\n", Newlines::Double => "\n\n", }; TextEdit { range: Range { start: import_location, end: import_location, }, new_text: ["import ", module_full_name, new_lines].concat(), } } pub fn insert_unqualified_import( import: &Import, code: &str, name: String, ) -> (u32, String) { let SrcSpan { start, end } = import.location; let import_code = code .get(start as usize..end as usize) .expect("Import location is invalid"); let has_brace = import_code.contains('}'); if has_brace { insert_into_braced_import(name, import.location, import_code) } else { insert_into_unbraced_import(name, import, import_code) } } // Handle inserting into an unbraced import fn insert_into_unbraced_import( name: String, import: &Import, import_code: &str, ) -> (u32, String) { let location = import.location; if import.as_name.is_none() { // Case: import module (location.end, format!(".{{{name}}}")) } else { // Case: import module as alias let as_pos = import_code .find(" as ") .expect("Expected ' as ' in import statement"); let before_as_pos = import_code .get(..as_pos) .and_then(|s| s.rfind(|c: char| !c.is_whitespace())) .map(|pos| location.start as usize + pos + 1) .expect("Expected non-whitespace character before ' as '"); (before_as_pos as u32, format!(".{{{name}}}")) } } // Handle inserting into a braced import fn insert_into_braced_import(name: String, location: SrcSpan, import_code: &str) -> (u32, String) { if let Some((pos, c)) = find_last_char_before_closing_brace(location, import_code) { // Case: import module.{Existing, } (as alias) if c == ',' { (pos as u32 + 1, format!(" {name}")) } else { // Case: import module.{Existing} (as alias) (pos as u32 + 1, format!(", {name}")) } } else { // Case: import module.{} (as alias) let left_brace_pos = import_code .find('{') .map(|pos| location.start as usize + pos) .expect("Expected '{' in import statement"); (left_brace_pos as u32 + 1, name) } } fn find_last_char_before_closing_brace( location: SrcSpan, import_code: &str, ) -> Option<(usize, char)> { let closing_brace_pos = import_code.rfind('}')?; let bytes = import_code.as_bytes(); let mut pos = closing_brace_pos; while pos > 0 { pos -= 1; let c = (*bytes.get(pos)?) as char; if c.is_whitespace() { continue; } if c == '{' { break; } return Some((location.start as usize + pos, c)); } None } ================================================ FILE: language-server/src/engine.rs ================================================ use camino::Utf8PathBuf; use ecow::{EcoString, eco_format}; use gleam_core::{ Error, Result, Warning, analyse::name::correct_name_case, ast::{ self, Constant, CustomType, DefinitionLocation, ModuleConstant, PatternUnusedArguments, SrcSpan, TypedArg, TypedClauseGuard, TypedConstant, TypedExpr, TypedFunction, TypedModule, TypedPattern, TypedRecordConstructor, }, build::{ ExpressionPosition, Located, Module, UnqualifiedImport, type_constructor_from_modules, }, config::PackageConfig, io::{BeamCompiler, CommandExecutor, FileSystemReader, FileSystemWriter}, line_numbers::LineNumbers, paths::ProjectPaths, type_::{ self, Deprecation, ModuleInterface, Type, TypeConstructor, ValueConstructor, ValueConstructorVariant, error::{Named, VariableSyntax}, printer::Printer, }, }; use itertools::Itertools; use lsp::CodeAction; use lsp_server::ResponseError; use lsp_types::{ self as lsp, DocumentSymbol, FoldingRange, FoldingRangeKind, Hover, HoverContents, MarkedString, Position, PrepareRenameResponse, Range, SignatureHelp, SymbolKind, SymbolTag, TextEdit, Url, WorkspaceEdit, }; use std::{collections::HashSet, sync::Arc}; use crate::{code_action::ReplaceUnderscoreWithType, rename::rename_module_alias}; use super::{ DownloadDependencies, MakeLocker, code_action::{ AddAnnotations, AddMissingTypeParameter, AddOmittedLabels, AnnotateTopLevelDefinitions, CodeActionBuilder, CollapseNestedCase, ConvertFromUse, ConvertToFunctionCall, ConvertToPipe, ConvertToUse, ExpandFunctionCapture, ExtractConstant, ExtractFunction, ExtractVariable, FillInMissingLabelledArgs, FillUnusedFields, FixBinaryOperation, FixTruncatedBitArraySegment, GenerateDynamicDecoder, GenerateFunction, GenerateJsonEncoder, GenerateVariant, InlineVariable, InterpolateString, LetAssertToCase, MergeCaseBranches, PatternMatchOnValue, RedundantTupleInCaseSubject, RemoveBlock, RemoveEchos, RemovePrivateOpaque, RemoveUnreachableCaseClauses, RemoveUnusedImports, UseLabelShorthandSyntax, WrapInBlock, code_action_add_missing_patterns, code_action_convert_qualified_constructor_to_unqualified, code_action_convert_unqualified_constructor_to_qualified, code_action_import_module, code_action_inexhaustive_let_to_case, }, compiler::LspProjectCompiler, completer::Completer, files::FileSystemProxy, progress::ProgressReporter, reference::{ FindVariableReferences, Referenced, VariableReferenceKind, find_module_references, reference_for_ast_node, }, rename::{RenameOutcome, RenameTarget, Renamed, rename_local_variable, rename_module_entity}, signature_help, src_span_to_lsp_range, }; #[derive(Debug, PartialEq, Eq)] pub struct Response { pub result: Result, pub warnings: Vec, pub compilation: Compilation, } #[derive(Debug, PartialEq, Eq)] pub enum Compilation { /// Compilation was attempted and succeeded for these modules. Yes(Vec), /// Compilation was not attempted for this operation. No, } #[derive(Debug)] pub struct LanguageServerEngine { pub(crate) paths: ProjectPaths, /// A compiler for the project that supports repeat compilation of the root /// package. /// In the event the project config changes this will need to be /// discarded and reloaded to handle any changes to dependencies. pub(crate) compiler: LspProjectCompiler>, modules_compiled_since_last_feedback: Vec, compiled_since_last_feedback: bool, error: Option, // Used to publish progress notifications to the client without waiting for // the usual request-response loop. progress_reporter: Reporter, /// Used to know if to show the "View on HexDocs" link /// when hovering on an imported value hex_deps: HashSet, } impl<'a, IO, Reporter> LanguageServerEngine where // IO to be supplied from outside of gleam-core IO: FileSystemReader + FileSystemWriter + BeamCompiler + CommandExecutor + DownloadDependencies + MakeLocker + Clone, // IO to be supplied from inside of gleam-core Reporter: ProgressReporter + Clone + 'a, { pub fn new( config: PackageConfig, progress_reporter: Reporter, io: FileSystemProxy, paths: ProjectPaths, ) -> Result { let locker = io.inner().make_locker(&paths, config.target)?; // Download dependencies to ensure they are up-to-date for this new // configuration and new instance of the compiler progress_reporter.dependency_downloading_started(); let manifest = io.inner().download_dependencies(&paths); progress_reporter.dependency_downloading_finished(); // NOTE: This must come after the progress reporter has finished! let manifest = manifest?; let compiler: LspProjectCompiler> = LspProjectCompiler::new(manifest, config, paths.clone(), io.clone(), locker)?; let hex_deps = compiler .project_compiler .packages .iter() .flat_map(|(k, v)| match &v.source { gleam_core::manifest::ManifestPackageSource::Hex { .. } => { Some(EcoString::from(k.as_str())) } gleam_core::manifest::ManifestPackageSource::Git { .. } | gleam_core::manifest::ManifestPackageSource::Local { .. } => None, }) .collect(); Ok(Self { modules_compiled_since_last_feedback: vec![], compiled_since_last_feedback: false, progress_reporter, compiler, paths, error: None, hex_deps, }) } pub fn compile_please(&mut self) -> Response<()> { self.respond(Self::compile) } /// Compile the project if we are in one. Otherwise do nothing. fn compile(&mut self) -> Result<(), Error> { self.compiled_since_last_feedback = true; self.progress_reporter.compilation_started(); let outcome = self.compiler.compile(); self.progress_reporter.compilation_finished(); let result = outcome // Register which modules have changed .map(|modules| self.modules_compiled_since_last_feedback.extend(modules)) // Return the error, if present .into_result(); self.error = match &result { Ok(_) => None, Err(error) => Some(error.clone()), }; result } fn take_warnings(&mut self) -> Vec { self.compiler.take_warnings() } // TODO: implement unqualified imported module functions // pub fn goto_definition( &mut self, params: lsp::GotoDefinitionParams, ) -> Response> { self.respond(|this| { let params = params.text_document_position_params; let (line_numbers, node) = match this.node_at_position(¶ms) { Some(location) => location, None => return Ok(None), }; let Some(location) = node.definition_location(this.compiler.project_compiler.get_importable_modules()) else { return Ok(None); }; Ok(this.definition_location_to_lsp_location(&line_numbers, ¶ms, location)) }) } pub(crate) fn goto_type_definition( &mut self, params: lsp_types::GotoDefinitionParams, ) -> Response> { self.respond(|this| { let params = params.text_document_position_params; let (line_numbers, node) = match this.node_at_position(¶ms) { Some(location) => location, None => return Ok(vec![]), }; let Some(locations) = node .type_definition_locations(this.compiler.project_compiler.get_importable_modules()) else { return Ok(vec![]); }; let locations = locations .into_iter() .filter_map(|location| { this.definition_location_to_lsp_location(&line_numbers, ¶ms, location) }) .collect_vec(); Ok(locations) }) } fn definition_location_to_lsp_location( &self, line_numbers: &LineNumbers, params: &lsp_types::TextDocumentPositionParams, location: DefinitionLocation, ) -> Option { let (uri, line_numbers) = match location.module { None => (params.text_document.uri.clone(), line_numbers), Some(name) => { let module = self.compiler.get_source(&name)?; let url = Url::parse(&format!("file:///{}", &module.path)) .expect("goto definition URL parse"); (url, &module.line_numbers) } }; let range = src_span_to_lsp_range(location.span, line_numbers); Some(lsp::Location { uri, range }) } pub fn completion( &mut self, params: lsp::TextDocumentPositionParams, src: EcoString, ) -> Response>> { self.respond(|this| { let module = match this.module_for_uri(¶ms.text_document.uri) { Some(m) => m, None => return Ok(None), }; let mut completer = Completer::new(&src, ¶ms, &this.compiler, module); let byte_index = completer.module_line_numbers.byte_index(params.position); // If in comment context, do not provide completions if module.extra.is_within_comment(byte_index) { return Ok(None); } // Check current file contents if the user is writing an import // and handle separately from the rest of the completion flow // Check if an import is being written if let Some(value) = completer.import_completions() { return value; } let Some(found) = module.find_node(byte_index) else { return Ok(None); }; let completions = match found { Located::PatternSpread { .. } => None, Located::Pattern(_pattern) => None, Located::StringPrefixPatternVariable { .. } => None, // Do not show completions when typing inside a string. Located::Expression { expression: TypedExpr::String { .. }, .. } | Located::Constant(Constant::String { .. }) => None, Located::Expression { expression: TypedExpr::Call { fun, arguments, .. } | TypedExpr::RecordUpdate { constructor: fun, arguments, .. }, .. } => { let mut completions = vec![]; completions.append(&mut completer.completion_values()); completions.append(&mut completer.completion_labels(fun, arguments)); Some(completions) } Located::Expression { expression: TypedExpr::RecordAccess { record, type_, .. }, .. } => { completer.expected_type = Some(type_.clone()); let mut completions = vec![]; completions.append(&mut completer.completion_values()); completions.append(&mut completer.completion_field_accessors(record.type_())); Some(completions) } Located::Expression { position: ExpressionPosition::ArgumentOrLabel { called_function, function_arguments, }, .. } => { let mut completions = vec![]; completions.append(&mut completer.completion_values()); completions.append( &mut completer.completion_labels(called_function, function_arguments), ); Some(completions) } Located::Expression { expression, .. } => { completer.expected_type = Some(expression.type_()); Some(completer.completion_values()) } Located::ModuleFunction(_) => Some(completer.completion_types()), Located::Statement(_) => Some(completer.completion_values()), Located::FunctionBody(_) => Some(completer.completion_values()), Located::ModuleTypeAlias(_) | Located::ModuleCustomType(_) | Located::VariantConstructorDefinition(_) => Some(completer.completion_types()), // If the import completions returned no results and we are in an import then // we should try to provide completions for unqualified values Located::ModuleImport(import) => this .compiler .get_module_interface(import.module.as_str()) .map(|importing_module| { completer.unqualified_completions_from_module(importing_module, true) }), Located::ModuleConstant(_) | Located::Constant(_) => { Some(completer.completion_values()) } Located::UnqualifiedImport(_) => None, Located::Arg(_) => None, Located::Annotation { .. } => Some(completer.completion_types()), Located::Label(_, _) => None, Located::ModuleName { layer: ast::Layer::Type, .. } => Some(completer.completion_types()), Located::ModuleName { layer: ast::Layer::Value, .. } => Some(completer.completion_values()), Located::ClauseGuard(_) => Some(completer.completion_values()), }; Ok(completions) }) } pub fn code_actions( &mut self, params: lsp::CodeActionParams, ) -> Response>> { self.respond(|this| { let mut actions = vec![]; let Some(module) = this.module_for_uri(¶ms.text_document.uri) else { return Ok(None); }; let lines = LineNumbers::new(&module.code); code_action_unused_values(module, &lines, ¶ms, &mut actions); actions.extend(RemoveUnusedImports::new(module, &lines, ¶ms).code_actions()); code_action_convert_qualified_constructor_to_unqualified( module, &this.compiler, &lines, ¶ms, &mut actions, ); code_action_convert_unqualified_constructor_to_qualified( module, &lines, ¶ms, &mut actions, ); code_action_fix_names(&lines, ¶ms, &this.error, &mut actions); code_action_import_module(module, &lines, ¶ms, &this.error, &mut actions); code_action_add_missing_patterns(module, &lines, ¶ms, &this.error, &mut actions); actions .extend(RemoveUnreachableCaseClauses::new(module, &lines, ¶ms).code_actions()); actions.extend(CollapseNestedCase::new(module, &lines, ¶ms).code_actions()); code_action_inexhaustive_let_to_case( module, &lines, ¶ms, &this.error, &mut actions, ); actions.extend(MergeCaseBranches::new(module, &lines, ¶ms).code_actions()); actions.extend(FixBinaryOperation::new(module, &lines, ¶ms).code_actions()); actions .extend(FixTruncatedBitArraySegment::new(module, &lines, ¶ms).code_actions()); actions.extend(LetAssertToCase::new(module, &lines, ¶ms).code_actions()); actions .extend(RedundantTupleInCaseSubject::new(module, &lines, ¶ms).code_actions()); actions.extend(UseLabelShorthandSyntax::new(module, &lines, ¶ms).code_actions()); actions.extend(FillInMissingLabelledArgs::new(module, &lines, ¶ms).code_actions()); actions.extend(ConvertFromUse::new(module, &lines, ¶ms).code_actions()); actions.extend(RemoveEchos::new(module, &lines, ¶ms).code_actions()); actions.extend(ConvertToUse::new(module, &lines, ¶ms).code_actions()); actions.extend(ExpandFunctionCapture::new(module, &lines, ¶ms).code_actions()); actions.extend(FillUnusedFields::new(module, &lines, ¶ms).code_actions()); actions.extend(InterpolateString::new(module, &lines, ¶ms).code_actions()); actions.extend(ExtractVariable::new(module, &lines, ¶ms).code_actions()); actions.extend(ExtractConstant::new(module, &lines, ¶ms).code_actions()); actions.extend( GenerateFunction::new(module, &this.compiler.modules, &lines, ¶ms) .code_actions(), ); actions.extend( GenerateVariant::new(module, &this.compiler, &lines, ¶ms).code_actions(), ); actions.extend(ConvertToPipe::new(module, &lines, ¶ms).code_actions()); actions.extend(ConvertToFunctionCall::new(module, &lines, ¶ms).code_actions()); actions.extend( PatternMatchOnValue::new(module, &lines, ¶ms, &this.compiler).code_actions(), ); actions.extend(AddOmittedLabels::new(module, &lines, ¶ms).code_actions()); actions.extend(InlineVariable::new(module, &lines, ¶ms).code_actions()); actions.extend(WrapInBlock::new(module, &lines, ¶ms).code_actions()); actions.extend(RemoveBlock::new(module, &lines, ¶ms).code_actions()); actions.extend(RemovePrivateOpaque::new(module, &lines, ¶ms).code_actions()); actions.extend(ExtractFunction::new(module, &lines, ¶ms).code_actions()); GenerateDynamicDecoder::new(module, &lines, ¶ms, &mut actions, &this.compiler) .code_actions(); GenerateJsonEncoder::new( module, &lines, ¶ms, &mut actions, &this.compiler.project_compiler.config, ) .code_actions(); AddAnnotations::new(module, &lines, ¶ms).code_action(&mut actions); actions .extend(AnnotateTopLevelDefinitions::new(module, &lines, ¶ms).code_actions()); actions.extend(AddMissingTypeParameter::new(module, &lines, ¶ms).code_actions()); actions.extend(ReplaceUnderscoreWithType::new(module, &lines, ¶ms).code_actions()); Ok(if actions.is_empty() { None } else { Some(actions) }) }) } pub fn document_symbol( &mut self, params: lsp::DocumentSymbolParams, ) -> Response> { self.respond(|this| { let mut symbols = vec![]; let Some(module) = this.module_for_uri(¶ms.text_document.uri) else { return Ok(symbols); }; let line_numbers = LineNumbers::new(&module.code); for function in &module.ast.definitions.functions { // By default, the function's location ends right after the return type. // For the full symbol range, have it end at the end of the body. // Also include the documentation, if available. // // By convention, the symbol span starts from the leading slash in the // documentation comment's marker ('///'), not from its content (of which // we have the position), so we must convert the content start position // to the leading slash's position. let full_function_span = SrcSpan { start: function .documentation .as_ref() .map(|(doc_start, _)| get_doc_marker_position(*doc_start)) .unwrap_or(function.location.start), end: function.end_position, }; let (name_location, name) = function .name .as_ref() .expect("Function in a definition must be named"); // The 'deprecated' field is deprecated, but we have to specify it anyway // to be able to construct the 'DocumentSymbol' type, so // we suppress the warning. We specify 'None' as specifying 'Some' // is what is actually deprecated. #[allow(deprecated)] symbols.push(DocumentSymbol { name: name.to_string(), detail: Some( Printer::new(&module.ast.names) .print_type(&get_function_type(function)) .to_string(), ), kind: SymbolKind::FUNCTION, tags: make_deprecated_symbol_tag(&function.deprecation), deprecated: None, range: src_span_to_lsp_range(full_function_span, &line_numbers), selection_range: src_span_to_lsp_range(*name_location, &line_numbers), children: None, }); } for alias in &module.ast.definitions.type_aliases { let full_alias_span = match alias.documentation { Some((doc_position, _)) => { SrcSpan::new(get_doc_marker_position(doc_position), alias.location.end) } None => alias.location, }; // The 'deprecated' field is deprecated, but we have to specify it anyway // to be able to construct the 'DocumentSymbol' type, so // we suppress the warning. We specify 'None' as specifying 'Some' // is what is actually deprecated. #[allow(deprecated)] symbols.push(DocumentSymbol { name: alias.alias.to_string(), detail: Some( Printer::new(&module.ast.names) // If we print with aliases, we end up printing the alias which the user // is currently hovering, which is not helpful. Instead, we print the // raw type, so the user can see which type the alias represents .print_type_without_aliases(&alias.type_) .to_string(), ), kind: SymbolKind::CLASS, tags: make_deprecated_symbol_tag(&alias.deprecation), deprecated: None, range: src_span_to_lsp_range(full_alias_span, &line_numbers), selection_range: src_span_to_lsp_range(alias.name_location, &line_numbers), children: None, }); } for custom_type in &module.ast.definitions.custom_types { symbols.push(custom_type_symbol(custom_type, &line_numbers, module)); } for constant in &module.ast.definitions.constants { // `ModuleConstant.location` ends at the constant's name or type. // For the full symbol span, necessary for `range`, we need to // include the constant value as well. // Also include the documentation at the start, if available. let full_constant_span = SrcSpan { start: constant .documentation .as_ref() .map(|(doc_start, _)| get_doc_marker_position(*doc_start)) .unwrap_or(constant.location.start), end: constant.value.location().end, }; // The 'deprecated' field is deprecated, but we have to specify it anyway // to be able to construct the 'DocumentSymbol' type, so // we suppress the warning. We specify 'None' as specifying 'Some' // is what is actually deprecated. #[allow(deprecated)] symbols.push(DocumentSymbol { name: constant.name.to_string(), detail: Some( Printer::new(&module.ast.names) .print_type(&constant.type_) .to_string(), ), kind: SymbolKind::CONSTANT, tags: make_deprecated_symbol_tag(&constant.deprecation), deprecated: None, range: src_span_to_lsp_range(full_constant_span, &line_numbers), selection_range: src_span_to_lsp_range(constant.name_location, &line_numbers), children: None, }); } Ok(symbols) }) } pub fn folding_range( &mut self, params: lsp::FoldingRangeParams, ) -> Response> { self.respond(|this| { let mut ranges: Vec = vec![]; let Some(module) = this.module_for_uri(¶ms.text_document.uri) else { return Ok(vec![]); }; let line_numbers = LineNumbers::new(&module.code); for import in import_folding_spans(&module.ast.definitions.imports, &module.code, &line_numbers) { let Some(range) = folding_range_for_span(import, &line_numbers, Some(FoldingRangeKind::Imports)) else { continue; }; ranges.push(range); } for type_ in &module.ast.definitions.custom_types { let span = type_.full_location(); let Some(range) = folding_range_for_span(span, &line_numbers, None) else { continue; }; ranges.push(range); } for constant in &module.ast.definitions.constants { let span = SrcSpan::new(constant.location.start, constant.value.location().end); let Some(range) = folding_range_for_span(span, &line_numbers, None) else { continue; }; ranges.push(range); } for alias in &module.ast.definitions.type_aliases { let span = alias.location; let Some(range) = folding_range_for_span(span, &line_numbers, None) else { continue; }; ranges.push(range); } for function in &module.ast.definitions.functions { let Some(body_start) = function.body_start else { continue; }; let span = SrcSpan::new(body_start, function.end_position); let Some(range) = folding_range_for_span(span, &line_numbers, None) else { continue; }; ranges.push(range); } ranges.sort_by_key(|range| range.start_line); Ok(ranges) }) } /// Check whether a particular module is in the same package as this one fn is_same_package(&self, current_module: &Module, module_name: &str) -> bool { let other_module = self .compiler .project_compiler .get_importable_modules() .get(module_name); match other_module { // We can't rename values from other packages if we are not aliasing an unqualified import. Some(module) => module.package == current_module.ast.type_info.package, None => false, } } pub fn prepare_rename( &mut self, params: lsp::TextDocumentPositionParams, ) -> Response> { self.respond(|this| { let (lines, found) = match this.node_at_position(¶ms) { Some(value) => value, None => return Ok(None), }; let Some(current_module) = this.module_for_uri(¶ms.text_document.uri) else { return Ok(None); }; let success_response = |location| { Some(PrepareRenameResponse::Range(src_span_to_lsp_range( location, &lines, ))) }; let byte_index = lines.byte_index(params.position); let referenced = reference_for_ast_node(found, ¤t_module.name); Ok(match referenced { Some(Referenced::LocalVariable { location, origin, .. }) if location.contains(byte_index) => match origin.map(|origin| origin.syntax) { Some(VariableSyntax::Generated) => None, Some( VariableSyntax::Variable(label) | VariableSyntax::LabelShorthand(label), ) => success_response(SrcSpan { start: location.start, end: label .len() .try_into() .map(|len: u32| location.start + len) .unwrap_or(location.end), }), Some(VariableSyntax::AssignmentPattern) | None => success_response(location), }, Some( Referenced::ModuleValue { module, location, target_kind, .. } | Referenced::ModuleType { module, location, target_kind, .. }, ) if location.contains(byte_index) => { // We can't rename types or values from other packages if we are not aliasing an unqualified import. let rename_allowed = match target_kind { RenameTarget::Qualified => this.is_same_package(current_module, &module), RenameTarget::Unqualified | RenameTarget::Definition => true, }; if rename_allowed { success_response(location) } else { None } } Some(Referenced::ModuleName { location, .. }) => success_response(location), _ => None, }) }) } pub fn rename( &mut self, params: lsp::RenameParams, ) -> Response, ResponseError>> { self.respond(|this| { let position = ¶ms.text_document_position; let (lines, found) = match this.node_at_position(position) { Some(value) => value, None => return Ok(RenameOutcome::NoRenames.into_result()), }; let Some(module) = this.module_for_uri(&position.text_document.uri) else { return Ok(RenameOutcome::NoRenames.into_result()); }; let referenced = reference_for_ast_node(found, &module.name); Ok(match referenced { Some(Referenced::LocalVariable { origin, definition_location, name, .. }) => { let rename_kind = match origin.map(|origin| origin.syntax) { Some(VariableSyntax::Generated) => { return Ok(RenameOutcome::NoRenames.into_result()); } Some(VariableSyntax::LabelShorthand(_)) => { VariableReferenceKind::LabelShorthand } Some( VariableSyntax::AssignmentPattern | VariableSyntax::Variable { .. }, ) | None => VariableReferenceKind::Variable, }; rename_local_variable( module, &lines, ¶ms, definition_location, name, rename_kind, ) .into_result() } Some(Referenced::ModuleValue { module: module_name, target_kind, name, name_kind, .. }) => rename_module_entity( ¶ms, module, this.compiler.project_compiler.get_importable_modules(), &this.compiler.sources, Renamed { module_name: &module_name, name: &name, name_kind, target_kind, layer: ast::Layer::Value, }, ) .into_result(), Some(Referenced::ModuleType { module: module_name, target_kind, name, .. }) => rename_module_entity( ¶ms, module, this.compiler.project_compiler.get_importable_modules(), &this.compiler.sources, Renamed { module_name: &module_name, name: &name, name_kind: Named::Type, target_kind, layer: ast::Layer::Type, }, ) .into_result(), Some(Referenced::ModuleName { module_name, module_alias, .. }) => rename_module_alias(module, &lines, ¶ms, &module_name, &module_alias) .into_result(), None => RenameOutcome::NoRenames.into_result(), }) }) } pub fn find_references( &mut self, params: lsp::ReferenceParams, ) -> Response>> { self.respond(|this| { let position = ¶ms.text_document_position; let (lines, found) = match this.node_at_position(position) { Some(value) => value, None => return Ok(None), }; let uri = position.text_document.uri.clone(); let Some(module) = this.module_for_uri(&uri) else { return Ok(None); }; let byte_index = lines.byte_index(position.position); let referenced = reference_for_ast_node(found, &module.name); Ok(match referenced { Some(Referenced::LocalVariable { origin, definition_location, location, name, }) if location.contains(byte_index) => match origin.map(|origin| origin.syntax) { Some(VariableSyntax::Generated) => None, Some( VariableSyntax::LabelShorthand(_) | VariableSyntax::AssignmentPattern | VariableSyntax::Variable { .. }, ) | None => { let variable_references = FindVariableReferences::new(definition_location, name) .find_in_module(&module.ast); let mut reference_locations = Vec::with_capacity(variable_references.len() + 1); reference_locations.push(lsp::Location { uri: uri.clone(), range: src_span_to_lsp_range(definition_location, &lines), }); for reference in variable_references { reference_locations.push(lsp::Location { uri: uri.clone(), range: src_span_to_lsp_range(reference.location, &lines), }) } Some(reference_locations) } }, Some(Referenced::ModuleValue { module, name, location, .. }) if location.contains(byte_index) => Some(find_module_references( module, name, this.compiler.project_compiler.get_importable_modules(), &this.compiler.sources, ast::Layer::Value, )), Some(Referenced::ModuleType { module, name, location, .. }) if location.contains(byte_index) => Some(find_module_references( module, name, this.compiler.project_compiler.get_importable_modules(), &this.compiler.sources, ast::Layer::Type, )), _ => None, }) }) } fn respond(&mut self, handler: impl FnOnce(&mut Self) -> Result) -> Response { let result = handler(self); let warnings = self.take_warnings(); // TODO: test. Ensure hover doesn't report as compiled let compilation = if self.compiled_since_last_feedback { let modules = std::mem::take(&mut self.modules_compiled_since_last_feedback); self.compiled_since_last_feedback = false; Compilation::Yes(modules) } else { Compilation::No }; Response { result, warnings, compilation, } } pub fn hover(&mut self, params: lsp::HoverParams) -> Response> { self.respond(|this| { let params = params.text_document_position_params; let (lines, found) = match this.node_at_position(¶ms) { Some(value) => value, None => return Ok(None), }; let Some(module) = this.module_for_uri(¶ms.text_document.uri) else { return Ok(None); }; Ok(match found { Located::Statement(_) => None, // TODO: hover for statement Located::ModuleFunction(function) => { Some(hover_for_function_head(function, lines, module)) } Located::ModuleConstant(constant) => { Some(hover_for_module_constant(constant, lines, module)) } Located::Constant(constant) => Some(hover_for_constant(constant, lines, module)), Located::ModuleImport(import) => { let Some(module) = this.compiler.get_module_interface(&import.module) else { return Ok(None); }; Some(hover_for_module( module, import.location, &lines, &this.hex_deps, )) } Located::ModuleCustomType(custom_type) => { Some(hover_for_custom_type(custom_type, lines)) } Located::ModuleTypeAlias(_) => None, Located::VariantConstructorDefinition(constructor) => { Some(hover_for_constructor(constructor, lines, module)) } Located::UnqualifiedImport(UnqualifiedImport { name, module: module_name, is_type, location, }) => this .compiler .get_module_interface(module_name.as_str()) .and_then(|module_interface| { if is_type { module_interface.types.get(name).map(|constructor| { hover_for_annotation( *location, constructor.type_.as_ref(), Some(constructor), lines, module, ) }) } else { module_interface.values.get(name).map(|v| { let m = if this.hex_deps.contains(&module_interface.package) { Some(module_interface) } else { None }; hover_for_imported_value(v, location, lines, m, name, module) }) } }), Located::Pattern(pattern) => Some(hover_for_pattern(pattern, lines, module)), Located::PatternSpread { spread_location, pattern, } => { let range = Some(src_span_to_lsp_range(spread_location, &lines)); let mut printer = Printer::new(&module.ast.names); let PatternUnusedArguments { positional, labelled, } = pattern.unused_arguments().unwrap_or_default(); let positional = positional .iter() .map(|type_| format!("- `{}`", printer.print_type(type_))) .join("\n"); let labelled = labelled .iter() .map(|(label, type_)| { format!("- `{}: {}`", label, printer.print_type(type_)) }) .join("\n"); let content = match (positional.is_empty(), labelled.is_empty()) { (true, false) => format!("Unused labelled fields:\n{labelled}"), (false, true) => format!("Unused positional fields:\n{positional}"), (_, _) => format!( "Unused positional fields: {positional} Unused labelled fields: {labelled}" ), }; Some(Hover { contents: HoverContents::Scalar(MarkedString::from_markdown(content)), range, }) } Located::StringPrefixPatternVariable { location, .. } => Some( hover_for_string_prefix_pattern_variable(location, &lines, module), ), Located::Expression { expression, .. } => Some(hover_for_expression( expression, lines, module, &this.hex_deps, )), Located::Arg(arg) => Some(hover_for_function_argument(arg, lines, module)), Located::FunctionBody(_) => None, Located::Annotation { ast, type_ } => { let type_constructor = type_constructor_from_modules( this.compiler.project_compiler.get_importable_modules(), type_.clone(), ); Some(hover_for_annotation( ast.location(), &type_, type_constructor, lines, module, )) } Located::Label(location, type_) => { Some(hover_for_label(location, type_, lines, module)) } Located::ModuleName { location, module_name, .. } => { let Some(module) = this.compiler.get_module_interface(&module_name) else { return Ok(None); }; Some(hover_for_module(module, location, &lines, &this.hex_deps)) } Located::ClauseGuard(guard) => Some(hover_for_clause_guard(guard, lines, module)), }) }) } pub(crate) fn signature_help( &mut self, params: lsp_types::SignatureHelpParams, ) -> Response> { self.respond(|this| { let Some(module) = this.module_for_uri(¶ms.text_document_position_params.text_document.uri) else { return Ok(None); }; match this.node_at_position(¶ms.text_document_position_params) { Some((_lines, Located::Expression { expression, .. })) => { Ok(signature_help::for_expression(expression, module)) } Some((_lines, _located)) => Ok(None), None => Ok(None), } }) } fn module_node_at_position( &self, params: &lsp::TextDocumentPositionParams, module: &'a Module, ) -> Option<(LineNumbers, Located<'a>)> { let line_numbers = LineNumbers::new(&module.code); let byte_index = line_numbers.byte_index(params.position); let node = module.find_node(byte_index); let node = node?; Some((line_numbers, node)) } fn node_at_position( &self, params: &lsp::TextDocumentPositionParams, ) -> Option<(LineNumbers, Located<'_>)> { let module = self.module_for_uri(¶ms.text_document.uri)?; self.module_node_at_position(params, module) } fn module_for_uri(&self, uri: &Url) -> Option<&Module> { // The to_file_path method is available on these platforms #[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))] let path = uri.to_file_path().expect("URL file"); #[cfg(not(any(unix, windows, target_os = "redox", target_os = "wasi")))] let path: Utf8PathBuf = uri.path().into(); let components = path .strip_prefix(self.paths.root()) .ok()? .components() .skip(1) .map(|c| c.as_os_str().to_string_lossy()); let module_name: EcoString = Itertools::intersperse(components, "/".into()) .collect::() .strip_suffix(".gleam")? .into(); self.compiler.modules.get(&module_name) } } fn import_folding_spans( imports: &[ast::Import], code: &str, line_numbers: &LineNumbers, ) -> Vec { let mut spans = vec![]; let mut imports = imports.iter(); let Some(first_import) = imports.next() else { return spans; }; let mut previous_line = src_span_to_lsp_range( SrcSpan::new(first_import.location.start, first_import.location.start), line_numbers, ) .start .line; let mut current_start = first_import.location.start; let mut current_end = first_import.location.end; let mut current_len = 1; for import in imports { let next_line = src_span_to_lsp_range( SrcSpan::new(import.location.start, import.location.start), line_numbers, ) .start .line; let separated_by_blank_line = next_line > previous_line + 1; let between = &code[current_end as usize..import.location.start as usize]; let has_non_whitespace_between = between.chars().any(|char| !char.is_whitespace()); if separated_by_blank_line || has_non_whitespace_between { if current_len > 1 { spans.push(SrcSpan::new(current_start, current_end)); } current_start = import.location.start; current_end = import.location.end; current_len = 1; } else { current_end = import.location.end; current_len += 1; } previous_line = next_line; } if current_len > 1 { spans.push(SrcSpan::new(current_start, current_end)); } spans } fn folding_range_for_span( span: SrcSpan, line_numbers: &LineNumbers, kind: Option, ) -> Option { let range = src_span_to_lsp_range(span, line_numbers); if range.start.line >= range.end.line { return None; } Some(FoldingRange { start_line: range.start.line, start_character: None, end_line: range.end.line, end_character: None, kind, collapsed_text: None, }) } fn custom_type_symbol( type_: &CustomType>, line_numbers: &LineNumbers, module: &Module, ) -> DocumentSymbol { let constructors = type_ .constructors .iter() .map(|constructor| { let mut arguments = vec![]; // List named arguments as field symbols. for argument in &constructor.arguments { let Some((label_location, label)) = &argument.label else { continue; }; let full_arg_span = match argument.doc { Some((doc_position, _)) => { SrcSpan::new(get_doc_marker_position(doc_position), argument.location.end) } None => argument.location, }; // The 'deprecated' field is deprecated, but we have to specify it anyway // to be able to construct the 'DocumentSymbol' type, so // we suppress the warning. We specify 'None' as specifying 'Some' // is what is actually deprecated. #[allow(deprecated)] arguments.push(DocumentSymbol { name: label.to_string(), detail: Some( Printer::new(&module.ast.names) .print_type(&argument.type_) .to_string(), ), kind: SymbolKind::FIELD, tags: None, deprecated: None, range: src_span_to_lsp_range(full_arg_span, line_numbers), selection_range: src_span_to_lsp_range(*label_location, line_numbers), children: None, }); } // Start from the documentation if available, otherwise from the constructor's name, // all the way to the end of its arguments. let full_constructor_span = SrcSpan { start: constructor .documentation .as_ref() .map(|(doc_start, _)| get_doc_marker_position(*doc_start)) .unwrap_or(constructor.location.start), end: constructor.location.end, }; // The 'deprecated' field is deprecated, but we have to specify it anyway // to be able to construct the 'DocumentSymbol' type, so // we suppress the warning. We specify 'None' as specifying 'Some' // is what is actually deprecated. #[allow(deprecated)] DocumentSymbol { name: constructor.name.to_string(), detail: None, kind: if constructor.arguments.is_empty() { SymbolKind::ENUM_MEMBER } else { SymbolKind::CONSTRUCTOR }, tags: make_deprecated_symbol_tag(&constructor.deprecation), deprecated: None, range: src_span_to_lsp_range(full_constructor_span, line_numbers), selection_range: src_span_to_lsp_range(constructor.name_location, line_numbers), children: if arguments.is_empty() { None } else { Some(arguments) }, } }) .collect_vec(); // The type's location, by default, ranges from "(pub) type" to the end of its name. // We need it to range to the end of its constructors instead for the full symbol range. // We also include documentation, if available, by LSP convention. let full_type_span = SrcSpan { start: type_ .documentation .as_ref() .map(|(doc_start, _)| get_doc_marker_position(*doc_start)) .unwrap_or(type_.location.start), end: type_.end_position, }; // The 'deprecated' field is deprecated, but we have to specify it anyway // to be able to construct the 'DocumentSymbol' type, so // we suppress the warning. We specify 'None' as specifying 'Some' // is what is actually deprecated. #[allow(deprecated)] DocumentSymbol { name: type_.name.to_string(), detail: None, kind: SymbolKind::CLASS, tags: make_deprecated_symbol_tag(&type_.deprecation), deprecated: None, range: src_span_to_lsp_range(full_type_span, line_numbers), selection_range: src_span_to_lsp_range(type_.name_location, line_numbers), children: if constructors.is_empty() { None } else { Some(constructors) }, } } fn hover_for_pattern(pattern: &TypedPattern, line_numbers: LineNumbers, module: &Module) -> Hover { let documentation = pattern.get_documentation().unwrap_or_default(); // Show the type of the hovered node to the user let type_ = Printer::new(&module.ast.names).print_type(pattern.type_().as_ref()); let contents = format!( "```gleam {type_} ``` {documentation}" ); Hover { contents: HoverContents::Scalar(MarkedString::String(contents)), range: Some(src_span_to_lsp_range(pattern.location(), &line_numbers)), } } fn get_function_type(fun: &TypedFunction) -> Type { Type::Fn { arguments: fun .arguments .iter() .map(|argument| argument.type_.clone()) .collect(), return_: fun.return_type.clone(), } } fn hover_for_function_head( fun: &TypedFunction, line_numbers: LineNumbers, module: &Module, ) -> Hover { let empty_str = EcoString::from(""); let documentation = fun .documentation .as_ref() .map(|(_, doc)| doc) .unwrap_or(&empty_str); let function_type = get_function_type(fun); let formatted_type = Printer::new(&module.ast.names).print_type(&function_type); let contents = format!( "```gleam {formatted_type} ``` {documentation}" ); Hover { contents: HoverContents::Scalar(MarkedString::String(contents)), range: Some(src_span_to_lsp_range(fun.location, &line_numbers)), } } fn hover_for_function_argument( argument: &TypedArg, line_numbers: LineNumbers, module: &Module, ) -> Hover { let type_ = Printer::new(&module.ast.names).print_type(&argument.type_); let contents = format!("```gleam\n{type_}\n```"); Hover { contents: HoverContents::Scalar(MarkedString::String(contents)), range: Some(src_span_to_lsp_range(argument.location, &line_numbers)), } } fn hover_for_annotation( location: SrcSpan, annotation_type: &Type, type_constructor: Option<&TypeConstructor>, line_numbers: LineNumbers, module: &Module, ) -> Hover { let empty_str = EcoString::from(""); let documentation = type_constructor .and_then(|constructor| constructor.documentation.as_ref()) .unwrap_or(&empty_str); // If a user is hovering an annotation, it's not very useful to show the // local representation of that type, since that's probably what they see // in the source code anyway. So here, we print the raw type, // which is probably more helpful. let type_ = Printer::new(&module.ast.names).print_type_without_aliases(annotation_type); let contents = format!( "```gleam {type_} ``` {documentation}" ); Hover { contents: HoverContents::Scalar(MarkedString::String(contents)), range: Some(src_span_to_lsp_range(location, &line_numbers)), } } fn hover_for_label( location: SrcSpan, type_: Arc, line_numbers: LineNumbers, module: &Module, ) -> Hover { let type_ = Printer::new(&module.ast.names).print_type(&type_); let contents = format!("```gleam\n{type_}\n```"); Hover { contents: HoverContents::Scalar(MarkedString::String(contents)), range: Some(src_span_to_lsp_range(location, &line_numbers)), } } fn hover_for_module_constant( constant: &ModuleConstant, EcoString>, line_numbers: LineNumbers, module: &Module, ) -> Hover { let empty_str = EcoString::from(""); let type_ = Printer::new(&module.ast.names).print_type(&constant.type_); let documentation = constant .documentation .as_ref() .map(|(_, doc)| doc) .unwrap_or(&empty_str); let contents = format!("```gleam\n{type_}\n```\n{documentation}"); Hover { contents: HoverContents::Scalar(MarkedString::String(contents)), range: Some(src_span_to_lsp_range(constant.location, &line_numbers)), } } fn hover_for_constant( constant: &TypedConstant, line_numbers: LineNumbers, module: &Module, ) -> Hover { let type_ = Printer::new(&module.ast.names).print_type(&constant.type_()); let contents = format!("```gleam\n{type_}\n```"); Hover { contents: HoverContents::Scalar(MarkedString::String(contents)), range: Some(src_span_to_lsp_range(constant.location(), &line_numbers)), } } fn hover_for_clause_guard( guard: &TypedClauseGuard, line_numbers: LineNumbers, module: &Module, ) -> Hover { let type_ = Printer::new(&module.ast.names).print_type(&guard.type_()); let contents = format!("```gleam\n{type_}\n```"); Hover { contents: HoverContents::Scalar(MarkedString::String(contents)), range: Some(src_span_to_lsp_range(guard.location(), &line_numbers)), } } fn hover_for_string_prefix_pattern_variable( location: SrcSpan, lines: &LineNumbers, module: &Module, ) -> Hover { let type_ = Printer::new(&module.ast.names).print_type(&type_::string()); let contents = format!("```gleam\n{type_}\n```"); Hover { contents: HoverContents::Scalar(MarkedString::String(contents)), range: Some(src_span_to_lsp_range(location, lines)), } } fn hover_for_expression( expression: &TypedExpr, line_numbers: LineNumbers, module: &Module, hex_deps: &HashSet, ) -> Hover { let documentation = expression.get_documentation().unwrap_or_default(); let link_section = get_expr_qualified_name(expression) .and_then(|(module_name, name)| { get_hexdocs_link_section(module_name, name, &module.ast, hex_deps) }) .unwrap_or("".to_string()); // Show the type of the hovered node to the user let type_ = Printer::new(&module.ast.names).print_type(expression.type_().as_ref()); let contents = format!( "```gleam {type_} ``` {documentation}{link_section}" ); Hover { contents: HoverContents::Scalar(MarkedString::String(contents)), range: Some(src_span_to_lsp_range(expression.location(), &line_numbers)), } } fn hover_for_imported_value( value: &ValueConstructor, location: &SrcSpan, line_numbers: LineNumbers, hex_module_imported_from: Option<&ModuleInterface>, name: &EcoString, module: &Module, ) -> Hover { let documentation = value.get_documentation().unwrap_or_default(); let link_section = hex_module_imported_from.map_or("".to_string(), |m| { format_hexdocs_link_section(m.package.as_str(), m.name.as_str(), Some(name)) }); // Show the type of the hovered node to the user let type_ = Printer::new(&module.ast.names).print_type(value.type_.as_ref()); let contents = format!( "```gleam {type_} ``` {documentation}{link_section}" ); Hover { contents: HoverContents::Scalar(MarkedString::String(contents)), range: Some(src_span_to_lsp_range(*location, &line_numbers)), } } fn hover_for_module( module: &ModuleInterface, location: SrcSpan, line_numbers: &LineNumbers, hex_deps: &HashSet, ) -> Hover { let documentation = module.documentation.join("\n"); let name = &module.name; let link_section = if hex_deps.contains(&module.package) { format_hexdocs_link_section(&module.package, name, None) } else { String::new() }; let contents = format!( "```gleam {name} ``` {documentation} {link_section}", ); Hover { contents: HoverContents::Scalar(MarkedString::String(contents)), range: Some(src_span_to_lsp_range(location, line_numbers)), } } fn hover_for_custom_type(type_: &CustomType>, line_numbers: LineNumbers) -> Hover { let name = &type_.name; let documentation = type_ .documentation .as_ref() .map(|(_, documentation)| documentation.clone()) .unwrap_or_default(); let contents = format!("```gleam\n{name}\n```\n{documentation}"); Hover { contents: HoverContents::Scalar(MarkedString::String(contents)), range: Some(src_span_to_lsp_range(type_.full_location(), &line_numbers)), } } fn hover_for_constructor( constructor: &TypedRecordConstructor, line_numbers: LineNumbers, module: &Module, ) -> Hover { let mut printer = Printer::new(&module.ast.names); let arguments = constructor .arguments .iter() .map(|argument| match &argument.label { Some((_, label)) => eco_format!("{label}: {}", printer.print_type(&argument.type_)), None => printer.print_type(&argument.type_), }) .join(", "); let documentation = constructor .documentation .as_ref() .map(|(_, documentation)| documentation.clone()) .unwrap_or_default(); let constructor_doc = if arguments.is_empty() { constructor.name.clone() } else { eco_format!("{}({arguments})", constructor.name) }; let contents = format!("```gleam\n{constructor_doc}\n```\n{documentation}"); Hover { contents: HoverContents::Scalar(MarkedString::String(contents)), range: Some(src_span_to_lsp_range(constructor.location, &line_numbers)), } } // Returns true if any part of either range overlaps with the other. pub fn overlaps(a: Range, b: Range) -> bool { position_within(a.start, b) || position_within(a.end, b) || position_within(b.start, a) || position_within(b.end, a) } // Returns true if a range is contained within another. pub fn within(a: Range, b: Range) -> bool { position_within(a.start, b) && position_within(a.end, b) } // Returns true if a position is within a range. fn position_within(position: Position, range: Range) -> bool { position >= range.start && position <= range.end } /// Builds the code action to assign an unused value to `_`. /// fn code_action_unused_values( module: &Module, line_numbers: &LineNumbers, params: &lsp::CodeActionParams, actions: &mut Vec, ) { let uri = ¶ms.text_document.uri; let mut unused_values: Vec<&SrcSpan> = module .ast .type_info .warnings .iter() .filter_map(|warning| { if let type_::Warning::ImplicitlyDiscardedResult { location } = warning { Some(location) } else { None } }) .collect(); if unused_values.is_empty() { return; } // Sort spans by start position, with longer spans coming first unused_values.sort_by_key(|span| (span.start, -(span.len() as i64))); let mut processed_lsp_range = Vec::new(); for unused in unused_values { let SrcSpan { start, end } = *unused; let hover_range = src_span_to_lsp_range(SrcSpan::new(start, end), line_numbers); // Check if this span is contained within any previously processed span if processed_lsp_range .iter() .any(|&prev_lsp_range| within(hover_range, prev_lsp_range)) { continue; } // Check if the cursor is within this span if !within(params.range, hover_range) { continue; } let edit = TextEdit { range: src_span_to_lsp_range(SrcSpan::new(start, start), line_numbers), new_text: "let _ = ".into(), }; CodeActionBuilder::new("Assign unused Result value to `_`") .kind(lsp_types::CodeActionKind::QUICKFIX) .changes(uri.clone(), vec![edit]) .preferred(true) .push_to(actions); processed_lsp_range.push(hover_range); } } struct NameCorrection { pub location: SrcSpan, pub correction: EcoString, } fn code_action_fix_names( line_numbers: &LineNumbers, params: &lsp::CodeActionParams, error: &Option, actions: &mut Vec, ) { let uri = ¶ms.text_document.uri; let Some(Error::Type { errors, .. }) = error else { return; }; let name_corrections = errors .iter() .filter_map(|error| { if let type_::Error::BadName { location, name, kind, } = error { Some(NameCorrection { correction: correct_name_case(name, *kind), location: *location, }) } else { None } }) .collect_vec(); if name_corrections.is_empty() { return; } for name_correction in name_corrections { let NameCorrection { location, correction, } = name_correction; let range = src_span_to_lsp_range(location, line_numbers); // Check if the user's cursor is on the invalid name if overlaps(params.range, range) { let edit = TextEdit { range, new_text: correction.to_string(), }; CodeActionBuilder::new(&format!("Rename to {correction}")) .kind(lsp_types::CodeActionKind::QUICKFIX) .changes(uri.clone(), vec![edit]) .preferred(true) .push_to(actions); } } } fn get_expr_qualified_name(expression: &TypedExpr) -> Option<(&EcoString, &EcoString)> { match expression { TypedExpr::Var { name, constructor, .. } if constructor.publicity.is_importable() => match &constructor.variant { ValueConstructorVariant::ModuleFn { module: module_name, .. } => Some((module_name, name)), ValueConstructorVariant::ModuleConstant { module: module_name, .. } => Some((module_name, name)), ValueConstructorVariant::LocalVariable { .. } | ValueConstructorVariant::Record { .. } => None, }, TypedExpr::ModuleSelect { label, module_name, .. } => Some((module_name, label)), TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Var { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } | TypedExpr::Invalid { .. } => None, } } fn format_hexdocs_link_section( package_name: &str, module_name: &str, name: Option<&str>, ) -> String { let link = match name { Some(name) => format!("https://hexdocs.pm/{package_name}/{module_name}.html#{name}"), None => format!("https://hexdocs.pm/{package_name}/{module_name}.html"), }; format!("\nView on [HexDocs]({link})") } fn get_hexdocs_link_section( module_name: &str, name: &str, ast: &TypedModule, hex_deps: &HashSet, ) -> Option { let package_name = ast.definitions.imports.iter().find_map(|import| { if import.module == module_name && hex_deps.contains(&import.package) { Some(&import.package) } else { None } })?; Some(format_hexdocs_link_section( package_name, module_name, Some(name), )) } /// Converts the source start position of a documentation comment's contents into /// the position of the leading slash in its marker ('///'). fn get_doc_marker_position(content_pos: u32) -> u32 { content_pos.saturating_sub(3) } fn make_deprecated_symbol_tag(deprecation: &Deprecation) -> Option> { deprecation .is_deprecated() .then(|| vec![SymbolTag::DEPRECATED]) } ================================================ FILE: language-server/src/feedback.rs ================================================ use gleam_core::{Error, Warning, diagnostic::Diagnostic}; use std::collections::{HashMap, HashSet}; use camino::Utf8PathBuf; use super::engine::Compilation; #[derive(Debug, Default, PartialEq, Eq)] pub struct Feedback { pub diagnostics: HashMap>, pub messages: Vec, } impl Feedback { /// Set the diagnostics for a file to an empty vector. This will overwrite /// any existing diagnostics on the client. pub fn unset_existing_diagnostics(&mut self, path: Utf8PathBuf) { _ = self.diagnostics.insert(path, vec![]); } pub fn append_diagnostic(&mut self, path: Utf8PathBuf, diagnostic: Diagnostic) { self.diagnostics.entry(path).or_default().push(diagnostic); } /// No feedback at all. /// pub fn none() -> Feedback { Default::default() } /// Add all the content of another feedback to this feedback. /// pub fn append_feedback(&mut self, feedback: Feedback) { for (path, diagnostics) in feedback.diagnostics { // Any new diagnostics for a file will overwrite any existing ones. _ = self.diagnostics.insert(path, diagnostics); } for diagnostic in feedback.messages { self.append_message(diagnostic); } } fn append_message(&mut self, diagnostic: Diagnostic) { self.messages.push(diagnostic); } } /// When an operation succeeds or fails we want to send diagnostics and /// messages to the client for displaying to the user. This object converts /// Gleam warnings, errors, etc to these feedback items. /// /// Gleam has incremental compilation so we cannot erase all previous /// diagnostics and replace each time new diagnostics are available; if a file /// has not been recompiled then any diagnostics it had previously are still /// valid and must not be erased. /// To do this we keep track of which files have diagnostics and only overwrite /// them if the file has been recompiled. /// #[derive(Debug, Default)] pub struct FeedbackBookKeeper { files_with_warnings: HashSet, files_with_errors: HashSet, } impl FeedbackBookKeeper { /// Send diagnostics for any warnings and remove any diagnostics for files /// that have compiled without warnings. /// pub fn response(&mut self, compilation: Compilation, warnings: Vec) -> Feedback { let mut feedback = Feedback::default(); if let Compilation::Yes(compiled_modules) = compilation { // Any existing diagnostics for files that have been compiled are no // longer valid so we set an empty vector of diagnostics for the files // to erase their diagnostics. for path in compiled_modules { let has_existing_diagnostics = self.files_with_warnings.remove(&path); if has_existing_diagnostics { feedback.unset_existing_diagnostics(path); } } // Compilation was attempted and there is no error (which there is not // in this function) then it means that compilation has succeeded, so // there should be no error diagnostics. // We don't limit this to files that have been compiled as a previous // cached version could be used instead of a recompile. self.unset_errors(&mut feedback); } for warning in warnings { self.insert_warning(&mut feedback, warning); } feedback } fn unset_errors(&mut self, feedback: &mut Feedback) { // TODO: avoid clobbering warnings. They should be preserved rather than // removed with the errors here. We will need to store the warnings and // re-send them. for path in self.files_with_errors.drain() { feedback.unset_existing_diagnostics(path); } } /// Compilation failed, boo! /// /// Send diagnostics for any warnings and remove any diagnostics for files /// that have compiled without warnings, AND ALSO send diagnostics for the /// error that caused compilation to fail. /// pub fn build_with_error( &mut self, error: Error, compilation: Compilation, warnings: Vec, ) -> Feedback { let diagnostics = error.to_diagnostics(); let mut feedback = self.response(compilation, warnings); // A new error means that any existing errors are no longer valid. Unset them. self.unset_errors(&mut feedback); for diagnostic in diagnostics { match diagnostic.location.as_ref().map(|l| l.path.clone()) { Some(path) => { _ = self.files_with_errors.insert(path.clone()); feedback.append_diagnostic(path, diagnostic); } None => { feedback.append_message(diagnostic); } } } feedback } pub fn error(&mut self, error: Error) -> Feedback { self.build_with_error(error, Compilation::No, vec![]) } fn insert_warning(&mut self, feedback: &mut Feedback, warning: Warning) { let diagnostic = warning.to_diagnostic(); if let Some(path) = diagnostic.location.as_ref().map(|l| l.path.clone()) { _ = self.files_with_warnings.insert(path.clone()); feedback.append_diagnostic(path, diagnostic); } } } #[cfg(test)] mod tests { use std::assert_eq; use super::*; use gleam_core::{ ast::SrcSpan, diagnostic::Level, parse::error::{ParseError, ParseErrorType}, type_, }; #[test] fn feedback() { let mut book_keeper = FeedbackBookKeeper::default(); let file1 = Utf8PathBuf::from("src/file1.gleam"); let file2 = Utf8PathBuf::from("src/file2.gleam"); let file3 = Utf8PathBuf::from("src/file3.gleam"); let warning1 = Warning::Type { path: file1.clone(), src: "src".into(), warning: type_::Warning::NoFieldsRecordUpdate { location: SrcSpan::new(1, 2), }, }; let warning2 = Warning::Type { path: file2.clone(), src: "src".into(), warning: type_::Warning::NoFieldsRecordUpdate { location: SrcSpan::new(1, 2), }, }; let feedback = book_keeper.response( Compilation::Yes(vec![file1.clone()]), vec![warning1.clone(), warning1.clone(), warning2.clone()], ); assert_eq!( Feedback { diagnostics: HashMap::from([ ( file1.clone(), vec![warning1.to_diagnostic(), warning1.to_diagnostic(),] ), (file2.clone(), vec![warning2.to_diagnostic(),]) ]), messages: vec![], }, feedback ); let feedback = book_keeper.response( Compilation::Yes(vec![file1.clone(), file2.clone(), file3]), vec![], ); assert_eq!( Feedback { diagnostics: HashMap::from([ // File 1 and 2 had diagnostics before so they have been unset (file1, vec![]), (file2, vec![]), // File 3 had no diagnostics so does not need to to be unset ]), messages: vec![], }, feedback ); } #[test] fn locationless_error() { // The failed method sets an additional messages for errors without a // location. let mut book_keeper = FeedbackBookKeeper::default(); let file1 = Utf8PathBuf::from("src/file1.gleam"); let warning1 = Warning::Type { path: file1.clone(), src: "src".into(), warning: type_::Warning::NoFieldsRecordUpdate { location: SrcSpan::new(1, 2), }, }; let locationless_error = Error::Gzip("Hello!".into()); let feedback = book_keeper.build_with_error( locationless_error.clone(), Compilation::Yes(vec![]), vec![warning1.clone()], ); assert_eq!( Feedback { diagnostics: HashMap::from([(file1, vec![warning1.to_diagnostic()])]), messages: locationless_error.to_diagnostics(), }, feedback ); } #[test] fn error() { // The failed method sets an additional diagnostic if the error has a // location. let mut book_keeper = FeedbackBookKeeper::default(); let file1 = Utf8PathBuf::from("src/file1.gleam"); let file3 = Utf8PathBuf::from("src/file2.gleam"); let warning1 = Warning::Type { path: file1.clone(), src: "src".into(), warning: type_::Warning::NoFieldsRecordUpdate { location: SrcSpan::new(1, 2), }, }; let error = Error::Parse { path: file3.clone(), src: "blah".into(), error: Box::new(ParseError { error: ParseErrorType::ConcatPatternVariableLeftHandSide, location: SrcSpan::new(1, 4), }), }; let feedback = book_keeper.build_with_error( error.clone(), Compilation::Yes(vec![]), vec![warning1.clone()], ); assert_eq!( Feedback { diagnostics: HashMap::from([ (file1, vec![warning1.to_diagnostic()]), (file3.clone(), error.to_diagnostics()), ]), messages: vec![], }, feedback ); // The error diagnostic should be removed if the file compiles later. let feedback = book_keeper.response(Compilation::Yes(vec![file3.clone()]), vec![]); assert_eq!( Feedback { diagnostics: HashMap::from([(file3, vec![])]), messages: vec![], }, feedback ); } // https://github.com/gleam-lang/gleam/issues/2093 #[test] fn successful_compilation_removes_error_diagnostic() { // It is possible for a compile error to be fixed but the module that // had the error to not actually be recompiled. // // 1. File is OK // 2. File is edited to an invalid state // 3. A compile error is emitted // 4. File is edited back to the earlier valid state // 5. File is not recompiled as the cache from step 1 is still valid // // Because of this the compiled files iterator does not contain the // file, so we need to make sure that the error is removed through other // means, such as tracking which files have errors and removing them all // when a successful compilation occurs. let mut book_keeper = FeedbackBookKeeper::default(); let file1 = Utf8PathBuf::from("src/file1.gleam"); let file2 = Utf8PathBuf::from("src/file2.gleam"); let error = Error::Parse { path: file1.clone(), src: "blah".into(), error: Box::new(ParseError { error: ParseErrorType::ConcatPatternVariableLeftHandSide, location: SrcSpan::new(1, 4), }), }; let feedback = book_keeper.build_with_error(error.clone(), Compilation::Yes(vec![]), vec![]); assert_eq!( Feedback { diagnostics: HashMap::from([(file1.clone(), error.to_diagnostics())]), messages: vec![], }, feedback ); // The error diagnostic should be removed on a successful compilation, // even though the file is not in the compiled files iterator. let feedback = book_keeper.response(Compilation::Yes(vec![file2]), vec![]); assert_eq!( Feedback { diagnostics: HashMap::from([(file1, vec![])]), messages: vec![], }, feedback ); } // https://github.com/gleam-lang/gleam/issues/2122 #[test] fn second_failure_unsets_previous_error() { let mut book_keeper = FeedbackBookKeeper::default(); let file1 = Utf8PathBuf::from("src/file1.gleam"); let file2 = Utf8PathBuf::from("src/file2.gleam"); let error = |file: &camino::Utf8Path| Error::Parse { path: file.to_path_buf(), src: "blah".into(), error: Box::new(ParseError { error: ParseErrorType::ConcatPatternVariableLeftHandSide, location: SrcSpan::new(1, 4), }), }; let feedback = book_keeper.build_with_error(error(&file1), Compilation::Yes(vec![]), vec![]); assert_eq!( Feedback { diagnostics: HashMap::from([(file1.clone(), error(&file1).to_diagnostics())]), messages: vec![], }, feedback ); let feedback = book_keeper.build_with_error(error(&file2), Compilation::Yes(vec![]), vec![]); assert_eq!( Feedback { diagnostics: HashMap::from([ // Unset the previous error (file1, vec![]), // Set the new one (file2.clone(), error(&file2).to_diagnostics()), ]), messages: vec![], }, feedback ); } // https://github.com/gleam-lang/gleam/issues/2105 #[test] fn successful_non_compilation_does_not_remove_error_diagnostic() { let mut book_keeper = FeedbackBookKeeper::default(); let file1 = Utf8PathBuf::from("src/file1.gleam"); let error = Error::Parse { path: file1.clone(), src: "blah".into(), error: Box::new(ParseError { error: ParseErrorType::ConcatPatternVariableLeftHandSide, location: SrcSpan::new(1, 4), }), }; let feedback = book_keeper.build_with_error(error.clone(), Compilation::Yes(vec![]), vec![]); assert_eq!( Feedback { diagnostics: HashMap::from([(file1, error.to_diagnostics())]), messages: vec![], }, feedback ); // The error diagnostic should not be removed, nothing has been // successfully compiled. let feedback = book_keeper.response(Compilation::No, vec![]); assert_eq!( Feedback { diagnostics: HashMap::new(), messages: vec![], }, feedback ); } #[test] fn append_feedback_new_file() { let mut feedback = Feedback { diagnostics: HashMap::from([( Utf8PathBuf::from("src/file1.gleam"), vec![Diagnostic { location: None, hint: None, text: "Error 1".to_string(), title: "Error 1".to_string(), level: Level::Error, }], )]), messages: vec![Diagnostic { location: None, hint: None, text: "Error 2".to_string(), title: "Error 2".to_string(), level: Level::Error, }], }; feedback.append_feedback(Feedback { diagnostics: HashMap::from([( Utf8PathBuf::from("src/file2.gleam"), vec![Diagnostic { location: None, hint: None, text: "Error 3".to_string(), title: "Error 3".to_string(), level: Level::Error, }], )]), messages: vec![], }); assert_eq!( feedback, Feedback { diagnostics: HashMap::from([ ( Utf8PathBuf::from("src/file1.gleam"), vec![Diagnostic { location: None, hint: None, text: "Error 1".to_string(), title: "Error 1".to_string(), level: Level::Error, }], ), ( Utf8PathBuf::from("src/file2.gleam"), vec![Diagnostic { location: None, hint: None, text: "Error 3".to_string(), title: "Error 3".to_string(), level: Level::Error, }], ), ]), messages: vec![Diagnostic { location: None, hint: None, text: "Error 2".to_string(), title: "Error 2".to_string(), level: Level::Error, },], } ); } #[test] fn append_feedback_same_file() { let mut feedback = Feedback { diagnostics: HashMap::from([( Utf8PathBuf::from("src/file1.gleam"), vec![Diagnostic { location: None, hint: None, text: "Error 1".to_string(), title: "Error 1".to_string(), level: Level::Error, }], )]), messages: vec![Diagnostic { location: None, hint: None, text: "Error 2".to_string(), title: "Error 2".to_string(), level: Level::Error, }], }; feedback.append_feedback(Feedback { diagnostics: HashMap::from([( Utf8PathBuf::from("src/file1.gleam"), vec![Diagnostic { location: None, hint: None, text: "Error 3".to_string(), title: "Error 3".to_string(), level: Level::Error, }], )]), messages: vec![], }); assert_eq!( feedback, Feedback { diagnostics: HashMap::from([( Utf8PathBuf::from("src/file1.gleam"), vec![Diagnostic { location: None, hint: None, text: "Error 3".to_string(), title: "Error 3".to_string(), level: Level::Error, }], ),]), messages: vec![Diagnostic { location: None, hint: None, text: "Error 2".to_string(), title: "Error 2".to_string(), level: Level::Error, },], } ); } #[test] fn append_feedback_new_message() { let mut feedback = Feedback { diagnostics: HashMap::from([( Utf8PathBuf::from("src/file1.gleam"), vec![Diagnostic { location: None, hint: None, text: "Error 1".to_string(), title: "Error 1".to_string(), level: Level::Error, }], )]), messages: vec![Diagnostic { location: None, hint: None, text: "Error 2".to_string(), title: "Error 2".to_string(), level: Level::Error, }], }; feedback.append_feedback(Feedback { diagnostics: HashMap::from([]), messages: vec![Diagnostic { location: None, hint: None, text: "Error 3".to_string(), title: "Error 3".to_string(), level: Level::Error, }], }); assert_eq!( feedback, Feedback { diagnostics: HashMap::from([( Utf8PathBuf::from("src/file1.gleam"), vec![Diagnostic { location: None, hint: None, text: "Error 1".to_string(), title: "Error 1".to_string(), level: Level::Error, },], ),]), messages: vec![ Diagnostic { location: None, hint: None, text: "Error 2".to_string(), title: "Error 2".to_string(), level: Level::Error, }, Diagnostic { location: None, hint: None, text: "Error 3".to_string(), title: "Error 3".to_string(), level: Level::Error, } ], } ); } #[test] fn append_feedback_new_file_blank() { let mut feedback = Feedback { diagnostics: HashMap::from([( Utf8PathBuf::from("src/file1.gleam"), vec![Diagnostic { location: None, hint: None, text: "Error 1".to_string(), title: "Error 1".to_string(), level: Level::Error, }], )]), messages: vec![Diagnostic { location: None, hint: None, text: "Error 2".to_string(), title: "Error 2".to_string(), level: Level::Error, }], }; feedback.append_feedback(Feedback { diagnostics: HashMap::from([(Utf8PathBuf::from("src/file2.gleam"), vec![])]), messages: vec![], }); assert_eq!( feedback, Feedback { diagnostics: HashMap::from([ ( Utf8PathBuf::from("src/file1.gleam"), vec![Diagnostic { location: None, hint: None, text: "Error 1".to_string(), title: "Error 1".to_string(), level: Level::Error, },], ), (Utf8PathBuf::from("src/file2.gleam"), vec![],), ]), messages: vec![Diagnostic { location: None, hint: None, text: "Error 2".to_string(), title: "Error 2".to_string(), level: Level::Error, },], } ); } #[test] fn append_feedback_existing_file_blank() { let mut feedback = Feedback { diagnostics: HashMap::from([( Utf8PathBuf::from("src/file1.gleam"), vec![Diagnostic { location: None, hint: None, text: "Error 1".to_string(), title: "Error 1".to_string(), level: Level::Error, }], )]), messages: vec![Diagnostic { location: None, hint: None, text: "Error 2".to_string(), title: "Error 2".to_string(), level: Level::Error, }], }; feedback.append_feedback(Feedback { diagnostics: HashMap::from([(Utf8PathBuf::from("src/file1.gleam"), vec![])]), messages: vec![], }); assert_eq!( feedback, Feedback { diagnostics: HashMap::from([(Utf8PathBuf::from("src/file1.gleam"), vec![],),]), messages: vec![Diagnostic { location: None, hint: None, text: "Error 2".to_string(), title: "Error 2".to_string(), level: Level::Error, },], } ); } } ================================================ FILE: language-server/src/files.rs ================================================ use std::collections::HashSet; use std::time::SystemTime; use debug_ignore::DebugIgnore; use gleam_core::{ Result, error::Error, io::{ BeamCompiler, Command, CommandExecutor, FileSystemReader, FileSystemWriter, ReadDir, Stdio, WrappedReader, memory::InMemoryFileSystem, }, }; use camino::{Utf8Path, Utf8PathBuf}; // A proxy intended for `LanguageServer` to use when files are modified in // memory but not yet saved to disc by the client. // // Uses the `IO` for writing directly to disk, or `InMemoryFileSystem` to // cache files that were not yet saved. Reading files will always first try the // `InMemoryFileSystem` first and fallback to use the `ProjectIO` if the file // was not found in the cache. // #[derive(Debug, Clone)] pub struct FileSystemProxy { io: DebugIgnore, edit_cache: InMemoryFileSystem, } impl FileSystemProxy where IO: FileSystemWriter + FileSystemReader + CommandExecutor, { pub fn new(io: IO) -> Self { Self { io: io.into(), edit_cache: InMemoryFileSystem::new(), } } pub fn inner(&self) -> &IO { &self.io } pub fn write_mem_cache(&mut self, path: &Utf8Path, content: &str) -> Result<()> { let write_result = self.edit_cache.write(path, content); self.edit_cache .try_set_modification_time(path, SystemTime::now())?; write_result } pub fn delete_mem_cache(&self, path: &Utf8Path) -> Result<()> { if self.edit_cache.is_directory(path) { self.edit_cache.delete_directory(path) } else { self.edit_cache.delete_file(path) } } } // All write operations goes to disk (for mem-cache use the dedicated `_mem_cache` methods) impl FileSystemWriter for FileSystemProxy where IO: FileSystemWriter, { fn mkdir(&self, path: &Utf8Path) -> Result<()> { self.io.mkdir(path) } fn write(&self, path: &Utf8Path, content: &str) -> Result<()> { self.io.write(path, content) } fn write_bytes(&self, path: &Utf8Path, content: &[u8]) -> Result<()> { self.io.write_bytes(path, content) } fn delete_directory(&self, path: &Utf8Path) -> Result<()> { self.io.delete_directory(path) } fn copy(&self, from: &Utf8Path, to: &Utf8Path) -> Result<()> { self.io.copy(from, to) } fn copy_dir(&self, from: &Utf8Path, to: &Utf8Path) -> Result<()> { self.io.copy_dir(from, to) } fn hardlink(&self, from: &Utf8Path, to: &Utf8Path) -> Result<()> { self.io.hardlink(from, to) } fn symlink_dir(&self, from: &Utf8Path, to: &Utf8Path) -> Result<()> { self.io.symlink_dir(from, to) } fn delete_file(&self, path: &Utf8Path) -> Result<()> { self.io.delete_file(path) } fn exists(&self, path: &Utf8Path) -> bool { self.io.exists(path) } } impl FileSystemReader for FileSystemProxy where IO: FileSystemReader, { fn read_dir(&self, path: &Utf8Path) -> Result { self.io.read_dir(path) } fn read(&self, path: &Utf8Path) -> Result { match self.edit_cache.read(path) { result @ Ok(_) => result, Err(_) => self.io.read(path), } } fn read_bytes(&self, path: &Utf8Path) -> Result> { match self.edit_cache.read_bytes(path) { result @ Ok(_) => result, Err(_) => self.io.read_bytes(path), } } fn reader(&self, path: &Utf8Path) -> Result { self.io.reader(path) } // Cache overrides existence of file fn is_file(&self, path: &Utf8Path) -> bool { self.edit_cache.is_file(path) || self.io.is_file(path) } // Cache overrides existence of directory fn is_directory(&self, path: &Utf8Path) -> bool { self.edit_cache.is_directory(path) || self.io.is_directory(path) } fn modification_time(&self, path: &Utf8Path) -> Result { match self.edit_cache.modification_time(path) { result @ Ok(_) => result, Err(_) => self.io.modification_time(path), } } fn canonicalise(&self, path: &Utf8Path) -> Result { self.io.canonicalise(path) } } impl CommandExecutor for FileSystemProxy where IO: CommandExecutor, { fn exec(&self, _command: Command) -> Result { panic!("The language server is not permitted to create subprocesses") } } impl BeamCompiler for FileSystemProxy where IO: BeamCompiler, { fn compile_beam( &self, _out: &Utf8Path, _lib: &Utf8Path, _modules: &HashSet, _stdio: Stdio, ) -> Result, Error> { panic!("The language server is not permitted to create subprocesses") } } ================================================ FILE: language-server/src/lib.rs ================================================ #![warn( clippy::all, clippy::dbg_macro, clippy::todo, clippy::mem_forget, clippy::filter_map_next, clippy::needless_continue, clippy::needless_borrow, clippy::match_wildcard_for_single_variants, clippy::imprecise_flops, clippy::suboptimal_flops, clippy::lossy_float_literal, clippy::rest_pat_in_fully_bound_structs, clippy::fn_params_excessive_bools, clippy::inefficient_to_string, clippy::linkedlist, clippy::macro_use_imports, clippy::option_option, clippy::verbose_file_reads, clippy::unnested_or_patterns, rust_2018_idioms, missing_debug_implementations, missing_copy_implementations, trivial_casts, trivial_numeric_casts, nonstandard_style, unexpected_cfgs, unused_import_braces, unused_qualifications )] #![deny( clippy::await_holding_lock, clippy::disallowed_methods, clippy::if_let_mutex, clippy::indexing_slicing, clippy::mem_forget, clippy::ok_expect, clippy::unimplemented, clippy::unwrap_used, unsafe_code, unstable_features, unused_results )] #![allow( clippy::assign_op_pattern, clippy::to_string_trait_impl, clippy::match_single_binding, clippy::match_like_matches_macro, clippy::inconsistent_struct_constructor, clippy::len_without_is_empty )] mod code_action; mod compiler; mod completer; mod edits; mod engine; mod feedback; mod files; mod messages; mod progress; mod reference; mod rename; mod router; mod server; mod signature_help; #[cfg(test)] mod tests; pub use server::LanguageServer; use camino::Utf8PathBuf; use gleam_core::{ Result, ast::SrcSpan, build::Target, line_numbers::LineNumbers, manifest::Manifest, paths::ProjectPaths, }; use lsp_types::{Position, Range, TextEdit, Url}; use std::any::Any; #[derive(Debug)] pub struct LockGuard(pub Box); pub trait Locker { fn lock_for_build(&self) -> Result; } pub trait MakeLocker { fn make_locker(&self, paths: &ProjectPaths, target: Target) -> Result>; } pub trait DownloadDependencies { fn download_dependencies(&self, paths: &ProjectPaths) -> Result; } pub fn src_span_to_lsp_range(location: SrcSpan, line_numbers: &LineNumbers) -> Range { let start = line_numbers.line_and_column_number(location.start); let end = line_numbers.line_and_column_number(location.end); Range::new( Position::new(start.line - 1, start.column - 1), Position::new(end.line - 1, end.column - 1), ) } pub fn lsp_range_to_src_span(range: Range, line_numbers: &LineNumbers) -> SrcSpan { let start = line_numbers.byte_index(range.start); let end = line_numbers.byte_index(range.end); SrcSpan { start, end } } /// A little wrapper around LineNumbers to make it easier to build text edits. /// #[derive(Debug)] pub struct TextEdits<'a> { line_numbers: &'a LineNumbers, edits: Vec, } impl<'a> TextEdits<'a> { pub fn new(line_numbers: &'a LineNumbers) -> Self { TextEdits { line_numbers, edits: vec![], } } pub fn src_span_to_lsp_range(&self, location: SrcSpan) -> Range { src_span_to_lsp_range(location, self.line_numbers) } pub fn lsp_range_to_src_span(&self, range: Range) -> SrcSpan { lsp_range_to_src_span(range, self.line_numbers) } pub fn replace(&mut self, location: SrcSpan, new_text: String) { self.edits.push(TextEdit { range: src_span_to_lsp_range(location, self.line_numbers), new_text, }) } pub fn insert(&mut self, at: u32, new_text: String) { self.replace(SrcSpan { start: at, end: at }, new_text) } pub fn delete(&mut self, location: SrcSpan) { self.replace(location, "".to_string()) } fn delete_range(&mut self, range: Range) { self.edits.push(TextEdit { range, new_text: "".into(), }) } } fn path(uri: &Url) -> Utf8PathBuf { // The to_file_path method is available on these platforms #[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))] return Utf8PathBuf::from_path_buf(uri.to_file_path().expect("URL file")) .expect("Non Utf8 Path"); #[cfg(not(any(unix, windows, target_os = "redox", target_os = "wasi")))] return Utf8PathBuf::from_path_buf(uri.path().into()).expect("Non Utf8 Path"); } fn url_from_path(path: &str) -> Option { // The targets for which `from_file_path` is defined #[cfg(any( unix, windows, target_os = "redox", target_os = "wasi", target_os = "hermit" ))] let uri = Url::from_file_path(path).ok(); #[cfg(not(any( unix, windows, target_os = "redox", target_os = "wasi", target_os = "hermit" )))] let uri = Url::parse(&format!("file://{path}")).ok(); uri } ================================================ FILE: language-server/src/messages.rs ================================================ use camino::Utf8PathBuf; use lsp::{ notification::{DidChangeWatchedFiles, DidOpenTextDocument}, request::GotoDefinition, }; use lsp_types::{ self as lsp, notification::{DidChangeTextDocument, DidCloseTextDocument, DidSaveTextDocument}, request::{ CodeActionRequest, Completion, DocumentSymbolRequest, FoldingRangeRequest, Formatting, GotoTypeDefinition, HoverRequest, PrepareRenameRequest, References, Rename, SignatureHelpRequest, }, }; use std::time::Duration; #[derive(Debug)] pub enum Message { Request(lsp_server::RequestId, Request), Notification(Notification), } #[derive(Debug)] pub enum Request { Format(lsp::DocumentFormattingParams), Hover(lsp::HoverParams), GoToDefinition(lsp::GotoDefinitionParams), GoToTypeDefinition(lsp::GotoDefinitionParams), Completion(lsp::CompletionParams), CodeAction(lsp::CodeActionParams), SignatureHelp(lsp::SignatureHelpParams), DocumentSymbol(lsp::DocumentSymbolParams), FoldingRange(lsp::FoldingRangeParams), PrepareRename(lsp::TextDocumentPositionParams), Rename(lsp::RenameParams), FindReferences(lsp::ReferenceParams), } impl Request { fn extract(request: lsp_server::Request) -> Option { let id = request.id.clone(); match request.method.as_str() { "textDocument/formatting" => { let params = cast_request::(request); Some(Message::Request(id, Request::Format(params))) } "textDocument/hover" => { let params = cast_request::(request); Some(Message::Request(id, Request::Hover(params))) } "textDocument/definition" => { let params = cast_request::(request); Some(Message::Request(id, Request::GoToDefinition(params))) } "textDocument/completion" => { let params = cast_request::(request); Some(Message::Request(id, Request::Completion(params))) } "textDocument/codeAction" => { let params = cast_request::(request); Some(Message::Request(id, Request::CodeAction(params))) } "textDocument/signatureHelp" => { let params = cast_request::(request); Some(Message::Request(id, Request::SignatureHelp(params))) } "textDocument/documentSymbol" => { let params = cast_request::(request); Some(Message::Request(id, Request::DocumentSymbol(params))) } "textDocument/foldingRange" => { let params = cast_request::(request); Some(Message::Request(id, Request::FoldingRange(params))) } "textDocument/rename" => { let params = cast_request::(request); Some(Message::Request(id, Request::Rename(params))) } "textDocument/prepareRename" => { let params = cast_request::(request); Some(Message::Request(id, Request::PrepareRename(params))) } "textDocument/typeDefinition" => { let params = cast_request::(request); Some(Message::Request(id, Request::GoToTypeDefinition(params))) } "textDocument/references" => { let params = cast_request::(request); Some(Message::Request(id, Request::FindReferences(params))) } _ => None, } } } #[derive(Debug)] pub enum Notification { /// A Gleam file has been modified in memory, and the new text is provided. SourceFileChangedInMemory { path: Utf8PathBuf, text: String }, /// A Gleam file has been saved or closed in the editor. SourceFileMatchesDisc { path: Utf8PathBuf }, /// gleam.toml has changed. ConfigFileChanged { path: Utf8PathBuf }, /// It's time to compile all open projects. CompilePlease, } impl Notification { fn extract(notification: lsp_server::Notification) -> Option { match notification.method.as_str() { "textDocument/didOpen" => { let params = cast_notification::(notification); let notification = Notification::SourceFileChangedInMemory { path: super::path(¶ms.text_document.uri), text: params.text_document.text, }; Some(Message::Notification(notification)) } "textDocument/didChange" => { let params = cast_notification::(notification); let notification = Notification::SourceFileChangedInMemory { path: super::path(¶ms.text_document.uri), text: params.content_changes.into_iter().next_back()?.text, }; Some(Message::Notification(notification)) } "textDocument/didSave" => { let params = cast_notification::(notification); let notification = Notification::SourceFileMatchesDisc { path: super::path(¶ms.text_document.uri), }; Some(Message::Notification(notification)) } "textDocument/didClose" => { let params = cast_notification::(notification); let notification = Notification::SourceFileMatchesDisc { path: super::path(¶ms.text_document.uri), }; Some(Message::Notification(notification)) } "workspace/didChangeWatchedFiles" => { let params = cast_notification::(notification); let notification = Notification::ConfigFileChanged { path: super::path(¶ms.changes.into_iter().next_back()?.uri), }; Some(Message::Notification(notification)) } _ => None, } } } pub enum Next { MorePlease, Handle(Vec), Stop, } /// The message buffer pulls messages from the client until one of the following /// happens: /// - A shutdown request is received. /// - A short pause in messages is detected, indicating the programmer has /// stopped typing for a moment and would benefit from feedback. /// - A request type message is received, which requires an immediate response. /// pub struct MessageBuffer { messages: Vec, } impl MessageBuffer { pub fn new() -> Self { Self { messages: Vec::new(), } } pub fn receive(&mut self, conn: &lsp_server::Connection) -> Next { let pause = Duration::from_millis(100); // If the buffer is empty, wait indefinitely for the first message. // If the buffer is not empty, wait for a short time to see if more messages are // coming before processing the ones we have. let message = if self.messages.is_empty() { Some(conn.receiver.recv().expect("Receiving LSP message")) } else { conn.receiver.recv_timeout(pause).ok() }; // If have have not received a message then it means there is a pause in the // messages from the client, implying the programmer has stopped typing. Process // the currently enqueued messages. let message = match message { Some(message) => message, None => { // A compile please message it added in the instance of this // pause of activity so that the client gets feedback on the // state of the code as it is now. self.push_compile_please_message(); return Next::Handle(self.take_messages()); } }; match message { lsp_server::Message::Request(r) if self.shutdown(conn, &r) => Next::Stop, lsp_server::Message::Request(r) => self.request(r), lsp_server::Message::Response(r) => self.response(r), lsp_server::Message::Notification(n) => self.notification(n), } } fn request(&mut self, r: lsp_server::Request) -> Next { let Some(message) = Request::extract(r) else { return Next::MorePlease; }; // Compile the code prior to attempting to process the response, to // ensure that the response is based on the latest code. self.push_compile_please_message(); self.messages.push(message); Next::Handle(self.take_messages()) } fn notification(&mut self, n: lsp_server::Notification) -> Next { // A new notification telling us that an edit has been made, or // something along those lines. if let Some(message) = Notification::extract(n) { self.messages.push(message); } // Ask for more messages (or a pause), at which point we'll start processing. Next::MorePlease } fn response(&mut self, _: lsp_server::Response) -> Next { // We do not use or expect responses from the client currently. Next::MorePlease } /// Add a `CompilePlease` message which will prompt the engine to compile /// the projects. /// fn push_compile_please_message(&mut self) { let message = Notification::CompilePlease; let value = Message::Notification(message); self.messages.push(value); } fn take_messages(&mut self) -> Vec { std::mem::take(&mut self.messages) } fn shutdown( &mut self, connection: &lsp_server::Connection, request: &lsp_server::Request, ) -> bool { connection.handle_shutdown(request).expect("LSP shutdown") } } fn cast_request(request: lsp_server::Request) -> R::Params where R: lsp::request::Request, R::Params: serde::de::DeserializeOwned, { let (_, params) = request.extract(R::METHOD).expect("cast request"); params } fn cast_notification(notification: lsp_server::Notification) -> N::Params where N: lsp::notification::Notification, N::Params: serde::de::DeserializeOwned, { notification .extract::(N::METHOD) .expect("cast notification") } ================================================ FILE: language-server/src/progress.rs ================================================ use debug_ignore::DebugIgnore; use lsp_types::{ InitializeParams, NumberOrString, ProgressParams, ProgressParamsValue, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressCreateParams, WorkDoneProgressEnd, }; const DOWNLOADING_TOKEN: &str = "downloading-dependencies"; pub trait ProgressReporter { fn compilation_started(&self); fn compilation_finished(&self); fn dependency_downloading_started(&self); fn dependency_downloading_finished(&self); } // Used to publish progress notifications to the client without waiting for // the usual request-response loop of the language server. #[derive(Debug, Clone)] pub struct ConnectionProgressReporter<'a> { connection: DebugIgnore<&'a lsp_server::Connection>, } impl<'a> ConnectionProgressReporter<'a> { pub fn new( connection: &'a lsp_server::Connection, // We don't actually need these but we take them anyway to ensure that // this object is only created after the server has been initialised. // If it was created before then the creation of the progress token // would fail. _initialise_params: &InitializeParams, ) -> Self { create_token(DOWNLOADING_TOKEN, connection); Self { connection: connection.into(), } } fn send_notification(&self, token: &str, work_done: WorkDoneProgress) { let params = ProgressParams { token: NumberOrString::String(token.to_string()), value: ProgressParamsValue::WorkDone(work_done), }; let notification = lsp_server::Notification { method: "$/progress".into(), params: serde_json::to_value(params).expect("ProgressParams json"), }; self.connection .sender .send(lsp_server::Message::Notification(notification)) .expect("send_work_done_notification send") } } impl ProgressReporter for ConnectionProgressReporter<'_> { fn compilation_started(&self) { // Do nothing. This is only used for tests currently. // In future we could make this emit a message to the client if compilation is taking a // long time. } fn compilation_finished(&self) { // Do nothing. This is only used for tests currently. } fn dependency_downloading_started(&self) { let title = "Downloading Gleam dependencies"; self.send_notification(DOWNLOADING_TOKEN, begin_message(title)); } fn dependency_downloading_finished(&self) { self.send_notification(DOWNLOADING_TOKEN, end_message()); } } fn end_message() -> WorkDoneProgress { WorkDoneProgress::End(WorkDoneProgressEnd { message: None }) } fn begin_message(title: &str) -> WorkDoneProgress { WorkDoneProgress::Begin(WorkDoneProgressBegin { title: title.into(), cancellable: Some(false), message: None, percentage: None, }) } fn create_token(token: &str, connection: &lsp_server::Connection) { let params = WorkDoneProgressCreateParams { token: NumberOrString::String(token.into()), }; let request = lsp_server::Request { id: format!("create-token--{token}").into(), method: "window/workDoneProgress/create".into(), params: serde_json::to_value(params).expect("WorkDoneProgressCreateParams json"), }; connection .sender .send(lsp_server::Message::Request(request)) .expect("WorkDoneProgressCreate"); } ================================================ FILE: language-server/src/reference.rs ================================================ use std::collections::{HashMap, HashSet}; use ecow::EcoString; use lsp_types::Location; use gleam_core::{ analyse, ast::{ self, ArgNames, AssignName, BitArraySize, ClauseGuard, CustomType, Function, ModuleConstant, Pattern, RecordConstructor, SrcSpan, TypedExpr, TypedModule, visit::Visit, }, build::Located, type_::{ ModuleInterface, ModuleValueConstructor, Type, ValueConstructor, ValueConstructorVariant, error::{Named, VariableOrigin}, }, }; use super::{ compiler::ModuleSourceInformation, rename::RenameTarget, src_span_to_lsp_range, url_from_path, }; #[derive(Debug)] pub enum Referenced { LocalVariable { definition_location: SrcSpan, location: SrcSpan, origin: Option, name: EcoString, }, ModuleName { module_name: EcoString, module_alias: EcoString, location: SrcSpan, }, ModuleValue { module: EcoString, name: EcoString, location: SrcSpan, name_kind: Named, target_kind: RenameTarget, }, ModuleType { module: EcoString, name: EcoString, location: SrcSpan, target_kind: RenameTarget, }, } pub fn reference_for_ast_node( found: Located<'_>, current_module: &EcoString, ) -> Option { match found { Located::Expression { expression: TypedExpr::Var { constructor: ValueConstructor { variant: ValueConstructorVariant::LocalVariable { location: definition_location, origin, }, .. }, location, name, }, .. } => Some(Referenced::LocalVariable { definition_location: *definition_location, location: *location, origin: Some(origin.clone()), name: name.clone(), }), Located::Pattern(Pattern::Variable { location, origin, name, .. }) => Some(Referenced::LocalVariable { definition_location: *location, location: *location, origin: Some(origin.clone()), name: name.clone(), }), Located::Pattern(Pattern::BitArraySize(BitArraySize::Variable { constructor, location, name, .. })) => constructor .as_ref() .and_then(|constructor| match &constructor.variant { ValueConstructorVariant::LocalVariable { location: definition_location, origin, } => Some(Referenced::LocalVariable { definition_location: *definition_location, location: *location, origin: Some(origin.clone()), name: name.clone(), }), ValueConstructorVariant::ModuleConstant { .. } | ValueConstructorVariant::ModuleFn { .. } | ValueConstructorVariant::Record { .. } => None, }), Located::Pattern(Pattern::Assign { location, name, .. }) => { Some(Referenced::LocalVariable { definition_location: *location, location: *location, origin: None, name: name.clone(), }) } Located::Arg(arg) => match &arg.names { ArgNames::Named { location, name } | ArgNames::NamedLabelled { name_location: location, name, .. } => Some(Referenced::LocalVariable { definition_location: *location, location: *location, origin: None, name: name.clone(), }), ArgNames::Discard { .. } | ArgNames::LabelledDiscard { .. } => None, }, Located::Expression { expression: TypedExpr::Var { constructor: ValueConstructor { variant: ValueConstructorVariant::ModuleConstant { module, .. } | ValueConstructorVariant::ModuleFn { module, .. }, .. }, name, location, .. }, .. } => Some(Referenced::ModuleValue { module: module.clone(), name: name.clone(), location: *location, name_kind: Named::Function, target_kind: RenameTarget::Unqualified, }), Located::Expression { expression: TypedExpr::ModuleSelect { module_name, label, constructor: ModuleValueConstructor::Fn { .. } | ModuleValueConstructor::Constant { .. }, location, field_start, .. }, .. } => Some(Referenced::ModuleValue { module: module_name.clone(), name: label.clone(), location: SrcSpan::new(*field_start, location.end), name_kind: Named::Function, target_kind: RenameTarget::Qualified, }), Located::ModuleFunction(Function { name: Some((location, name)), .. }) | Located::ModuleConstant(ModuleConstant { name, name_location: location, .. }) => Some(Referenced::ModuleValue { module: current_module.clone(), name: name.clone(), location: *location, name_kind: Named::Function, target_kind: RenameTarget::Definition, }), Located::Expression { expression: TypedExpr::Var { constructor: ValueConstructor { variant: ValueConstructorVariant::Record { module, name, .. }, .. }, location, .. }, .. } => Some(Referenced::ModuleValue { module: module.clone(), name: name.clone(), location: *location, name_kind: Named::CustomTypeVariant, target_kind: RenameTarget::Unqualified, }), Located::Expression { expression: TypedExpr::ModuleSelect { module_name, label, constructor: ModuleValueConstructor::Record { .. }, location, field_start, .. }, .. } => Some(Referenced::ModuleValue { module: module_name.clone(), name: label.clone(), location: SrcSpan::new(*field_start, location.end), name_kind: Named::CustomTypeVariant, target_kind: RenameTarget::Qualified, }), Located::VariantConstructorDefinition(RecordConstructor { name, name_location, .. }) => Some(Referenced::ModuleValue { module: current_module.clone(), name: name.clone(), location: *name_location, name_kind: Named::CustomTypeVariant, target_kind: RenameTarget::Definition, }), Located::Pattern(Pattern::Constructor { constructor: analyse::Inferred::Known(constructor), module: module_select, name_location: location, .. }) => Some(Referenced::ModuleValue { module: constructor.module.clone(), name: constructor.name.clone(), location: *location, name_kind: Named::CustomTypeVariant, target_kind: if module_select.is_some() { RenameTarget::Qualified } else { RenameTarget::Unqualified }, }), Located::StringPrefixPatternVariable { location, name, .. } => { Some(Referenced::LocalVariable { definition_location: location, location, origin: None, name: name.clone(), }) } Located::Annotation { ast, type_ } => match type_.named_type_name() { Some((module, name)) => { let (target_kind, location) = match ast { ast::TypeAst::Constructor(constructor) => { let kind = if constructor.module.is_some() { RenameTarget::Qualified } else { RenameTarget::Unqualified }; (kind, constructor.name_location) } ast::TypeAst::Fn(_) | ast::TypeAst::Var(_) | ast::TypeAst::Tuple(_) | ast::TypeAst::Hole(_) => (RenameTarget::Unqualified, ast.location()), }; Some(Referenced::ModuleType { module, name, location, target_kind, }) } None => None, }, Located::ModuleCustomType(CustomType { name, name_location, .. }) => Some(Referenced::ModuleType { module: current_module.clone(), name: name.clone(), location: *name_location, target_kind: RenameTarget::Definition, }), Located::ModuleName { location, module_name, module_alias, .. } => Some(Referenced::ModuleName { module_name, module_alias, location, }), Located::ModuleImport(import) => { let module_name = match &import.as_name { Some(( AssignName::Variable(module_alias) | AssignName::Discard(module_alias), alias_location, )) => Referenced::ModuleName { module_name: import.module.clone(), module_alias: module_alias.clone(), location: SrcSpan { start: alias_location.end - (module_alias.len() as u32), end: alias_location.end, }, }, None => Referenced::ModuleName { module_name: import.module.clone(), module_alias: import .module .split('/') .next_back() .map(EcoString::from) .unwrap_or_else(|| import.module.clone()), location: import.module_location, }, }; Some(module_name) } Located::ClauseGuard(ClauseGuard::Var { location, type_: _, name, definition_location, origin, }) => Some(Referenced::LocalVariable { definition_location: *definition_location, location: *location, origin: Some(origin.clone()), name: name.clone(), }), Located::ClauseGuard(ClauseGuard::ModuleSelect { location, field_start, label, module_name, .. }) => Some(Referenced::ModuleValue { module: module_name.clone(), name: label.clone(), location: SrcSpan::new(*field_start, location.end), name_kind: Named::Function, target_kind: RenameTarget::Qualified, }), Located::Pattern(_) | Located::ClauseGuard(_) | Located::PatternSpread { .. } | Located::Statement(_) | Located::Expression { .. } | Located::FunctionBody(_) | Located::UnqualifiedImport(_) | Located::Label(..) | Located::Constant(_) | Located::ModuleFunction(_) | Located::ModuleTypeAlias(_) => None, } } pub fn find_module_references( module_name: EcoString, name: EcoString, modules: &im::HashMap, sources: &HashMap, layer: ast::Layer, ) -> Vec { let mut reference_locations = Vec::new(); for module in modules.values() { if module.name == module_name || module.references.imported_modules.contains(&module_name) { let Some(source_information) = sources.get(&module.name) else { continue; }; find_references_in_module( &module_name, &name, module, source_information, &mut reference_locations, layer, ); } } reference_locations } fn find_references_in_module( module_name: &EcoString, name: &EcoString, module: &ModuleInterface, source_information: &ModuleSourceInformation, reference_locations: &mut Vec, layer: ast::Layer, ) { let reference_map = match layer { ast::Layer::Value => &module.references.value_references, ast::Layer::Type => &module.references.type_references, }; let Some(references) = reference_map.get(&(module_name.clone(), name.clone())) else { return; }; let Some(uri) = url_from_path(source_information.path.as_str()) else { return; }; for reference in references { reference_locations.push(Location { uri: uri.clone(), range: src_span_to_lsp_range(reference.location, &source_information.line_numbers), }); } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct VariableReference { pub location: SrcSpan, pub kind: VariableReferenceKind, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum VariableReferenceKind { Variable, LabelShorthand, } /// How to treat variables defined in alternative patterns enum AlternativeVariable { Track, Ignore, } pub struct FindVariableReferences { // Due to the structure of some AST nodes (for example, record updates), // when we traverse the AST it is possible to accidentally duplicate references. // To avoid this, we use a `HashSet` instead of a `Vec` here. // See: https://github.com/gleam-lang/gleam/issues/4859 and the linked PR. references: HashSet, definition_location: DefinitionLocation, alternative_variable: AlternativeVariable, name: EcoString, } /// Where the variable we're finding references for is defined. /// enum DefinitionLocation { /// This is the location where the variable is defined, nothing special is /// going on here. For example: /// /// ```gleam /// let wibble = 1 /// // ^^^^^^ Definition location for `wibble` /// wibble + 1 /// ^^^^^^ /// // `wibble` used here, defined earlier /// ``` /// Regular { location: SrcSpan }, /// When dealing with alternative patterns and aliases we need special care: /// each usage wil always reference the first alternative where a variable /// is defined and not the following ones. For example: /// /// ```gleam /// case wibble { /// [] as var | [_] as var -> var /// // ^^^ ^^^ If we look where `var` thinks it's defined /// // It will say it's defined here! /// } /// ``` /// /// This poses a problem if we start the renaming from the second /// alternative pattern: /// /// ```gleam /// case wibble { /// [] as var | [_] as var -> var /// // ^^^ Since `var` uses the first alternative as its /// // definition location, this would not be considered /// // a reference to that same var. /// } /// ``` /// /// So we keep track of the location of this definition, but we also need /// to store the location of the first definition in the alternative case /// (that's `first_alternative_location`), so that when we look for /// references we can check against this one that is canonically used by /// expressions in the AST /// Alternative { location: SrcSpan, first_alternative_location: SrcSpan, }, } impl FindVariableReferences { pub fn new(variable_definition_location: SrcSpan, variable_name: EcoString) -> Self { Self { references: HashSet::new(), definition_location: DefinitionLocation::Regular { location: variable_definition_location, }, alternative_variable: AlternativeVariable::Ignore, name: variable_name, } } /// Where the definition for which we're accumulating references is /// originally defined. In case of alternative patterns this will point to /// the first occurrence of that name! Look at the docs for /// `DefinitionLocation` to learn more on why this is needed. /// fn definition_origin_location(&self) -> SrcSpan { match self.definition_location { DefinitionLocation::Regular { location } | DefinitionLocation::Alternative { first_alternative_location: location, .. } => location, } } /// This is the location of the definition for which we're accumulating /// references. In most cases you'll want to use `definition_origin_location`. /// The difference between the two is explained in greater detail in the docs /// for `DefinitionLocation`. /// fn definition_location(&self) -> SrcSpan { match self.definition_location { DefinitionLocation::Regular { location } | DefinitionLocation::Alternative { location, .. } => location, } } fn update_alternative_origin(&mut self, alternative_location: SrcSpan) { match self.definition_location { // We've found the location of the origin of an alternative pattern. DefinitionLocation::Regular { location } if alternative_location < location => { self.definition_location = DefinitionLocation::Alternative { location, first_alternative_location: alternative_location, }; } // Since the new alternative location we've found is smaller, that // is the actual first one for the alternative pattern! DefinitionLocation::Alternative { location, first_alternative_location, } if alternative_location < first_alternative_location => { self.definition_location = DefinitionLocation::Alternative { location, first_alternative_location: alternative_location, }; } DefinitionLocation::Regular { .. } | DefinitionLocation::Alternative { .. } => (), }; } pub fn find_in_module(mut self, module: &TypedModule) -> HashSet { self.visit_typed_module(module); self.references } pub fn find(mut self, expression: &TypedExpr) -> HashSet { self.visit_typed_expr(expression); self.references } fn register_alternative_definition(&mut self, name: &EcoString, location: &SrcSpan) { match self.alternative_variable { // If we are inside the same alternative pattern as the target // variable and the name is the same, this is an alternative definition // of the same variable. We don't register the reference if this is // the exact variable though, as that would result in a duplicated // reference. AlternativeVariable::Track if *name == self.name && *location != self.definition_location() => { self.update_alternative_origin(*location); _ = self.references.insert(VariableReference { location: *location, kind: VariableReferenceKind::Variable, }); } AlternativeVariable::Track | AlternativeVariable::Ignore => {} } } } impl<'ast> Visit<'ast> for FindVariableReferences { fn visit_typed_function(&mut self, fun: &'ast ast::TypedFunction) { if fun .full_location() .contains(self.definition_origin_location().start) { ast::visit::visit_typed_function(self, fun); } } fn visit_typed_expr_var( &mut self, location: &'ast SrcSpan, constructor: &'ast ValueConstructor, _name: &'ast EcoString, ) { match constructor.variant { ValueConstructorVariant::LocalVariable { location: definition_location, .. } if definition_location == self.definition_origin_location() => { _ = self.references.insert(VariableReference { location: *location, kind: VariableReferenceKind::Variable, }); } ValueConstructorVariant::LocalVariable { .. } | ValueConstructorVariant::ModuleConstant { .. } | ValueConstructorVariant::ModuleFn { .. } | ValueConstructorVariant::Record { .. } => {} } } fn visit_typed_clause_guard_var( &mut self, location: &'ast SrcSpan, _name: &'ast EcoString, _type_: &'ast std::sync::Arc, definition_location: &'ast SrcSpan, _origin: &'ast VariableOrigin, ) { if *definition_location == self.definition_origin_location() { _ = self.references.insert(VariableReference { location: *location, kind: VariableReferenceKind::Variable, }); } } fn visit_typed_clause(&mut self, clause: &'ast ast::TypedClause) { // If this alternative pattern contains the variable we are finding // references for, we track that so we can find alternative definitions // of the target variable. if clause .pattern_location() .contains(self.definition_origin_location().start) { self.alternative_variable = AlternativeVariable::Track; } for pattern in clause.pattern.iter() { self.visit_typed_pattern(pattern); } for patterns in clause.alternative_patterns.iter() { for pattern in patterns { self.visit_typed_pattern(pattern); } } self.alternative_variable = AlternativeVariable::Ignore; if let Some(guard) = &clause.guard { self.visit_typed_clause_guard(guard); } self.visit_typed_expr(&clause.then); } fn visit_typed_pattern_variable( &mut self, location: &'ast SrcSpan, name: &'ast EcoString, _type_: &'ast std::sync::Arc, _origin: &'ast VariableOrigin, ) { self.register_alternative_definition(name, location); } fn visit_typed_pattern_assign( &mut self, location: &'ast SrcSpan, name: &'ast EcoString, pattern: &'ast ast::TypedPattern, ) { self.register_alternative_definition(name, location); ast::visit::visit_typed_pattern_assign(self, location, name, pattern); } fn visit_typed_bit_array_size_variable( &mut self, location: &'ast SrcSpan, _name: &'ast EcoString, constructor: &'ast Option>, _type_: &'ast std::sync::Arc, ) { let variant = match constructor { Some(constructor) => &constructor.variant, None => return, }; match variant { ValueConstructorVariant::LocalVariable { location: definition_location, .. } if *definition_location == self.definition_origin_location() => { _ = self.references.insert(VariableReference { location: *location, kind: VariableReferenceKind::Variable, }); } ValueConstructorVariant::LocalVariable { .. } | ValueConstructorVariant::ModuleConstant { .. } | ValueConstructorVariant::ModuleFn { .. } | ValueConstructorVariant::Record { .. } => {} } } fn visit_typed_call_arg(&mut self, arg: &'ast gleam_core::type_::TypedCallArg) { if let TypedExpr::Var { location, constructor, .. } = &arg.value { match &constructor.variant { ValueConstructorVariant::LocalVariable { location: definition_location, .. } if arg.uses_label_shorthand() && *definition_location == self.definition_origin_location() => { _ = self.references.insert(VariableReference { location: *location, kind: VariableReferenceKind::LabelShorthand, }); return; } ValueConstructorVariant::LocalVariable { .. } | ValueConstructorVariant::ModuleConstant { .. } | ValueConstructorVariant::ModuleFn { .. } | ValueConstructorVariant::Record { .. } => {} } } ast::visit::visit_typed_call_arg(self, arg); } fn visit_typed_pattern_string_prefix( &mut self, location: &'ast SrcSpan, left_location: &'ast SrcSpan, left_side_assignment: &'ast Option<(EcoString, SrcSpan)>, right_location: &'ast SrcSpan, left_side_string: &'ast EcoString, right_side_assignment: &'ast AssignName, ) { // Handle the prefix alias in alternative pattern: "prefix" as name | "other_prefix" as name if let Some((name, left_side_assignment_location)) = left_side_assignment { self.register_alternative_definition(name, left_side_assignment_location); } // Handle the suffix in alternative pattern: "prefix" <> name | "other_prefix" <> name match right_side_assignment { AssignName::Variable(name) => { self.register_alternative_definition(name, right_location) } AssignName::Discard(_) => {} } ast::visit::visit_typed_pattern_string_prefix( self, location, left_location, left_side_assignment, right_location, left_side_string, right_side_assignment, ); } } pub struct ModuleNameReference { pub location: SrcSpan, pub kind: ModuleNameReferenceKind, } pub enum ModuleNameReferenceKind { Import, AliasedImport, ModuleSelect, } pub struct FindModuleNameReferences<'a> { pub references: Vec, pub module_name: &'a EcoString, pub module_alias: &'a EcoString, } impl<'ast> Visit<'ast> for FindModuleNameReferences<'_> { fn visit_typed_module(&mut self, module: &'ast TypedModule) { ast::visit::visit_typed_module(self, module); } fn visit_typed_clause_guard(&mut self, guard: &'ast ast::TypedClauseGuard) { ast::visit::visit_typed_clause_guard(self, guard); } fn visit_typed_import(&mut self, import: &'ast ast::TypedImport) { match import.as_name.as_ref() { None => { if import.module == *self.module_name { self.references.push(ModuleNameReference { location: import.location, kind: ModuleNameReferenceKind::Import, }) } } Some((AssignName::Variable(alias) | AssignName::Discard(alias), alias_location)) => { if alias == self.module_alias { self.references.push(ModuleNameReference { location: *alias_location, kind: ModuleNameReferenceKind::AliasedImport, }) } } } ast::visit::visit_typed_import(self, import); } fn visit_typed_clause_guard_module_select( &mut self, location: &'ast SrcSpan, field_start: &'ast u32, definition_location: &'ast SrcSpan, type_: &'ast std::sync::Arc, label: &'ast EcoString, module_name: &'ast EcoString, module_alias: &'ast EcoString, literal: &'ast ast::TypedConstant, ) { if module_alias == self.module_alias { self.references.push(ModuleNameReference { location: SrcSpan::new( location.start, location.start + (module_alias.len() as u32), ), kind: ModuleNameReferenceKind::ModuleSelect, }); } ast::visit::visit_typed_clause_guard_module_select( self, location, field_start, definition_location, type_, label, module_name, module_alias, literal, ); } fn visit_typed_expr_module_select( &mut self, location: &'ast SrcSpan, field_start: &'ast u32, type_: &'ast std::sync::Arc, label: &'ast EcoString, module_name: &'ast EcoString, module_alias: &'ast EcoString, constructor: &'ast ModuleValueConstructor, ) { if module_alias == self.module_alias { self.references.push(ModuleNameReference { location: SrcSpan::new( location.start, location.start + (module_alias.len() as u32), ), kind: ModuleNameReferenceKind::ModuleSelect, }); } ast::visit::visit_typed_expr_module_select( self, location, field_start, type_, label, module_name, module_alias, constructor, ); } fn visit_type_ast_constructor( &mut self, location: &'ast SrcSpan, name_location: &'ast SrcSpan, module: &'ast Option<(EcoString, SrcSpan)>, name: &'ast EcoString, arguments: &'ast [ast::TypeAst], arguments_types: Option>>, ) { if let Some((module_alias, module_location)) = module && module_alias == self.module_alias { self.references.push(ModuleNameReference { location: *module_location, kind: ModuleNameReferenceKind::ModuleSelect, }) } ast::visit::visit_type_ast_constructor( self, location, name_location, module, name, arguments, arguments_types, ); } fn visit_typed_constant_record( &mut self, location: &'ast SrcSpan, module: &'ast Option<(EcoString, SrcSpan)>, name: &'ast EcoString, arguments: &'ast Vec>, tag: &'ast EcoString, type_: &'ast std::sync::Arc, field_map: &'ast analyse::Inferred, record_constructor: &'ast Option>, ) { if let Some((module_alias, module_location)) = module && module_alias == self.module_alias { self.references.push(ModuleNameReference { location: *module_location, kind: ModuleNameReferenceKind::ModuleSelect, }) } ast::visit::visit_typed_constant_record( self, location, module, name, arguments, tag, type_, field_map, record_constructor, ); } fn visit_typed_constant_record_update( &mut self, location: &'ast SrcSpan, constructor_location: &'ast SrcSpan, module: &'ast Option<(EcoString, SrcSpan)>, name: &'ast EcoString, record: &'ast ast::RecordBeingUpdated, arguments: &'ast [ast::RecordUpdateArg], tag: &'ast EcoString, type_: &'ast std::sync::Arc, field_map: &'ast analyse::Inferred, ) { if let Some((module_alias, module_location)) = module && module_alias == self.module_alias { self.references.push(ModuleNameReference { location: *module_location, kind: ModuleNameReferenceKind::ModuleSelect, }) } ast::visit::visit_typed_constant_record_update( self, location, constructor_location, module, name, record, arguments, tag, type_, field_map, ) } fn visit_typed_constant_var( &mut self, _location: &'ast SrcSpan, module: &'ast Option<(EcoString, SrcSpan)>, _name: &'ast EcoString, _constructor: &'ast Option>, _type_: &'ast std::sync::Arc, ) { if let Some((module_alias, module_location)) = module && module_alias == self.module_alias { self.references.push(ModuleNameReference { location: *module_location, kind: ModuleNameReferenceKind::ModuleSelect, }) } } fn visit_typed_pattern_constructor( &mut self, location: &'ast SrcSpan, name_location: &'ast SrcSpan, name: &'ast EcoString, arguments: &'ast Vec>, module: &'ast Option<(EcoString, SrcSpan)>, constructor: &'ast analyse::Inferred, spread: &'ast Option, type_: &'ast std::sync::Arc, ) { if let Some((module_alias, module_location)) = module && module_alias == self.module_alias { self.references.push(ModuleNameReference { location: *module_location, kind: ModuleNameReferenceKind::ModuleSelect, }); } ast::visit::visit_typed_pattern_constructor( self, location, name_location, name, arguments, module, constructor, spread, type_, ); } } ================================================ FILE: language-server/src/rename.rs ================================================ use std::collections::HashMap; use ecow::EcoString; use lsp_server::ResponseError; use lsp_types::{Range, RenameParams, TextEdit, Url, WorkspaceEdit}; use gleam_core::{ analyse::name, ast::{self, SrcSpan, visit::Visit}, build::Module, line_numbers::LineNumbers, reference::ReferenceKind, type_::{ModuleInterface, error::Named}, }; use crate::reference::{self, ModuleNameReferenceKind}; use super::{ TextEdits, compiler::ModuleSourceInformation, edits::{self, Newlines, add_newlines_after_import, position_of_first_definition_if_import}, reference::FindVariableReferences, reference::VariableReferenceKind, url_from_path, }; fn workspace_edit(uri: Url, edits: Vec) -> WorkspaceEdit { let mut changes = HashMap::new(); let _ = changes.insert(uri, edits); WorkspaceEdit { changes: Some(changes), document_changes: None, change_annotations: None, } } pub enum RenameOutcome { InvalidName { name: EcoString }, NoRenames, Renamed { edit: WorkspaceEdit }, } /// Error code for when a request has invalid params as described in: /// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#errorCodes /// const INVALID_PARAMS: i32 = -32602; impl RenameOutcome { /// Turns the outcome of renaming into a value that's suitable to be used as /// a response in the language server engine. /// pub fn into_result(self) -> Result, ResponseError> { match self { RenameOutcome::NoRenames => Ok(None), RenameOutcome::Renamed { edit } => Ok(Some(edit)), RenameOutcome::InvalidName { name } => Err(ResponseError { code: INVALID_PARAMS, message: format!("{name} is not a valid name"), data: None, }), } } } pub fn rename_local_variable( module: &Module, line_numbers: &LineNumbers, params: &RenameParams, definition_location: SrcSpan, name: EcoString, kind: VariableReferenceKind, ) -> RenameOutcome { let new_name = EcoString::from(¶ms.new_name); if name::check_name_case(Default::default(), &new_name, Named::Variable).is_err() { return RenameOutcome::InvalidName { name: new_name }; } let uri = params.text_document_position.text_document.uri.clone(); let mut edits = TextEdits::new(line_numbers); let references = FindVariableReferences::new(definition_location, name).find_in_module(&module.ast); match kind { VariableReferenceKind::Variable => { edits.replace(definition_location, params.new_name.clone()) } VariableReferenceKind::LabelShorthand => { edits.insert(definition_location.end, format!(" {}", params.new_name)) } } for reference in references { match reference.kind { VariableReferenceKind::Variable => { edits.replace(reference.location, params.new_name.clone()) } VariableReferenceKind::LabelShorthand => { edits.insert(reference.location.end, format!(" {}", params.new_name)) } } } RenameOutcome::Renamed { edit: workspace_edit(uri, edits.edits), } } #[derive(Debug)] pub enum RenameTarget { Qualified, Unqualified, Definition, } pub struct Renamed<'a> { pub module_name: &'a EcoString, pub name: &'a EcoString, pub name_kind: Named, pub target_kind: RenameTarget, pub layer: ast::Layer, } pub fn rename_module_entity( params: &RenameParams, current_module: &Module, modules: &im::HashMap, sources: &HashMap, renamed: Renamed<'_>, ) -> RenameOutcome { let new_name = EcoString::from(¶ms.new_name); if name::check_name_case( // We don't care about the actual error here, just whether the name is valid, // so we just use the default span. SrcSpan::default(), &new_name, renamed.name_kind, ) .is_err() { return RenameOutcome::InvalidName { name: new_name }; } match renamed.target_kind { // When renaming an unqualified import, instead of renaming the original // value, we simply want to alias it in the current module. // It's an unqualified import if we are referencing it using unqualified // syntax, and it is from a different module. RenameTarget::Unqualified if renamed.module_name != ¤t_module.name => { return alias_references_in_module( params, current_module, renamed.module_name, renamed.name, renamed.layer, ); } RenameTarget::Unqualified | RenameTarget::Qualified | RenameTarget::Definition => {} } let mut workspace_edit = WorkspaceEdit { changes: Some(HashMap::new()), document_changes: None, change_annotations: None, }; for module in modules.values() { if &module.name == renamed.module_name || module .references .imported_modules .contains(renamed.module_name) { let Some(source_information) = sources.get(&module.name) else { continue; }; rename_references_in_module( module, source_information, &mut workspace_edit, renamed.module_name, renamed.name, params.new_name.clone(), renamed.layer, ); } } RenameOutcome::Renamed { edit: workspace_edit, } } fn rename_references_in_module( module: &ModuleInterface, source_information: &ModuleSourceInformation, workspace_edit: &mut WorkspaceEdit, module_name: &EcoString, name: &EcoString, new_name: String, layer: ast::Layer, ) { let reference_map = match layer { ast::Layer::Value => &module.references.value_references, ast::Layer::Type => &module.references.type_references, }; let Some(references) = reference_map.get(&(module_name.clone(), name.clone())) else { return; }; let mut edits = TextEdits::new(&source_information.line_numbers); for reference in references { match reference.kind { // If the reference is an alias, the alias name will remain unchanged. ReferenceKind::Alias => {} ReferenceKind::Qualified | ReferenceKind::Unqualified | ReferenceKind::Import | ReferenceKind::Definition => edits.replace(reference.location, new_name.clone()), } } let Some(uri) = url_from_path(source_information.path.as_str()) else { return; }; if let Some(changes) = workspace_edit.changes.as_mut() { _ = changes.insert(uri, edits.edits); } } fn alias_references_in_module( params: &RenameParams, module: &Module, module_name: &EcoString, name: &EcoString, layer: ast::Layer, ) -> RenameOutcome { let reference_map = match layer { ast::Layer::Value => &module.ast.type_info.references.value_references, ast::Layer::Type => &module.ast.type_info.references.type_references, }; let Some(references) = reference_map.get(&(module_name.clone(), name.clone())) else { return RenameOutcome::NoRenames; }; let mut edits = TextEdits::new(&module.ast.type_info.line_numbers); let mut found_import = false; for reference in references { match reference.kind { ReferenceKind::Qualified => {} ReferenceKind::Unqualified | ReferenceKind::Alias => { edits.replace(reference.location, params.new_name.clone()) } ReferenceKind::Import => { edits.insert(reference.location.end, format!(" as {}", params.new_name)); found_import = true; } ReferenceKind::Definition => {} } } // If we didn't find the import for the aliased type or value, then this is // a prelude value and we need to add the import so we can alias it. if !found_import { let unqualified_import = match layer { ast::Layer::Value => format!("{name} as {}", params.new_name), ast::Layer::Type => format!("type {name} as {}", params.new_name), }; let import = module .ast .definitions .imports .iter() .find(|import| import.module == *module_name); if let Some(import) = import { let (position, new_text) = edits::insert_unqualified_import(import, &module.code, unqualified_import); edits.insert(position, new_text); } else { add_import(module, module_name, unqualified_import, &mut edits); } } RenameOutcome::Renamed { edit: workspace_edit( params.text_document_position.text_document.uri.clone(), edits.edits, ), } } fn add_import( module: &Module, module_name: &EcoString, unqualified_import: String, edits: &mut TextEdits<'_>, ) { let position_of_first_import_if_present = position_of_first_definition_if_import(module, &module.ast.type_info.line_numbers); let first_is_import = position_of_first_import_if_present.is_some(); let import_location = position_of_first_import_if_present.unwrap_or_default(); let after_import_newlines = add_newlines_after_import( import_location, first_is_import, &module.ast.type_info.line_numbers, &module.code, ); let newlines = match after_import_newlines { Newlines::Single => "\n", Newlines::Double => "\n\n", }; edits.edits.push(TextEdit { range: Range { start: import_location, end: import_location, }, new_text: format!("import {module_name}.{{{unqualified_import}}}{newlines}",), }); } pub fn rename_module_alias( module: &Module, line_numbers: &LineNumbers, params: &RenameParams, module_name: &EcoString, module_alias: &EcoString, ) -> RenameOutcome { let new_name = EcoString::from(¶ms.new_name); if name::check_name_case(SrcSpan::default(), &new_name, Named::Variable).is_err() { return RenameOutcome::InvalidName { name: new_name }; } let uri = params.text_document_position.text_document.uri.clone(); let mut edits = TextEdits::new(line_numbers); let mut finder = reference::FindModuleNameReferences { references: Vec::new(), module_name, module_alias, }; finder.visit_typed_module(&module.ast); let original_module_name = module_name.split('/').next_back().unwrap_or(""); for reference in finder.references { match reference.kind { ModuleNameReferenceKind::Import => { edits.insert(reference.location.end, format!(" as {}", ¶ms.new_name)) } ModuleNameReferenceKind::AliasedImport => { if params.new_name == original_module_name { edits.delete(SrcSpan::new( reference.location.start - 1, reference.location.end, )); } else { edits.replace(reference.location, format!("as {}", ¶ms.new_name)) } } ModuleNameReferenceKind::ModuleSelect => { edits.replace(reference.location, params.new_name.to_string()) } } } RenameOutcome::Renamed { edit: workspace_edit(uri, edits.edits), } } ================================================ FILE: language-server/src/router.rs ================================================ use gleam_core::{ Error, Result, build::SourceFingerprint, error::{FileIoAction, FileKind}, io::{BeamCompiler, CommandExecutor, FileSystemReader, FileSystemWriter}, paths::ProjectPaths, }; use std::{ collections::{HashMap, hash_map::Entry}, time::SystemTime, }; use camino::{Utf8Path, Utf8PathBuf}; use super::{ DownloadDependencies, MakeLocker, engine::LanguageServerEngine, feedback::FeedbackBookKeeper, files::FileSystemProxy, progress::ProgressReporter, }; /// The language server instance serves a language client, typically a text /// editor. The editor could have multiple Gleam projects open at once, so run /// an instance of the language server engine for each project. /// /// This router is responsible for finding or creating an engine for a given /// file using the nearest parent `gleam.toml` file. /// #[derive(Debug)] pub(crate) struct Router { io: FileSystemProxy, engines: HashMap>, progress_reporter: Reporter, } impl Router where // IO to be supplied from outside of gleam-core IO: FileSystemReader + FileSystemWriter + BeamCompiler + CommandExecutor + DownloadDependencies + MakeLocker + Clone, // IO to be supplied from inside of gleam-core Reporter: ProgressReporter + Clone, { pub fn new(progress_reporter: Reporter, io: FileSystemProxy) -> Self { Self { io, engines: HashMap::new(), progress_reporter, } } pub fn project_path(&self, path: &Utf8Path) -> Option { find_gleam_project_parent(&self.io, path) } pub fn project_for_path( &mut self, path: Utf8PathBuf, ) -> Result>> { // If the path is the root of a known project then return it. Otherwise // find the nearest parent project. let path = if self.engines.contains_key(&path) { path } else { let Some(path) = find_gleam_project_parent(&self.io, &path) else { return Ok(None); }; path }; // If the gleam.toml has changed or the build directory is missing // (e.g. `gleam clean`), then discard the project as the target, // deps, etc may have changed and we need to rebuild taking them into // account. if let Some(project) = self.engines.get(&path) { let paths = ProjectPaths::new(path.clone()); if !self.io.exists(&paths.build_directory()) || Self::gleam_toml_changed(&paths, project, &self.io)? { let _ = self.engines.remove(&path); } } // Look up the project, creating a new one if it does not exist. Ok(Some(match self.engines.entry(path.clone()) { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(entry) => { let project = Self::new_project(path, self.io.clone(), self.progress_reporter.clone())?; entry.insert(project) } })) } /// Has gleam.toml changed since the last time we saw this project? fn gleam_toml_changed( paths: &ProjectPaths, project: &Project, io: &FileSystemProxy, ) -> Result { // Get the location of gleam.toml for this project let config_path = paths.root_config(); // See if the file modification time has changed. if io.modification_time(&config_path)? == project.gleam_toml_modification_time { return Ok(false); // Not changed } // The mtime has changed. This might not be a content change, so let's // check the hash. let toml = io.read(&config_path)?; let gleam_toml_changed = project.gleam_toml_fingerprint != SourceFingerprint::new(&toml); Ok(gleam_toml_changed) } pub fn delete_engine_for_path(&mut self, path: &Utf8Path) { if let Some(path) = find_gleam_project_parent(&self.io, path) { _ = self.engines.remove(&path); } } fn new_project( path: Utf8PathBuf, io: FileSystemProxy, progress_reporter: Reporter, ) -> Result, Error> { tracing::info!(?path, "creating_new_language_server_engine"); let paths = ProjectPaths::new(path); let config_path = paths.root_config(); let modification_time = io.modification_time(&config_path)?; let toml = io.read(&config_path)?; let config = toml::from_str(&toml).map_err(|e| Error::FileIo { action: FileIoAction::Parse, kind: FileKind::File, path: config_path, err: Some(e.to_string()), })?; let engine = LanguageServerEngine::new(config, progress_reporter, io, paths)?; let project = Project { engine, feedback: FeedbackBookKeeper::default(), gleam_toml_modification_time: modification_time, gleam_toml_fingerprint: SourceFingerprint::new(&toml), }; Ok(project) } } /// Given a given path, find the nearest parent directory containing a /// `gleam.toml` file. /// /// The file must be in either the `src`, `test` or `dev` directory if it is not a /// `.gleam` file. fn find_gleam_project_parent(io: &IO, path: &Utf8Path) -> Option where IO: FileSystemReader, { let is_module = path.extension().map(|x| x == "gleam").unwrap_or(false); let mut directory = path.to_path_buf(); // If we are finding the gleam project of a directory then we want to check the directory itself let is_directory = path.extension().is_none(); if is_directory { directory.push("src"); } while let Some(root) = directory.parent() { // If there's no gleam.toml in the root then we continue to the next parent. if !io.is_file(&root.join("gleam.toml")) { _ = directory.pop(); continue; } // If it is a Gleam module then it must reside in the src, test dev directory. if is_module && !(directory.ends_with("test") || directory.ends_with("src") || directory.ends_with("dev")) { _ = directory.pop(); continue; } return Some(root.to_path_buf()); } None } #[derive(Debug)] pub(crate) struct Project { pub engine: LanguageServerEngine, pub feedback: FeedbackBookKeeper, pub gleam_toml_modification_time: SystemTime, pub gleam_toml_fingerprint: SourceFingerprint, } #[cfg(test)] mod find_gleam_project_parent_tests { use super::*; use gleam_core::io::{FileSystemWriter, memory::InMemoryFileSystem}; #[test] fn root() { let io = InMemoryFileSystem::new(); assert_eq!(find_gleam_project_parent(&io, Utf8Path::new("/")), None); } #[test] fn outside_a_project() { let io = InMemoryFileSystem::new(); assert_eq!( find_gleam_project_parent(&io, Utf8Path::new("/app/src/one.gleam")), None ); } #[test] fn gleam_toml_itself() { let io = InMemoryFileSystem::new(); io.write(Utf8Path::new("/app/gleam.toml"), "").unwrap(); assert_eq!( find_gleam_project_parent(&io, Utf8Path::new("/app/gleam.toml")), Some(Utf8PathBuf::from("/app")) ); } #[test] fn directory_with_gleam_toml() { let io = InMemoryFileSystem::new(); io.write(Utf8Path::new("/app/gleam.toml"), "").unwrap(); assert_eq!( find_gleam_project_parent(&io, Utf8Path::new("/app")), Some(Utf8PathBuf::from("/app")) ); } #[test] fn test_module() { let io = InMemoryFileSystem::new(); io.write(Utf8Path::new("/app/gleam.toml"), "").unwrap(); assert_eq!( find_gleam_project_parent(&io, Utf8Path::new("/app/test/one/two/three.gleam")), Some(Utf8PathBuf::from("/app")) ); } #[test] fn src_module() { let io = InMemoryFileSystem::new(); io.write(Utf8Path::new("/app/gleam.toml"), "").unwrap(); assert_eq!( find_gleam_project_parent(&io, Utf8Path::new("/app/src/one/two/three.gleam")), Some(Utf8PathBuf::from("/app")) ); } #[test] fn dev_module() { let io = InMemoryFileSystem::new(); io.write(Utf8Path::new("/app/gleam.toml"), "").unwrap(); assert_eq!( find_gleam_project_parent(&io, Utf8Path::new("/app/dev/one/two/three.gleam")), Some(Utf8PathBuf::from("/app")) ); } // https://github.com/gleam-lang/gleam/issues/2121 #[test] fn module_in_project_but_not_src_or_test_or_dev() { let io = InMemoryFileSystem::new(); io.write(Utf8Path::new("/app/gleam.toml"), "").unwrap(); assert_eq!( find_gleam_project_parent(&io, Utf8Path::new("/app/other/one/two/three.gleam")), None, ); } #[test] fn nested_projects() { let io = InMemoryFileSystem::new(); io.write(Utf8Path::new("/app/gleam.toml"), "").unwrap(); io.write(Utf8Path::new("/app/examples/wibble/gleam.toml"), "") .unwrap(); assert_eq!( find_gleam_project_parent(&io, Utf8Path::new("/app/src/one.gleam")), Some(Utf8PathBuf::from("/app")) ); assert_eq!( find_gleam_project_parent(&io, Utf8Path::new("/app/examples/wibble/src/one.gleam")), Some(Utf8PathBuf::from("/app/examples/wibble")) ); } } ================================================ FILE: language-server/src/server.rs ================================================ use super::{ DownloadDependencies, MakeLocker, engine::{self, LanguageServerEngine}, feedback::{Feedback, FeedbackBookKeeper}, files::FileSystemProxy, messages::{Message, MessageBuffer, Next, Notification, Request}, progress::ConnectionProgressReporter, router::Router, src_span_to_lsp_range, }; use camino::{Utf8Path, Utf8PathBuf}; use debug_ignore::DebugIgnore; use gleam_core::{ Result, diagnostic::{Diagnostic, ExtraLabel, Level}, io::{BeamCompiler, CommandExecutor, FileSystemReader, FileSystemWriter}, line_numbers::LineNumbers, }; use lsp_server::ResponseError; use lsp_types::{ self as lsp, HoverProviderCapability, InitializeParams, Position, PublishDiagnosticsParams, Range, RenameOptions, TextEdit, Url, }; use serde_json::Value as Json; use std::collections::{HashMap, HashSet}; /// This class is responsible for handling the language server protocol and /// delegating the work to the engine. /// /// - Configuring watching of the `gleam.toml` file. /// - Decoding requests. /// - Encoding responses. /// - Sending diagnostics and messages to the client. /// - Tracking the state of diagnostics and messages. /// - Performing the initialisation handshake. /// #[derive(Debug)] pub struct LanguageServer<'a, IO> { initialise_params: InitializeParams, connection: DebugIgnore<&'a lsp_server::Connection>, outside_of_project_feedback: FeedbackBookKeeper, router: Router>, changed_projects: HashSet, io: FileSystemProxy, } impl<'a, IO> LanguageServer<'a, IO> where IO: FileSystemReader + FileSystemWriter + BeamCompiler + CommandExecutor + DownloadDependencies + MakeLocker + Clone, { pub fn new(connection: &'a lsp_server::Connection, io: IO) -> Result { let initialise_params = initialisation_handshake(connection); let reporter = ConnectionProgressReporter::new(connection, &initialise_params); let io = FileSystemProxy::new(io); let router = Router::new(reporter, io.clone()); Ok(Self { connection: connection.into(), initialise_params, changed_projects: HashSet::new(), outside_of_project_feedback: FeedbackBookKeeper::default(), router, io, }) } pub fn run(&mut self) -> Result<()> { self.start_watching_gleam_toml(); let mut buffer = MessageBuffer::new(); loop { match buffer.receive(*self.connection) { Next::Stop => break, Next::MorePlease => (), Next::Handle(messages) => { for message in messages { self.handle_message(message); } } } } Ok(()) } fn handle_message(&mut self, message: Message) { match message { Message::Request(id, request) => self.handle_request(id, request), Message::Notification(notification) => self.handle_notification(notification), } } fn handle_request(&mut self, id: lsp_server::RequestId, request: Request) { let (outcome, feedback) = match request { Request::Format(param) => self.format(param), Request::Hover(param) => self.hover(param), Request::GoToDefinition(param) => self.goto_definition(param), Request::Completion(param) => self.completion(param), Request::CodeAction(param) => self.code_action(param), Request::SignatureHelp(param) => self.signature_help(param), Request::DocumentSymbol(param) => self.document_symbol(param), Request::FoldingRange(param) => self.folding_range(param), Request::PrepareRename(param) => self.prepare_rename(param), Request::Rename(param) => self.rename(param), Request::GoToTypeDefinition(param) => self.goto_type_definition(param), Request::FindReferences(param) => self.find_references(param), }; self.publish_feedback(feedback); let response = match outcome { Ok(payload) => lsp_server::Response { id, error: None, result: Some(payload), }, Err(error) => lsp_server::Response { id, error: Some(error), result: None, }, }; self.connection .sender .send(lsp_server::Message::Response(response)) .expect("channel send LSP response") } fn handle_notification(&mut self, notification: Notification) { let feedback = match notification { Notification::CompilePlease => self.compile_please(), Notification::SourceFileMatchesDisc { path } => self.discard_in_memory_cache(path), Notification::SourceFileChangedInMemory { path, text } => { self.cache_file_in_memory(path, text) } Notification::ConfigFileChanged { path } => self.watched_files_changed(path), }; self.publish_feedback(feedback); } fn publish_feedback(&self, feedback: Feedback) { self.publish_diagnostics(feedback.diagnostics); self.publish_messages(feedback.messages); } fn publish_diagnostics(&self, diagnostics: HashMap>) { for (path, diagnostics) in diagnostics { let diagnostics = diagnostics .into_iter() .flat_map(diagnostic_to_lsp) .collect::>(); let uri = path_to_uri(path); // Publish the diagnostics let diagnostic_params = PublishDiagnosticsParams { uri, diagnostics, version: None, }; let notification = lsp_server::Notification { method: "textDocument/publishDiagnostics".into(), params: serde_json::to_value(diagnostic_params) .expect("textDocument/publishDiagnostics to json"), }; self.connection .sender .send(lsp_server::Message::Notification(notification)) .expect("send textDocument/publishDiagnostics"); } } fn start_watching_gleam_toml(&mut self) { let supports_watch_files = self .initialise_params .capabilities .workspace .as_ref() .and_then(|w| w.did_change_watched_files) .map(|wf| wf.dynamic_registration.unwrap_or(false)) .unwrap_or(false); if !supports_watch_files { tracing::warn!("lsp_client_cannot_watch_gleam_toml"); return; } // Register gleam.toml as a watched file so we get a notification when // it changes and thus know that we need to rebuild the entire project. let watch_config = lsp::Registration { id: "watch-gleam-toml".into(), method: "workspace/didChangeWatchedFiles".into(), register_options: Some( serde_json::value::to_value(lsp::DidChangeWatchedFilesRegistrationOptions { watchers: vec![lsp::FileSystemWatcher { glob_pattern: "**/gleam.toml".to_string().into(), kind: Some(lsp::WatchKind::Change), }], }) .expect("workspace/didChangeWatchedFiles to json"), ), }; let request = lsp_server::Request { id: 1.into(), method: "client/registerCapability".into(), params: serde_json::value::to_value(lsp::RegistrationParams { registrations: vec![watch_config], }) .expect("client/registerCapability to json"), }; self.connection .sender .send(lsp_server::Message::Request(request)) .expect("send client/registerCapability"); } fn publish_messages(&self, messages: Vec) { for message in messages { let params = lsp::ShowMessageParams { typ: match message.level { Level::Error => lsp::MessageType::ERROR, Level::Warning => lsp::MessageType::WARNING, }, message: message.text, }; let notification = lsp_server::Notification { method: "window/showMessage".into(), params: serde_json::to_value(params).expect("window/showMessage to json"), }; self.connection .sender .send(lsp_server::Message::Notification(notification)) .expect("send window/showMessage"); } } fn respond_with_engine( &mut self, path: Utf8PathBuf, handler: Handler, ) -> (Result, Feedback) where T: serde::Serialize, Handler: FnOnce( &mut LanguageServerEngine>, ) -> engine::Response, { self.fallible_respond_with_engine(path, |engine| { let response = handler(engine); engine::Response { result: response.result.map(Ok), warnings: response.warnings, compilation: response.compilation, } }) } fn fallible_respond_with_engine( &mut self, path: Utf8PathBuf, handler: Handler, ) -> (Result, Feedback) where T: serde::Serialize, Handler: FnOnce( &mut LanguageServerEngine>, ) -> engine::Response>, { match self.router.project_for_path(path) { Ok(Some(project)) => { let engine::Response { result, warnings, compilation, } = handler(&mut project.engine); match result { Ok(Ok(value)) => { let feedback = project.feedback.response(compilation, warnings); let json = serde_json::to_value(value).expect("response to json"); (Ok(json), feedback) } Ok(Err(error)) => { let feedback = project.feedback.response(compilation, warnings); (Err(error), feedback) } Err(e) => { let feedback = project.feedback.build_with_error(e, compilation, warnings); (Ok(Json::Null), feedback) } } } Ok(None) => (Ok(Json::Null), Feedback::default()), Err(error) => ( Ok(Json::Null), self.outside_of_project_feedback.error(error), ), } } fn path_error_response( &mut self, path: Utf8PathBuf, error: gleam_core::Error, ) -> (Result, Feedback) { let feedback = match self.router.project_for_path(path) { Ok(Some(project)) => project.feedback.error(error), Ok(None) | Err(_) => self.outside_of_project_feedback.error(error), }; (Ok(Json::Null), feedback) } fn format( &mut self, params: lsp::DocumentFormattingParams, ) -> (Result, Feedback) { let path = super::path(¶ms.text_document.uri); let mut new_text = String::new(); let src = match self.io.read(&path) { Ok(src) => src.into(), Err(error) => return self.path_error_response(path, error), }; if let Err(error) = gleam_core::format::pretty(&mut new_text, &src, &path) { return self.path_error_response(path, error); } let line_count = src.lines().count() as u32; let edit = TextEdit { range: Range::new(Position::new(0, 0), Position::new(line_count, 0)), new_text, }; let json = serde_json::to_value(vec![edit]).expect("to JSON value"); (Ok(json), Feedback::default()) } fn hover(&mut self, params: lsp::HoverParams) -> (Result, Feedback) { let path = super::path(¶ms.text_document_position_params.text_document.uri); self.respond_with_engine(path, |engine| engine.hover(params)) } fn goto_definition( &mut self, params: lsp::GotoDefinitionParams, ) -> (Result, Feedback) { let path = super::path(¶ms.text_document_position_params.text_document.uri); self.respond_with_engine(path, |engine| engine.goto_definition(params)) } fn goto_type_definition( &mut self, params: lsp_types::GotoDefinitionParams, ) -> (Result, Feedback) { let path = super::path(¶ms.text_document_position_params.text_document.uri); self.respond_with_engine(path, |engine| engine.goto_type_definition(params)) } fn completion( &mut self, params: lsp::CompletionParams, ) -> (Result, Feedback) { let path = super::path(¶ms.text_document_position.text_document.uri); let src = match self.io.read(&path) { Ok(src) => src.into(), Err(error) => return self.path_error_response(path, error), }; self.respond_with_engine(path, |engine| { engine.completion(params.text_document_position, src) }) } fn signature_help( &mut self, params: lsp_types::SignatureHelpParams, ) -> (Result, Feedback) { let path = super::path(¶ms.text_document_position_params.text_document.uri); self.respond_with_engine(path, |engine| engine.signature_help(params)) } fn code_action( &mut self, params: lsp::CodeActionParams, ) -> (Result, Feedback) { let path = super::path(¶ms.text_document.uri); self.respond_with_engine(path, |engine| engine.code_actions(params)) } fn document_symbol( &mut self, params: lsp::DocumentSymbolParams, ) -> (Result, Feedback) { let path = super::path(¶ms.text_document.uri); self.respond_with_engine(path, |engine| engine.document_symbol(params)) } fn folding_range( &mut self, params: lsp::FoldingRangeParams, ) -> (Result, Feedback) { let path = super::path(¶ms.text_document.uri); self.respond_with_engine(path, |engine| engine.folding_range(params)) } fn prepare_rename( &mut self, params: lsp::TextDocumentPositionParams, ) -> (Result, Feedback) { let path = super::path(¶ms.text_document.uri); self.respond_with_engine(path, |engine| engine.prepare_rename(params)) } fn rename(&mut self, params: lsp::RenameParams) -> (Result, Feedback) { let path = super::path(¶ms.text_document_position.text_document.uri); self.fallible_respond_with_engine( path, |engine: &mut LanguageServerEngine>| { engine.rename(params) }, ) } fn find_references( &mut self, params: lsp_types::ReferenceParams, ) -> (Result, Feedback) { let path = super::path(¶ms.text_document_position.text_document.uri); self.respond_with_engine(path, |engine| engine.find_references(params)) } fn cache_file_in_memory(&mut self, path: Utf8PathBuf, text: String) -> Feedback { self.project_changed(&path); if let Err(error) = self.io.write_mem_cache(&path, &text) { return self.outside_of_project_feedback.error(error); } Feedback::none() } fn discard_in_memory_cache(&mut self, path: Utf8PathBuf) -> Feedback { self.project_changed(&path); if let Err(error) = self.io.delete_mem_cache(&path) { return self.outside_of_project_feedback.error(error); } Feedback::none() } fn watched_files_changed(&mut self, path: Utf8PathBuf) -> Feedback { self.router.delete_engine_for_path(&path); Feedback::none() } fn compile_please(&mut self) -> Feedback { let mut accumulator = Feedback::none(); let projects = std::mem::take(&mut self.changed_projects); for path in projects { let (_, feedback) = self.respond_with_engine(path, |this| this.compile_please()); accumulator.append_feedback(feedback); } accumulator } fn project_changed(&mut self, path: &Utf8Path) { let project_path = self.router.project_path(path); if let Some(project_path) = project_path { _ = self.changed_projects.insert(project_path); } } } fn initialisation_handshake(connection: &lsp_server::Connection) -> InitializeParams { let server_capabilities = lsp::ServerCapabilities { text_document_sync: Some(lsp::TextDocumentSyncCapability::Options( lsp::TextDocumentSyncOptions { open_close: Some(true), change: Some(lsp::TextDocumentSyncKind::FULL), will_save: None, will_save_wait_until: None, save: Some(lsp::TextDocumentSyncSaveOptions::SaveOptions( lsp::SaveOptions { include_text: Some(false), }, )), }, )), selection_range_provider: None, hover_provider: Some(HoverProviderCapability::Simple(true)), completion_provider: Some(lsp::CompletionOptions { resolve_provider: None, trigger_characters: Some(vec![".".into()]), all_commit_characters: None, work_done_progress_options: lsp::WorkDoneProgressOptions { work_done_progress: None, }, completion_item: None, }), signature_help_provider: Some(lsp::SignatureHelpOptions { trigger_characters: Some(vec!["(".into(), ",".into(), ":".into()]), retrigger_characters: None, work_done_progress_options: lsp::WorkDoneProgressOptions { work_done_progress: None, }, }), definition_provider: Some(lsp::OneOf::Left(true)), type_definition_provider: Some(lsp::TypeDefinitionProviderCapability::Simple(true)), implementation_provider: None, references_provider: Some(lsp::OneOf::Left(true)), document_highlight_provider: None, document_symbol_provider: Some(lsp::OneOf::Left(true)), workspace_symbol_provider: None, code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)), code_lens_provider: None, document_formatting_provider: Some(lsp::OneOf::Left(true)), document_range_formatting_provider: None, document_on_type_formatting_provider: None, rename_provider: Some(lsp::OneOf::Right(RenameOptions { prepare_provider: Some(true), work_done_progress_options: lsp::WorkDoneProgressOptions { work_done_progress: None, }, })), document_link_provider: None, color_provider: None, folding_range_provider: Some(lsp::FoldingRangeProviderCapability::Simple(true)), declaration_provider: None, execute_command_provider: None, workspace: None, call_hierarchy_provider: None, semantic_tokens_provider: None, moniker_provider: None, linked_editing_range_provider: None, experimental: None, position_encoding: None, inline_value_provider: None, inlay_hint_provider: None, diagnostic_provider: None, }; let server_capabilities_json = serde_json::to_value(server_capabilities).expect("server_capabilities_serde"); let initialise_params_json = connection .initialize(server_capabilities_json) .expect("LSP initialize"); let initialise_params: InitializeParams = serde_json::from_value(initialise_params_json).expect("LSP InitializeParams from json"); initialise_params } fn diagnostic_to_lsp(diagnostic: Diagnostic) -> Vec { let severity = match diagnostic.level { Level::Error => lsp::DiagnosticSeverity::ERROR, Level::Warning => lsp::DiagnosticSeverity::WARNING, }; let hint = diagnostic.hint; let mut text = diagnostic.title; if let Some(label) = diagnostic .location .as_ref() .and_then(|location| location.label.text.as_deref()) { text.push_str("\n\n"); text.push_str(label); if !label.ends_with(['.', '?']) { text.push('.'); } } if !diagnostic.text.is_empty() { text.push_str("\n\n"); text.push_str(&diagnostic.text); } // TODO: Redesign the diagnostic type so that we can be sure there is always // a location. Locationless diagnostics would be handled separately. let location = diagnostic .location .expect("Diagnostic given to LSP without location"); let line_numbers = LineNumbers::new(&location.src); let path = path_to_uri(location.path); let range = src_span_to_lsp_range(location.label.span, &line_numbers); let main = lsp::Diagnostic { range, severity: Some(severity), code: None, code_description: None, source: None, message: text, related_information: related_information( &hint, &location.extra_labels, &path, &line_numbers, range, ), tags: None, data: None, }; match hint { Some(hint) => { let hint = lsp::Diagnostic { severity: Some(lsp::DiagnosticSeverity::HINT), message: hint, // Some editors require this kind of "link" to group diagnostics. // For example, in Zed "go to next diagnostic" would move you from // the warning to the hint in the same location without this. related_information: Some(vec![lsp::DiagnosticRelatedInformation { location: lsp::Location { uri: path, range }, message: String::new(), }]), ..main.clone() }; vec![main, hint] } None => vec![main], } } fn related_information( hint: &Option, extra_labels: &[ExtraLabel], path: &Url, line_numbers: &LineNumbers, range: Range, ) -> Option> { let mut related_info = Vec::with_capacity(extra_labels.len() + 1); // The hint is included as a dedicated diagnostic _and_ the related information // to maximize compatibility if let Some(hint) = hint { let hint = lsp::DiagnosticRelatedInformation { message: hint.clone(), location: lsp::Location { uri: path.clone(), range, }, }; related_info.push(hint); } let additional_info = extra_labels.iter().map(|extra| { let message = extra.label.text.clone().unwrap_or_default(); let location = match &extra.src_info { Some((src, path)) => { let line_numbers = LineNumbers::new(src); lsp::Location { uri: path_to_uri(path.clone()), range: src_span_to_lsp_range(extra.label.span, &line_numbers), } } _ => lsp::Location { uri: path.clone(), range: src_span_to_lsp_range(extra.label.span, line_numbers), }, }; lsp::DiagnosticRelatedInformation { location, message } }); related_info.extend(additional_info); if related_info.is_empty() { None } else { Some(related_info) } } fn path_to_uri(path: Utf8PathBuf) -> Url { let mut file: String = "file://".into(); file.push_str(&path.as_os_str().to_string_lossy()); Url::parse(&file).expect("path_to_uri URL parse") } ================================================ FILE: language-server/src/signature_help.rs ================================================ use std::{ collections::{HashMap, HashSet}, sync::Arc, }; use ecow::EcoString; use lsp_types::{ Documentation, MarkupContent, MarkupKind, ParameterInformation, ParameterLabel, SignatureHelp, SignatureInformation, }; use gleam_core::{ ast::{CallArg, ImplicitCallArgOrigin, TypedExpr}, build::Module, type_::{FieldMap, ModuleValueConstructor, Type, printer::Printer}, }; pub fn for_expression(expr: &TypedExpr, module: &Module) -> Option { // If we're inside a function call we can provide signature help, // otherwise we don't want anything to pop up. let TypedExpr::Call { fun, arguments, .. } = expr else { return None; }; match fun.as_ref() { // If the thing being called is a local variable then we want to // use it's name as the function name to be used in the signature // help. TypedExpr::Var { constructor, name, .. } => signature_help( name.clone(), fun, arguments, constructor.field_map(), module, ), // If we're making a qualified call to another module's function // then we want to show its type, documentation and the exact name // being used (that is "."). // // eg. list.map(|) // ^ When the cursor is here we are going to show // "list.map(List(a), with: fn(a) -> b) -> List(b)" // as the help signature. // TypedExpr::ModuleSelect { module_alias, label, constructor, .. } => { let field_map = match constructor { ModuleValueConstructor::Constant { .. } => None, ModuleValueConstructor::Record { field_map, .. } | ModuleValueConstructor::Fn { field_map, .. } => field_map.into(), }; let name = format!("{module_alias}.{label}").into(); signature_help(name, fun, arguments, field_map, module) } // If the function being called is an invalid node we don't want to // provide any hint, otherwise one might be under the impression that // that function actually exists somewhere. // TypedExpr::Invalid { .. } => None, // In all other cases we can't figure out a good name to show in the // signature help so we use an anonymous `fn` as the name to be // shown. // // eg. fn(a){a}(|) // ^ When the cursor is here we are going to show // "fn(a: a) -> a" as the help signature. // TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } | TypedExpr::Block { .. } | TypedExpr::Pipeline { .. } | TypedExpr::Fn { .. } | TypedExpr::List { .. } | TypedExpr::Call { .. } | TypedExpr::BinOp { .. } | TypedExpr::Case { .. } | TypedExpr::RecordAccess { .. } | TypedExpr::PositionalAccess { .. } | TypedExpr::Tuple { .. } | TypedExpr::TupleIndex { .. } | TypedExpr::Todo { .. } | TypedExpr::Panic { .. } | TypedExpr::Echo { .. } | TypedExpr::BitArray { .. } | TypedExpr::RecordUpdate { .. } | TypedExpr::NegateBool { .. } | TypedExpr::NegateInt { .. } => signature_help("fn".into(), fun, arguments, None, module), } } /// Show the signature help of a function with the given name. /// Besides the function's typed expression `fun`, this function needs a bit of /// additional data to properly display a useful help signature: /// /// - `fun_name` is used as the display name of the function in the help /// signature. /// - `supplied_arguments` are arguments being passed to the function call, those /// might not be of the correct arity or have wrong types but are used to /// deduce which argument should be highlighted next in the help signature. /// - `field_map` is the function's field map (if any) that will be used to /// display labels and understand which labelled argument should be /// highlighted next in the help signature. /// fn signature_help( fun_name: EcoString, fun: &TypedExpr, supplied_arguments: &[CallArg], field_map: Option<&FieldMap>, module: &Module, ) -> Option { let (arguments, return_) = fun.type_().fn_types()?; // If the function has no arguments, we don't want to show any help. let arity = arguments.len() as u32; if arity == 0 { return None; } let index_to_label = match field_map { Some(field_map) => field_map .fields .iter() .map(|(name, index)| (*index, name)) .collect(), None => HashMap::new(), }; let printer = Printer::new(&module.ast.names); let (label, parameters) = print_signature_help(printer, fun_name, arguments, return_, &index_to_label); let active_parameter = active_parameter_index(arity, supplied_arguments, index_to_label) // If we don't want to highlight any arg in the suggestion we have to // explicitly provide an out of bound index. .or(Some(arity)); Some(SignatureHelp { signatures: vec![SignatureInformation { label, documentation: fun.get_documentation().map(|d| { Documentation::MarkupContent(MarkupContent { kind: MarkupKind::Markdown, value: d.into(), }) }), parameters: Some(parameters), active_parameter: None, }], active_signature: Some(0), active_parameter, }) } fn active_parameter_index( arity: u32, supplied_arguments: &[CallArg], mut index_to_label: HashMap, ) -> Option { let mut is_use_call = false; let mut found_labelled_argument = false; let mut used_labels = HashSet::new(); let mut supplied_unlabelled_arguments = 0; let unlabelled_arguments = arity - index_to_label.len() as u32; for (i, arg) in supplied_arguments.iter().enumerate() { // If there's an unlabelled argument after a labelled one, we can't // figure out what to suggest since arguments were passed in a wrong // order. if found_labelled_argument && arg.label.is_none() && !arg.is_implicit() { return None; } // Once we reach to an implicit use argument (be it the callback or the // missing implicitly inserted ones) we can break since those must be // the last arguments of the function and are not explicitly supplied by // the programmer. if let Some(ImplicitCallArgOrigin::Use | ImplicitCallArgOrigin::IncorrectArityUse) = arg.implicit { is_use_call = true; break; } match &arg.label { Some(label) => { found_labelled_argument = true; let _ = used_labels.insert(label); } // If the argument is unlabelled we just remove the label // corresponding to it from the field map since it has already been // passed as an unlabelled argument. None => { supplied_unlabelled_arguments += 1; let _ = index_to_label.remove(&(i as u32)); } } } let active_index = if supplied_unlabelled_arguments < unlabelled_arguments { if found_labelled_argument { // If I have supplied some labelled args but I haven't supplied all // unlabelled args before a labelled one then we can't safely // suggest anything as the next argument. None } else { // If I haven't supplied enough unlabelled arguments then I have to // set the next one as active (be it labelled or not). Some(supplied_unlabelled_arguments) } } else { // If I have supplied all the unlabelled arguments (and we could have // also supplied some labelled ones as unlabelled!) then we pick the // leftmost labelled argument that hasn't been supplied yet. index_to_label .into_iter() .filter(|(_index, label)| !used_labels.contains(label)) .map(|(index, _label)| index) .min() .or(Some(supplied_arguments.len() as u32)) }; // If we're showing hints for a use call and we end up deciding that the // only index we can suggest is the one of the use callback then we do not // highlight it or it would lead people into believing they can manually // pass that argument in. if is_use_call && active_index == Some(arity - 1) { None } else { active_index } } /// To produce a signature that can be used by the LS, we need to also keep /// track of the arguments' positions in the printed signature. So this function /// prints the signature help producing at the same time a list of correct /// `ParameterInformation` for all its arguments. /// fn print_signature_help( mut printer: Printer<'_>, function_name: EcoString, arguments: Vec>, return_: Arc, index_to_label: &HashMap, ) -> (String, Vec) { let arguments_count = arguments.len(); let mut signature = format!("{function_name}("); let mut parameter_informations = Vec::with_capacity(arguments_count); for (i, argument) in arguments.iter().enumerate() { let arg_start = signature.len(); if let Some(label) = index_to_label.get(&(i as u32)) { signature.push_str(label); signature.push_str(": "); } signature.push_str(&printer.print_type(argument)); let arg_end = signature.len(); let label = ParameterLabel::LabelOffsets([arg_start as u32, arg_end as u32]); parameter_informations.push(ParameterInformation { label, documentation: None, }); let is_last = i == arguments_count - 1; if !is_last { signature.push_str(", "); } } signature.push_str(") -> "); signature.push_str(&printer.print_type(&return_)); (signature, parameter_informations) } ================================================ FILE: language-server/src/tests/action.rs ================================================ use itertools::Itertools; use lsp_types::{ CodeActionContext, CodeActionParams, PartialResultParams, Position, Range, Url, WorkDoneProgressParams, }; use super::*; fn code_actions(tester: &TestProject<'_>, range: Range) -> Option> { let position = Position { line: 0, character: 0, }; tester.at(position, |engine, params, _| { let params = CodeActionParams { text_document: params.text_document, range, context: CodeActionContext::default(), work_done_progress_params: WorkDoneProgressParams::default(), partial_result_params: PartialResultParams::default(), }; engine.code_actions(params).result.unwrap() }) } fn actions_with_title( titles: Vec<&str>, tester: &TestProject<'_>, range: Range, ) -> Vec { code_actions(tester, range) .into_iter() .flatten() .filter(|action| titles.contains(&action.title.as_str())) .collect_vec() } fn owned_actions_with_title( titles: Vec<&str>, tester: TestProject<'_>, range: Range, ) -> Vec { actions_with_title(titles, &tester, range) } fn apply_code_action(title: &str, tester: TestProject<'_>, range: Range) -> String { let titles = vec![title]; let changes = actions_with_title(titles, &tester, range) .pop() .expect("No action with the given title") .edit .expect("No workspace edit found") .changes .expect("No text edit found"); apply_code_edit(tester, changes) } fn apply_code_edit( tester: TestProject<'_>, changes: HashMap>, ) -> String { let mut changed_files: HashMap = HashMap::new(); for (uri, change) in changes { let code = match changed_files.get(&uri) { Some(code) => code, None => tester .src_from_module_url(&uri) .unwrap_or_else(|| panic!("no src for url {uri:?}")), }; let code = super::apply_code_edit(code, change); let _ = changed_files.insert(uri, code); } show_code_edits(tester, changed_files) } fn show_code_edits(tester: TestProject<'_>, changed_files: HashMap) -> String { let format_code = |url: &Url, code: &String| { format!( "// --- Edits applied to module '{}'\n{}", tester.module_name_from_url(url).expect("a module"), code ) }; // If the file that changed is the main one we just show its code. if changed_files.len() == 1 { let mut changed = changed_files.iter().peekable(); let (url, code) = changed.peek().unwrap(); if tester.module_name_from_url(url) == Some("app".into()) { code.to_string() } else { format_code(url, code) } } else { // If more than a single file changed we want to add the name of the // file before each! changed_files .iter() .map(|(url, code)| format_code(url, code)) .join("\n") } } const REMOVE_UNUSED_IMPORTS: &str = "Remove unused imports"; const REMOVE_REDUNDANT_TUPLES: &str = "Remove redundant tuples"; const CONVERT_TO_CASE: &str = "Convert to case"; const USE_LABEL_SHORTHAND_SYNTAX: &str = "Use label shorthand syntax"; const FILL_LABELS: &str = "Fill labels"; const ASSIGN_UNUSED_RESULT: &str = "Assign unused Result value to `_`"; const ADD_MISSING_PATTERNS: &str = "Add missing patterns"; const ADD_ANNOTATION: &str = "Add type annotation"; const ADD_ANNOTATIONS: &str = "Add type annotations"; const ANNOTATE_TOP_LEVEL_DEFINITIONS: &str = "Annotate all top level definitions"; const CONVERT_FROM_USE: &str = "Convert from `use`"; const CONVERT_TO_USE: &str = "Convert to `use`"; const EXTRACT_VARIABLE: &str = "Extract variable"; const EXTRACT_CONSTANT: &str = "Extract constant"; const EXPAND_FUNCTION_CAPTURE: &str = "Expand function capture"; const GENERATE_DYNAMIC_DECODER: &str = "Generate dynamic decoder"; const GENERATE_TO_JSON_FUNCTION: &str = "Generate to-JSON function"; const PATTERN_MATCH_ON_ARGUMENT: &str = "Pattern match on argument"; const PATTERN_MATCH_ON_VARIABLE: &str = "Pattern match on variable"; const GENERATE_FUNCTION: &str = "Generate function"; const CONVERT_TO_FUNCTION_CALL: &str = "Convert to function call"; const INLINE_VARIABLE: &str = "Inline variable"; const CONVERT_TO_PIPE: &str = "Convert to pipe"; const INTERPOLATE_STRING: &str = "Interpolate string"; const FILL_UNUSED_FIELDS: &str = "Fill unused fields"; const REMOVE_ALL_ECHOS_FROM_THIS_MODULE: &str = "Remove all `echo`s from this module"; const WRAP_IN_BLOCK: &str = "Wrap in block"; const GENERATE_VARIANT: &str = "Generate variant"; const REMOVE_BLOCK: &str = "Remove block"; const REMOVE_OPAQUE_FROM_PRIVATE_TYPE: &str = "Remove opaque from private type"; const COLLAPSE_NESTED_CASE: &str = "Collapse nested case"; const REMOVE_UNREACHABLE_CLAUSES: &str = "Remove unreachable clauses"; const ADD_OMITTED_LABELS: &str = "Add omitted labels"; const EXTRACT_FUNCTION: &str = "Extract function"; const MERGE_CASE_BRANCHES: &str = "Merge case branches"; const ADD_MISSING_TYPE_PARAMETER: &str = "Add missing type parameter"; const REPLACE_UNDERSCORE_WITH_TYPE: &str = "Replace `_` with type"; macro_rules! assert_code_action { ($title:expr, $code:literal, $range:expr $(,)?) => { let project = TestProject::for_source($code); assert_code_action!($title, project, $range); }; ($title:expr, $project:expr, $range:expr $(,)?) => { let src = $project.src; let range = $range.find_range(src); let result = apply_code_action($title, $project, range); let output = format!( "----- BEFORE ACTION\n{}\n\n----- AFTER ACTION\n{}", hover::show_hover(src, range, range.end), result ); insta::assert_snapshot!(insta::internals::AutoName, output, src); }; } macro_rules! assert_no_code_actions { ($title:ident $(| $titles:ident)*, $code:literal, $range:expr $(,)?) => { let project = TestProject::for_source($code); assert_no_code_actions!($title $(| $titles)*, project, $range); }; ($title:ident $(| $titles:ident)*, $project:expr, $range:expr $(,)?) => { let src = $project.src; let range = $range.find_range(src); let all_titles = vec![$title $(, $titles)*]; let expected: Vec = vec![]; let result = owned_actions_with_title(all_titles, $project, range); assert_eq!(expected, result); }; } #[test] fn fix_truncated_segment_1() { let name = "Replace with `1`"; assert_code_action!( name, r#" pub fn main() { <<1, 257, 259:size(1)>> }"#, find_position_of("257").to_selection() ); } #[test] fn fix_truncated_segment_2() { let name = "Replace with `0`"; assert_code_action!( name, r#" pub fn main() { <<1, 1024:size(10)>> }"#, find_position_of("size").to_selection() ); } #[test] fn generate_variant_with_fields_in_same_module() { assert_code_action!( GENERATE_VARIANT, r#" pub type Wibble { Wibble } pub fn main() -> Wibble { Wobble(1) }"#, find_position_of("Wobble").to_selection() ); } #[test] fn generate_variant_with_no_fields_in_same_module() { assert_code_action!( GENERATE_VARIANT, r#" pub type Wibble { Wibble } pub fn main() -> Wibble { Wobble }"#, find_position_of("Wobble").to_selection() ); } #[test] fn generate_variant_with_labels_in_same_module() { assert_code_action!( GENERATE_VARIANT, r#" pub type Wibble { Wibble } pub fn main() -> Wibble { Wobble("hello", label: 1) }"#, find_position_of("Wobble").to_selection() ); } #[test] fn generate_variant_from_pattern_with_fields() { assert_code_action!( GENERATE_VARIANT, r#" pub type Wibble { Wibble } pub fn new() { Wibble } pub fn main() -> Wibble { let assert Wobble(1) = new() } "#, find_position_of("Wobble").to_selection() ); } #[test] fn generate_variant_from_pattern_with_labelled_fields() { assert_code_action!( GENERATE_VARIANT, r#" pub type Wibble { Wibble } pub fn new() { Wibble } pub fn main() -> Wibble { let assert Wobble("hello", label: 1) = new() } "#, find_position_of("Wobble").to_selection() ); } #[test] fn generate_variant_from_pattern_with_no_fields() { assert_code_action!( GENERATE_VARIANT, r#" pub type Wibble { Wibble } pub fn new() { Wibble } pub fn main() -> Wibble { let assert Wobble = new() } "#, find_position_of("Wobble").to_selection() ); } #[test] fn generate_unqualified_variant_in_other_module() { let src = r#" import other pub fn main() -> other.Wibble { let assert Wobble = new() } pub fn new() -> other.Wibble { todo } "#; assert_code_action!( GENERATE_VARIANT, TestProject::for_source(src).add_module("other", "pub type Wibble"), find_position_of("Wobble").to_selection() ); } #[test] fn generate_qualified_variant_in_other_module() { let src = r#" import other pub fn main() -> other.Wibble { let assert other.Wobble = new() } pub fn new() -> other.Wibble { todo } "#; assert_code_action!( GENERATE_VARIANT, TestProject::for_source(src).add_module("other", "pub type Wibble"), find_position_of("Wobble").to_selection() ); } #[test] fn do_not_generate_variant_if_one_with_the_same_name_exists() { assert_no_code_actions!( GENERATE_VARIANT, r#" pub fn main() -> Wibble { let assert Wobble = new() } pub type Wibble { Wobble(n: Int) } pub fn new() -> Wibble { todo } "#, find_position_of("Wobble").to_selection() ); } #[test] fn do_not_generate_variant_if_one_with_the_same_name_exists_in_other_module() { let src = r#" import other.{type Wibble} pub fn main() -> Wibble { let assert Wobble = new() } pub fn new() -> Wibble { todo } "#; assert_no_code_actions!( GENERATE_VARIANT, TestProject::for_source(src).add_module("other", "pub type Wibble { Wobble(String) }"), find_position_of("Wobble").to_selection() ); } #[test] fn do_not_generate_qualified_variant_if_one_with_the_same_name_exists_in_other_module() { let src = r#" import other.{type Wibble} pub fn main() -> Wibble { let assert other.Wobble = new() } pub fn new() -> Wibble { todo } "#; assert_no_code_actions!( GENERATE_VARIANT, TestProject::for_source(src).add_module("other", "pub type Wibble { Wobble(String) }"), find_position_of("Wobble").to_selection() ); } #[test] fn fill_unused_fields_with_ignored_labelled_fields() { assert_code_action!( FILL_UNUSED_FIELDS, r#" pub type Wibble { Wibble(Int, label1: String, label2: Int) } pub fn main() { let Wibble(_, ..) = todo }"#, find_position_of("..").to_selection() ); } #[test] fn fill_unused_fields_with_ignored_positional_fields() { assert_code_action!( FILL_UNUSED_FIELDS, r#" pub type Wibble { Wibble(Int, label1: String, label2: Int) } pub fn main() { let Wibble(label1:, label2:, ..) = todo }"#, find_position_of("..").to_selection() ); } #[test] fn fill_unused_fields_with_all_positional_fields() { assert_code_action!( FILL_UNUSED_FIELDS, r#" pub type Wibble { Wibble(Int, String) } pub fn main() { let Wibble(..) = todo }"#, find_position_of("..").to_selection() ); } #[test] fn fill_unused_fields_with_ignored_mixed_fields() { assert_code_action!( FILL_UNUSED_FIELDS, r#" pub type Wibble { Wibble(Int, String, label1: String, label2: Int) } pub fn main() { let Wibble(_, label2:, ..) = todo }"#, find_position_of("..").to_selection() ); } #[test] fn fill_unused_fields_with_all_ignored_fields() { assert_code_action!( FILL_UNUSED_FIELDS, r#" pub type Wibble { Wibble(Int, label1: String, label2: Int) } pub fn main() { let Wibble(..) = todo }"#, find_position_of("..").to_selection() ); } #[test] fn fill_unused_fields_with_ignored_fields_never_calls_a_positional_arg_as_a_labelled_one() { assert_code_action!( FILL_UNUSED_FIELDS, r#" pub type Wibble { Wibble(Int, int: Int) } pub fn main() { let Wibble(..) = todo }"#, find_position_of("..").to_selection() ); } #[test] fn remove_echo() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, "pub fn main() { echo 1 + 2 }", find_position_of("echo").to_selection() ); } #[test] fn remove_echo_with_message() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, r#"pub fn main() { echo 1 + 2 as "message" }"#, find_position_of("echo").to_selection() ); } #[test] fn remove_echo_with_message_and_comment() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, r#"pub fn main() { echo 1 + 2 // Hello! as "message" }"#, find_position_of("echo").to_selection() ); } #[test] fn remove_echo_with_message_and_comment_2() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, r#"pub fn main() { echo 1 + 2 as // Hello! "message" }"#, find_position_of("echo").to_selection() ); } #[test] fn remove_echo_with_message_and_comment_3() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, r#"pub fn main() { echo 1 + 2 as // Hello! "message" Nil }"#, find_position_of("echo").to_selection() ); } #[test] fn remove_echo_selecting_expression() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, "pub fn main() { echo 1 + 2 }", find_position_of("1").select_until(find_position_of("2")) ); } #[test] fn remove_echo_selecting_message() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, r#"pub fn main() { echo 1 + 2 as "message" }"#, find_position_of("message").to_selection() ); } #[test] fn remove_echo_as_function_arg() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, "pub fn main() { wibble([], echo 1 + 2) }", find_position_of("1").to_selection() ); } #[test] fn remove_echo_in_pipeline_step() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, "pub fn main() { [1, 2, 3] |> echo |> wibble }", find_position_of("echo").to_selection() ); } #[test] fn remove_echo_in_pipeline_step_with_message() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, r#"pub fn main() { [1, 2, 3] |> echo as message |> wibble }"#, find_position_of("echo").to_selection() ); } #[test] fn remove_echo_in_single_line_pipeline_step() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, "pub fn main() { [1, 2, 3] |> echo |> wibble }", find_position_of("echo").to_selection() ); } #[test] fn remove_echo_in_single_line_pipeline_step_with_message() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, r#"pub fn main() { [1, 2, 3] |> echo as "message" |> wibble }"#, find_position_of("echo").to_selection() ); } #[test] fn remove_echo_last_in_long_pipeline_step() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, "pub fn main() { [1, 2, 3] |> wibble |> echo }", find_position_of("echo").to_selection() ); } #[test] fn remove_echo_last_in_long_pipeline_step_with_message() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, r#"pub fn main() { [1, 2, 3] |> wibble |> echo as "message" }"#, find_position_of("echo").to_selection() ); } #[test] fn remove_echo_last_in_short_pipeline_step() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, "pub fn main() { [1, 2, 3] |> echo }", find_position_of("echo").to_selection() ); } #[test] fn remove_echo_last_in_short_pipeline_step_with_message() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, r#"pub fn main() { [1, 2, 3] |> echo as "message" }"#, find_position_of("echo").to_selection() ); } #[test] fn remove_echo_before_pipeline() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, "pub fn main() { echo [1, 2, 3] |> wibble }", find_position_of("echo").to_selection() ); } #[test] fn remove_echo_before_pipeline_selecting_step() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, "pub fn main() { echo [1, 2, 3] |> wibble }", find_position_of("wibble").to_selection() ); } #[test] fn remove_echo_removes_all_echos() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, "pub fn main() { echo wibble(echo 1, 2) }", find_position_of("echo").nth_occurrence(2).to_selection() ); } #[test] fn remove_echo_removes_all_echos_1() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, "pub fn main() { echo 1 |> echo |> echo |> wibble |> echo echo wibble(echo 1, echo 2) echo 1 }", find_position_of("echo").nth_occurrence(2).to_selection() ); } #[test] fn remove_echo_removes_entire_echo_statement_used_with_literals() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, "pub fn main() { echo 1 Nil }", find_position_of("echo").to_selection() ); } #[test] fn remove_echo_removes_entire_echo_statement_used_with_literals_and_message() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, r#"pub fn main() { echo 1 as "message" Nil }"#, find_position_of("echo").to_selection() ); } #[test] fn remove_echo_removes_entire_echo_statement_used_with_a_var() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, "pub fn main() { let a = 1 echo a Nil }", find_position_of("echo").to_selection() ); } #[test] fn remove_echo_removes_multiple_entire_echo_statement_used_with_literals() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, r#"pub fn main() { echo 1 echo "wibble" Nil }"#, find_position_of("echo").to_selection() ); } #[test] fn remove_echo_removes_multiple_entire_echo_statement_used_with_literals_but_stops_at_comments() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, r#"pub fn main() { echo 1 // Oh no I hope I'm not deleted by the code action!! Nil }"#, find_position_of("echo").to_selection() ); } #[test] fn remove_echo_removes_entire_echo_statement_used_with_literals_in_a_fn() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, "pub fn main() { fn() { echo 1 Nil } }", find_position_of("echo").to_selection() ); } #[test] fn remove_echo_removes_multiple_entire_echo_statement_used_with_literals_in_a_fn() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, r#"pub fn main() { fn() { echo 1 echo "wibble" Nil } }"#, find_position_of("echo").to_selection() ); } #[test] fn remove_echo_removes_does_not_remove_entire_echo_statement_if_its_the_return() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, "pub fn main() { echo 1 }", find_position_of("echo").to_selection() ); } #[test] fn remove_echo_with_message_removes_does_not_remove_entire_echo_statement_if_its_the_return() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, r#"pub fn main() { echo 1 as "message" }"#, find_position_of("echo").to_selection() ); } #[test] fn remove_echo_removes_does_not_remove_entire_echo_statement_if_its_the_return_of_a_fn() { assert_code_action!( REMOVE_ALL_ECHOS_FROM_THIS_MODULE, r#"pub fn main() { fn() { echo 1 } }"#, find_position_of("echo").to_selection() ); } #[test] fn split_string() { assert_code_action!( INTERPOLATE_STRING, r#"pub fn main() { "wibble wobble woo" }"#, find_position_of("wobble").to_selection() ); } #[test] fn no_split_string_right_at_the_start() { assert_no_code_actions!( INTERPOLATE_STRING, r#"pub fn main() { "wibble wobble woo" }"#, find_position_of("wibble").to_selection() ); } #[test] fn no_split_string_right_at_the_end() { assert_no_code_actions!( INTERPOLATE_STRING, r#"pub fn main() { "wibble wobble woo" }"#, find_position_of("\"").nth_occurrence(2).to_selection() ); } #[test] fn no_split_string_before_the_start() { assert_no_code_actions!( INTERPOLATE_STRING, r#"pub fn main() { "wibble wobble woo" }"#, find_position_of("\"").to_selection() ); } #[test] fn no_split_string_after_the_end() { assert_no_code_actions!( INTERPOLATE_STRING, r#"pub fn main() { "wibble wobble woo"//we need this comment so we can put the cursor _after_ the closing quote }"#, find_position_of("\"/").under_last_char().to_selection() ); } #[test] fn interpolate_string_inside_string() { assert_code_action!( INTERPOLATE_STRING, r#"pub fn main() { "wibble wobble woo" }"#, find_position_of("wobble").select_until(find_position_of("wobble ").under_last_char()), ); } #[test] fn splitting_string_as_first_pipeline_step_inserts_brackets() { assert_code_action!( INTERPOLATE_STRING, r#"pub fn main() { "wibble wobble" |> io.println }"#, find_position_of(" wobble").to_selection(), ); } #[test] fn interpolating_string_as_first_pipeline_step_inserts_brackets() { assert_code_action!( INTERPOLATE_STRING, r#"pub fn main() { "wibble wobble woo" |> io.println }"#, find_position_of("wobble ").select_until(find_position_of("wobble ").under_last_char()), ); } #[test] fn test_remove_unused_simple() { let src = " // test import // comment list as lispy import result import option pub fn main() { result.is_ok } "; assert_code_action!( REMOVE_UNUSED_IMPORTS, TestProject::for_source(src) .add_hex_module("list", "") .add_hex_module("result", "") .add_hex_module("option", ""), find_position_of("// test").select_until(find_position_of("option")), ); } #[test] fn test_remove_unused_start_of_file() { let src = "import option import result pub fn main() { result.is_ok } "; assert_code_action!( REMOVE_UNUSED_IMPORTS, TestProject::for_source(src) .add_hex_module("option", "") .add_hex_module("result", ""), find_position_of("import").select_until(find_position_of("pub")), ); } #[test] fn test_remove_unused_alias() { let src = " // test import result.{is_ok} as res import option pub fn main() { is_ok } "; assert_code_action!( REMOVE_UNUSED_IMPORTS, TestProject::for_source(src) .add_hex_module("result", "pub fn is_ok() {}") .add_hex_module("option", ""), find_position_of("// test").select_until(find_position_of("pub")), ); } #[test] fn test_remove_unused_value() { let src = " // test import result.{is_ok} import option pub fn main() { result.is_ok } "; assert_code_action!( REMOVE_UNUSED_IMPORTS, TestProject::for_source(src) .add_hex_module("result", "pub fn is_ok() {}") .add_hex_module("option", ""), find_position_of("// test").select_until(find_position_of("pub")), ); } #[test] fn test_remove_aliased_unused_value() { let src = " // test import result.{is_ok as ok} import option pub fn main() { result.is_ok } "; assert_code_action!( REMOVE_UNUSED_IMPORTS, TestProject::for_source(src) .add_hex_module("result", "pub fn is_ok() {}") .add_hex_module("option", ""), find_position_of("// test").select_until(find_position_of("pub")), ); } #[test] fn test_remove_multiple_unused_values() { let src = " // test import result.{type Unused, used, unused, unused_again, type Used, used_again} pub fn main(x: Used) { #(used, used_again) } "; assert_code_action!( REMOVE_UNUSED_IMPORTS, TestProject::for_source(src).add_hex_module( "result", " pub const used = 1 pub const unused = 2 pub const unused_again = 3 pub const used_again = 4 pub type Unused pub type Used " ), find_position_of("// test").select_until(find_position_of("pub")), ); } #[test] fn test_remove_multiple_unused_values_2() { let src = " // test import result.{type Unused, used, unused, type Used, unused_again} pub fn main(x: Used) { used } "; assert_code_action!( REMOVE_UNUSED_IMPORTS, TestProject::for_source(src).add_hex_module( "result", " pub const used = 1 pub const unused = 2 pub const unused_again = 3 pub type Unused pub type Used " ), find_position_of("// test").select_until(find_position_of("pub")), ); } #[test] fn test_remove_entire_unused_import() { let src = " // test import result.{unused, unused_again} pub fn main() { todo } "; assert_code_action!( REMOVE_UNUSED_IMPORTS, TestProject::for_source(src).add_hex_module( "result", " pub const used = 1 pub const unused = 2 pub const unused_again = 3 pub type Unused pub type Used " ), find_position_of("// test").select_until(find_position_of("pub")), ); } #[test] fn test_remove_redundant_tuple_in_case_subject_simple() { assert_code_action!( REMOVE_REDUNDANT_TUPLES, "pub fn main() { case #(1) { #(a) -> 0 } case #(1, 2) { #(a, b) -> 0 } }", find_position_of("case").select_until(find_position_of("#(1, 2)").under_last_char()) ); } #[test] fn test_remove_redundant_tuple_with_catch_all_pattern() { assert_code_action!( REMOVE_REDUNDANT_TUPLES, "pub fn main() { case #(1, 2) { #(1, 2) -> 0 _ -> 1 } }", find_position_of("case").select_until(find_position_of("#(1, 2)").under_last_char()) ); } #[test] fn test_remove_multiple_redundant_tuple_with_catch_all_pattern() { assert_code_action!( REMOVE_REDUNDANT_TUPLES, "pub fn main() { case #(1, 2), #(3, 4) { #(2, 2), #(2, 2) -> 0 #(1, 2), _ -> 0 _, #(1, 2) -> 0 _, _ -> 1 } }", find_position_of("case").select_until(find_position_of("#(3, 4)")) ); } #[test] fn test_remove_redundant_tuple_in_case_subject_nested() { assert_code_action!( REMOVE_REDUNDANT_TUPLES, "pub fn main() { case #(case #(0) { #(a) -> 0 }) { #(b) -> 0 } }", find_position_of("case").select_until(find_position_of("#(b)")) ); } #[test] fn test_remove_redundant_tuple_in_case_retain_extras() { assert_code_action!( REMOVE_REDUNDANT_TUPLES, " pub fn main() { case #( // first comment 1, // second comment 2, 3 // third comment before comma , // fourth comment after comma ) { #( // first comment a, // second comment b, c // third comment before comma , // fourth comment after comma ) -> 0 } } ", find_position_of("#").select_until(find_position_of("// first")) ); } #[test] fn test_remove_redundant_tuple_in_case_subject_ignore_empty_tuple() { assert_no_code_actions!( REMOVE_REDUNDANT_TUPLES, " pub fn main() { case #() { #() -> 0 } } ", find_position_of("case").select_until(find_position_of("0")) ); } #[test] fn test_remove_redundant_tuple_in_case_subject_only_safe_remove() { assert_code_action!( REMOVE_REDUNDANT_TUPLES, " pub fn main() { case #(0), #(1) { #(1), #(b) -> 0 a, #(0) -> 1 // The first of this clause is not a tuple #(a), #(b) -> 2 } } ", find_position_of("#(0)").select_until(find_position_of("#(1)")) ); } #[test] fn rename_invalid_const() { assert_code_action!( "Rename to my_invalid_constant", "const myInvalid_Constant = 42", find_position_of("_Constant").to_selection(), ); } #[test] fn rename_invalid_parameter() { assert_code_action!( "Rename to num_a", "fn add(numA: Int, num_b: Int) { numA + num_b }", find_position_of("numA").to_selection() ); } #[test] fn rename_invalid_parameter_name2() { assert_code_action!( "Rename to param_name", "fn pass(label paramName: Bool) { paramName }", find_position_of("paramName").to_selection() ); } #[test] fn rename_invalid_parameter_name3() { assert_code_action!( "Rename to num_a", "pub fn main() { let add = fn(numA: Int, num_b: Int) { numA + num_b } }", find_position_of("let add").select_until(find_position_of("num_b")) ); } #[test] fn rename_invalid_parameter_discard() { assert_code_action!( "Rename to _ignore_me", "fn ignore(_ignoreMe: Bool) { 98 }", find_position_of("ignore").select_until(find_position_of("98")) ); } #[test] fn rename_invalid_parameter_discard_name2() { assert_code_action!( "Rename to _ignore_me", "fn ignore(labelled_discard _ignoreMe: Bool) { 98 }", find_position_of("ignore").select_until(find_position_of("98")) ); } #[test] fn rename_invalid_parameter_discard_name3() { assert_code_action!( "Rename to _ignore_me", "pub fn main() { let ignore = fn(_ignoreMe: Bool) { 98 } }", find_position_of("ignore").select_until(find_position_of("98")) ); } #[test] fn rename_invalid_parameter_label() { assert_code_action!( "Rename to this_is_a_label", "fn func(thisIsALabel param: Int) { param }", find_position_of("thisIs").select_until(find_position_of("Int")) ); } #[test] fn rename_invalid_parameter_label2() { assert_code_action!( "Rename to this_is_a_label", "fn ignore(thisIsALabel _ignore: Int) { 25 }", find_position_of("thisIs").under_char('i').to_selection() ); } #[test] fn rename_invalid_constructor() { assert_code_action!( "Rename to TheConstructor", "type MyType { The_Constructor(Int) }", find_position_of("The_").under_char('h').to_selection(), ); } #[test] fn rename_invalid_constructor_arg() { assert_code_action!( "Rename to inner_int", "type IntWrapper { IntWrapper(innerInt: Int) }", find_position_of("IntWrapper") .nth_occurrence(2) .select_until(find_position_of(": Int")) ); } #[test] fn rename_invalid_custom_type() { assert_code_action!( "Rename to BoxedValue", "type Boxed_value { Box(Int) }", find_position_of("Box").select_until(find_position_of("_value")) ); } #[test] fn rename_invalid_type_alias() { assert_code_action!( "Rename to FancyBool", "type Fancy_Bool = Bool", find_position_of("Fancy") .under_char('a') .select_until(find_position_of("=")) ); } #[test] fn rename_invalid_function() { assert_code_action!( "Rename to do_stuff", "fn doStuff() {}", find_position_of("fn").select_until(find_position_of("{}")) ); } #[test] fn rename_invalid_variable() { assert_code_action!( "Rename to the_answer", "pub fn main() { let theAnswer = 42 }", find_position_of("theAnswer").select_until(find_position_of("Answer")) ); } #[test] fn rename_invalid_variable_discard() { assert_code_action!( "Rename to _boring_number", "pub fn main() { let _boringNumber = 72 }", find_position_of("let").select_until(find_position_of("72")) ); } #[test] fn rename_invalid_use() { assert_code_action!( "Rename to use_var", "fn use_test(f) { f(Nil) } pub fn main() {use useVar <- use_test()}", find_position_of("use") .nth_occurrence(2) .select_until(find_position_of("use_test()")) ); } #[test] fn rename_invalid_use_discard() { assert_code_action!( "Rename to _discard_var", "fn use_test(f) { f(Nil) } pub fn main() {use _discardVar <- use_test()}", find_position_of("_discardVar") .under_last_char() .to_selection() ); } #[test] fn rename_invalid_pattern_assignment() { assert_code_action!( "Rename to the_answer", "pub fn main() { let assert 42 as theAnswer = 42 }", find_position_of("let").select_until(find_position_of("= 42")) ); } #[test] fn rename_invalid_list_pattern() { assert_code_action!( "Rename to the_element", "pub fn main() { let assert [theElement] = [9.4] }", find_position_of("assert").select_until(find_position_of("9.4")) ); } #[test] fn rename_invalid_list_pattern_discard() { assert_code_action!( "Rename to _elem_one", "pub fn main() { let assert [_elemOne] = [False] }", find_position_of("[_elemOne]") .under_char('O') .to_selection() ); } #[test] fn rename_invalid_constructor_pattern() { assert_code_action!( "Rename to inner_value", "pub type Box { Box(Int) } pub fn main() { let Box(innerValue) = Box(203) }", find_position_of("innerValue").to_selection() ); } #[test] fn rename_invalid_constructor_pattern_discard() { assert_code_action!( "Rename to _ignored_inner", "pub type Box { Box(Int) } pub fn main() { let Box(_ignoredInner) = Box(203) }", find_position_of("_").select_until(find_position_of("203")) ); } #[test] fn rename_invalid_tuple_pattern() { assert_code_action!( "Rename to second_value", "pub fn main() { let #(a, secondValue) = #(1, 2) }", find_position_of("secondValue") .select_until(find_position_of("secondValue").under_char('n')) ); } #[test] fn rename_invalid_tuple_pattern_discard() { assert_code_action!( "Rename to _second_value", "pub fn main() { let #(a, _secondValue) = #(1, 2) }", find_position_of("_secondValue") .under_char('_') .select_until(find_position_of("#(1, 2)")) ); } #[test] fn rename_invalid_bit_array_pattern() { assert_code_action!( "Rename to bit_value", "pub fn main() { let assert <> = <<73>> }", find_position_of("<<").select_until(find_position_of(">>")) ); } #[test] fn rename_invalid_bit_array_pattern_discard() { assert_code_action!( "Rename to _i_dont_care", "pub fn main() { let assert <<_iDontCare>> = <<97>> }", find_position_of("<<").select_until(find_position_of("Care")) ); } #[test] fn rename_invalid_string_prefix_pattern() { assert_code_action!( "Rename to cool_suffix", r#"pub fn main() { let assert "prefix" <> coolSuffix = "prefix-suffix" }"#, find_position_of("<>").select_until(find_position_of("-suffix")) ); } #[test] fn rename_invalid_string_prefix_pattern_discard() { assert_code_action!( "Rename to _boring_suffix", r#"pub fn main() { let assert "prefix" <> _boringSuffix = "prefix-suffix" }"#, find_position_of("<>").select_until(find_position_of("Suffix")) ); } #[test] fn rename_invalid_string_prefix_pattern_alias() { assert_code_action!( "Rename to the_prefix", r#"pub fn main() { let assert "prefix" as thePrefix <> _suffix = "prefix-suffix" }"#, find_position_of("prefix").select_until(find_position_of("-suffix")) ); } #[test] fn rename_invalid_case_variable() { assert_code_action!( "Rename to twenty_one", "pub fn main() { case 21 { twentyOne -> {Nil} } }", find_position_of("case").select_until(find_position_of("Nil")) ); } #[test] fn rename_invalid_case_variable_discard() { assert_code_action!( "Rename to _twenty_one", "pub fn main() { case 21 { _twentyOne -> {Nil} } }", find_position_of("21").select_until(find_position_of("->")) ); } #[test] fn rename_invalid_type_parameter_name() { assert_code_action!( "Rename to inner_type", "type Wrapper(innerType) {}", find_position_of("innerType").select_until(find_position_of(")")) ); } #[test] fn rename_invalid_type_alias_parameter_name() { assert_code_action!( "Rename to phantom_type", "type Phantom(phantomType) = Int", find_position_of("phantomType").select_until(find_position_of(")")) ); } #[test] fn rename_invalid_function_type_parameter_name() { assert_code_action!( "Rename to some_type", "fn identity(value: someType) { value }", find_position_of("someType").select_until(find_position_of(")")) ); } #[test] fn test_convert_assert_result_to_case() { assert_code_action!( CONVERT_TO_CASE, "pub fn main() { let assert Ok(value) = Ok(1) }", find_position_of("assert").select_until(find_position_of("assert").under_char('r')), ); } #[test] fn test_convert_let_assert_to_case_indented() { assert_code_action!( CONVERT_TO_CASE, "pub fn main() { { let assert Ok(value) = Ok(1) } }", find_position_of("Ok").to_selection() ); } #[test] fn test_convert_let_assert_to_case_multi_variables() { assert_code_action!( CONVERT_TO_CASE, "pub fn main() { let assert [var1, var2, _var3, var4] = [1, 2, 3, 4] }", find_position_of("var1").select_until(find_position_of("_var").under_last_char()) ); } #[test] fn test_convert_let_assert_to_case_discard() { assert_code_action!( CONVERT_TO_CASE, "pub fn main() { let assert [_elem] = [6] }", find_position_of("assert").select_until(find_position_of("[6]").under_last_char()), ); } #[test] fn test_convert_let_assert_to_case_no_variables() { assert_code_action!( CONVERT_TO_CASE, "pub fn main() { let assert [] = [] }", find_position_of("[]").to_selection(), ); } #[test] fn test_convert_let_assert_alias_to_case() { assert_code_action!( CONVERT_TO_CASE, "pub fn main() { let assert 10 as ten = 10 }", find_position_of("as").select_until(find_position_of("ten")), ); } #[test] fn test_convert_let_assert_tuple_to_case() { assert_code_action!( CONVERT_TO_CASE, "pub fn main() { let assert #(first, 10, third) = #(5, 10, 15) } ", find_position_of("let").to_selection(), ); } #[test] fn test_convert_let_assert_bit_array_to_case() { assert_code_action!( CONVERT_TO_CASE, "pub fn main() { let assert <> = <<73, 98>> }", find_position_of("bits").select_until(find_position_of("2")), ); } #[test] fn test_convert_let_assert_string_prefix_to_case() { assert_code_action!( CONVERT_TO_CASE, r#"pub fn main() { let assert "_" <> thing = "_Hello" }"#, find_position_of("_").to_selection() ); } #[test] fn test_convert_let_assert_string_prefix_pattern_alias_to_case() { assert_code_action!( CONVERT_TO_CASE, r#"pub fn main() { let assert "123" as one_two_three <> rest = "123456" }"#, find_position_of("123").select_until(find_position_of("123456")), ); } #[test] fn test_convert_inner_let_assert_to_case() { assert_code_action!( CONVERT_TO_CASE, r#"pub fn main() { let assert [wibble] = { let assert Ok(wobble) = { Ok(1) } [wobble] } }"#, find_position_of("wobble").under_char('l').to_selection() ); } #[test] fn test_convert_outer_let_assert_to_case() { assert_code_action!( CONVERT_TO_CASE, r#"pub fn main() { let assert [wibble] = { let assert Ok(wobble) = { Ok(1) } [wobble] } }"#, find_position_of("wibble") .under_char('i') .select_until(find_position_of("= {")), ); } #[test] fn test_convert_let_assert_with_message_to_case() { assert_code_action!( CONVERT_TO_CASE, r#" pub fn expect(value, message) { let assert Ok(inner) = value as message inner } "#, find_position_of("assert").select_until(find_position_of("=")), ); } #[test] fn test_convert_assert_custom_type_with_label_shorthands_to_case() { assert_code_action!( CONVERT_TO_CASE, " pub type Wibble { Wibble(arg: Int, arg2: Float) } pub fn main() { let assert Wibble(arg2:, ..) = Wibble(arg: 1, arg2: 1.0) } ", find_position_of("arg2:,").select_until(find_position_of("1.0")), ); } #[test] fn test_convert_assert_does_not_appear_if_the_entire_module_is_selected() { assert_no_code_actions!( CONVERT_TO_CASE, " pub type Wibble { Wibble(arg: Int, arg2: Float) } pub fn main() { let assert Wibble(arg2:, ..) = Wibble(arg: 1, arg2: 1.0) let assert Wibble(arg2:, ..) = Wibble(arg: 1, arg2: 1.0) } // end ", find_position_of("pub").select_until(find_position_of("// end")), ); } #[test] fn label_shorthand_action_works_on_labelled_call_args() { assert_code_action!( USE_LABEL_SHORTHAND_SYNTAX, r#" pub fn main() { let arg1 = 1 let arg2 = 2 wibble(arg2: arg2, arg1: arg1) } pub fn wibble(arg1 arg1, arg2 arg2) { Nil } "#, find_position_of("wibble") .under_char('i') .select_until(find_position_of("arg1: arg1")), ); } #[test] fn label_shorthand_action_works_on_labelled_constructor_call_args() { assert_code_action!( USE_LABEL_SHORTHAND_SYNTAX, r#" pub fn main() { let arg1 = 1 let arg2 = 2 Wibble(arg2: arg2, arg1: arg1) } pub type Wibble { Wibble(arg1: Int, arg2: Int) } "#, find_position_of("Wibble").select_until(find_position_of("arg1: arg1").under_char(':')), ); } #[test] fn label_shorthand_action_only_applies_to_selected_args() { assert_code_action!( USE_LABEL_SHORTHAND_SYNTAX, r#" pub fn main() { let arg1 = 1 let arg2 = 2 Wibble(arg2: arg2, arg1: arg1) } pub type Wibble { Wibble(arg1: Int, arg2: Int) } "#, find_position_of("Wibble").select_until(find_position_of("arg2: arg2").under_char(':')), ); } #[test] fn label_shorthand_action_works_on_labelled_update_call_args() { assert_code_action!( USE_LABEL_SHORTHAND_SYNTAX, r#" pub fn main() { let arg1 = 1 Wibble(..todo, arg1: arg1) } pub type Wibble { Wibble(arg1: Int, arg2: Int) } "#, find_position_of("..todo").select_until(find_position_of("arg1: arg1").under_last_char()), ); } #[test] fn label_shorthand_action_works_on_labelled_pattern_call_args() { assert_code_action!( USE_LABEL_SHORTHAND_SYNTAX, r#" pub fn main() { let Wibble(arg1: arg1, arg2: arg2) = todo arg1 + arg2 } pub type Wibble { Wibble(arg1: Int, arg2: Int) } "#, find_position_of("let").select_until(find_position_of("todo").under_last_char()), ); } #[test] fn label_shorthand_action_doesnt_come_up_for_arguments_with_different_label() { assert_no_code_actions!( USE_LABEL_SHORTHAND_SYNTAX, r#" pub fn main() { let Wibble(arg1: arg_1, arg2: arg_2) = todo arg_1 + arg_2 } pub type Wibble { Wibble(arg1: Int, arg2: Int) } "#, find_position_of("arg_1").select_until(find_position_of("arg_2").under_last_char()) ); } #[test] fn fill_in_labelled_args_with_some_arguments_already_supplied() { assert_code_action!( FILL_LABELS, r#" pub fn main() { wibble(1,) } pub fn wibble(arg1 arg1, arg2 arg2) { Nil } "#, find_position_of("wibble(").under_char('b').to_selection(), ); } #[test] fn fill_in_labelled_args_with_some_arguments_already_supplied_2() { assert_code_action!( FILL_LABELS, r#" pub fn main() { wibble(arg2: 1) } pub fn wibble(arg1 arg1, arg2 arg2) { Nil } "#, find_position_of("wibble(").to_selection(), ); } #[test] fn fill_in_labelled_args_with_some_arguments_already_supplied_3() { assert_code_action!( FILL_LABELS, r#" pub fn main() { wibble(1, arg3: 2) } pub fn wibble(arg1 arg1, arg2 arg2, arg3 arg3) { Nil } "#, find_position_of("wibble(").to_selection(), ); } #[test] fn fill_in_labelled_args_works_with_regular_function() { assert_code_action!( FILL_LABELS, r#" pub fn main() { wibble() } pub fn wibble(arg1 arg1, arg2 arg2) { Nil } "#, find_position_of("wibble(").to_selection(), ); } #[test] fn fill_in_labelled_args_works_with_record_constructor() { assert_code_action!( FILL_LABELS, r#" pub fn main() { Wibble() } pub type Wibble { Wibble(arg1: Int, arg2: String) } "#, find_position_of("Wibble").select_until(find_position_of("Wibble()").under_last_char()), ); } #[test] fn fill_in_labelled_args_works_with_pattern_and_no_parentheses() { assert_code_action!( FILL_LABELS, r#" pub fn main() { let assert Ok(Wibble) = Wibble(1, "2") } pub type Wibble { Wibble(arg1: Int, arg2: String) } "#, find_position_of("Wibble").select_until(find_position_of("Wibble").under_last_char()), ); } #[test] fn fill_in_labelled_args_works_with_pattern_and_parentheses() { assert_code_action!( FILL_LABELS, r#" pub fn main() { let assert Ok(Wibble()) = Wibble(1, "2") } pub type Wibble { Wibble(arg1: Int, arg2: String) } "#, find_position_of("Wibble").select_until(find_position_of("Wibble").under_last_char()), ); } #[test] fn fill_in_labelled_args_works_with_pattern_and_parentheses_with_spaces() { assert_code_action!( FILL_LABELS, r#" pub fn main() { let assert Ok(Wibble ()) = Wibble(1, "2") } pub type Wibble { Wibble(arg1: Int, arg2: String) } "#, find_position_of("Wibble").select_until(find_position_of("Wibble").under_last_char()), ); } #[test] fn fill_in_labelled_args_works_with_pipes() { assert_code_action!( FILL_LABELS, r#" pub fn main() { 1 |> wibble() } pub fn wibble(arg1 arg1, arg2 arg2) { Nil } "#, find_position_of("wibble()") .under_last_char() .to_selection(), ); } #[test] fn fill_in_labelled_args_works_with_pipes_2() { assert_code_action!( FILL_LABELS, r#" pub fn main() { 1 |> wibble() } pub fn wibble(not_labelled, arg1 arg1, arg2 arg2) { Nil } "#, find_position_of("wibble()") .under_last_char() .to_selection(), ); } #[test] fn fill_in_labelled_args_works_with_use() { assert_code_action!( FILL_LABELS, r#" pub fn main() { use <- wibble() todo } pub fn wibble(arg1 arg1, arg2 arg2) { Nil } "#, find_position_of("wibble(").select_until(find_position_of("wibble()").under_last_char()), ); } #[test] fn fill_in_labelled_args_works_with_use_2() { assert_code_action!( FILL_LABELS, r#" pub fn main() { use <- wibble(arg1: 1) todo } pub fn wibble(arg1 arg1, arg2 arg2, arg3 arg3) { Nil } "#, find_position_of("wibble(").select_until(find_position_of("1").under_last_char()), ); } #[test] fn fill_in_labelled_args_works_with_use_3() { assert_code_action!( FILL_LABELS, r#" pub fn main() { use <- wibble(arg2: 2) todo } pub fn wibble(arg1 arg1, arg2 arg2, arg3 arg3) { Nil } "#, find_position_of("wibble(").select_until(find_position_of("2").under_last_char()), ); } #[test] fn fill_in_labelled_args_selects_innermost_function() { assert_code_action!( FILL_LABELS, r#" pub fn main() { wibble( wibble() ) } pub fn wibble(arg1 arg1, arg2 arg2) { Nil } "#, find_position_of("wibble()") .under_last_char() .to_selection(), ); } #[test] fn fill_labels_uses_variable_in_scope_with_matching_type() { assert_code_action!( FILL_LABELS, r#" pub type Player { Player(name: String, team: String) } pub fn main() { let name = "Priya" Player(team: "BLU") } "#, find_position_of("Player").nth_occurrence(3).to_selection(), ); } #[test] fn fill_labels_falls_back_to_todo_when_type_does_not_match() { assert_code_action!( FILL_LABELS, r#" pub type Player { Player(name: String, team: String) } pub fn main() { let name = 1 Player(team: "BLU") } "#, find_position_of("Player").nth_occurrence(3).to_selection(), ); } #[test] fn fill_labels_uses_function_argument_in_scope() { assert_code_action!( FILL_LABELS, r#" pub type Player { Player(name: String, team: String) } pub fn create_player(name: String) { Player(team: "BLU") } "#, find_position_of("Player").nth_occurrence(3).to_selection(), ); } #[test] fn fill_labels_ignores_variable_defined_after_call() { assert_code_action!( FILL_LABELS, r#" pub type Player { Player(name: String, team: String) } pub fn main() { Player(team: "BLU") let name = "Priya" } "#, find_position_of("Player").nth_occurrence(3).to_selection(), ); } #[test] fn fill_labels_multiple_fields_some_matching() { assert_code_action!( FILL_LABELS, r#" pub type Player { Player(name: String, age: Int, team: String) } pub fn main() { let name = "Priya" let age = "not an int" Player() } "#, find_position_of("Player").nth_occurrence(3).to_selection(), ); } #[test] fn fill_labels_all_fields_have_matching_variables() { assert_code_action!( FILL_LABELS, r#" pub type Player { Player(name: String, age: Int, team: String) } pub fn main() { let name = "Priya" let age = 25 let team = "BLU" Player() } "#, find_position_of("Player").nth_occurrence(3).to_selection(), ); } #[test] fn fill_labels_inside_anonymous_function() { assert_code_action!( FILL_LABELS, r#" pub type Player { Player(name: String, team: String) } pub fn main() { let name = "Outer" let callback = fn() { let name = "Inner" Player(team: "BLU") } } "#, find_position_of("Player").nth_occurrence(3).to_selection(), ); } #[test] fn fill_labels_variable_from_outer_scope_not_shadowed() { assert_code_action!( FILL_LABELS, r#" pub type Player { Player(name: String, team: String) } pub fn main() { let name = "Outer" let callback = fn() { Player(team: "BLU") } } "#, find_position_of("Player").nth_occurrence(3).to_selection(), ); } #[test] fn fill_labels_inside_assignment_with_same_name_as_field() { assert_code_action!( FILL_LABELS, r#" pub type Player { Player(name: String, team: String) } pub fn main() { let name = Player(team: "BLU") } "#, find_position_of("Player").nth_occurrence(3).to_selection(), ); } #[test] fn fill_labels_ignores_underscore_prefixed_variables() { assert_code_action!( FILL_LABELS, r#" pub type Player { Player(name: String, team: String) } pub fn main() { let _name = "Priya" Player(team: "BLU") } "#, find_position_of("Player").nth_occurrence(3).to_selection(), ); } #[test] fn fill_labels_variable_out_of_scope_in_block() { assert_code_action!( FILL_LABELS, r#" pub type Player { Player(name: String, team: String) } pub fn main() { { let name = "Priya" } Player(team: "BLU") } "#, find_position_of("Player").nth_occurrence(3).to_selection(), ); } #[test] fn fill_labels_variable_in_scope_from_case_pattern() { assert_code_action!( FILL_LABELS, r#" pub type Player { Player(name: String, team: String) } pub fn main(result: Result(String, Nil)) { case result { Ok(name) -> Player(team: "BLU") Error(_) -> Player(team: "RED", name: "Unknown") } } "#, find_position_of("Player").nth_occurrence(3).to_selection(), ); } #[test] fn fill_labels_generic_type_matching() { assert_code_action!( FILL_LABELS, r#" pub type Container(a) { Container(value: a, label: String) } pub fn main() { let value = 42 let label = "test" Container() } "#, find_position_of("Container") .nth_occurrence(3) .to_selection(), ); } #[test] fn use_label_shorthand_works_for_nested_calls() { assert_code_action!( USE_LABEL_SHORTHAND_SYNTAX, r#" pub fn wibble(arg arg: Int) -> Int { arg } pub fn main() { let arg = 1 wibble(wibble(arg: arg)) } "#, find_position_of("main").select_until(find_position_of("}").nth_occurrence(2)), ); } #[test] fn use_label_shorthand_works_for_nested_record_updates() { assert_code_action!( USE_LABEL_SHORTHAND_SYNTAX, r#" pub type Wibble { Wibble(arg: Int, arg2: Wobble) } pub type Wobble { Wobble(arg: Int, arg2: String) } pub fn main() { let arg = 1 let arg2 = "a" Wibble(..todo, arg2: Wobble(arg: arg, arg2: arg2)) } "#, find_position_of("todo").select_until(find_position_of("arg2: arg2")), ); } #[test] fn use_label_shorthand_works_for_nested_patterns() { assert_code_action!( USE_LABEL_SHORTHAND_SYNTAX, r#" pub type Wibble { Wibble(arg: Int, arg2: Wobble) } pub type Wobble { Wobble(arg: Int, arg2: String) } pub fn main() { let Wibble(arg2: Wobble(arg: arg, arg2: arg2), ..) = todo } "#, find_position_of("main").select_until(find_position_of("todo")), ); } #[test] fn use_label_shorthand_works_for_alternative_patterns() { assert_code_action!( USE_LABEL_SHORTHAND_SYNTAX, r#" pub type Wibble { Wibble(arg: Int, arg2: String) } pub fn main() { case Wibble(1, "wibble") { Wibble(arg2: arg2, ..) | Wibble(arg: 1, arg2: arg2) -> todo } } "#, find_position_of("main").select_until(find_position_of("todo")), ); } #[test] fn test_assign_unused_result() { assert_code_action!( ASSIGN_UNUSED_RESULT, r#" pub fn main() { let x = 1 Ok(x) Nil } "#, find_position_of("Ok").select_until(find_position_of("(x)")), ); } #[test] fn test_assign_unused_result_in_block() { assert_code_action!( ASSIGN_UNUSED_RESULT, r#" pub fn main() { { let x = 1 Ok(x) Nil } Nil } "#, find_position_of("Ok").select_until(find_position_of("(x)")), ); } #[test] fn test_assign_unused_result_on_block_start() { assert_code_action!( ASSIGN_UNUSED_RESULT, r#" pub fn main() { { let x = 1 Ok(x) Ok(x) } Nil } "#, find_position_of("{").nth_occurrence(2).to_selection() ); } #[test] fn test_assign_unused_result_on_block_end() { assert_code_action!( ASSIGN_UNUSED_RESULT, r#" pub fn main() { { let x = 1 Ok(x) Ok(x) } Nil } "#, find_position_of("}").to_selection() ); } #[test] #[should_panic(expected = "No action with the given title")] fn test_assign_unused_result_inside_block() { assert_code_action!( ASSIGN_UNUSED_RESULT, r#" pub fn main() { { let x = 1 Nil Ok(x) } } "#, find_position_of("Ok").select_until(find_position_of("(x)")) ); } #[test] fn test_assign_unused_result_only_first_action() { assert_code_action!( ASSIGN_UNUSED_RESULT, r#" pub fn main() { let x = 1 Ok(x) Ok(x) Nil } "#, find_position_of("Ok").select_until(find_position_of("(x)")) ); } #[test] #[should_panic(expected = "No action with the given title")] fn test_assign_unused_result_not_on_return_value() { assert_code_action!( ASSIGN_UNUSED_RESULT, r#" pub fn main() { let x = 1 Ok(x) } "#, find_position_of("Ok").select_until(find_position_of("(x)")) ); } #[test] #[should_panic(expected = "No action with the given title")] fn test_assign_unused_result_not_on_return_value_in_block() { assert_code_action!( ASSIGN_UNUSED_RESULT, r#" pub fn main() { let _ = { let x = 1 Ok(x) } Nil }"#, find_position_of("Ok").select_until(find_position_of("(x)")) ); } #[test] fn test_import_module_from_function() { let src = " pub fn main() { result.is_ok() } "; assert_code_action!( "Import `result`", TestProject::for_source(src).add_hex_module("result", "pub fn is_ok() {}"), find_position_of("result").select_until(find_position_of(".")) ); } #[test] fn test_import_path_module_from_function() { let src = r#" pub fn main() { io.println("Hello, world!") } "#; assert_code_action!( "Import `gleam/io`", TestProject::for_source(src) .add_hex_module("gleam/io", "pub fn println(message: String) {}"), find_position_of("io").select_until(find_position_of(".")) ); } #[test] fn test_import_module_from_type() { let src = "type Wobble = wibble.Wubble"; assert_code_action!( "Import `mod/wibble`", TestProject::for_source(src).add_hex_module("mod/wibble", "pub type Wubble { Wubble }"), find_position_of("wibble").select_until(find_position_of(".")) ); } #[test] fn test_import_module_from_constructor() { let src = " pub fn main() { let value = values.Value(10) } "; assert_code_action!( "Import `values`", TestProject::for_source(src).add_hex_module("values", "pub type Value { Value(Int) }"), find_position_of("values").select_until(find_position_of(".")) ); } #[test] fn test_rename_module_for_imported() { let src = r#" import gleam/io pub fn main() { i.println("Hello, world!") } "#; assert_code_action!( "Did you mean `io`", TestProject::for_source(src) .add_hex_module("gleam/io", "pub fn println(message: String) {}"), find_position_of("i.").select_until(find_position_of("println")) ); } #[test] fn test_import_similar_module() { let src = " pub fn main() { reult.is_ok() } "; assert_code_action!( "Import `result`", TestProject::for_source(src).add_hex_module("result", "pub fn is_ok() {}"), find_position_of("reult").select_until(find_position_of(".")) ); } #[test] fn test_no_action_to_import_module_without_value() { // The language server should not suggest a code action // to import a module if it doesn't have a value with // the same name as we are trying to access let src = " pub fn main() { io.hello_world() } "; let title = "Import `io`"; assert_no_code_actions!( title, TestProject::for_source(src).add_hex_module("io", "pub fn println() {}"), find_position_of("io").select_until(find_position_of(".")) ); } #[test] fn test_no_action_to_import_module_without_type() { let src = "type Name = int.String"; let title = "Import `int`"; assert_no_code_actions!( title, TestProject::for_source(src).add_hex_module("int", ""), find_position_of("int").select_until(find_position_of(".")) ); } #[test] fn test_no_action_to_import_module_with_private_value() { // The language server should not suggest a code action // to import a module if the value we are trying to // access is private. let src = " pub fn main() { mod.internal() } "; let title = "Import `mod`"; assert_no_code_actions!( title, TestProject::for_source(src).add_hex_module("mod", "fn internal() {}"), find_position_of("mod").select_until(find_position_of(".")) ); } #[test] fn test_no_action_to_import_module_with_private_type() { let src = "type T = module.T"; let title = "Import `module`"; assert_no_code_actions!( title, TestProject::for_source(src).add_hex_module("module", "type T { T }"), find_position_of("module").select_until(find_position_of(".")) ); } #[test] fn test_no_action_to_import_module_with_constructor_named_same_as_type() { let src = "type NotAType = shapes.Rectangle"; let title = "Import `shapes`"; assert_no_code_actions!( title, TestProject::for_source(src) .add_hex_module("shapes", "pub type Shape { Rectangle, Circle }"), find_position_of("shapes").select_until(find_position_of(".")) ); } #[test] fn add_missing_patterns_bool() { assert_code_action!( ADD_MISSING_PATTERNS, " pub fn main(bool: Bool) { case bool {} } ", find_position_of("case").select_until(find_position_of("bool {")) ); } #[test] fn add_missing_patterns_custom_type() { assert_code_action!( ADD_MISSING_PATTERNS, " type Wibble { Wibble Wobble Wubble } pub fn main(wibble: Wibble) { case wibble { Wobble -> Nil } } ", find_position_of("case").select_until(find_position_of("wibble {")) ); } #[test] fn add_missing_patterns_tuple() { assert_code_action!( ADD_MISSING_PATTERNS, " pub fn main(two_at_once: #(Bool, Result(Int, Nil))) { case two_at_once { #(False, Error(_)) -> Nil } } ", find_position_of("case").select_until(find_position_of("two_at_once {")) ); } #[test] fn add_missing_patterns_list() { assert_code_action!( ADD_MISSING_PATTERNS, " pub fn main() { let list = [1, 2, 3] case list { [a, b, c, 4 as d] -> d } } ", find_position_of("case").select_until(find_position_of("list {")) ); } #[test] fn add_missing_patterns_infinite() { assert_code_action!( ADD_MISSING_PATTERNS, r#" pub fn main() { let value = 3 case value { 1 -> "one" 2 -> "two" 3 -> "three" } } "#, find_position_of("case").select_until(find_position_of("value {")) ); } #[test] fn add_missing_patterns_multi() { assert_code_action!( ADD_MISSING_PATTERNS, r#" pub fn main(a: Bool) { let b = 1 case a, b { } } "#, find_position_of("case").select_until(find_position_of("b {")) ); } #[test] fn add_missing_patterns_inline() { // Ensure we correctly detect the indentation, if the case expression // does not start at the beginning of the line assert_code_action!( ADD_MISSING_PATTERNS, r#" pub fn main(a: Bool) { let value = case a {} } "#, find_position_of("case").select_until(find_position_of("a {")) ); } #[test] fn import_module_from_pattern() { let src = " pub fn main(res) { case res { result.Ok(_) -> Nil result.Error(_) -> Nil } } "; assert_code_action!( "Import `result`", TestProject::for_source(src) .add_hex_module("result", "pub type Result(v, e) { Ok(v) Error(e) }"), find_position_of("result").select_until(find_position_of(".")) ); } #[test] fn annotate_function() { assert_code_action!( ADD_ANNOTATIONS, r#" pub fn add_one(thing) { thing + 1 } "#, find_position_of("fn").select_until(find_position_of("(")) ); } #[test] fn annotate_function_with_annotated_return_type() { assert_code_action!( ADD_ANNOTATION, r#" pub fn add_one(thing) -> Int { thing + 1 } "#, find_position_of("fn").select_until(find_position_of("(")) ); } #[test] fn annotate_function_with_partially_annotated_parameters() { assert_code_action!( ADD_ANNOTATION, r#" pub fn add(a: Float, b) -> Float { a +. b } "#, find_position_of("fn").select_until(find_position_of("(")) ); } #[test] fn no_code_action_for_fully_annotated_function() { assert_no_code_actions!( ADD_ANNOTATION | ADD_ANNOTATIONS, r#" pub fn do_a_thing(a: Int, b: Float) -> String { todo } "#, find_position_of("fn").select_until(find_position_of("(")) ); } #[test] fn annotate_anonymous_function() { assert_code_action!( ADD_ANNOTATIONS, r#" pub fn add_curry(a) { fn(b) { a + b } } "#, find_position_of("fn(").select_until(find_position_of("b)")) ); } #[test] fn annotate_anonymous_function_with_annotated_return_type() { assert_code_action!( ADD_ANNOTATION, r#" pub fn add_curry(a) { fn(b) -> Int { a + b } } "#, find_position_of("fn(").select_until(find_position_of("b)")) ); } #[test] fn annotate_anonymous_function_with_partially_annotated_parameters() { assert_code_action!( ADD_ANNOTATIONS, r#" pub fn main() { fn(a, b: Int, c) { a + b + c } } "#, find_position_of("fn(").select_until(find_position_of("c)")) ); } #[test] fn no_code_action_for_fully_annotated_anonymous_function() { assert_no_code_actions!( ADD_ANNOTATION | ADD_ANNOTATIONS, r#" pub fn main() { fn(a: Int, b: Int) -> Int { a - b } } "#, find_position_of("fn(").select_until(find_position_of("Int)")) ); } #[test] fn annotate_use() { assert_code_action!( ADD_ANNOTATIONS, r#" pub fn wibble(wobble: fn(Int, Int) -> Int) { wobble(1, 2) } pub fn main() { use a, b <- wibble a + b } "#, find_position_of("use").select_until(find_position_of("<-")) ); } #[test] fn annotate_use_with_partially_annotated_parameters() { assert_code_action!( ADD_ANNOTATION, r#" pub fn wibble(wobble: fn(Int, Int) -> Int) { wobble(1, 2) } pub fn main() { use a: Int, b <- wibble a + b } "#, find_position_of("use").select_until(find_position_of("<-")) ); } #[test] fn no_code_action_for_fully_annotated_use() { assert_no_code_actions!( ADD_ANNOTATION | ADD_ANNOTATIONS, r#" pub fn wibble(wobble: fn(Int, Int) -> Int) { wobble(1, 2) } pub fn main() { use a: Int, b: Int <- wibble a + b } "#, find_position_of("use").select_until(find_position_of("<-")) ); } #[test] fn annotate_constant() { assert_code_action!( ADD_ANNOTATION, r#" pub const my_constant = 20 "#, find_position_of("const").select_until(find_position_of("=")) ); } #[test] fn no_code_action_for_annotated_constant() { assert_no_code_actions!( ADD_ANNOTATION | ADD_ANNOTATIONS, r#" pub const PI: Float = 3.14159 "#, find_position_of("const").select_until(find_position_of("=")) ); } #[test] fn annotate_local_variable() { assert_code_action!( ADD_ANNOTATION, r#" pub fn main() { let my_value = 10 } "#, find_position_of("let").select_until(find_position_of("=")) ); } #[test] fn annotate_local_variable_with_pattern() { assert_code_action!( ADD_ANNOTATION, r#" type Wibble { Wibble(a: Int, b: Int, c: Int) } pub fn main() { let Wibble(a, b, c) = Wibble(1, 2, 3) } "#, find_position_of("let").select_until(find_position_of("=")) ); } #[test] fn annotate_local_variable_with_pattern2() { assert_code_action!( ADD_ANNOTATION, r#" pub fn main(values) { let #(left, right) = values } "#, find_position_of("let").select_until(find_position_of("=")) ); } #[test] fn annotate_local_variable_let_assert() { assert_code_action!( ADD_ANNOTATION, r#" pub fn fallible() -> Result(Int, Nil) { todo } pub fn main() { let assert Ok(value) = fallible() } "#, find_position_of("let").select_until(find_position_of("=")) ); } #[test] fn annotate_nested_local_variable() { assert_code_action!( ADD_ANNOTATION, r#" pub fn main() { let a = { let b = 10 b + 1 } } "#, find_position_of("let b").select_until(find_position_of("b =")) ); } #[test] fn no_code_action_for_annotated_local_variable() { assert_no_code_actions!( ADD_ANNOTATION | ADD_ANNOTATIONS, r#" pub fn main() { let typed: Int = 1.2 } "#, find_position_of("let").select_until(find_position_of("=")) ); } #[test] fn adding_annotations_correctly_prints_type_variables() { assert_code_action!( ADD_ANNOTATIONS, r#" pub fn map_result(input, function) { case input { Ok(value) -> Ok(function(value)) Error(error) -> Error(error) } } "#, find_position_of("fn").select_until(find_position_of("(")) ); } #[test] fn add_multiple_annotations() { assert_code_action!( ADD_ANNOTATIONS, r#" pub const my_constant = 20 pub fn add_my_constant(value) { let result = value + my_constant result } "#, find_position_of("pub const").select_until(find_position_of("}")) ); } #[test] fn different_annotations_create_compatible_type_variables() { assert_code_action!( ADD_ANNOTATIONS, r#" pub fn do_generic_things(a, b) { let a_value = a let b_value = b let other_value = a_value } "#, find_position_of("let a_value").select_until(find_position_of("}")) ); } #[test] fn adding_annotations_prints_type_variable_names() { assert_code_action!( ADD_ANNOTATIONS, r#" pub fn do_generic_things(a: type_a, b: type_b) { let a_value = a let b_value = b let other_value = a_value } "#, find_position_of("let a_value").select_until(find_position_of("}")) ); } #[test] fn adding_annotations_prints_contextual_types() { assert_code_action!( ADD_ANNOTATION, r#" pub type IntAlias = Int pub fn main() { let value = 20 } "#, find_position_of("let").select_until(find_position_of("value")) ); } #[test] fn adding_annotations_prints_contextual_types2() { assert_code_action!( ADD_ANNOTATION, r#" pub type Result pub fn main() { let value = Ok(12) } "#, find_position_of("let").select_until(find_position_of("=")) ); } #[test] fn adding_annotations_prints_contextual_types3() { let src = r#" import wibble pub fn main() { let value = wibble.Wibble } "#; assert_code_action!( ADD_ANNOTATION, TestProject::for_source(src).add_hex_module("wibble", "pub type Wibble { Wibble }"), find_position_of("let").select_until(find_position_of("=")) ); } #[test] fn add_annotation_triggers_on_function_curly_brace() { assert_code_action!( ADD_ANNOTATION, "pub fn main() { 1 }", find_position_of("{").to_selection(), ); } #[test] fn add_annotation_triggers_on_empty_space_before_function_curly_brace() { assert_code_action!( ADD_ANNOTATION, "pub fn main() { 1 }", find_position_of(" ").nth_occurrence(3).to_selection(), ); } #[test] fn adding_annotations_prints_contextual_types4() { let src = r#" import wibble as wobble pub fn main() { let value = wobble.Wibble } "#; assert_code_action!( ADD_ANNOTATION, TestProject::for_source(src).add_hex_module("wibble", "pub type Wibble { Wibble }"), find_position_of("let").select_until(find_position_of("=")) ); } #[test] fn adding_annotations_prints_contextual_types5() { let src = r#" import wibble.{type Wibble as Wobble} pub fn main() { let value = wibble.Wibble } "#; assert_code_action!( ADD_ANNOTATION, TestProject::for_source(src).add_hex_module("wibble", "pub type Wibble { Wibble }"), find_position_of("let").select_until(find_position_of("=")) ); } #[test] // https://github.com/gleam-lang/gleam/issues/3789 fn no_code_actions_to_add_annotations_for_pipe() { assert_no_code_actions!( ADD_ANNOTATION | ADD_ANNOTATIONS, r#" fn do_something(a: Int) { a } pub fn main() { 10 |> do_something } "#, find_position_of("10").select_until(find_position_of("|>")) ); } #[test] // https://github.com/gleam-lang/gleam/issues/3789#issuecomment-2455805734 fn add_correct_type_annotation_for_non_variable_use() { assert_code_action!( ADD_ANNOTATION, r#" fn usable(f) { f(#(1, 2)) } pub fn main() { use #(a, b) <- usable a + b } "#, find_position_of("use").select_until(find_position_of("b)")) ); } #[test] fn test_qualified_to_unqualified_import_basic_with_argument() { let src = r#" import option pub fn main() { option.Some(1) } "#; assert_code_action!( "Unqualify option.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of(".Some").select_until(find_position_of("(1)")), ); } #[test] fn test_qualified_to_unqualified_import_basic_record_without_argument() { let src = r#" import wobble pub fn main() { wobble.Wibble } "#; assert_code_action!( "Unqualify wobble.Wibble", TestProject::for_source(src).add_hex_module("wobble", "pub type Wobble { Wibble }"), find_position_of(".W").select_until(find_position_of("ibble")) ); } #[test] fn test_qualified_to_unqualified_import_custom_type_record_declaration() { let src = r#" import wobble pub type Wibble { Wibble(wibble: wobble.Wobble) } "#; assert_code_action!( "Unqualify wobble.Wobble", TestProject::for_source(src).add_hex_module("wobble", "pub type Wobble { Wibble }"), find_position_of(".").select_until(find_position_of("Wobble")) ); } #[test] fn test_qualified_to_unqualified_import_basic_type_without_argument() { let src = r#" import wobble pub fn identity(x: wobble.Wobble) -> wobble.Wobble { x } "#; assert_code_action!( "Unqualify wobble.Wobble", TestProject::for_source(src).add_hex_module("wobble", "pub type Wobble { Wibble }"), find_position_of(".").select_until(find_position_of("Wobble")) ); } #[test] fn test_qualified_to_unqualified_record_value_constructor_module_name() { let src = r#" import option pub fn main() { option.Some(1) } "#; assert_code_action!( "Unqualify option.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of("option").nth_occurrence(2).to_selection() ); } #[test] fn test_qualified_to_unqualified_import_basic_multiple() { let src = r#" import option pub fn main() { option.Some(1) option.Some(1) todo } "#; assert_code_action!( "Unqualify option.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of(".Some").select_until(find_position_of("(1)")), ); } #[test] fn test_qualified_to_unqualified_import_when_unqualified_exists() { let src = r#" import option.{Some} pub fn main() { option.Some(1) } "#; assert_code_action!( "Unqualify option.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of(".Some").select_until(find_position_of("(1)")), ); } #[test] fn test_qualified_to_unqualified_import_with_comma() { let src = r#" import option.{None, } pub fn main() { option.Some(1) } "#; assert_code_action!( "Unqualify option.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of(".Some").select_until(find_position_of("(1)")), ); } #[test] fn test_qualified_to_unqualified_import_with_comma_pos_not_end() { let src = r#" import option.{None, } as opt pub fn main() { opt.Some(1) } "#; assert_code_action!( "Unqualify opt.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of(".Some").select_until(find_position_of("(1)")), ); } #[test] fn test_qualified_to_unqualified_import_different_constructors() { let src = r#" import option pub fn main() { option.Some(1) option.None }"#; assert_code_action!( "Unqualify option.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of(".Some").select_until(find_position_of("(1)")), ); } #[test] fn test_qualified_to_unqualified_import_no_action_when_already_unqualified() { let src = r#" import option.{Some, None} pub fn main() { Some(1) Some(1) todo } "#; let title = "Unqualify option.Some"; assert_no_code_actions!( title, TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of("Some(").select_until(find_position_of("1)")), ); } #[test] fn test_qualified_to_unqualified_import_with_alias() { let src = r#" import option as opt pub fn main() { opt.Some(1) } "#; assert_code_action!( "Unqualify opt.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of("opt.Some").select_until(find_position_of("(1)")), ); } #[test] fn test_qualified_to_unqualified_import_with_alias_multiple() { let src = r#" import option as opt pub fn main() { opt.Some(1) opt.Some(1) } pub fn identity(x: opt.Option(Int)) -> opt.Option(Int) { opt.Some(1) x } "#; assert_code_action!( "Unqualify opt.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of("opt.Some").select_until(find_position_of("(1)")), ); } #[test] fn test_qualified_to_unqualified_import_multiple_imports() { let src = r#" import option import wobble pub fn main() { option.Some(2) wobble.Wibble(1) } "#; assert_code_action!( "Unqualify wobble.Wibble", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }") .add_hex_module("wobble", "pub type Wobble { Wibble(Int)} "), find_position_of(".Wibble").select_until(find_position_of("(1)")), ); } #[test] fn test_qualified_to_unqualified_import_in_case_with_argument() { let src = r#" import option pub fn main(x) { case option.Some(1) { option.Some(value) -> value option.None -> 0 } } "#; assert_code_action!( "Unqualify option.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of(".Some(").select_until(find_position_of("(1)")) ); } #[test] fn test_qualified_to_unqualified_import_in_case_without_argument() { let src = r#" import wobble pub fn main() { case wobble.Wibble { wobble.Wibble -> 1 wobble.Wubble(1) -> 2 } } "#; assert_code_action!( "Unqualify wobble.Wibble", TestProject::for_source(src) .add_hex_module("wobble", "pub type Wobble { Wibble Wubble(Int) }"), find_position_of(".W").select_until(find_position_of("ibble")) ); } #[test] fn test_qualified_to_unqualified_import_in_pattern() { let src = r#" import option pub fn main() -> Int { case option.Some(1) { option.Some(value) -> value option.None -> 0 } } "#; assert_code_action!( "Unqualify option.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of(".Some(va").select_until(find_position_of("lue)")), ); } #[test] fn test_qualified_to_unqualified_import_in_pattern_without_argument() { let src = r#" import wobble pub fn main() { case wobble.Wibble { wobble.Wibble -> 1 wobble.Wubble(1) -> 2 } let wob = wobble.Wibble todo } "#; assert_code_action!( "Unqualify wobble.Wibble", TestProject::for_source(src) .add_hex_module("wobble", "pub type Wobble { Wibble Wubble(Int) }"), find_position_of("wobble.W").select_until(find_position_of("ibble")) ); } #[test] fn test_qualified_to_unqualified_import_type() { let src = r#" import option pub fn main(x) -> option.Option(Int) { option.Some(1) } "#; assert_code_action!( "Unqualify option.Option", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of(".Option").select_until(find_position_of("(Int)")), ); } #[test] fn test_qualified_to_unqualified_import_nested_type_outer() { let src = r#" import option import wobble pub fn main(x) -> option.Option(wobble.Wibble) { todo } "#; assert_code_action!( "Unqualify option.Option", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }") .add_hex_module("wobble", "pub type Wibble { Wobble(Int) }"), find_position_of(".O").select_until(find_position_of("ption(")), ); } #[test] fn test_qualified_to_unqualified_import_nested_constructor_outer() { let src = r#" import option import wobble pub fn main(x) -> option.Option(wobble.Wibble) { option.Some(wobble.Wobble(1)) } "#; assert_code_action!( "Unqualify option.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }") .add_hex_module("wobble", "pub type Wibble { Wobble(Int) }"), find_position_of(".S").select_until(find_position_of("ome(")), ); } #[test] fn test_qualified_to_unqualified_import_nested_constructor_inner() { let src = r#" import option import wobble pub fn main(x) -> option.Option(wobble.Wibble) { option.Some(wobble.Wobble(1)) } "#; assert_code_action!( "Unqualify wobble.Wobble", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }") .add_hex_module("wobble", "pub type Wibble { Wobble(Int) }"), find_position_of(".Wobble").select_until(find_position_of("(1)")), ); } #[test] fn test_qualified_to_unqualified_import_nested_type_inner() { let src = r#" import option import wobble pub fn main(x) -> option.Option(wobble.Wibble) { todo } "#; assert_code_action!( "Unqualify wobble.Wibble", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }") .add_hex_module("wobble", "pub type Wibble { Wobble(Int) }"), find_position_of("wobble.").select_until(find_position_of("Wibble")), ); } #[test] fn test_qualified_to_unqualified_aliased_type() { let src = r#" import wobble pub fn main(x) -> wobble.Wibble(a) { todo } "#; assert_code_action!( "Unqualify wobble.Wibble", TestProject::for_source(src).add_hex_module("wobble", "pub type Wibble(a) = List(a)"), find_position_of("wobble.").select_until(find_position_of("Wibble")), ); } #[test] fn test_qualified_to_unqualified_aliased_type_with_multiple_imports() { let src = r#" import other/wobble as other import wibble/wobble pub fn main(x) -> wobble.Wibble(a) { todo } "#; assert_code_action!( "Unqualify wobble.Wibble", TestProject::for_source(src) .add_hex_module("wibble/wobble", "pub type Wibble(a) = List(a)") .add_hex_module("other/wobble", "pub type Wibble(a) = List(a)"), find_position_of("wobble.").select_until(find_position_of("Wibble")), ); } #[test] fn test_qualified_aliased_to_unqualified_aliased_type() { let src = r#" import wobble as wob pub fn main(x) -> wob.Wibble(a) { todo } "#; assert_code_action!( "Unqualify wob.Wibble", TestProject::for_source(src).add_hex_module("wobble", "pub type Wibble(a) = List(a)"), find_position_of("wob.").select_until(find_position_of("Wibble")), ); } #[test] fn test_qualified_to_unqualified_import_below_constructor() { let src = r#" pub fn main() { option.Some(1) } import option "#; assert_code_action!( "Unqualify option.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of(".Some").select_until(find_position_of("(1)")), ); } #[test] fn test_qualified_to_unqualified_import_between_constructors() { let src = r#" pub fn main() { option.Some(1) } import option pub fn identity(x: option.Option(Int)) -> option.Option(Int) { option.Some(1) x } "#; assert_code_action!( "Unqualify option.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of(".Some").select_until(find_position_of("(1)")), ); } #[test] fn test_qualified_to_unqualified_import_multiple_line() { let src = r#" import option.{ type Option, None, } pub fn main() { option.Some(1) } "#; assert_code_action!( "Unqualify option.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of(".Some").select_until(find_position_of("(1)")), ); } #[test] fn test_qualified_to_unqualified_import_multiple_line_bad_format_with_trailing_comma() { let src = r#" import option.{type Option, None, } pub fn main() { option.Some(1) } "#; assert_code_action!( "Unqualify option.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of(".Some").select_until(find_position_of("(1)")), ); } #[test] fn test_qualified_to_unqualified_import_multiple_line_bad_format_multiple_whitespace() { let src = r#" import option.{ } pub fn main() { option.Some(1) } "#; assert_code_action!( "Unqualify option.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of(".Some").select_until(find_position_of("(1)")), ); } #[test] fn test_qualified_to_unqualified_import_multiple_line_bad_format_without_trailing_comma() { let src = r#" import option.{type Option, None } pub fn main() { option.Some(1) } "#; assert_code_action!( "Unqualify option.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of(".Some").select_until(find_position_of("(1)")), ); } #[test] fn test_qualified_to_unqualified_import_multiple_line_aliased() { let src = r#" import option.{ type Option, None} as opt pub fn main() { opt.Some(1) } "#; assert_code_action!( "Unqualify opt.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of(".Some").select_until(find_position_of("(1)")), ); } #[test] fn test_qualified_to_unqualified_import_in_list_and_tuple() { let src = r#" import option pub fn main() { let list = [option.Some(1), option.None] let tuple = #(option.Some(2), option.None) } "#; assert_code_action!( "Unqualify option.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of("option.Some").select_until(find_position_of("(1)")), ); } #[test] fn test_qualified_to_unqualified_import_multiple_generic_type() { let src = r#" import result pub fn main() -> result.Result(Int, String) { result.Ok(1) } "#; assert_code_action!( "Unqualify result.Result", TestProject::for_source(src) .add_hex_module("result", "pub type Result(a, e) { Ok(a) Error(e) }"), find_position_of(".Result").select_until(find_position_of("(Int")), ); } #[test] fn test_qualified_to_unqualified_import_constructor_as_argument() { let src = r#" import option pub fn main() { option.map(option.Some(1), fn(x) { x + 1 }) } "#; assert_code_action!( "Unqualify option.Some", TestProject::for_source(src).add_hex_module( "option", " pub type Option(v) { Some(v) None } pub fn map(a, f) { todo } " ), find_position_of("option.Some").select_until(find_position_of("(1)")), ); } #[test] fn test_qualified_to_unqualified_import_constructor_different_module_same_type_inner() { let src = r#" import option import opt pub fn main() -> option.Option(opt.Option(Int)) { todo } "#; assert_code_action!( "Unqualify opt.Option", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }") .add_hex_module("opt", "pub type Option(v) { Some(v) None }"), find_position_of("opt.Option").select_until(find_position_of("(Int)")), ); } #[test] fn test_qualified_to_unqualified_import_constructor_different_module_same_type_outer() { let src = r#" import option import opt pub fn main() -> option.Option(opt.Option(Int)) { todo } "#; assert_code_action!( "Unqualify option.Option", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }") .add_hex_module("opt", "pub type Option(v) { Some(v) None }"), find_position_of("option.").select_until(find_position_of("Option(")), ); } #[test] fn test_qualified_to_unqualified_import_constructor_different_module_same_name_inner() { let src = r#" import option import opt pub fn main() { option.Some(opt.Some(1)) todo } "#; assert_code_action!( "Unqualify opt.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }") .add_hex_module("opt", "pub type Option(v) { Some(v) None }"), find_position_of("opt.Some").select_until(find_position_of("(1)")), ); } #[test] fn test_qualified_to_unqualified_import_constructor_different_module_same_name_outer() { let src = r#" import option import opt pub fn main() { option.Some(opt.Some(1)) } "#; assert_code_action!( "Unqualify option.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }") .add_hex_module("opt", "pub type Option(v) { Some(v) None }"), find_position_of("option.").select_until(find_position_of("Some(")), ); } #[test] fn test_qualified_to_unqualified_import_constructor_complex_pattern() { let src = r#" import option pub fn main() { case [option.Some(1), option.None] { [option.None, ..] -> todo [option.Some(_), ..] -> todo _ -> todo } case option.Some(1), option.Some(2) { option.None, option.Some(_) -> todo option.Some(_), option.Some(val) -> todo _ -> todo } } "#; assert_code_action!( "Unqualify option.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of("option.").select_until(find_position_of("Some(")), ); } #[test] fn test_qualified_to_unqualified_import_constructor_constructor_name_exists() { let src = r#" import option.{Some} import opt pub fn main() -> option.Option(opt.Option(Int)) { Some(opt.Some(1)) } "#; let title = "Unqualify opt.Some"; assert_no_code_actions!( title, TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }") .add_hex_module("opt", "pub type Option(v) { Some(v) None }"), find_position_of("opt.").select_until(find_position_of(".Some(")), ); } #[test] fn test_qualified_to_unqualified_import_constructor_constructor_name_exists_below() { let src = r#" import opt pub fn main() -> option.Option(opt.Option(Int)) { Some(opt.Some(1)) } import option.{Some} "#; let title = "Unqualify opt.Some"; assert_no_code_actions!( title, TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }") .add_hex_module("opt", "pub type Option(v) { Some(v) None }"), find_position_of("opt.").select_until(find_position_of(".Some(")), ); } #[test] fn test_qualified_to_unqualified_import_type_constructor_constructor_name_exists() { let src = r#" import option.{type Option} import opt pub fn main() -> Option(opt.Option(Int)) { option.Some(opt.Some(1)) } "#; let title = "Unqualify opt.Option"; assert_no_code_actions!( title, TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }") .add_hex_module("opt", "pub type Option(v) { Some(v) None }"), find_position_of("opt.").select_until(find_position_of(".Option(")), ); } #[test] fn test_qualified_to_unqualified_import_type_constructor_constructor_name_exists_below() { let src = r#" import opt pub fn main() -> Option(opt.Option(Int)) { option.Some(opt.Some(1)) } import option.{type Option} "#; let title = "Unqualify opt.Option"; assert_no_code_actions!( title, TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }") .add_hex_module("opt", "pub type Option(v) { Some(v) None }"), find_position_of("opt.").select_until(find_position_of(".Option(")), ); } #[test] fn test_qualified_to_unqualified_import_in_constant_var() { let src = r#" import option const none = option.None "#; let title = "Unqualify option.None"; assert_code_action!( title, TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of("None").to_selection(), ); } #[test] fn test_qualified_to_unqualified_import_in_constant_record() { let src = r#" import option const some = option.Some(1) "#; let title = "Unqualify option.Some"; assert_code_action!( title, TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of("Some").to_selection(), ); } #[test] fn test_qualified_to_unqualified_import_in_nested_constant() { let src = r#" import option type Result(a) { Result(expected: a, actual: option.Option(a)) } const zero = Result(0, option.None) "#; let title = "Unqualify option.None"; assert_code_action!( title, TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of("None").to_selection(), ); } #[test] fn test_qualified_to_unqualified_import_constant_multiple_occurrences() { let src = r#" import option const a = option.None const b = option.Some(option.None) "#; let title = "Unqualify option.None"; assert_code_action!( title, TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of("None").nth_occurrence(1).to_selection(), ); } #[test] fn test_qualified_to_unqualified_import_not_offered_for_function_in_constant() { let src = r#" import list const mapper = list.map "#; let title = "Unqualify list.map"; assert_no_code_actions!( title, TestProject::for_source(src).add_hex_module("list", "pub fn map(list, f) { todo }"), find_position_of("list.map").to_selection(), ); } #[test] fn test_qualified_to_unqualified_import_not_offered_for_module_constant_in_constant() { let src = r#" import mymath const x = mymath.pi "#; let title = "Unqualify mymath.pi"; assert_no_code_actions!( title, TestProject::for_source(src).add_hex_module("mymath", "pub const pi = 3.14159"), find_position_of("pi").to_selection(), ); } #[test] fn test_qualified_to_unqualified_import_from_constant_also_updates_functions() { let src = r#" import option const default = option.None pub fn get_or_default(value: a) { case value { option.Some(x) -> x option.None -> value } } "#; let title = "Unqualify option.None"; assert_code_action!( title, TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of("None").nth_occurrence(1).to_selection(), ); } #[test] fn test_qualified_to_unqualified_import_from_function_also_updates_constants() { let src = r#" import option const default = option.None pub fn get_or_default(value: a) { case value { option.Some(x) -> x option.None -> value } } "#; let title = "Unqualify option.None"; assert_code_action!( title, TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of("None").nth_occurrence(2).to_selection(), ); } #[test] fn test_unqualified_to_qualified_import_function() { let src = r#" import list.{map} pub fn main() { let identity = map([1, 2, 3], fn(x) { x }) let double = map([1, 2, 3], fn(x) { x * 2 }) } "#; assert_code_action!( "Qualify map as list.map", TestProject::for_source(src).add_hex_module("list", "pub fn map(list, f) { todo }"), find_position_of("map(").select_until(find_position_of("[1, 2, 3]")), ); } #[test] fn test_unqualified_to_qualified_import_constant() { let src = r#" import mymath.{pi} pub fn circle_area(radius: Float) -> Float { pi *. radius *. radius } pub fn circle_circumference(radius: Float) -> Float { 2. *. pi *. radius } "#; assert_code_action!( "Qualify pi as mymath.pi", TestProject::for_source(src).add_hex_module("mymath", "pub const pi = 3.14159"), find_position_of("pi *.").select_until(find_position_of(" radius")), ); } #[test] fn test_unqualified_to_qualified_import_record_constructor() { let src = r#" import user.{type User, User} pub fn create_user(name: String) -> User { User(name: name, id: 1) } "#; assert_code_action!( "Qualify User as user.User", TestProject::for_source(src) .add_hex_module("user", "pub type User { User(name: String, id: Int) }"), find_position_of("User(").select_until(find_position_of("name: name")), ); } #[test] fn test_unqualified_to_qualified_import_after_constructor() { let src = r#" pub fn create_user(name: String) -> User { User(name: name, id: 1) } import user.{type User, User} "#; assert_code_action!( "Qualify User as user.User", TestProject::for_source(src) .add_hex_module("user", "pub type User { User(name: String, id: Int) }"), find_position_of("User(").select_until(find_position_of("name: name")), ); } #[test] fn test_unqualified_to_qualified_import_between_constructors() { let src = r#" pub fn create_user(name: String) -> User { User(name: name, id: 1) } import user.{type User, User} pub fn user_list(users: List(User)) -> List(String) { [User(name: "John", id: 1), User(name: "Jane", id: 2)] } "#; assert_code_action!( "Qualify User as user.User", TestProject::for_source(src) .add_hex_module("user", "pub type User { User(name: String, id: Int) }"), find_position_of("User(").select_until(find_position_of("name: name")), ); } #[test] fn test_unqualified_to_qualified_import_multiple_occurrences() { let src = r#" import list.{map, filter} pub fn process_list(items: List(Int)) -> List(Int) { items |> map(fn(x) { x + 1 }) |> map(fn(x) { x * 2 }) } "#; assert_code_action!( "Qualify map as list.map", TestProject::for_source(src).add_hex_module( "list", "pub fn map(list: List(a), with fun: fn(a) -> b) -> List(b) { todo }" ), find_position_of("|> map").select_until(find_position_of("(fn(x)")), ); } #[test] fn test_unqualified_to_qualified_import_in_pattern_matching() { let src = r#" import result.{type Result, Ok, Error} pub fn process_result(res: Result(Int, String)) -> Int { case res { Ok(value) -> value Error(_) -> 0 } } "#; assert_code_action!( "Qualify Ok as result.Ok", TestProject::for_source(src) .add_hex_module("result", "pub type Result(a, e) { Ok(a) Error(e) }"), find_position_of("Ok(").select_until(find_position_of("value)")), ); } #[test] fn test_unqualified_to_qualified_import_type_annotation() { let src = r#" import option.{type Option, Some} pub fn maybe_increment(x: Option(Int)) -> Option(Int) { case x { Some(value) -> Some(value + 1) _ -> x } } "#; assert_code_action!( "Qualify Option as option.Option", TestProject::for_source(src) .add_hex_module("option", "pub type Option(a) { Some(a) None }"), find_position_of("Opt") .nth_occurrence(2) .select_until(find_position_of("ion(")), ); } #[test] fn test_unqualified_to_qualified_import_nested_function_call() { let src = r#" import list.{map, flatten} import operation.{double} pub fn process_names(names: List(List(Int))) -> List(Int) { names |> flatten |> map(double) } "#; assert_code_action!( "Qualify double as operation.double", TestProject::for_source(src) .add_hex_module( "list", "pub fn map(list: List(a), with fun: fn(a) -> b) -> List(b) { todo } pub fn flatten(lists: List(List(a))) -> List(a) { todo }" ) .add_hex_module("operation", "pub fn double(s: Int) -> Int { todo }"), find_position_of("(dou").select_until(find_position_of("ble)")), ); } #[test] fn test_unqualified_to_qualified_import_with_alias() { let src = r#" import list.{map as transform} pub fn double_list(items: List(Int)) -> List(Int) { transform(items, fn(x) { x * 2 }) } "#; assert_code_action!( "Qualify transform as list.map", TestProject::for_source(src).add_hex_module( "list", "pub fn map(list: List(a), with fun: fn(a) -> b) -> List(b) { todo }" ), find_position_of("transform(").select_until(find_position_of("items,")), ); } #[test] fn test_unqualified_to_qualified_import_with_alias_and_module_alias() { let src = r#" import list.{map as transform} as lst pub fn double_list(items: List(Int)) -> List(Int) { transform(items, fn(x) { x * 2 }) } "#; assert_code_action!( "Qualify transform as lst.map", TestProject::for_source(src).add_hex_module( "list", "pub fn map(list: List(a), with fun: fn(a) -> b) -> List(b) { todo }" ), find_position_of("transform(").select_until(find_position_of("items,")), ); } #[test] fn test_unqualified_to_qualified_import_import_discarded() { let src = r#" import list.{map as transform} as _ pub fn double_list(items: List(Int)) -> List(Int) { transform(items, fn(x) { x * 2 }) } "#; let title = "Qualify transform as list.map"; assert_no_code_actions!( title, TestProject::for_source(src).add_hex_module( "list", "pub fn map(list: List(a), with fun: fn(a) -> b) -> List(b) { todo }" ), find_position_of("transform(").select_until(find_position_of("items,")), ); } #[test] fn test_unqualified_to_qualified_import_bad_formatted_type_constructor() { let src = r#" import option.{type Option, Some} pub fn maybe_increment(x: Option(Int)) -> Option(Int) { case x { Some(value) -> Some(value + 1) _ -> x } } "#; assert_code_action!( "Qualify Option as option.Option", TestProject::for_source(src) .add_hex_module("option", "pub type Option(a) { Some(a) None }"), find_position_of("Opt") .nth_occurrence(2) .select_until(find_position_of("ion(")), ); } #[test] fn test_unqualified_to_qualified_import_bad_formatted_type_constructor_with_alias() { let src = r#" import option.{type Option as Maybe, Some} pub fn maybe_increment(x: Maybe(Int)) -> Maybe(Int) { case x { Some(value) -> Some(value + 1) _ -> x } } "#; assert_code_action!( "Qualify Maybe as option.Option", TestProject::for_source(src) .add_hex_module("option", "pub type Option(a) { Some(a) None }"), find_position_of("May") .nth_occurrence(2) .select_until(find_position_of("be(")), ); } #[test] fn test_unqualified_to_qualified_import_bad_formatted_comma() { let src = r#" import option.{type Option , Some} pub fn maybe_increment(x: Option(Int)) -> Option(Int) { case x { Some(value) -> Some(value + 1) _ -> x } } "#; assert_code_action!( "Qualify Option as option.Option", TestProject::for_source(src) .add_hex_module("option", "pub type Option(a) { Some(a) None }"), find_position_of("Opt") .nth_occurrence(2) .select_until(find_position_of("ion(")), ); } #[test] fn test_unqualified_to_qualified_import_in_list_and_tuple() { let src = r#" import option.{Some} pub fn main() { let list = [Some(1), option.None] let tuple = #(Some(2), option.None) } "#; assert_code_action!( "Qualify Some as option.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of("Some(").select_until(find_position_of("1)")), ); } #[test] fn test_unqualified_to_qualified_import_constructor_complex_pattern() { let src = r#" import option.{None, Some} pub fn main() { case [Some(1), None] { [None, ..] -> todo [Some(_), ..] -> todo _ -> todo } case Some(1), Some(2) { None, Some(_) -> todo Some(_), Some(val) -> todo _ -> todo } } "#; assert_code_action!( "Qualify Some as option.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of("Some(").select_until(find_position_of("1)")), ); } #[test] fn test_unqualified_to_qualified_import_multiple_line_aliased() { let src = r#" import option.{ type Option, None, Some } as opt pub fn main() { Some(1) } "#; assert_code_action!( "Qualify Some as opt.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of("Some") .nth_occurrence(2) .select_until(find_position_of("(1)")), ); } #[test] fn test_unqualified_to_qualified_import_multiple_line_bad_format_without_trailing_comma() { let src = r#" import option.{type Option, Some } pub fn main() { Some(1) } "#; assert_code_action!( "Qualify Some as option.Some", TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of("Some") .nth_occurrence(2) .select_until(find_position_of("(1)")), ); } #[test] fn test_unqualified_to_qualified_import_variable_shadowing() { let src = r#" import wibble.{wobble} pub fn example() { echo wobble let wobble = 1 echo wobble let _ = fn(wobble) { echo wobble } todo } "#; assert_code_action!( "Qualify wobble as wibble.wobble", TestProject::for_source(src).add_hex_module("wibble", "pub fn wobble() { todo }"), find_position_of("wob") .nth_occurrence(2) .select_until(find_position_of("ble").nth_occurrence(3)) ); } #[test] fn test_unqualified_to_qualified_import_in_constant_var() { let src = r#" import option.{None} const none = None "#; let title = "Qualify None as option.None"; assert_code_action!( title, TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of("None").nth_occurrence(2).to_selection(), ); } #[test] fn test_unqualified_to_qualified_import_in_constant_record() { let src = r#" import option.{Some} const some = Some(1) "#; let title = "Qualify Some as option.Some"; assert_code_action!( title, TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of("Some").nth_occurrence(2).to_selection() ); } #[test] fn test_unqualified_to_qualified_import_in_nested_constant() { let src = r#" import option.{type Option, None} type Result(a) { Result(expected: a, actual: Option(a)) } const zero = Result(0, None) "#; let title = "Qualify None as option.None"; assert_code_action!( title, TestProject::for_source(src) .add_hex_module("option", "pub type Option(v) { Some(v) None }"), find_position_of("None").nth_occurrence(2).to_selection(), ); } /* TODO: implement qualified unused location #[test] fn test_remove_unused_qualified_action() { let code = " // test import map.{Map, delete} "; let expected = " // test "; assert_eq!(remove_unused_action(code), expected.to_string()) } #[test] fn test_remove_unused_qualified_partial_action() { let code = " // test import result.{is_ok, is_err} pub fn main() { is_ok } "; let expected = " // test import result.{is_ok} pub fn main() { is_ok } "; assert_eq!(remove_unused_action(code), expected.to_string()) } #[test] fn test_remove_unused_qualified_partial2_action() { let code = " // test import result.{all, is_ok, is_err} pub fn main() { is_ok } "; let expected = " // test import result.{ is_ok} pub fn main() { is_ok } "; assert_eq!(remove_unused_action(code), expected.to_string()) } #[test] fn test_remove_unused_qualified_partial3_action() { let code = " // test import result.{all, is_ok, is_err} as res pub fn main() { is_ok } "; let expected = " // test import result.{ is_ok} as res pub fn main() { is_ok } "; assert_eq!(remove_unused_action(code), expected.to_string()) } */ #[test] fn convert_from_use_expression_with_no_parens() { let src = r#" pub fn main() { use <- wibble todo todo } fn wibble(f) { f() } "#; assert_code_action!( CONVERT_FROM_USE, TestProject::for_source(src), find_position_of("use").select_until(find_position_of("wibble")), ); } #[test] fn convert_from_use_expression_with_empty_parens() { let src = r#" pub fn main() { use <- wibble() todo todo } fn wibble(f) { f() } "#; assert_code_action!( CONVERT_FROM_USE, TestProject::for_source(src), find_position_of("use").to_selection(), ); } #[test] fn convert_from_use_expression_with_parens_and_other_args() { let src = r#" pub fn main() { use <- wibble(1, 2) todo todo } fn wibble(n, m, f) { f() } "#; assert_code_action!( CONVERT_FROM_USE, TestProject::for_source(src), find_position_of("wibble").select_until(find_position_of("1")), ); } #[test] fn convert_from_use_expression_with_single_pattern() { let src = r#" pub fn main() { use a <- wibble(1, 2) todo todo } fn wibble(n, m, f) { f(1) } "#; assert_code_action!( CONVERT_FROM_USE, TestProject::for_source(src), find_position_of("a <-").to_selection(), ); } #[test] fn convert_from_use_expression_with_multiple_patterns() { let src = r#" pub fn main() { use a, b <- wibble(1, 2) todo todo } fn wibble(n, m, f) { f(1, 2) } "#; assert_code_action!( CONVERT_FROM_USE, TestProject::for_source(src), find_position_of("wibble").to_selection(), ); } #[test] fn desugar_nested_use_expressions_picks_inner_under_cursor() { let src = r#" pub fn main() { use a, b <- wibble(1, 2) use a, b <- wibble(a, b) todo } fn wibble(n, m, f) { f(1, 2) } "#; assert_code_action!( CONVERT_FROM_USE, TestProject::for_source(src), find_position_of("use").nth_occurrence(2).to_selection(), ); } #[test] fn convert_from_use_only_triggers_on_the_use_line() { let src = r#" pub fn main() { use a, b <- wibble(1, 2) todo } fn wibble(n, m, f) { f(1, 2) } "#; assert_no_code_actions!( CONVERT_FROM_USE, TestProject::for_source(src), find_position_of("todo").to_selection(), ); } #[test] fn desugar_nested_use_expressions_picks_inner_under_cursor_2() { let src = r#" pub fn main() { use a, b <- wibble(1, 2) use a, b <- wibble(a, b) todo } fn wibble(n, m, f) { f(1, 2) } "#; assert_code_action!( CONVERT_FROM_USE, TestProject::for_source(src), find_position_of("<-").select_until(find_position_of("wibble")), ); } #[test] fn convert_from_use_expression_with_type_annotations() { let src = r#" pub fn main() { use a: Int, b: Int <- wibble(1, 2) todo } fn wibble(n, m, f) { f(1, 2) } "#; assert_code_action!( CONVERT_FROM_USE, TestProject::for_source(src), find_position_of("<-").select_until(find_position_of("wibble")), ); } #[test] fn convert_from_use_expression_doesnt_work_with_complex_patterns() { let src = r#" pub fn main() { use #(a, b), 1 <- wibble(1, 2) todo } fn wibble(n, m, f) { f(todo, todo) } "#; assert_no_code_actions!( CONVERT_FROM_USE, TestProject::for_source(src), find_position_of("<-").select_until(find_position_of("wibble")), ); } #[test] fn convert_from_use_with_labels() { let src = r#" pub fn main() { use a <- wibble(one: 1, two: 2) todo } fn wibble(one _, two _, three f) { f(1) } "#; assert_code_action!( CONVERT_FROM_USE, TestProject::for_source(src), find_position_of("use").to_selection(), ); } #[test] fn convert_from_use_with_labels_2() { let src = r#" pub fn main() { use a <- wibble(1, two: 2) todo } fn wibble(one _, two _, three f) { f(1) } "#; assert_code_action!( CONVERT_FROM_USE, TestProject::for_source(src), find_position_of("use").to_selection(), ); } #[test] fn convert_from_use_with_labels_3() { let src = r#" pub fn main() { use a <- wibble(1, three: 3) todo } fn wibble(one _, two f, three _) { f(1) } "#; assert_code_action!( CONVERT_FROM_USE, TestProject::for_source(src), find_position_of("use").to_selection(), ); } #[test] fn convert_from_use_with_labels_4() { let src = r#" pub fn main() { use a <- wibble(two: 2, three: 3) todo } fn wibble(one f, two _, three _) { f(1) } "#; assert_code_action!( CONVERT_FROM_USE, TestProject::for_source(src), find_position_of("use").to_selection(), ); } #[test] // https://github.com/gleam-lang/gleam/issues/4149 fn convert_from_use_with_trailing_comma() { let src = r#" pub fn main() { use a, b <- wibble(1, 2,) todo } fn wibble(n, m, f) { f(todo, todo) } "#; assert_code_action!( CONVERT_FROM_USE, TestProject::for_source(src), find_position_of("<-").select_until(find_position_of("wibble")), ); } #[test] fn convert_from_use_with_trailing_comma_2() { let src = r#" pub fn main() { use a, b <- wibble( 1, 2, ) todo } fn wibble(n, m, f) { f(todo, todo) } "#; assert_code_action!( CONVERT_FROM_USE, TestProject::for_source(src), find_position_of("<-").select_until(find_position_of("wibble")), ); } #[test] fn convert_from_use_with_trailing_comma_and_label() { let src = r#" pub fn main() { use a, b <- wibble( 1, wibble: 2, ) todo } fn wibble(n, wibble m, wobble f) { f(todo, todo) } "#; assert_code_action!( CONVERT_FROM_USE, TestProject::for_source(src), find_position_of("<-").select_until(find_position_of("wibble")), ); } #[test] fn convert_from_use_multiline_with_no_trailing_comma() { let src = r#" pub fn main() { use a, b <- wibble( 1, 2 ) todo } fn wibble(n, m, f) { f(todo, todo) } "#; assert_code_action!( CONVERT_FROM_USE, TestProject::for_source(src), find_position_of("<-").select_until(find_position_of("wibble")), ); } #[test] fn turn_call_into_use_with_single_line_body() { let src = r#" pub fn main() { wibble(fn(a, b) { todo }) } fn wibble(f) { f(todo, todo) } "#; assert_code_action!( CONVERT_TO_USE, TestProject::for_source(src), find_position_of("wibble").to_selection(), ); } #[test] fn turn_call_into_use_with_fn_with_no_args() { let src = r#" pub fn main() { wibble(fn() { todo }) } fn wibble(f) { f() } "#; assert_code_action!( CONVERT_TO_USE, TestProject::for_source(src), find_position_of("wibble").to_selection(), ); } #[test] fn turn_call_with_multiple_arguments_into_use() { let src = r#" pub fn main() { wibble(1, 2, fn(a) { todo }) } fn wibble(m, n, f) { f(1) } "#; assert_code_action!( CONVERT_TO_USE, TestProject::for_source(src), find_position_of("todo").to_selection(), ); } #[test] fn turn_call_with_multiline_fn_into_use() { let src = r#" pub fn main() { wibble(1, 2, fn(a) { todo case todo { _ -> todo } }) } fn wibble(m, n, f) { f(1) } "#; assert_code_action!( CONVERT_TO_USE, TestProject::for_source(src), find_position_of("1, 2").select_until(find_position_of("fn(a)")), ); } #[test] fn turn_call_with_fn_with_type_annotations_into_use() { let src = r#" pub fn main() { wibble(1, 2, fn(a: Int) { todo }) } fn wibble(m, n, f) { f(1) } "#; assert_code_action!( CONVERT_TO_USE, TestProject::for_source(src), find_position_of("wibble").select_until(find_position_of("a: ")), ); } #[test] fn turn_call_into_use_only_works_on_last_call_in_a_block() { let src = r#" pub fn main() { wibble(10, 20, fn(a) { todo }) wibble(1, 2, fn(a) { todo }) } fn wibble(m, n, f) { f(1) } "#; assert_no_code_actions!( CONVERT_TO_USE, TestProject::for_source(src), find_position_of("10").to_selection(), ); } #[test] fn turn_call_into_use_only_works_on_last_call_in_a_block_2() { let src = r#" pub fn main() { { wibble(10, 20, fn(a) { todo }) wibble(1, 2, fn(a) { todo }) } Nil } fn wibble(m, n, f) { f(1) } "#; assert_no_code_actions!( CONVERT_TO_USE, TestProject::for_source(src), find_position_of("10").to_selection(), ); } #[test] fn turn_call_into_use_with_last_function_in_a_block() { let src = r#" pub fn main() { { wibble(10, 20, fn(a) { todo }) wibble(1, 11, fn(a) { todo }) } Nil } fn wibble(m, n, f) { f(1) } "#; assert_code_action!( CONVERT_TO_USE, TestProject::for_source(src), find_position_of("wibble(1,").select_until(find_position_of("11")), ); } #[test] fn turn_call_into_use_starts_from_innermost_function() { let src = r#" pub fn main() { wibble(10, 20, fn(a) { wibble(30, 40, fn(b) { a + b }) }) } fn wibble(m, n, f) { f(1) } "#; assert_code_action!( CONVERT_TO_USE, TestProject::for_source(src), find_position_of("30").select_until(find_position_of("40")), ); } #[test] fn turn_call_into_use_with_another_use_in_the_way() { let src = r#" pub fn main() { wibble(10, 20, fn(a) { use b <- wibble(30, 40) a + b }) } fn wibble(m, n, f) { f(1) } "#; assert_code_action!( CONVERT_TO_USE, TestProject::for_source(src), find_position_of("use").to_selection(), ); } #[test] fn turn_call_into_use_with_module_function() { let src = r#" import other pub fn main() { other.wibble(10, 20, fn(a) { todo a + b }) } "#; assert_code_action!( CONVERT_TO_USE, TestProject::for_source(src).add_module("other", "pub fn wibble(n, m, f) { todo }"), find_position_of("wibble").to_selection(), ); } #[test] // https://github.com/gleam-lang/gleam/issues/4498 fn turn_call_into_use_with_out_of_order_arguments() { assert_code_action!( CONVERT_TO_USE, r#" pub fn main() { fold(0, over: [], with: fn (a, b) { todo }) } fn fold(over list: List(a), from acc: acc, with fun: fn(acc, a) -> acc) -> acc { todo } "#, find_position_of("fold").to_selection(), ); } #[test] fn inexhaustive_let_result_to_case() { assert_code_action!( CONVERT_TO_CASE, "pub fn main(result) { let Ok(value) = result }", find_position_of("let").select_until(find_position_of("=")), ); } #[test] fn inexhaustive_let_to_case_indented() { assert_code_action!( CONVERT_TO_CASE, "pub fn main(result) { { let Ok(value) = result } }", find_position_of("let").select_until(find_position_of("=")), ); } #[test] fn inexhaustive_let_to_case_multi_variables() { assert_code_action!( CONVERT_TO_CASE, "pub fn main() { let [var1, var2, _var3, var4] = [1, 2, 3, 4] }", find_position_of("let").select_until(find_position_of("=")), ); } #[test] fn inexhaustive_let_to_case_discard() { assert_code_action!( CONVERT_TO_CASE, "pub fn main() { let [_elem] = [6] }", find_position_of("let").select_until(find_position_of("=")), ); } #[test] fn inexhaustive_let_to_case_no_variables() { assert_code_action!( CONVERT_TO_CASE, "pub fn main() { let [] = [] }", find_position_of("let").select_until(find_position_of("=")), ); } #[test] fn inexhaustive_let_alias_to_case() { assert_code_action!( CONVERT_TO_CASE, "pub fn main() { let 10 as ten = 10 }", find_position_of("let").select_until(find_position_of("=")), ); } #[test] fn inexhaustive_let_tuple_to_case() { assert_code_action!( CONVERT_TO_CASE, "pub fn main() { let #(first, 10, third) = #(5, 10, 15) } ", find_position_of("let").select_until(find_position_of("=")), ); } #[test] fn inexhaustive_let_bit_array_to_case() { assert_code_action!( CONVERT_TO_CASE, "pub fn main() { let <> = <<73, 98>> }", find_position_of("let").select_until(find_position_of("=")), ); } #[test] fn inexhaustive_let_string_prefix_to_case() { assert_code_action!( CONVERT_TO_CASE, r#"pub fn main() { let "_" <> thing = "_Hello" }"#, find_position_of("let").select_until(find_position_of("=")), ); } #[test] fn inexhaustive_let_string_prefix_pattern_alias_to_case() { assert_code_action!( CONVERT_TO_CASE, r#"pub fn main() { let "123" as one_two_three <> rest = "123456" }"#, find_position_of("let").select_until(find_position_of("=")), ); } #[test] fn inner_inexhaustive_let_to_case() { assert_code_action!( CONVERT_TO_CASE, r#"pub fn main(result) { let [wibble] = { let Ok(wobble) = { result } [wobble] } }"#, find_position_of("let Ok").select_until(find_position_of(") =")), ); } #[test] fn outer_inexhaustive_let_to_case() { assert_code_action!( CONVERT_TO_CASE, r#"pub fn main(result) { let [wibble] = { let Ok(wobble) = { result } [wobble] } }"#, find_position_of("let [").select_until(find_position_of("] =")), ); } #[test] fn no_code_action_for_exhaustive_let_to_case() { assert_no_code_actions!( CONVERT_TO_CASE, r#"pub fn first(pair) { let #(first, second) = pair first }"#, find_position_of("let").select_until(find_position_of("=")), ); } #[test] fn extract_variable() { assert_code_action!( EXTRACT_VARIABLE, r#"pub fn main() { list.map([1, 2, 3], int.add(1, _)) }"#, find_position_of("[1").select_until(find_position_of("2")) ); } #[test] fn extract_variable_ignores_names_in_other_branches() { assert_code_action!( EXTRACT_VARIABLE, r#"pub fn main(x: Result(_, _)) { case x { Ok(_) -> { let int = 1 int + 2 } Error(_) -> wibble(11) } }"#, find_position_of("11").to_selection() ); } #[test] fn extract_variable_ignores_names_in_other_branches_2() { assert_code_action!( EXTRACT_VARIABLE, r#" pub fn main(x: Result(_, _)) { case x { Ok(_) -> { let int = 1 int + 2 } Error(_) -> { wibble(11) } } } "#, find_position_of("11").to_selection() ); } #[test] fn extract_variable_ignores_names_in_other_blocks() { assert_code_action!( EXTRACT_VARIABLE, r#" pub fn main() { { let int = 1 int + 2 } wibble(11) } "#, find_position_of("11").to_selection() ); } #[test] fn extract_variable_ignores_names_in_anonymous_functions() { assert_code_action!( EXTRACT_VARIABLE, r#" pub fn main() { let fun = fn() { let int = 1 int + 2 } wibble(11) } "#, find_position_of("11").to_selection() ); } #[test] fn extract_variable_does_not_shadow_names_in_anonymous_function() { assert_code_action!( EXTRACT_VARIABLE, r#" pub fn main() { let fun = fn() { let int = 1 wibble(11) } fun() } "#, find_position_of("11").to_selection() ); } #[test] fn extract_variable_does_not_shadow_name_in_same_branch() { assert_code_action!( EXTRACT_VARIABLE, r#" pub fn main(x: Result(_, _)) { case x { Ok(_) -> { let int = 1 wibble(11) } Error(_) -> { panic } } } "#, find_position_of("11").to_selection() ); } #[test] fn extract_variable_does_not_shadow_name_in_same_block() { assert_code_action!( EXTRACT_VARIABLE, r#" pub fn main() { let result = { let int = 1 wibble(11) } result }"#, find_position_of("11").to_selection() ); } #[test] fn extract_variable_does_not_extract_a_variable() { assert_no_code_actions!( EXTRACT_VARIABLE, r#"pub fn main() { let z = 1 let a = [1, 2, z] }"#, find_position_of("z").nth_occurrence(2).to_selection() ); } #[test] fn extract_variable_does_not_extract_top_level_statement() { assert_no_code_actions!( EXTRACT_VARIABLE, r#"pub fn main() { let wibble = 1 }"#, find_position_of("1").to_selection() ); } #[test] fn extract_variable_does_not_extract_top_level_statement_inside_block() { assert_no_code_actions!( EXTRACT_VARIABLE, r#"pub fn main() { let x = { let y = "y" let w = "w" <> y w } }"#, find_position_of("y").nth_occurrence(2).to_selection() ); } #[test] fn extract_variable_does_not_extract_top_level_statement_inside_use() { assert_no_code_actions!( EXTRACT_VARIABLE, " pub fn main() { use x <- try(Ok(1)) let y = 2 Ok(y + x) } pub fn try(result: Result(a, e), fun: fn(a) -> Result(b, e)) -> Result(b, e) { todo } ", find_position_of("2").to_selection() ); } #[test] fn extract_variable_does_not_extract_use() { assert_no_code_actions!( EXTRACT_VARIABLE, " pub fn main() { use x <- try(Ok(1)) Ok(x) } pub fn try(result: Result(a, e), fun: fn(a) -> Result(b, e)) -> Result(b, e) { todo } ", find_position_of("use").to_selection() ); } #[test] fn extract_variable_does_not_extract_panic() { assert_no_code_actions!( EXTRACT_VARIABLE, r#"pub fn main() { let x = 1 panic }"#, find_position_of("panic").to_selection() ); } #[test] fn extract_variable_does_not_extract_echo() { assert_no_code_actions!( EXTRACT_VARIABLE, r#"pub fn main() { let x = 1 echo x }"#, find_position_of("echo").to_selection() ); } #[test] fn extract_variable_does_not_extract_assignment() { assert_no_code_actions!( EXTRACT_VARIABLE, r#"pub fn main() { let x = 1 }"#, find_position_of("x").to_selection() ); } #[test] fn extract_variable_does_not_extract_record_variable_in_record_update() { assert_no_code_actions!( EXTRACT_VARIABLE, r#" type Wibble { Wibble(one: Int, two: Int) } pub fn main() { let wibble = todo Wibble(..wibble, one: 1) }"#, find_position_of("wibble").nth_occurrence(2).to_selection() ); } #[test] fn extract_variable_from_arg_in_pipelined_call() { assert_code_action!( EXTRACT_VARIABLE, " pub fn main() { let adder = add let x = [4, 5, 6] |> map2([1, 2, 3], adder) x } pub fn map2(list1: List(a), list2: List(b), fun: fn(a, b) -> c) -> List(c) { todo } pub fn add(a: Int, b: Int) -> Int { todo } ", find_position_of("[1").to_selection() ); } #[test] fn extract_variable_from_arg_in_pipelined_call_to_capture() { assert_code_action!( EXTRACT_VARIABLE, " pub fn main() { let adder = add let x = adder |> reduce([1, 2, 3], _) x } pub fn reduce(list: List(a), fun: fn(a, a) -> a) -> Result(a, Nil) { todo } pub fn add(a: Int, b: Int) -> Int { todo } ", find_position_of("[1").to_selection() ); } #[test] fn extract_variable_from_arg_in_pipelined_call_of_function_to_capture() { assert_code_action!( EXTRACT_VARIABLE, " pub fn main() { fn(total, item) { total + item } |> fold(with: _, from: 0, over: [1, 2, 3]) } pub fn fold(over l: List(a), from i: t, with f: fn(t, a) -> t) -> acc { todo } ", find_position_of("fold").to_selection() ); } #[test] fn extract_variable_from_arg_in_nested_function_called_in_pipeline() { assert_code_action!( EXTRACT_VARIABLE, " pub fn main() { let result = [1, 2, 3] |> map(add(_, 1)) |> map(subtract(_, 9)) result } pub fn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo } pub fn add(a: Int, b: Int) -> Int { todo } pub fn subtract(a: Int, b: Int) -> Int { todo } ", find_position_of("9").to_selection() ); } #[test] fn extract_variable_does_not_extract_an_entire_pipeline_step() { assert_no_code_actions!( EXTRACT_VARIABLE, " pub fn main() { [1, 2, 3] |> map(todo) |> map(todo) } fn map(list, fun) { todo } ", find_position_of("map").to_selection() ); } #[test] fn extract_variable_does_not_extract_the_last_pipeline_step() { assert_no_code_actions!( EXTRACT_VARIABLE, r#"pub fn main() { [1, 2, 3] |> map(todo) |> map(todo) } fn map(list, fun) { todo } "#, find_position_of("map").to_selection() ); } #[test] fn extract_variable_2() { assert_code_action!( EXTRACT_VARIABLE, " pub fn main() { map([1, 2, 3], add(1, _)) } pub fn add(n, m) { todo } pub fn map(l, f) { todo } ", find_position_of("add").to_selection() ); } #[test] fn extract_variable_from_capture_arguments() { assert_no_code_actions!( EXTRACT_VARIABLE, r#"pub fn main() { int.add(1, _) }"#, find_position_of("_").to_selection() ); } #[test] fn extract_variable_from_capture_arguments_2() { assert_code_action!( EXTRACT_VARIABLE, r#"pub fn main() { int.add(11, _) }"#, find_position_of("11").to_selection() ); } #[test] fn extract_variable_3() { assert_code_action!( EXTRACT_VARIABLE, r#"pub fn main() { list.map([1, 2, 3], todo, todo) }"#, find_position_of("todo") .nth_occurrence(2) .select_until(find_position_of("todo)").under_last_char()) ); } #[test] fn extract_variable_inside_multiline_function_call() { assert_code_action!( EXTRACT_VARIABLE, r#"pub fn main() { list.map( [1, 2, 3], int.add(1, _), ) }"#, find_position_of("[1").to_selection() ); } #[test] fn extract_variable_in_case_branch() { assert_code_action!( EXTRACT_VARIABLE, r#"pub fn main() { case wibble { _ -> [1, 2, 3] } }"#, find_position_of("[1").to_selection() ); } #[test] fn extract_variable_in_multiline_case_subject_branch() { assert_code_action!( EXTRACT_VARIABLE, r#"pub fn main() { case list.map( [1, 2, 3], int.add(1, _) ) { _ -> todo } }"#, find_position_of("[1").to_selection() ); } #[test] fn extract_variable_in_case_branch_using_var() { assert_code_action!( EXTRACT_VARIABLE, r#"pub fn main() { case todo { Ok(value) -> 2 * value + 1 Error(_) -> panic } }"#, find_position_of("2").select_until(find_position_of("value").nth_occurrence(2)) ); } #[test] fn extract_variable_in_case_branch_from_second_arg() { assert_code_action!( EXTRACT_VARIABLE, r#"pub fn main() { case todo { Ok(_) -> #(Ok(1), Error("s")) Error(_) -> panic } }"#, find_position_of("E").to_selection() ); } #[test] fn extract_variable_in_use() { assert_code_action!( EXTRACT_VARIABLE, r#"pub fn main() { use <- wibble([1, 2, 3]) todo }"#, find_position_of("[1").to_selection() ); } #[test] fn extract_variable_inside_use_body() { assert_code_action!( EXTRACT_VARIABLE, r#"pub fn main() { use <- wibble(todo) list.map([1, 2, 3], int.add(1, _)) todo }"#, find_position_of("[1").to_selection() ); } #[test] fn extract_variable_in_multiline_use() { assert_code_action!( EXTRACT_VARIABLE, r#"pub fn main() { use <- wibble( [1, 2, 3] ) todo }"#, find_position_of("[1").to_selection() ); } #[test] fn extract_variable_after_nested_anonymous_function() { assert_code_action!( EXTRACT_VARIABLE, r#"pub fn main() { let f = fn() { let x = 1 + 2 let ff = fn() { let y = x + 3 let z = y + x z } let z = x * 4 z } let y = 5 + 6 f() }"#, find_position_of("6").to_selection() ); } #[test] fn extract_variable_in_nested_anonymous_function() { assert_code_action!( EXTRACT_VARIABLE, r#"pub fn main() { let f = fn() { let x = 1 + 2 let ff = fn() { let y = x + 3 let z = y + x z } let z = x * 4 z } let y = 5 + 6 f() }"#, find_position_of("4").to_selection() ); } #[test] fn extract_variable_in_double_nested_anonymous_function() { assert_code_action!( EXTRACT_VARIABLE, r#"pub fn main() { let f = fn() { let x = 1 + 2 let ff = fn() { let y = x + 3 let z = y + x z } let z = x * 4 z } let y = 5 + 6 f() }"#, find_position_of("3").to_selection() ); } #[test] fn extract_variable_in_block() { assert_code_action!( EXTRACT_VARIABLE, r#"pub fn main() { { todo wibble([1, 2, 3]) todo } }"#, find_position_of("2").select_until(find_position_of("3")) ); } #[test] fn extract_variable_and_dont_shadow_existing_variable_in_operator() { let src = "import gleam/int import random_import as int_2 const int_3 = 3 fn int_4() { 4 } fn isolated_scope() { let int_6 = 6 int_6 + 1 } pub fn main() { let int_5 = 5 let result = int_5 + 6 result } "; assert_code_action!( EXTRACT_VARIABLE, TestProject::for_source(src) .add_hex_module("gleam/int", "") .add_hex_module("random_import", ""), find_position_of("6").nth_occurrence(4).to_selection(), ); } #[test] fn extract_variable_and_dont_shadow_existing_variable_in_argument() { assert_code_action!( EXTRACT_VARIABLE, r#"fn wibble(a, b) { a + b } fn main() { let int = 1 wibble(int, 2) }"#, find_position_of("2").to_selection() ); } #[test] fn extract_constant_from_call_argument_with_bit_array() { assert_code_action!( EXTRACT_CONSTANT, r#"import gleam/io pub fn main() { io.debug(<<3:size(8)>>) }"#, find_position_of("<").select_until(find_position_of("<").nth_occurrence(2)) ); } #[test] fn extract_constant_from_call_argument_with_float() { assert_code_action!( EXTRACT_CONSTANT, r#"import gleam/float pub fn main() { float.ceiling(1.9998) }"#, find_position_of("1").select_until(find_position_of("8")) ); } #[test] fn extract_constant_from_call_argument_with_int() { assert_code_action!( EXTRACT_CONSTANT, r#"import gleam/list pub fn main() { list.sample([4, 5, 6], 2) }"#, find_position_of("2").to_selection() ); } #[test] fn extract_constant_from_call_argument_with_list() { assert_code_action!( EXTRACT_CONSTANT, r#"import gleam/io pub fn main() { io.debug(["constant", "another constant"]) }"#, find_position_of("[").to_selection() ); } #[test] fn extract_constant_from_call_argument_with_nested_inside() { assert_code_action!( EXTRACT_CONSTANT, r#"import gleam/list pub fn main() { list.unzip([#(1, 2), #(3, 4)]) }"#, find_position_of("#").select_until(find_position_of("(").nth_occurrence(3)) ); } #[test] fn extract_constant_from_call_argument_with_nested_outside() { assert_code_action!( EXTRACT_CONSTANT, r#"import gleam/list pub fn main() { list.unzip([#(1, 2), #(3, 4)]) }"#, find_position_of("[").to_selection() ); } #[test] fn extract_constant_from_call_argument_with_string() { assert_code_action!( EXTRACT_CONSTANT, r#"import gleam/io pub fn main() { io.print("constant") }"#, find_position_of("\"").select_until(find_position_of("\"")) ); } #[test] fn extract_constant_from_call_argument_with_tuple() { assert_code_action!( EXTRACT_CONSTANT, r#"import gleam/io pub fn main() { io.debug(#(1, 2, 3)) }"#, find_position_of("#").select_until(find_position_of("(").nth_occurrence(3)) ); } #[test] fn extract_constant_from_declaration_of_float() { assert_code_action!( EXTRACT_CONSTANT, r#"pub fn main() { let c = 3.1415 }"#, find_position_of("3").select_until(find_position_of("5")) ); } #[test] fn extract_constant_from_whole_declaration_of_float() { assert_code_action!( EXTRACT_CONSTANT, r#"import gleam/io pub fn main() { let c = 3.1415 io.debug(c) }"#, find_position_of("l") .nth_occurrence(2) .select_until(find_position_of("c")) ); } #[test] fn extract_constant_from_declaration_of_bit_array() { assert_code_action!( EXTRACT_CONSTANT, r#"pub fn main() { let b = <<"arr":utf32>> }"#, find_position_of("u") .nth_occurrence(2) .select_until(find_position_of(">").nth_occurrence(2)) ); } #[test] fn extract_constant_from_whole_declaration_of_bit_array() { assert_code_action!( EXTRACT_CONSTANT, r#"import gleam/io const n = 24 pub fn main() { let bits = <<8080:size(n)>> bits }"#, find_position_of("l") .nth_occurrence(2) .select_until(find_position_of("s").nth_occurrence(2)) ); } #[test] fn extract_constant_from_declaration_of_bin_op() { assert_code_action!( EXTRACT_CONSTANT, r#"pub fn main() { let twelve = "1" <> "2" }"#, find_position_of("<").to_selection() ); } #[test] fn extract_constant_from_whole_declaration_of_bin_op() { assert_code_action!( EXTRACT_CONSTANT, r#"import gleam/io pub fn main() { let twelve = "1" <> "2" io.print(twelve) }"#, find_position_of("l") .nth_occurrence(2) .select_until(find_position_of("e").nth_occurrence(4)) ); } #[test] fn extract_constant_from_declaration_of_int() { assert_code_action!( EXTRACT_CONSTANT, r#"pub fn main() { let c = 125 }"#, find_position_of("1").select_until(find_position_of("5")) ); } #[test] fn extract_constant_from_whole_declaration_of_int() { assert_code_action!( EXTRACT_CONSTANT, r#"import gleam/io pub fn main() { let c = 125 io.debug(c) }"#, find_position_of("l") .nth_occurrence(2) .select_until(find_position_of("c")) ); } #[test] fn extract_constant_from_declaration_of_list() { assert_code_action!( EXTRACT_CONSTANT, r#"pub fn main() { let c = [3.1415, 0.33333333] }"#, find_position_of("[").to_selection() ); } #[test] fn extract_constant_from_whole_declaration_of_list() { assert_code_action!( EXTRACT_CONSTANT, r#"import gleam/io pub fn main() { let c = [3.1415, 0.33333333] io.debug(c) }"#, find_position_of("l") .nth_occurrence(2) .select_until(find_position_of("c")) ); } #[test] fn extract_constant_from_declaration_of_nested_inside() { assert_code_action!( EXTRACT_CONSTANT, r#"pub fn main() { let c = #([1, 2, 3], [3, 2, 1]) }"#, find_position_of("[").to_selection() ); } #[test] fn extract_constant_from_declaration_of_nested_outside() { assert_code_action!( EXTRACT_CONSTANT, r#"pub fn main() { let c = #([1, 2, 3], [3, 2, 1]) }"#, find_position_of("#").select_until(find_position_of("(").nth_occurrence(2)) ); } #[test] fn extract_constant_from_whole_declaration_of_nested() { assert_code_action!( EXTRACT_CONSTANT, r#"import gleam/io pub fn main() { let c = #([1, 2, 3], [3, 2, 1]) io.debug(c) }"#, find_position_of("l") .nth_occurrence(2) .select_until(find_position_of("c")) ); } #[test] fn extract_constant_from_declaration_of_string() { assert_code_action!( EXTRACT_CONSTANT, r#"pub fn main() { let c = "constant" }"#, find_position_of("\"").select_until(find_position_of("\"")) ); } #[test] fn extract_constant_from_whole_declaration_of_string() { assert_code_action!( EXTRACT_CONSTANT, r#"import gleam/io pub fn main() { let c = "constant" io.debug(c) }"#, find_position_of("l") .nth_occurrence(2) .select_until(find_position_of("c")) ); } #[test] fn extract_constant_from_declaration_of_tuple() { assert_code_action!( EXTRACT_CONSTANT, r#"pub fn main() { let #(one, two, three) = #("one", "two", "three") }"#, find_position_of("#") .nth_occurrence(2) .select_until(find_position_of("(").nth_occurrence(3)) ); } #[test] fn extract_constant_from_whole_declaration_of_tuple() { assert_code_action!( EXTRACT_CONSTANT, r#"import gleam/io pub fn main() { let c = #("one", "two", "three") io.debug(c) }"#, find_position_of("l") .nth_occurrence(2) .select_until(find_position_of("c")) ); } #[test] fn extract_constant_from_literal_within_list() { assert_code_action!( EXTRACT_CONSTANT, r#"pub fn main() { let c = ["constant", todo] }"#, find_position_of("\"").select_until(find_position_of("\"")) ); } #[test] fn extract_constant_from_list_containing_constant() { assert_code_action!( EXTRACT_CONSTANT, r#"const something = "something" pub fn main() { let c = ["constant", something] }"#, find_position_of("[").to_selection() ); } #[test] fn extract_constant_from_literal_within_tuple() { assert_code_action!( EXTRACT_CONSTANT, r#"pub fn main() { let c = #(0.333334, todo) }"#, find_position_of("0").select_until(find_position_of("4")) ); } #[test] fn extract_constant_from_tuple_containing_constant() { assert_code_action!( EXTRACT_CONSTANT, r#"const something = "something" pub fn main() { let c = #(0.333334, something) }"#, find_position_of("#").select_until(find_position_of("(").nth_occurrence(2)) ); } #[test] fn extract_constant_from_nested_inside_in_expr() { assert_code_action!( EXTRACT_CONSTANT, r#"pub fn main() { [#("a", 0), #("b", 1), #("a", 2)] |> key_filter("a") }"#, find_position_of("#").select_until(find_position_of("(").nth_occurrence(2)) ); } #[test] fn extract_constant_from_nested_outside_in_expr() { assert_code_action!( EXTRACT_CONSTANT, r#"pub fn main() { [#("a", 0), #("b", 1), #("a", 2)] |> key_filter("a") }"#, find_position_of("[").to_selection() ); } #[test] fn extract_constant_from_return_of_float() { assert_code_action!( EXTRACT_CONSTANT, r#"pub fn main() { 0.25 }"#, find_position_of("0").select_until(find_position_of("5")) ); } #[test] fn extract_constant_from_return_of_int() { assert_code_action!( EXTRACT_CONSTANT, r#"pub fn main() { 100 }"#, find_position_of("1").select_until(find_position_of("0").nth_occurrence(2)) ); } #[test] fn extract_constant_from_return_of_list() { assert_code_action!( EXTRACT_CONSTANT, r#"pub fn main() { [1, 2, 3, 4] }"#, find_position_of("[").to_selection() ); } #[test] fn extract_constant_from_return_of_nested_outside() { assert_code_action!( EXTRACT_CONSTANT, r#"pub fn main() { [#(0.25, 0.75), #(0.5, 1.5)] }"#, find_position_of("#").select_until(find_position_of("(").nth_occurrence(2)) ); } #[test] fn extract_constant_from_return_of_string() { assert_code_action!( EXTRACT_CONSTANT, r#"pub fn main() { "constant" }"#, find_position_of("\"").select_until(find_position_of("\"")) ); } #[test] fn extract_constant_from_return_of_tuple() { assert_code_action!( EXTRACT_CONSTANT, r#"pub fn main() { #(0.25, 0.75) }"#, find_position_of("#").select_until(find_position_of("(").nth_occurrence(2)) ); } #[test] fn extract_constant_from_taken_name_by_function() { assert_code_action!( EXTRACT_CONSTANT, r#"fn floats() { [1.0, 2.0] } pub fn main() { [0.25, 0.75] }"#, find_position_of("[").nth_occurrence(2).to_selection() ); } #[test] fn extract_constant_from_taken_name_by_constant() { assert_code_action!( EXTRACT_CONSTANT, r#"const ints = [1, 2] pub fn main() { [5, 50] }"#, find_position_of("[").nth_occurrence(2).to_selection() ); } #[test] fn extract_constant_in_correct_position_1() { assert_code_action!( EXTRACT_CONSTANT, r#" fn first() { 1 } fn second() { 2 } fn third() { 3 } "#, find_position_of("1").to_selection() ); } #[test] fn extract_constant_in_correct_position_2() { assert_code_action!( EXTRACT_CONSTANT, r#" fn first() { 1 } fn second() { 2 } fn third() { 3 } "#, find_position_of("2").to_selection() ); } #[test] fn extract_constant_in_correct_position_3() { assert_code_action!( EXTRACT_CONSTANT, r#" fn first() { 1 } fn second() { 2 } fn third() { 3 } "#, find_position_of("3").to_selection() ); } #[test] fn extract_constant_declaration_with_proper_indentation() { assert_code_action!( EXTRACT_CONSTANT, r#" pub fn main() { let fahrenheit = { let degrees = 64 degrees } fahrenheit } "#, find_position_of("l") .nth_occurrence(2) .select_until(find_position_of("s")) ); } #[test] fn extract_constant_from_nil() { assert_code_action!( EXTRACT_CONSTANT, r#"pub fn main() { let x = Nil x } "#, find_position_of("l").select_until(find_position_of("x")) ); } #[test] fn extract_constant_from_non_record_variant_1() { assert_code_action!( EXTRACT_CONSTANT, r#"pub type Auth { Verified Unverified } pub fn main() { let a = Unverified let a = verify(something, a) a } "#, find_position_of("U") .nth_occurrence(2) .select_until(find_position_of("d").nth_occurrence(3)) ); } #[test] fn extract_constant_from_non_record_variant_2() { assert_code_action!( EXTRACT_CONSTANT, r#"pub type Auth { Verified Unverified } pub fn main() { let a = verify(something, Unverified) a } "#, find_position_of("U") .nth_occurrence(2) .select_until(find_position_of("d").nth_occurrence(3)) ); } #[test] fn extract_constant_from_record_variant_1() { assert_code_action!( EXTRACT_CONSTANT, r#"pub type Auth { Verified(String) Unverified } pub fn main() { let u = Verified("User") let v = verify(something, u) v }"#, find_position_of("l").select_until(find_position_of("u").nth_occurrence(4)) ); } #[test] fn extract_constant_from_record_variant_2() { assert_code_action!( EXTRACT_CONSTANT, r#"pub type Auth { Verified(Int) Unverified } const auth = True const id = 1234 pub fn main() { let v = verify(auth, Verified(id)) v }"#, find_position_of("V") .nth_occurrence(2) .select_until(find_position_of("(").nth_occurrence(4)) ); } #[test] fn extract_constant_from_inside_block() { assert_code_action!( EXTRACT_CONSTANT, r#"pub fn main() { let fahrenheit = { let degrees = 64 + 32 degrees } }"#, find_position_of("32").to_selection() ); } #[test] fn extract_constant_from_inside_case() { assert_code_action!( EXTRACT_CONSTANT, r#"pub fn main(result) { case result { Ok(value) -> value + 1 Error(_) -> panic } }"#, find_position_of("1").to_selection() ); } #[test] fn extract_constant_from_inside_use_1() { assert_code_action!( EXTRACT_CONSTANT, r#"pub fn main() { use x <- result.try(todo) Ok(123) }"#, find_position_of("Ok").to_selection() ); } #[test] fn extract_constant_from_inside_use_2() { assert_code_action!( EXTRACT_CONSTANT, r#"const number = 123 pub fn main() { use x <- result.try(todo) Ok(number) }"#, find_position_of("Ok").to_selection() ); } #[test] fn do_not_extract_constant_from_pattern() { assert_no_code_actions!( EXTRACT_CONSTANT, r#"pub fn main() { let #(one, two) = #(1, 2) one }"#, find_position_of("l").select_until(find_position_of("(").nth_occurrence(2)) ); } #[test] fn do_not_extract_constant_from_fn_call_1() { assert_no_code_actions!( EXTRACT_CONSTANT, r#"import gleam/io pub fn main() { io.print("constant") }"#, find_position_of("p") .nth_occurrence(3) .select_until(find_position_of("t").nth_occurrence(2)) ); } #[test] fn do_not_extract_constant_from_fn_call_2() { assert_no_code_actions!( EXTRACT_CONSTANT, r#"import gleam/list pub fn main() { let first = list.first([1, 2, 3]) first }"#, find_position_of("l") .nth_occurrence(3) .select_until(find_position_of("t").nth_occurrence(4)) ); } #[test] fn do_not_extract_constant_from_bin_op() { assert_no_code_actions!( EXTRACT_CONSTANT, r#"pub fn main() { let res = 64 < 32 res }"#, find_position_of("<").to_selection() ); } #[test] fn do_not_extract_constant_from_bit_array_1() { assert_no_code_actions!( EXTRACT_CONSTANT, r#"pub fn main() { let c = "constant" let res = <> res }"#, find_position_of("<").to_selection() ); } #[test] fn do_not_extract_constant_from_bit_array_2() { assert_no_code_actions!( EXTRACT_CONSTANT, r#"import gleam/io pub fn main() { let n = 1234 io.debug(<<8080:size(n)>>) }"#, find_position_of("<").to_selection() ); } #[test] fn do_not_extract_constant_from_bit_array_3() { assert_no_code_actions!( EXTRACT_CONSTANT, r#"import gleam/io pub fn main() { let l = 1234 let r = 1234 let result = <> result }"#, find_position_of("l") .nth_occurrence(5) .select_until(find_position_of("t").nth_occurrence(5)) ); } #[test] fn do_not_extract_constant_from_list_1() { assert_no_code_actions!( EXTRACT_CONSTANT, r#"pub fn main() { let c = ["constant", todo] c }"#, find_position_of("[").to_selection() ); } #[test] fn do_not_extract_constant_from_list_2() { assert_no_code_actions!( EXTRACT_CONSTANT, r#"pub fn main() { [10, todo] }"#, find_position_of("[").to_selection() ); } #[test] fn do_not_extract_constant_from_list_3() { assert_no_code_actions!( EXTRACT_CONSTANT, r#"pub fn main() { let c = [0.25, todo] c }"#, find_position_of("l").select_until(find_position_of("c")) ); } #[test] fn do_not_extract_constant_from_tuple_1() { assert_no_code_actions!( EXTRACT_CONSTANT, r#"pub fn main() { let c = #("constant", todo) c }"#, find_position_of("#").select_until(find_position_of("(")) ); } #[test] fn do_not_extract_constant_from_tuple_2() { assert_no_code_actions!( EXTRACT_CONSTANT, r#"pub fn main() { #(10, todo) }"#, find_position_of("#").select_until(find_position_of("(")) ); } #[test] fn do_not_extract_constant_from_tuple_3() { assert_no_code_actions!( EXTRACT_CONSTANT, r#"pub fn main() { let c = #(0.25, todo) c }"#, find_position_of("l").select_until(find_position_of("c")) ); } #[test] fn do_not_extract_constant_from_nested_1() { assert_no_code_actions!( EXTRACT_CONSTANT, r#"pub fn main() { let c = list.unzip([#(1, 2), #(3, todo)]) c }"#, find_position_of("[").to_selection() ); } #[test] fn do_not_extract_constant_from_nested_2() { assert_no_code_actions!( EXTRACT_CONSTANT, r#"pub fn main() { [[1.25, 1], [0.25, todo]] }"#, find_position_of("[").to_selection() ); } #[test] fn do_not_extract_constant_from_nested_3() { assert_no_code_actions!( EXTRACT_CONSTANT, r#"import gleam/list pub fn main() { let c = [#(1, 2), #(3, todo)] c }"#, find_position_of("l") .nth_occurrence(3) .select_until(find_position_of("c")) ); } #[test] fn do_not_extract_constant_from_record_1() { assert_no_code_actions!( EXTRACT_CONSTANT, r#"type Pair { Pair(Int, Int) } pub fn main() { let c = list.unzip([Pair(1, 2), Pair(3, todo)]) c }"#, find_position_of("P").nth_occurrence(4).to_selection() ); } #[test] fn do_not_extract_constant_from_record_2() { assert_no_code_actions!( EXTRACT_CONSTANT, r#"type Couple { Couple(l: Float, r: Float) } pub fn main() { #(Couple(1.25, 1.0), Couple(0.25, todo)) }"#, find_position_of("C").nth_occurrence(4).to_selection() ); } #[test] fn do_not_extract_constant_from_record_update() { assert_no_code_actions!( EXTRACT_CONSTANT, r#"type Couple { Couple(l: Int, r: Int) } pub fn main() { let c = Couple(1, 2) let cc = Couple(..c, 2) cc }"#, find_position_of("C") .nth_occurrence(4) .select_until(find_position_of("e").nth_occurrence(7)) ); } #[test] fn do_not_extract_constant_from_record_capture() { assert_no_code_actions!( EXTRACT_CONSTANT, r#"type Couple { Couple(l: Int, r: Int) } pub fn main() { let c = Couple(1, _) c }"#, find_position_of("C") .nth_occurrence(3) .select_until(find_position_of("e").nth_occurrence(5)) ); } #[test] fn do_not_extract_top_level_expression_statement() { assert_no_code_actions!( EXTRACT_VARIABLE, r#"pub fn main() { 1 } "#, find_position_of("1").to_selection() ); } #[test] fn do_not_extract_top_level_expression_in_let_statement() { assert_no_code_actions!( EXTRACT_VARIABLE, r#"pub fn main() { let a = 1 } "#, find_position_of("1").to_selection() ); } #[test] fn do_not_extract_top_level_module_call() { let src = r#" import list pub fn main() { list.map([1, 2, 3], todo) }"#; assert_no_code_actions!( EXTRACT_VARIABLE, TestProject::for_source(src).add_module("list", "pub fn map(l, f) { todo }"), find_position_of("map").to_selection() ); } #[test] fn expand_function_capture() { assert_code_action!( EXPAND_FUNCTION_CAPTURE, r#"pub fn main() { wibble(_, 1) }"#, find_position_of("_").to_selection() ); } #[test] fn expand_function_capture_2() { assert_code_action!( EXPAND_FUNCTION_CAPTURE, r#"pub fn main() { wibble(1, _) }"#, find_position_of("wibble").to_selection() ); } #[test] fn expand_function_capture_does_not_shadow_variables() { assert_code_action!( EXPAND_FUNCTION_CAPTURE, r#"pub fn main() { let value = 1 let value_2 = 2 wibble(value, _, value_2) }"#, find_position_of("wibble").to_selection() ); } #[test] fn expand_function_capture_picks_a_name_based_on_the_type_of_the_hole() { assert_code_action!( EXPAND_FUNCTION_CAPTURE, r#"pub fn main() { [1, 2, 3] |> map(add(_, 1)) } pub fn map(l: List(a), f: fn(a) -> b) -> List(b) { todo } pub fn add(n, m) { n + m } "#, find_position_of("add").to_selection() ); } #[test] fn generate_dynamic_decoder() { assert_code_action!( GENERATE_DYNAMIC_DECODER, " pub type Person { Person(name: String, age: Int, height: Float, is_cool: Bool, brain: BitArray) } ", find_position_of("type").to_selection() ); } #[test] fn generate_dynamic_decoder_complex_types() { let src = " import gleam/option import gleam/dynamic import gleam/dict pub type Something pub type Wibble(value) { Wibble( maybe: option.Option(Something), map: dict.Dict(String, List(value)), unknown: List(dynamic.Dynamic), ) } "; assert_code_action!( GENERATE_DYNAMIC_DECODER, TestProject::for_source(src) .add_module("gleam/option", "pub type Option(a)") .add_module("gleam/dynamic", "pub type Dynamic") .add_module("gleam/dict", "pub type Dict(k, v)"), find_position_of("type W").to_selection() ); } #[test] fn generate_dynamic_decoder_already_imported_module() { let src = " import gleam/dynamic/decode as dyn_dec pub type Wibble { Wibble(a: Int, b: Float, c: String) } "; assert_code_action!( GENERATE_DYNAMIC_DECODER, TestProject::for_source(src).add_module("gleam/dynamic/decode", "pub type Decoder(a)"), find_position_of("type W").to_selection() ); } #[test] fn generate_dynamic_decoder_tuple() { assert_code_action!( GENERATE_DYNAMIC_DECODER, " pub type Wibble { Wibble(tuple: #(Int, Float, #(String, Bool))) } ", find_position_of("type W").to_selection() ); } #[test] fn generate_dynamic_decoder_recursive_type() { let src = " import gleam/option pub type LinkedList { LinkedList(value: Int, next: option.Option(LinkedList)) } "; assert_code_action!( GENERATE_DYNAMIC_DECODER, TestProject::for_source(src).add_module("gleam/option", "pub type Option(a)"), find_position_of("type").to_selection() ); } #[test] fn generate_dynamic_decoder_for_multi_variant_type() { assert_code_action!( GENERATE_DYNAMIC_DECODER, " pub type Wibble { Wibble(wibble: Int, next: Wibble) Wobble(wobble: Float, text: String, values: List(Bool)) } ", find_position_of("type").to_selection() ); } #[test] fn generate_dynamic_decoder_for_multi_variant_type_multi_word_name() { assert_code_action!( GENERATE_DYNAMIC_DECODER, " pub type Wibble { OneTwo(wibble: Int, next: Wibble) ThreeFour(wobble: Float, text: String, values: List(Bool)) FiveSixSeven(one_two: Int) } ", find_position_of("type").to_selection() ); } #[test] fn generate_dynamic_decoder_for_variant_with_no_fields() { assert_code_action!( GENERATE_DYNAMIC_DECODER, " pub type Wibble { Wibble } ", find_position_of("type").to_selection() ); } #[test] fn generate_dynamic_decoder_for_variants_with_no_fields() { assert_code_action!( GENERATE_DYNAMIC_DECODER, " pub type Wibble { Wibble Wobble Woo } ", find_position_of("type").to_selection() ); } #[test] fn generate_dynamic_decoder_for_variants_with_mixed_fields() { assert_code_action!( GENERATE_DYNAMIC_DECODER, " pub type Wibble { Wibble Wobble(field: String, field2: Int) } ", find_position_of("type").to_selection() ); } #[test] fn no_code_action_to_generate_dynamic_decoder_for_type_without_labels() { assert_no_code_actions!( GENERATE_DYNAMIC_DECODER, " pub type Wibble { Wibble(Int, Int, String) } ", find_position_of("type").to_selection() ); } #[test] fn pattern_match_on_argument_empty_tuple() { assert_no_code_actions!( PATTERN_MATCH_ON_ARGUMENT, " pub fn main(tuple: #()) { todo } ", find_position_of("tuple").to_selection() ); } #[test] fn pattern_match_on_argument_single_item_tuple() { assert_code_action!( PATTERN_MATCH_ON_ARGUMENT, " pub fn main(tuple: #(Int)) { todo } ", find_position_of(":").to_selection() ); } #[test] fn pattern_match_on_argument_multi_item_tuple() { assert_code_action!( PATTERN_MATCH_ON_ARGUMENT, " pub fn main(tuple: #(Int, String, Bool)) { todo } ", find_position_of("tuple").select_until(find_position_of("Int")) ); } #[test] fn pattern_match_on_argument_uses_case_with_multiple_constructors() { assert_code_action!( PATTERN_MATCH_ON_ARGUMENT, " pub type CannotBeDestructured { One(one: String) Two(two: Int) } pub fn main(arg: CannotBeDestructured) { todo } ", find_position_of("arg").to_selection() ); } #[test] fn pattern_match_on_argument_with_multiple_constructors_is_nicely_formatted_in_function_with_empty_body() { assert_code_action!( PATTERN_MATCH_ON_ARGUMENT, " pub type CannotBeDestructured { One(one: String) Two(two: Int) } pub fn main(arg: CannotBeDestructured) {} ", find_position_of("arg").to_selection() ); } #[test] fn pattern_match_on_argument_uses_label_shorthand_syntax_for_labelled_arguments() { assert_code_action!( PATTERN_MATCH_ON_ARGUMENT, " pub type Wibble { Wobble(Int, String, i_want_to_see_this: String, and_this: Bool) } pub fn main(arg: Wibble) { todo } ", find_position_of("arg").select_until(find_position_of("Wibble").nth_occurrence(2)) ); } #[test] fn pattern_match_on_argument_with_private_type_from_same_module() { assert_code_action!( PATTERN_MATCH_ON_ARGUMENT, " type Wibble { Wobble(Int, String) } pub fn main(arg: Wibble) { todo } ", find_position_of("arg").select_until(find_position_of("Wibble").nth_occurrence(2)) ); } #[test] fn pattern_match_on_value_with_private_type_from_same_module() { assert_code_action!( PATTERN_MATCH_ON_VARIABLE, " type Wibble { Wobble(Int, String) } pub fn main() { let wibble = Wobble(1, \"Hello\") todo } ", find_position_of("wibble").to_selection() ); } #[test] fn pattern_match_on_clause_variable() { assert_code_action!( PATTERN_MATCH_ON_VARIABLE, " pub fn main() { case maybe_wibble() { Ok(something) -> 1 Error(_) -> 2 } } type Wibble { Wobble Woo } fn maybe_wibble() { Ok(Wobble) } ", find_position_of("something").to_selection() ); } #[test] fn pattern_match_on_clause_variable_with_label() { assert_code_action!( PATTERN_MATCH_ON_VARIABLE, " pub fn main() { case wibble() { Wobble(wibble: something) -> 1 _ -> 2 } } type Wibble { Wobble(wibble: Wibble) Woo } fn new() { Wobble } ", find_position_of("something").to_selection() ); } #[test] fn pattern_match_on_clause_variable_with_label_shorthand() { assert_code_action!( PATTERN_MATCH_ON_VARIABLE, " pub fn main() { case new() { Wobble(wibble:) -> 1 _ -> 2 } } type Wibble { Wobble(wibble: Wibble) Woo } fn new() { Wobble } ", find_position_of("wibble").to_selection() ); } #[test] fn pattern_match_on_clause_variable_nested_pattern() { assert_code_action!( PATTERN_MATCH_ON_VARIABLE, " pub fn main() { case maybe_wibble() { Ok(Wobble(something)) -> 1 Error(_) -> 2 } } type Wibble { Wobble(Wibble) Woo } fn maybe_wibble() { Ok(Woo) } ", find_position_of("something").to_selection() ); } #[test] fn pattern_match_on_clause_variable_with_block_body() { assert_code_action!( PATTERN_MATCH_ON_VARIABLE, " pub fn main() { case maybe_wibble() { Ok(something) -> { 1 2 } Error(_) -> 2 } } type Wibble { Wobble Woo } fn maybe_wibble() { Ok(Wobble) } ", find_position_of("something").to_selection() ); } #[test] fn pattern_match_on_argument_will_use_qualified_name() { let src = " import wibble pub fn main(arg: wibble.Wibble) { todo } "; let dep = " pub type Wibble { ThisShouldBeQualified(label: Int) } "; assert_code_action!( PATTERN_MATCH_ON_ARGUMENT, TestProject::for_source(src).add_module("wibble", dep), find_position_of("wibble").nth_occurrence(2).to_selection() ); } #[test] fn pattern_match_on_argument_will_use_unqualified_name() { let src = " import wibble.{ThisShouldBeUnqualified} pub fn main(arg: wibble.Wibble) { todo } "; let dep = " pub type Wibble { ThisShouldBeUnqualified(label: Int) } "; assert_code_action!( PATTERN_MATCH_ON_ARGUMENT, TestProject::for_source(src).add_module("wibble", dep), find_position_of("Wibble").to_selection() ); } #[test] fn pattern_match_on_argument_will_use_aliased_constructor_name() { let src = " import wibble.{Wobble as IWantToSeeThisName} pub fn main(arg: wibble.Wibble) { todo } "; let dep = " pub type Wibble { Wobble(label: Int) } "; assert_code_action!( PATTERN_MATCH_ON_ARGUMENT, TestProject::for_source(src).add_module("wibble", dep), find_position_of("arg").to_selection() ); } #[test] fn pattern_match_on_argument_will_use_aliased_module_name() { let src = " import wibble as i_want_to_see_this_name pub fn main(arg: i_want_to_see_this_name.Wibble) { todo } "; let dep = " pub type Wibble { Wobble(label: Int) } "; assert_code_action!( PATTERN_MATCH_ON_ARGUMENT, TestProject::for_source(src).add_dep_module("wibble", dep), find_position_of("arg").to_selection() ); } #[test] fn pattern_match_on_argument_not_available_for_internal_type() { let src = " import wibble pub fn main(arg: wobble.Wibble) { todo } "; let dep = " @internal pub type Wibble { Wobble(label: Int) } "; assert_no_code_actions!( PATTERN_MATCH_ON_ARGUMENT, TestProject::for_source(src).add_module("wibble", dep), find_position_of("arg").to_selection() ); } #[test] fn pattern_match_on_argument_available_for_internal_type_defined_in_current_module() { assert_code_action!( PATTERN_MATCH_ON_ARGUMENT, " @internal pub type Wibble { Wobble(label: Int) } pub fn main(arg: Wibble) { todo } ", find_position_of("arg").select_until(find_position_of("Wibble").nth_occurrence(2)) ); } #[test] fn pattern_match_on_argument_preserves_indentation_of_statement_following_inserted_let() { assert_code_action!( PATTERN_MATCH_ON_ARGUMENT, "pub fn main(arg: #(Int, String)) { todo //^^^^ This should still have two spaces of indentation! }", find_position_of("arg").to_selection() ); } #[test] fn pattern_match_on_argument_nicely_formats_code_when_used_on_function_with_empty_body() { assert_code_action!( PATTERN_MATCH_ON_ARGUMENT, "pub fn main(arg: #(Int, String)) {}", find_position_of("arg").to_selection() ); } #[test] fn pattern_match_on_argument_single_unlabelled_field_is_not_numbered() { assert_code_action!( PATTERN_MATCH_ON_ARGUMENT, " pub type Wibble { Wibble(Int) } pub fn main(arg: Wibble) {} ", find_position_of(":").to_selection() ); } #[test] fn pattern_match_on_let_assignment() { assert_code_action!( PATTERN_MATCH_ON_VARIABLE, " pub fn main() { let var = #(1, 2) } ", find_position_of("var").to_selection() ); } #[test] fn pattern_match_on_let_assignment_with_multiple_constructors() { assert_code_action!( PATTERN_MATCH_ON_VARIABLE, " pub type Wibble { Wobble Woo } pub fn main() { let var = Woo todo } ", find_position_of("var").to_selection() ); } #[test] fn pattern_match_on_use_assignment() { assert_code_action!( PATTERN_MATCH_ON_VARIABLE, " pub fn main() { use var <- f } fn f(g) { g(#(1, 2)) } ", find_position_of("var").to_selection() ); } #[test] fn pattern_match_on_use_assignment_with_multiple_constructors() { assert_code_action!( PATTERN_MATCH_ON_VARIABLE, " pub type Wibble { Wobble Woo } pub fn main() { use var <- f } fn f(g) { g(Wobble) } ", find_position_of("var").to_selection() ); } #[test] fn pattern_match_on_pattern_use_assignment() { assert_no_code_actions!( PATTERN_MATCH_ON_VARIABLE, " pub fn main() { use #(a, b) <- f } fn f(g) { g(#(1, 2)) } ", find_position_of("#").to_selection() ); } #[test] fn pattern_match_on_argument_works_on_fn_arguments() { assert_code_action!( PATTERN_MATCH_ON_ARGUMENT, " pub fn main() { [#(1, 2)] |> map(fn(tuple) {}) } fn map(list: List(a), fun: fn(a) -> b) { todo } ", find_position_of("tuple").to_selection() ); } #[test] fn pattern_match_on_argument_works_on_nested_fn_arguments() { assert_code_action!( PATTERN_MATCH_ON_ARGUMENT, " pub fn main() { map([[#(1, 2)]], fn(list) { map(list, fn(tuple) { todo }) }) } fn map(list: List(a), fun: fn(a) -> b) { todo } ", find_position_of("tuple").to_selection() ); } #[test] // https://github.com/gleam-lang/gleam/issues/5042 fn pattern_match_on_variable_crashes() { assert_code_action!( PATTERN_MATCH_ON_VARIABLE, r#" pub type Wibble { Wibble(Wobble) } pub type Wobble { Wobble Wubble } pub fn main() { let Wibble(wobble) = todo case todo { _ -> todo } } "#, find_position_of("wobble").to_selection() ); } #[test] fn generate_function_works_with_invalid_call() { assert_code_action!( GENERATE_FUNCTION, " pub fn main() -> Bool { wibble(1, True, 2.3) } ", find_position_of("wibble").to_selection() ); } #[test] fn generate_function_works_with_constants() { assert_code_action!( GENERATE_FUNCTION, "const wibble: fn(Int) -> String = wobble", find_position_of("wobble").to_selection() ); } #[test] fn generate_function_works_with_constants_2() { assert_code_action!( GENERATE_FUNCTION, " type Wibble(a) { Wibble(fun: fn(Int) -> a) } const wibble: Wibble(Int) = Wibble(missing) ", find_position_of("missing").to_selection() ); } #[test] fn generate_function_works_with_pipeline_steps() { assert_code_action!( GENERATE_FUNCTION, " pub fn main() { [1, 2, 3] |> sum |> int_to_string } fn int_to_string(n: Int) -> String { todo } ", find_position_of("sum").to_selection() ); } #[test] fn generate_function_works_with_pipeline_steps_1() { assert_code_action!( GENERATE_FUNCTION, " pub fn main() { [1, 2, 3] |> map(int_to_string) |> join } fn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo } fn join(n: List(String)) -> String { todo } ", find_position_of("int_to_string").to_selection() ); } // https://github.com/gleam-lang/gleam/issues/4177#event-15968345230 #[test] fn generate_function_picks_argument_name_based_on_type() { assert_code_action!( GENERATE_FUNCTION, " pub fn main() { wibble(\"Hello\", 1) } ", find_position_of("wibble").to_selection() ); } #[test] fn generate_function_picks_argument_name_based_on_record_access() { assert_code_action!( GENERATE_FUNCTION, " pub type User { User(id: Int, name: String) } pub fn go(user: User) { authenticate(user.id, user.name) } ", find_position_of("authenticate").to_selection() ); } #[test] fn generate_function_wont_generate_two_arguments_with_the_same_name_if_they_have_the_same_type() { assert_code_action!( GENERATE_FUNCTION, " pub fn main() { wibble(2, 1) } ", find_position_of("wibble").to_selection() ); } #[test] fn generate_function_takes_labels_into_account() { assert_code_action!( GENERATE_FUNCTION, " pub fn main() { wibble(2, n: 1) } ", find_position_of("wibble").to_selection() ); } #[test] fn generate_function_does_not_trigger_if_labels_are_in_the_wrong_order() { assert_no_code_actions!( GENERATE_FUNCTION, " pub fn main() { wibble(n: 2, 1) } ", find_position_of("wibble").to_selection() ); } #[test] fn generate_function_does_not_trigger_if_there_are_repeated_labels() { assert_no_code_actions!( GENERATE_FUNCTION, " pub fn main() { wibble(n: 2, n: 1) } ", find_position_of("wibble").to_selection() ); } #[test] fn generate_function_generates_argument_names_from_labels() { assert_code_action!( GENERATE_FUNCTION, " pub fn main() { add(1, addend: 10) } ", find_position_of("add").to_selection() ); } #[test] fn generate_function_generates_argument_names_from_variables() { assert_code_action!( GENERATE_FUNCTION, " pub fn main() { let wibble = 10 let wobble = 20 wubble(wibble, wobble, 14) } ", find_position_of("wubble").to_selection() ); } #[test] fn generate_function_labels_and_arguments_can_share_the_same_name() { assert_code_action!( GENERATE_FUNCTION, " pub fn main() { let wibble = 10 wubble(wibble, wibble: 14) } ", find_position_of("wubble").to_selection() ); } #[test] fn generate_function_arguments_with_same_name_get_renamed() { assert_code_action!( GENERATE_FUNCTION, " pub fn main() { let wibble = 10 wubble(wibble, wibble) } ", find_position_of("wubble").to_selection() ); } #[test] fn generate_function_arguments_with_labels_and_variables_uses_different_names() { assert_code_action!( GENERATE_FUNCTION, " pub fn main() { let list = [2, 4, 5] let value = 1 find(each: value, in: list) } ", find_position_of("find").to_selection() ); } #[test] fn pattern_match_on_argument_generates_unique_names_even_with_labels() { assert_code_action!( PATTERN_MATCH_ON_ARGUMENT, " pub type Wibble { Wibble(String, string: String) } pub fn main(wibble: Wibble) { todo } ", find_position_of("wibble").to_selection() ); } #[test] fn extract_variable_with_list_with_plural_name_does_not_add_another_s() { assert_code_action!( EXTRACT_VARIABLE, " pub fn main() { wibble([Names, Names]) } pub type Names { Names } ", find_position_of("[").to_selection() ); } #[test] fn convert_to_function_call_works_with_argument_in_first_position() { assert_code_action!( CONVERT_TO_FUNCTION_CALL, " pub fn main() { [1, 2, 3] |> map(todo) } fn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo } ", find_position_of("map").to_selection() ); } #[test] fn generate_json_encoder() { let src = " pub type Person { Person(name: String, age: Int, height: Float, is_cool: Bool) } "; assert_code_action!( GENERATE_TO_JSON_FUNCTION, TestProject::for_source(src).add_package_module( "gleam_json", "gleam/json", "pub type Json" ), find_position_of("type").to_selection() ); } #[test] fn convert_to_function_call_works_with_argument_in_first_position_2() { assert_code_action!( CONVERT_TO_FUNCTION_CALL, " pub fn main() { [1, 2, 3] |> wibble } fn wibble(a) { todo } ", find_position_of("wibble").to_selection() ); } #[test] fn generate_json_encoder_complex_types() { let src = " import gleam/option import gleam/dict pub type Something pub type Wibble(value) { Wibble( maybe: option.Option(Int), something: Something, map: dict.Dict(String, List(Float)), unknown: List(value), ) } "; assert_code_action!( GENERATE_TO_JSON_FUNCTION, TestProject::for_source(src) .add_module("gleam/option", "pub type Option(a)") .add_module("gleam/dict", "pub type Dict(k, v)") .add_package_module("gleam_json", "gleam/json", "pub type Json"), find_position_of("type W").to_selection() ); } #[test] fn convert_to_function_call_works_with_argument_in_first_position_3() { assert_code_action!( CONVERT_TO_FUNCTION_CALL, " pub fn main() { [1, 2, 3] |> wibble() } fn wibble(a) { todo } ", find_position_of("wibble").to_selection() ); } #[test] fn generate_json_encoder_already_imported_module() { let src = " import gleam/json as json_encoding pub type Wibble { Wibble(a: Int, b: Float, c: String) } "; assert_code_action!( GENERATE_TO_JSON_FUNCTION, TestProject::for_source(src).add_package_module( "gleam_json", "gleam/json", "pub type Json" ), find_position_of("type W").to_selection() ); } #[test] fn convert_to_function_call_works_with_argument_in_first_position_4() { assert_code_action!( CONVERT_TO_FUNCTION_CALL, " pub fn main() { [1, 2, 3] |> wibble.wobble } ", find_position_of("wibble").to_selection() ); } #[test] fn generate_json_encoder_tuple() { let src = " pub type Wibble { Wibble(tuple: #(Int, Float, #(String, Bool))) } "; assert_code_action!( GENERATE_TO_JSON_FUNCTION, TestProject::for_source(src).add_package_module( "gleam_json", "gleam/json", "pub type Json" ), find_position_of("type W").to_selection() ); } #[test] fn generate_json_encoder_for_variant_with_no_fields() { let src = " pub type Wibble { Wibble } "; assert_code_action!( GENERATE_TO_JSON_FUNCTION, TestProject::for_source(src).add_package_module( "gleam_json", "gleam/json", "pub type Json" ), find_position_of("type W").to_selection() ); } #[test] fn generate_json_encoder_for_type_with_multiple_variants_with_no_fields() { let src = " pub type Wibble { Wibble Wobble Woo } "; assert_code_action!( GENERATE_TO_JSON_FUNCTION, TestProject::for_source(src).add_package_module( "gleam_json", "gleam/json", "pub type Json" ), find_position_of("type W").to_selection() ); } #[test] fn generate_json_encoder_for_variants_with_mixed_fields() { let src = " pub type Wibble { Wibble Wobble(field: String, field1: Int) } "; assert_code_action!( GENERATE_TO_JSON_FUNCTION, TestProject::for_source(src).add_package_module( "gleam_json", "gleam/json", "pub type Json" ), find_position_of("type W").to_selection() ); } #[test] fn convert_to_function_call_works_with_function_producing_another_function() { assert_code_action!( CONVERT_TO_FUNCTION_CALL, " pub fn main() { 1 |> wibble(2) } fn wibble(c) -> fn(a) -> Nil { fn(_) { Nil } } ", find_position_of("wibble").to_selection() ); } #[test] fn generate_json_encoder_recursive_type() { let src = " import gleam/option.{Some} pub type LinkedList { LinkedList(value: Int, next: option.Option(LinkedList)) } "; assert_code_action!( GENERATE_TO_JSON_FUNCTION, TestProject::for_source(src) .add_module("gleam/option", "pub type Option(a) { Some(a) None }") .add_package_module("gleam_json", "gleam/json", "pub type Json"), find_position_of("type").to_selection() ); } #[test] fn convert_to_function_call_works_with_hole_in_first_position() { assert_code_action!( CONVERT_TO_FUNCTION_CALL, " pub fn main() { [1, 2, 3] |> map(_, todo) } fn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo } ", find_position_of("[").to_selection() ); } #[test] fn generate_json_encoder_list_of_tuples() { let src = " pub type Wibble { Wibble(values: List(#(Int, String))) } "; assert_code_action!( GENERATE_TO_JSON_FUNCTION, TestProject::for_source(src).add_package_module( "gleam_json", "gleam/json", "pub type Json" ), find_position_of("type").to_selection() ); } #[test] fn generate_json_encoder_for_multi_variant_type() { let src = " pub type Wibble { Wibble(wibble: Int, next: Wibble) Wobble(wobble: Float, text: String, values: List(Bool)) } "; assert_code_action!( GENERATE_TO_JSON_FUNCTION, TestProject::for_source(src).add_package_module( "gleam_json", "gleam/json", "pub type Json" ), find_position_of("type").to_selection() ); } #[test] fn generate_json_encoder_for_multi_variant_type_multi_word_name() { let src = " pub type Wibble { OneTwoThree(wibble: Int, next: Wibble) FourFive(wobble: Float, text: String, values: List(Bool)) SixSevenEight(one_two: Float) } "; assert_code_action!( GENERATE_TO_JSON_FUNCTION, TestProject::for_source(src).add_package_module( "gleam_json", "gleam/json", "pub type Json" ), find_position_of("type").to_selection() ); } #[test] fn convert_to_function_call_works_with_hole_not_in_first_position() { assert_code_action!( CONVERT_TO_FUNCTION_CALL, " pub fn main() { fn(a) { todo } |> map([1, 2, 3], _) } fn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo } ", find_position_of("fn(a)").select_until(find_position_of("map")) ); } #[test] fn convert_to_function_call_always_inlines_the_first_step() { assert_code_action!( CONVERT_TO_FUNCTION_CALL, " pub fn main() { [1, 2, 3] |> map(todo) |> filter(todo) } fn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo } fn filter(list: List(a), fun: fn(a) -> Bool) -> List(b) { todo } ", find_position_of("[1, 2, 3]").select_until(find_position_of("map")) ); } #[test] fn convert_to_function_call_works_with_labelled_argument() { assert_code_action!( CONVERT_TO_FUNCTION_CALL, " pub fn main() { [1, 2, 3] |> wibble(wobble: _, woo:) } ", find_position_of("wibble").to_selection() ); } #[test] fn convert_to_function_call_works_with_labelled_argument_2() { assert_code_action!( CONVERT_TO_FUNCTION_CALL, " pub fn main() { [1, 2, 3] |> wibble(wobble:, woo: _) } ", find_position_of("wibble").to_selection() ); } #[test] fn convert_to_function_call_works_when_piping_an_invalid_module_select() { assert_code_action!( CONVERT_TO_FUNCTION_CALL, " pub fn main() { wibble.wobble |> woo(_) } ", find_position_of("woo").to_selection() ); } #[test] fn convert_to_function_call_works_when_piping_a_module_select() { let src = " import wibble pub fn main() { wibble.wobble |> woo(_) } fn woo(n) { todo } "; assert_code_action!( CONVERT_TO_FUNCTION_CALL, TestProject::for_source(src).add_module("wibble", "pub const wobble = 1"), find_position_of("woo").to_selection() ); } #[test] fn convert_to_function_call_works_with_echo() { assert_code_action!( CONVERT_TO_FUNCTION_CALL, " pub fn main() { wibble.wobble |> echo } ", find_position_of("echo").to_selection() ); } #[test] fn no_code_action_to_generate_json_encoder_for_type_without_labels() { let src = " pub type Wibble { Wibble(Int, Int, String) } "; assert_no_code_actions!( GENERATE_TO_JSON_FUNCTION, TestProject::for_source(src).add_package_module( "gleam_json", "gleam/json", "pub type Json" ), find_position_of("type").to_selection() ); } #[test] fn no_code_action_to_generate_json_encoder_without_gleam_json_dependency() { assert_no_code_actions!( GENERATE_TO_JSON_FUNCTION, " pub type Wibble { Wibble(w: Int) } ", find_position_of("type").to_selection() ); } #[test] fn inline_variable() { let src = r#" import gleam/io pub fn main() { let message = "Hello!" io.println(message) } "#; assert_code_action!( INLINE_VARIABLE, TestProject::for_source(src).add_module("gleam/io", "pub fn println(value) {}"), find_position_of("message)").to_selection() ); } #[test] fn inline_variable_from_definition() { let src = r#" import gleam/io pub fn main() { let message = "Hello!" io.println(message) } "#; assert_code_action!( INLINE_VARIABLE, TestProject::for_source(src).add_module("gleam/io", "pub fn println(value) {}"), find_position_of("message =").to_selection() ); } #[test] fn inline_variable_in_nested_scope() { let src = r#" import gleam/io pub fn main() { let _ = { let message = "Hello!" io.println(message) } } "#; assert_code_action!( INLINE_VARIABLE, TestProject::for_source(src).add_module("gleam/io", "pub fn println(value) {}"), find_position_of("message =").to_selection() ); } #[test] fn inline_variable_in_case_scope() { let src = r#" import gleam/io pub fn main(x) { case x { True -> { let message = "Hello!" io.println(message) } False -> Nil } } "#; assert_code_action!( INLINE_VARIABLE, TestProject::for_source(src).add_module("gleam/io", "pub fn println(value) {}"), find_position_of("message =").to_selection() ); } #[test] fn inline_variable_when_over_let_keyword() { assert_code_action!( INLINE_VARIABLE, r#" pub fn main() { let x = 123 x + 1 } "#, find_position_of("let").to_selection() ); } #[test] fn no_code_action_to_inline_variable_used_multiple_times() { let src = r#" import gleam/io pub fn main() { let message = "Hello!" io.println(message) io.debug(message) } "#; assert_no_code_actions!( INLINE_VARIABLE, TestProject::for_source(src).add_module( "gleam/io", "pub fn println(value) {} pub fn debug(value) {}" ), find_position_of("message =").to_selection() ); } #[test] fn no_code_action_to_inline_variable_defined_in_complex_pattern() { let src = r#" import gleam/io pub fn main() { let #(message, second, _) = todo io.println(message) } "#; assert_no_code_actions!( INLINE_VARIABLE, TestProject::for_source(src).add_module("gleam/io", "pub fn println(value) {}"), find_position_of("message)").to_selection() ); } #[test] fn no_code_action_to_inline_variable_defined_in_case_clause() { let src = r#" import gleam/io pub fn main(result) { case result { Ok(value) -> value Error(message) -> { io.println(message) panic } } } "#; assert_no_code_actions!( INLINE_VARIABLE, TestProject::for_source(src).add_module("gleam/io", "pub fn println(value) {}"), find_position_of("message").nth_occurrence(2).to_selection() ); } #[test] fn convert_to_pipe_with_function_call_on_first_argument() { assert_code_action!( CONVERT_TO_PIPE, " pub fn main() { wibble(wobble, woo) } ", find_position_of("wobble").to_selection() ); } #[test] fn convert_to_pipe_with_function_call_on_second_argument() { assert_code_action!( CONVERT_TO_PIPE, " pub fn main() { wibble(wobble, woo) } ", find_position_of("woo").to_selection() ); } #[test] fn convert_to_pipe_with_function_call_on_function_name_extracts_first_argument() { assert_code_action!( CONVERT_TO_PIPE, " pub fn main() { wibble(wobble, woo) } ", find_position_of("wibble").to_selection() ); } #[test] fn convert_to_pipe_works_in_anonymous_function_inside_a_pipeline() { assert_code_action!( CONVERT_TO_PIPE, " pub fn main() { wibble |> wobble(fn() { woo(1) }) } ", find_position_of("woo").to_selection() ); } #[test] fn convert_to_pipe_works_in_final_step_of_a_pipeline() { assert_code_action!( CONVERT_TO_PIPE, " pub fn main() { wibble |> wobble(woo(1)) } ", find_position_of("woo").to_selection() ); } #[test] fn convert_to_pipe_with_function_call_with_labelled_arguments_inserts_hole() { assert_code_action!( CONVERT_TO_PIPE, " pub fn main() { wibble(wobble: 1, woo: 2) } ", find_position_of("wobble").to_selection() ); } #[test] fn convert_to_pipe_with_function_call_with_labelled_arguments_inserts_hole_2() { assert_code_action!( CONVERT_TO_PIPE, " pub fn main() { wibble(wobble: 1, woo: 2) } ", find_position_of("woo").to_selection() ); } #[test] fn convert_to_pipe_with_function_call_with_shorthand_labelled_argument_inserts_hole() { assert_code_action!( CONVERT_TO_PIPE, " pub fn main() { wibble(wobble:, woo:) } ", find_position_of("wobble").to_selection() ); } #[test] fn convert_to_pipe_with_function_call_with_shorthand_labelled_argument_inserts_hole_2() { assert_code_action!( CONVERT_TO_PIPE, " pub fn main() { wibble(wobble:, woo:) } ", find_position_of("woo").to_selection() ); } #[test] fn convert_to_pipe_on_first_step_of_pipeline() { assert_code_action!( CONVERT_TO_PIPE, " pub fn main() { wibble(wobble, woo) |> wobble } ", find_position_of("wibble").to_selection() ); } #[test] fn convert_to_pipe_not_allowed_on_other_pipeline_steps() { assert_no_code_actions!( CONVERT_TO_PIPE, " pub fn main() { wibble(wobble) |> wobble(woo) } ", find_position_of("woo").to_selection() ); } #[test] fn convert_to_pipe_with_function_returning_other_function() { assert_code_action!( CONVERT_TO_PIPE, " pub fn main() { wibble(wobble)(woo) } ", find_position_of("woo").to_selection() ); } #[test] fn convert_to_pipe_does_not_work_on_function_on_the_right_hand_side_of_use() { assert_no_code_actions!( CONVERT_TO_PIPE, " pub fn main() { use <- wibble(wobble) todo } ", find_position_of("wibble").to_selection() ); } #[test] fn convert_to_pipe_does_not_work_on_function_on_the_right_hand_side_of_use_2() { assert_no_code_actions!( CONVERT_TO_PIPE, " pub fn main() { use <- wibble(wobble) todo } ", find_position_of("todo").to_selection() ); } #[test] fn convert_to_pipe_does_not_work_on_function_with_capture() { assert_no_code_actions!( CONVERT_TO_PIPE, "import gleam/int pub fn main() { let sum = int.add(1, _) sum } ", find_position_of("1").to_selection() ); } #[test] fn convert_to_pipe_does_not_work_on_record_with_capture() { assert_no_code_actions!( CONVERT_TO_PIPE, "pub fn main() { Ok(_) } ", find_position_of("O").to_selection() ); } #[test] fn convert_to_pipe_works_inside_body_of_use() { assert_code_action!( CONVERT_TO_PIPE, " pub fn main() { use <- wibble(wobble) woo(123) } ", find_position_of("woo").to_selection() ); } #[test] fn convert_to_pipe_pipes_the_outermost_argument() { assert_code_action!( CONVERT_TO_PIPE, " pub fn main() { wibble(wobble(woo)) } ", find_position_of("wobble").to_selection() ); } #[test] fn convert_to_pipe_when_first_arg_is_a_pipe_itself() { assert_code_action!( CONVERT_TO_PIPE, " pub fn main() { wibble(wobble |> woo, waa) } ", find_position_of("wibble").to_selection() ); } #[test] fn convert_to_pipe_with_string_concat_adds_braces() { assert_code_action!( CONVERT_TO_PIPE, " pub fn main() { wibble(wobble <> woo, waa) } ", find_position_of("wibble").to_selection() ); } #[test] fn convert_to_pipe_with_bool_operator_adds_braces() { assert_code_action!( CONVERT_TO_PIPE, " pub fn main() { wibble(wobble != woo, waa) } ", find_position_of("wibble").to_selection() ); } #[test] fn convert_to_pipe_with_sum_adds_no_braces() { assert_code_action!( CONVERT_TO_PIPE, " pub fn main() { wibble(1 + 1, waa) } ", find_position_of("wibble").to_selection() ); } #[test] fn convert_to_pipe_with_comparison_adds_braces() { assert_code_action!( CONVERT_TO_PIPE, " pub fn main() { wibble(1.0 >=. 0.0, waa) } ", find_position_of("wibble").to_selection() ); } #[test] fn convert_to_pipe_with_complex_binop_adds_braces() { assert_code_action!( CONVERT_TO_PIPE, " fn bug() { bool.guard(1 == 2 || 2 == 3, Nil, fn() { Nil }) } ", find_position_of("||").to_selection() ); } #[test] fn convert_to_pipe_with_nested_calls_picks_the_innermost_one() { assert_code_action!( CONVERT_TO_PIPE, " fn bug() { wibble(Wobble( field: woo(other_call(last)) )) } ", find_position_of("woo").to_selection() ); } // https://github.com/gleam-lang/gleam/issues/4342 #[test] fn inline_variable_in_record_update() { assert_code_action!( INLINE_VARIABLE, " type Couple { Couple(l: Int, r: Int) } pub fn main() { let c1 = Couple(l: 1, r: 1) let c2 = Couple(..c1, r: 1) } ", find_position_of("c1,").to_selection() ); } // https://github.com/gleam-lang/gleam/issues/4430 #[test] fn inline_variable_with_record_field() { assert_code_action!( INLINE_VARIABLE, " type Couple { Couple(l: Int, r: Int) } pub fn main() { let c1 = Couple(l: 1, r: 1) let c2 = c1.l echo c2 } ", find_position_of("c2").nth_occurrence(2).to_selection() ); } #[test] fn wrap_case_clause_in_block() { assert_code_action!( WRAP_IN_BLOCK, " pub fn f(option) { case option { Some(content) -> content None -> panic } }", find_position_of("content").nth_occurrence(2).to_selection() ); } #[test] fn wrap_nested_case_clause_in_block() { assert_code_action!( WRAP_IN_BLOCK, " pub fn f(result) { case result { Ok(reresult) -> { case reresult { Ok(w) -> w Error(_) -> panic } } Error(_) -> panic } }", find_position_of("w").nth_occurrence(2).to_selection() ); } #[test] fn wrap_case_clause_with_guard_in_block() { assert_code_action!( WRAP_IN_BLOCK, " pub fn f(option) { case option { Some(integer) if integer > 0 -> integer Some(integer) -> 0 None -> panic } }", find_position_of("integer").nth_occurrence(3).to_selection() ); } #[test] fn wrap_case_clause_with_multiple_patterns_in_block() { assert_code_action!( WRAP_IN_BLOCK, "pub type PokemonType { Air Water Fire } pub fn f(pokemon_type: PokemonType) { case pokemon_type { Water | Air -> soak() Fire -> burn() } }", find_position_of("soak").to_selection() ); } #[test] fn wrap_case_clause_inside_assignment_in_block() { assert_code_action!( WRAP_IN_BLOCK, r#"pub type PokemonType { Air Water Fire } pub fn f(pokemon_type: PokemonType) { let damage = case pokemon_type { Water -> soak() Fire -> burn() } "Pokemon did " <> damage }"#, find_position_of("burn").to_selection() ); } #[test] fn wrap_case_assignment_of_record_access_in_block() { assert_code_action!( WRAP_IN_BLOCK, r#" pub type Record { R(left: Int, right: Int) } pub fn main() { let r = R(1, 2) let l = r.left l } "#, find_position_of("left").nth_occurrence(2).to_selection() ); } #[test] fn do_not_wrap_case_clause_in_block_1() { assert_no_code_actions!( WRAP_IN_BLOCK, " pub fn f(option) { case option { Some(content) -> { content } None -> panic } }", find_position_of("content").nth_occurrence(2).to_selection() ); } #[test] fn do_not_wrap_case_clause_in_block_2() { assert_no_code_actions!( WRAP_IN_BLOCK, " pub fn f(result) { case result { Ok(reresult) -> { case reresult { Ok(w) -> { w } Error(_) -> panic } } Error(_) -> panic } }", find_position_of("w").nth_occurrence(2).to_selection() ); } #[test] fn do_not_wrap_case_clause_in_block_3() { assert_no_code_actions!( WRAP_IN_BLOCK, " pub fn f(option) { case option { Some(content) -> content None -> panic } }", find_position_of("Some(content)").to_selection() ); } #[test] fn wrap_assignment_value_in_block() { assert_code_action!( WRAP_IN_BLOCK, r#"pub fn main() { let var = "value" }"#, find_position_of("value").select_until(find_position_of("e").nth_occurrence(2)) ); } #[test] fn do_not_wrap_assignment_value_in_block() { assert_no_code_actions!( WRAP_IN_BLOCK, r#"pub fn main() { let var = "value" }"#, find_position_of("var").to_selection() ); } // https://github.com/gleam-lang/gleam/issues/4427 #[test] fn extract_constant_function() { assert_no_code_actions!( EXTRACT_CONSTANT, r#" fn print(x) { Nil } pub fn main() { print("Hello") } "#, find_position_of("print").nth_occurrence(2).to_selection() ); } #[test] fn fix_float_operator_on_ints() { let name = "Use `>=`"; assert_code_action!( name, r#" pub fn main() { 1 >=. 2 } "#, find_position_of("1").to_selection() ); } #[test] fn fix_float_operator_on_ints_2() { let name = "Use `-`"; assert_code_action!( name, r#" pub fn main() { 1 -. 2 } "#, find_position_of("1").select_until(find_position_of("2")) ); } #[test] fn fix_float_operator_on_ints_3() { let name = "Use `*`"; assert_code_action!( name, r#" pub fn main() { 1 *. wobble() } fn wobble() { 3 } "#, find_position_of("*.").to_selection() ); } #[test] fn fix_int_operator_on_floats() { let name = "Use `>=.`"; assert_code_action!( name, r#" pub fn main() { 1.0 >= 2.3 } "#, find_position_of("1").to_selection() ); } #[test] fn fix_int_operator_on_floats_2() { let name = "Use `-.`"; assert_code_action!( name, r#" pub fn main() { 1.12 - 2.0 } "#, find_position_of("1").select_until(find_position_of("2.0")) ); } #[test] fn fix_int_operator_on_floats_3() { let name = "Use `*.`"; assert_code_action!( name, r#" pub fn main() { 1.3 * wobble() } fn wobble() { 3.2 } "#, find_position_of("*").to_selection() ); } #[test] fn fix_plus_operator_on_strings() { let name = "Use `<>`"; assert_code_action!( name, r#" pub fn main() { "hello, " + name() } fn name() { "Jak" } "#, find_position_of("hello").select_until(find_position_of("name()")) ); } // https://github.com/gleam-lang/gleam/issues/4454 #[test] fn unqualify_already_imported_type() { let src = " import wibble.{type Wibble} pub fn main() -> wibble.Wibble { todo } "; assert_code_action!( "Unqualify wibble.Wibble", TestProject::for_source(src).add_hex_module("wibble", "pub type Wibble"), find_position_of("wibble.Wibble").to_selection(), ); } #[test] fn fill_labels_pattern_constructor() { assert_code_action!( FILL_LABELS, " pub type Wibble { Wibble(a: Int, b: Float, c: String) Wobble(d: Bool, e: BitArray, f: List(Result(String, Nil))) } pub fn main(w: Wibble) { case w { Wibble(..) -> todo Wobble() -> todo } } ", find_position_of("Wobble()").to_selection(), ); } #[test] fn fill_labels_pattern_constructor_let_assignment() { assert_code_action!( FILL_LABELS, " pub type Wibble { Wibble(a: Int, b: Float, c: String) } pub fn main() { let Wibble() = todo } ", find_position_of("Wibble()").to_selection(), ); } #[test] fn fill_labels_pattern_constructor_with_some_labels() { assert_code_action!( FILL_LABELS, " pub type Wibble { Wibble(a: Int, b: Float, c: String) Wobble(d: Bool, e: BitArray, f: List(Result(String, Nil))) } pub fn main(w: Wibble) { case w { Wobble(e: <<>>) -> todo _ -> todo } } ", find_position_of("Wobble(e").to_selection(), ); } #[test] fn fill_labels_nested_pattern_constructor() { assert_code_action!( FILL_LABELS, " pub type Wibble { Wibble(a: Int, b: Float, c: String) Wobble(d: Bool, e: BitArray, f: List(Result(String, Nil))) } pub fn main() { case todo { #([Wobble()], 2, 3) -> todo _ -> todo } } ", find_position_of("Wobble()").to_selection(), ); } #[test] // https://github.com/gleam-lang/gleam/issues/4499 fn fill_labels_with_function_with_unlabelled_arguments() { assert_no_code_actions!( FILL_LABELS, " pub fn main() { fold(0, over: [], with: fn(acc, item) { acc + item }) } pub fn fold(over list, from initial, with fun) { todo }", find_position_of("fold").to_selection(), ); } #[test] fn add_missing_patterns_with_labels() { assert_code_action!( ADD_MISSING_PATTERNS, " pub type Wibble { Wibble(integer: Int, float: Float) Wobble(string: String, bool: Bool) } pub fn main(w: Wibble) { case w {} } ", find_position_of("case w").select_until(find_position_of("{}")), ); } // https://github.com/gleam-lang/gleam/issues/3628#issuecomment-2543342212 #[test] fn add_missing_patterns_multibyte_grapheme() { assert_code_action!( ADD_MISSING_PATTERNS, r#" // ä fn wibble() { case True {} } "#, find_position_of("case").select_until(find_position_of("True {")) ); } // https://github.com/gleam-lang/gleam/issues/4626 #[test] fn add_missing_patterns_opaque_type() { let src = " import mod pub fn main(w: mod.Wibble) { case w {} } "; assert_code_action!( ADD_MISSING_PATTERNS, TestProject::for_source(src).add_hex_module( "mod", "pub opaque type Wibble { Wibble(Int) Wobble(String) }" ), find_position_of("{}").to_selection(), ); } #[test] fn pattern_match_on_variable_does_not_add_patterns_for_internal_type() { let src = " import wibble pub type Type { Type(wibble: wibble.Wibble, list: List(Int)) } pub fn main(thing: Type) { case thing { Type(wibble:, ..) -> todo } } "; assert_no_code_actions!( PATTERN_MATCH_ON_VARIABLE, TestProject::for_source(src).add_module( "wibble", "@internal pub type Wibble { Wibble(Int) Wobble(String) }" ), find_position_of("wibble").nth_occurrence(3).to_selection(), ); } #[test] fn pattern_match_on_argument_does_not_add_patterns_for_internal_type() { let src = " import wibble pub fn main(thing: wibble.Wibble) {} "; assert_no_code_actions!( PATTERN_MATCH_ON_ARGUMENT, TestProject::for_source(src).add_module( "wibble", "@internal pub type Wibble { Wibble(Int) Wobble(String) }" ), find_position_of("thing").to_selection(), ); } #[test] fn pattern_match_on_argument_adds_patterns_for_internal_type_inside_module_where_it_is_defined() { let src = " @internal pub type Wibble { Wibble(Int) Wobble(String) } pub fn main(thing: Wibble) {} "; assert_code_action!( PATTERN_MATCH_ON_ARGUMENT, TestProject::for_source(src), find_position_of("thing").to_selection(), ); } #[test] fn add_missing_patterns_adds_a_discard_for_opaque_type() { let src = " import wibble pub fn main(w: wibble.Wibble) { case w {} } "; assert_code_action!( ADD_MISSING_PATTERNS, TestProject::for_source(src).add_module( "wibble", "@internal pub type Wibble { Wibble(Int) Wobble(String) }" ), find_position_of("{}").to_selection(), ); } #[test] fn add_missing_patterns_adds_a_discard_for_opaque_type_1() { let src = " import wibble pub type Type { Type(wibble: wibble.Wibble, list: List(Int)) } pub fn main(thing: Type) { case thing {} } "; assert_code_action!( ADD_MISSING_PATTERNS, TestProject::for_source(src).add_module( "wibble", "@internal pub type Wibble { Wibble(Int) Wobble(String) }" ), find_position_of("{}").to_selection(), ); } #[test] fn add_missing_patterns_adds_a_discard_for_opaque_type_2() { let src = " import wibble pub type Type { Type(wibble.Wibble) } pub fn main(thing: Type) { case thing {} } "; assert_code_action!( ADD_MISSING_PATTERNS, TestProject::for_source(src).add_module( "wibble", "@internal pub type Wibble { Wibble(Int) Wobble(String) }" ), find_position_of("{}").to_selection(), ); } #[test] fn add_missing_patterns_adds_patterns_for_internal_type_inside_same_module_where_it_is_defined() { let src = " @internal pub type Wibble { Wibble(Int) Wobble(String) } pub fn main(thing: Wibble) { case thing {} } "; assert_code_action!( ADD_MISSING_PATTERNS, TestProject::for_source(src), find_position_of("thing").nth_occurrence(2).to_selection(), ); } // https://github.com/gleam-lang/gleam/issues/4653 #[test] fn generate_function_capture() { assert_code_action!( GENERATE_FUNCTION, " fn map(list: List(a), f: fn(a) -> b) -> List(b) { todo } pub fn main() { map([1, 2, 3], add(_, 1)) } ", find_position_of("add").to_selection() ); } // https://github.com/gleam-lang/gleam/issues/4660#issuecomment-2932371619 #[test] fn inline_variable_label_shorthand() { assert_code_action!( INLINE_VARIABLE, " pub type Example { Example(sum: Int, nil: Nil) } pub fn main() { let sum = 1 + 1 Example(Nil, sum:) } ", find_position_of("sum = ").to_selection() ); } // https://github.com/gleam-lang/gleam/issues/4660 #[test] fn no_inline_variable_action_for_parameter() { assert_no_code_actions!( INLINE_VARIABLE, " pub fn main() { let x = fn(something) { something } x } ", find_position_of("something") .nth_occurrence(2) .to_selection() ); } #[test] fn no_inline_variable_action_when_spanning_multiple_items() { assert_no_code_actions!( INLINE_VARIABLE, " pub fn main(x: Int, y: Int) { let a = 1 let b = 2 main(a, b) } ", find_position_of("main") .nth_occurrence(2) .select_until(find_position_of(")").nth_occurrence(2)) ); } #[test] fn no_inline_variable_action_for_use_pattern() { assert_no_code_actions!( INLINE_VARIABLE, " pub fn main() { let x = { use something <- todo something } x } ", find_position_of("something").to_selection() ); } #[test] fn no_inline_variable_action_for_case_pattern() { assert_no_code_actions!( INLINE_VARIABLE, " pub fn main() { let x = case todo { something -> something } x } ", find_position_of("something") .nth_occurrence(2) .to_selection() ); } // https://github.com/gleam-lang/gleam/issues/4675 #[test] fn extract_variable_use() { assert_no_code_actions!( EXTRACT_VARIABLE, " pub fn main() { #({ use <- todo todo }) } ", find_position_of("use").to_selection() ); } #[test] fn extract_variable_in_anonymous_fn_in_argument() { assert_code_action!( EXTRACT_VARIABLE, "fn map(value, fn_over_value) { todo } pub fn main() { 1 |> Ok |> map(fn(value) { value + 2 }) }", find_position_of("2").to_selection() ); } #[test] fn extract_variable_starting_pipeline_steps() { assert_code_action!( EXTRACT_VARIABLE, "fn map(value, fn_over_value) { todo } pub fn main() { 1 |> Ok |> map(fn(value) { value + 2 }) }", find_position_of("1").select_until(find_position_of("Ok")) ); } #[test] fn do_not_extract_top_level_variable_in_anonymous_fn_in_argument() { assert_no_code_actions!( EXTRACT_VARIABLE, "fn map(value, fn_over_value) { todo } pub fn main() { 1 |> Ok |> map(fn(value) { value + 1 }) }", find_position_of("value").nth_occurrence(4).to_selection() ); } // https://github.com/gleam-lang/gleam/issues/4739 #[test] fn do_not_import_internal_modules() { const IMPORT_MODULE: &str = "Import `package/internal`"; let code = " pub fn main() { internal.some_internal_function() } "; assert_no_code_actions!( IMPORT_MODULE, TestProject::for_source(code).add_package_module( "package", "package/internal", "pub fn some_internal_function() { todo }" ), find_position_of("internal").to_selection() ); } #[test] fn import_internal_module_from_same_package() { let code = " pub fn main() { internal.some_internal_function() } "; assert_code_action!( "Import `app/internal`", TestProject::for_source(code).add_package_module( "app", "app/internal", "pub fn some_internal_function() { todo }" ), find_position_of("internal").to_selection() ); } #[test] fn remove_block_1() { assert_code_action!( REMOVE_BLOCK, "pub fn main() { { 1 } } ", find_position_of("1").to_selection() ); } #[test] fn remove_block_2() { assert_code_action!( REMOVE_BLOCK, "pub fn main() { { main() <> 2 } } ", find_position_of("}").to_selection() ); } #[test] fn remove_block_3() { assert_code_action!( REMOVE_BLOCK, "pub fn main() { case 1 { _ -> { main() <> 2 } } } ", find_position_of("{").nth_occurrence(3).to_selection() ); } #[test] fn remove_block_triggers_on_the_innermost_selected_block() { assert_code_action!( REMOVE_BLOCK, "pub fn main(x) { { main({ 1 }) } } ", find_position_of("1").to_selection() ); } #[test] fn remove_block_does_not_unwrap_a_let_assignment() { assert_no_code_actions!( REMOVE_BLOCK, "pub fn main(x) { { let a = 1 } } ", find_position_of("let").to_selection() ); } #[test] fn remove_block_unwraps_a_single_expression_in_a_binop() { assert_code_action!( REMOVE_BLOCK, "pub fn main(x) { { main(1) } * 3 } ", find_position_of("main").nth_occurrence(2).to_selection() ); } #[test] fn remove_block_does_not_unwrap_a_binop() { assert_no_code_actions!( REMOVE_BLOCK, "pub fn main(x) { { 1 * 2 } + 3 } ", find_position_of("1").to_selection() ); } #[test] fn remove_block_does_not_unwrap_a_block_with_multiple_statements() { assert_no_code_actions!( REMOVE_BLOCK, "pub fn main(x) { { main(1) main(2) } } ", find_position_of("1").to_selection() ); } #[test] fn remove_opaque_from_private_type() { assert_code_action!( REMOVE_OPAQUE_FROM_PRIVATE_TYPE, "opaque type Wibble { Wobble } ", find_position_of("Wibble").to_selection() ); } #[test] fn allow_further_pattern_matching_on_let_tuple_destructuring() { assert_code_action!( PATTERN_MATCH_ON_VARIABLE, "pub fn main(x) { let #(one, other) = #(Ok(1), Error(Nil)) } ", find_position_of("one").to_selection() ); } #[test] fn allow_further_pattern_matching_on_let_record_destructuring() { assert_code_action!( PATTERN_MATCH_ON_VARIABLE, "pub fn main(x) { let Wibble(field:) = Wibble(Ok(Nil)) } pub type Wibble { Wibble(field: Result(Nil, String)) } ", find_position_of("field").to_selection() ); } #[test] fn allow_further_pattern_matching_on_asserted_result() { assert_code_action!( PATTERN_MATCH_ON_VARIABLE, "pub fn main(x) { let assert Ok(one) = Ok(Error(Nil)) } ", find_position_of("one").to_selection() ); } #[test] fn allow_further_pattern_matching_on_asserted_list() { assert_code_action!( PATTERN_MATCH_ON_VARIABLE, "pub fn main(x) { let assert [first, ..] = [Ok(Nil), ..todo] todo } ", find_position_of("first").to_selection() ); } #[test] fn pattern_match_on_list_variable() { assert_code_action!( PATTERN_MATCH_ON_ARGUMENT, "pub fn main(a_list: List(a)) { todo }", find_position_of("a_list").to_selection() ); } #[test] fn pattern_match_on_list_tail() { assert_code_action!( PATTERN_MATCH_ON_VARIABLE, "pub fn main(a_list: List(a)) { case a_list { [] -> todo [first, ..rest] -> todo } }", find_position_of("rest").to_selection() ); } #[test] fn pattern_match_on_list_tail_with_strange_whitespace() { assert_code_action!( PATTERN_MATCH_ON_VARIABLE, "pub fn main(a_list: List(a)) { case a_list { [] -> todo [first, .. rest] -> todo } }", find_position_of(" ").to_selection() ); } #[test] fn pattern_match_on_list_tail_used_in_a_branch() { assert_code_action!( PATTERN_MATCH_ON_VARIABLE, "pub fn main(a_list: List(a)) { case a_list { [] -> todo [first, ..rest] -> rest } }", find_position_of("rest").to_selection() ); } #[test] fn pattern_match_on_list_tail_with_shadowed_name() { assert_code_action!( PATTERN_MATCH_ON_VARIABLE, "pub fn main(a_list: List(a)) { case a_list { [] -> todo [rest, ..else_] -> todo } }", find_position_of("else_").to_selection() ); } #[test] fn collapse_nested_case() { assert_code_action!( COLLAPSE_NESTED_CASE, "pub fn main(x) { case x { Ok(var) -> case var { 1 -> 2 2 -> 4 _ -> -1 } _ -> todo } }", find_position_of("var").to_selection() ); } #[test] fn collapse_nested_case_works_with_blocks() { assert_code_action!( COLLAPSE_NESTED_CASE, "pub fn main(x) { case x { Ok(var) -> { case var { 1 -> 2 2 -> 4 _ -> -1 } } _ -> todo } }", find_position_of("var").to_selection() ); } #[test] fn collapse_nested_case_works_with_patterns_defining_multiple_variables() { assert_code_action!( COLLAPSE_NESTED_CASE, "pub fn main(x) { case x { Wibble(var, var2) -> case var { 1 -> 2 2 -> 4 _ -> -1 } Wobble -> todo } } pub type Wibble { Wibble(Int, String) Wobble } ", find_position_of("var").to_selection() ); } #[test] fn collapse_nested_case_does_not_remove_labels() { assert_code_action!( COLLAPSE_NESTED_CASE, "pub fn main(x) { case x { Wibble(field2:, field: wibble) -> case wibble { 1 -> 2 2 -> 4 _ -> -1 } Wobble -> todo } } pub type Wibble { Wibble(field: Int, field2: String) Wobble } ", find_position_of("field").to_selection() ); } #[test] fn collapse_nested_case_does_not_remove_labels_with_shorthand_syntax() { assert_code_action!( COLLAPSE_NESTED_CASE, "pub fn main(x) { case x { Wibble(field2:, field:) -> case field { 1 -> 2 2 -> 4 _ -> -1 } Wobble -> todo } } pub type Wibble { Wibble(field: Int, field2: String) Wobble } ", find_position_of("field").to_selection() ); } #[test] fn collapse_nested_case_works_with_alternative_patterns() { assert_code_action!( COLLAPSE_NESTED_CASE, "pub fn main(x) { case x { [first, ..rest] -> case first { 1 | 2 -> True 3 | 4 | 5 -> False _ -> False } [] -> True } } ", find_position_of("first").to_selection() ); } #[test] fn collapse_nested_case_aliases_variable_if_it_is_used() { assert_code_action!( COLLAPSE_NESTED_CASE, "pub fn main(x) { case x { [first, ..rest] -> case first { 1 | 2 -> first 3 | 4 | 5 -> 5 _ -> 0 } [] -> -1 } } ", find_position_of("first").to_selection() ); } #[test] fn collapse_nested_case_does_not_ignore_outer_guards() { assert_code_action!( COLLAPSE_NESTED_CASE, "pub fn main(x) { case x { [first, ..rest] if True -> case first { 1 -> 1.1 _ -> 0.0 *. 10.0 } [] -> 1.1 } } ", find_position_of("first").to_selection() ); } #[test] fn collapse_nested_case_does_not_ignore_inner_guards() { assert_code_action!( COLLAPSE_NESTED_CASE, "pub fn main(x) { case x { [first, ..rest] -> case first { 1 -> 1.1 _ if True -> 0.0 *. 10.0 _ -> 0.0 } [] -> 1.1 } } ", find_position_of("first").to_selection() ); } #[test] fn collapse_nested_case_combines_inner_and_outer_guards() { assert_code_action!( COLLAPSE_NESTED_CASE, "pub fn main(x) { case x { [first, ..rest] if False -> case first { 1 if False -> 1.1 _ if True -> 0.0 *. 10.0 _ -> 0.0 } [] -> 1.1 } } ", find_position_of("first").to_selection() ); } #[test] fn collapse_nested_case_combines_inner_and_outer_guards_and_adds_parentheses_when_needed() { assert_code_action!( COLLAPSE_NESTED_CASE, "pub fn main(x) { case x { [first, ..rest] if False || True -> case first { 1 if False && True -> 1.1 _ if True || False -> 0.0 *. 10.0 _ -> 0.0 } [] -> 1.1 } } ", find_position_of("first").to_selection() ); } #[test] fn collapse_nested_case_combines_list_with_unformatted_tail() { assert_code_action!( COLLAPSE_NESTED_CASE, r#"pub fn main(elems: List(String)) { case elems { [first, .. rest_of_list] -> case rest_of_list { [] -> 0 ["first"] -> 1 [_, "second"] -> 2 [_, "second", "third", _] -> 4 _ -> -1 } _ -> -1 } }"#, find_position_of("rest").to_selection() ); } #[test] fn collapse_nested_case_combines_list_with_tail() { assert_code_action!( COLLAPSE_NESTED_CASE, r#"pub fn main(elems: List(List(Int))) { case elems { [] -> 0 [_, ..tail] -> case tail { [] -> -1 [[1]] -> 1 [_, [3, 4]] -> 3 [_, _, [5, 6, 7], _] -> 5 tail_list -> { echo tail_list -1 } } _ -> -1 } }"#, find_position_of("tail").to_selection() ); } // https://github.com/gleam-lang/gleam/issues/3786 #[test] fn type_variables_from_other_functions_do_not_change_annotations() { assert_code_action!( ADD_ANNOTATIONS, " fn wibble(a: a, b: b, c: c) -> d { todo } fn pair(a, b) { #(a, b) } ", find_position_of("pair").to_selection() ); } #[test] fn type_variables_from_other_functions_do_not_change_annotations_constant() { assert_code_action!( ADD_ANNOTATION, " fn wibble(a: a, b: b, c: c) -> d { todo } const empty = [] ", find_position_of("empty").to_selection() ); } #[test] fn type_variables_are_not_duplicated_when_adding_annotations() { assert_code_action!( ADD_ANNOTATIONS, " fn wibble(a: a, b: b, c: c) -> d { todo } fn many_args(a, b, c, d: d, e: a, f, g) { todo } ", find_position_of("many_args").to_selection() ); } #[test] fn type_variables_in_let_bindings_are_considered_when_adding_annotations() { assert_code_action!( ADD_ANNOTATIONS, " fn wibble(a, b, c) { let x: a = todo fn(a: b, b: c) -> d { todo } } ", find_position_of("wibble").to_selection() ); } #[test] fn generated_function_annotations_are_not_affected_by_other_functions() { assert_code_action!( GENERATE_FUNCTION, " fn wibble(a: a, b: b, c: c) -> d { todo } pub fn main() { let x = todo let y = todo let #(a, b) = something(x, y) b } ", find_position_of("something").to_selection() ); } #[test] fn generate_function_in_other_module() { let src = " import wibble pub fn main() { wibble.wibble() wibble.wobble() } "; assert_code_action!( GENERATE_FUNCTION, TestProject::for_source(src).add_module("wibble", "pub fn wibble() {}"), find_position_of("wobble").to_selection() ); } #[test] fn generate_function_is_not_offered_for_variants() { assert_no_code_actions!( GENERATE_FUNCTION, " pub type Wibble pub fn main() -> Wibble { Wobble(1) } ", find_position_of("Wobble").to_selection() ); } #[test] fn generating_function_in_other_module_uses_local_names() { let src = r#" import wibble pub fn main() -> List(Nil) { wibble.wibble(1, #(True, "Hello")) } "#; assert_code_action!( GENERATE_FUNCTION, TestProject::for_source(src).add_module( "wibble", "import gleam.{type Int as Number, type Bool as Boolean, type String as Text, type Nil as Nothing}" ), find_position_of("wibble(").to_selection() ); } #[test] fn generating_function_in_other_module_uses_labels() { let src = r#" import wibble pub fn main() { wibble.wibble("Unlabelled", int: 1, bool: True) } "#; assert_code_action!( GENERATE_FUNCTION, TestProject::for_source(src).add_module("wibble", ""), find_position_of("wibble(").to_selection() ); } #[test] fn no_code_action_to_generate_existing_function_in_other_module() { let src = r#" import wibble pub fn main() { wibble.wibble(1, 2, 3) } "#; assert_no_code_actions!( GENERATE_FUNCTION, TestProject::for_source(src).add_module("wibble", "pub fn wibble(a, b, c) { a + b + c }"), find_position_of("wibble(").to_selection() ); } #[test] fn do_not_generate_function_in_other_package() { let src = r#" import maths pub fn main() { maths.add(1, 2) maths.subtract(1, 2) } "#; assert_no_code_actions!( GENERATE_FUNCTION, TestProject::for_source(src).add_dep_module("maths", "pub fn add(a, b) { a + b }"), find_position_of("subtract").to_selection() ); } #[test] fn remove_unreachable_clauses() { assert_code_action!( REMOVE_UNREACHABLE_CLAUSES, "pub fn main(x) { case x { Ok(n) -> 1 Ok(_) -> 2 Error(_) -> todo Ok(1) -> 3 } } ", find_position_of("Ok(1)").to_selection() ); } #[test] fn remove_unreachable_branches_does_not_pop_up_if_all_branches_are_reachable() { assert_no_code_actions!( REMOVE_UNREACHABLE_CLAUSES, "pub fn main(x) { case x { Ok(n) -> 1 Error(_) -> todo } case x { Ok(n) -> todo Ok(_) -> todo _ -> todo } } ", find_position_of("Ok(n)").to_selection() ); } #[test] fn add_type_annotations_public_alias_to_internal_type_aliased_module() { let src = " import package as pkg pub fn main() { pkg.make_wibble() } "; assert_code_action!( ADD_ANNOTATION, TestProject::for_source(src) .add_package_module( "package", "package", " import package/internal pub type Wibble = internal.Wibble pub fn make_wibble() { internal.Wibble } " ) .add_package_module("package", "package/internal", "pub type Wibble { Wibble }"), find_position_of("main").to_selection(), ); } // https://github.com/gleam-lang/gleam/issues/3898 #[test] fn add_type_annotations_public_alias_to_internal_type() { let src = " import package pub fn main() { package.make_wibble() } "; assert_code_action!( ADD_ANNOTATION, TestProject::for_source(src) .add_package_module( "package", "package", " import package/internal pub type Wibble = internal.Wibble pub fn make_wibble() { internal.Wibble } " ) .add_package_module("package", "package/internal", "pub type Wibble { Wibble }"), find_position_of("main").to_selection(), ); } #[test] fn add_type_annotations_public_alias_to_internal_generic_type() { let src = " import package pub fn main() { package.make_wibble(10) } "; assert_code_action!( ADD_ANNOTATION, TestProject::for_source(src) .add_package_module( "package", "package", " import package/internal pub type Wibble(a, b) = internal.Wibble(a, b) pub fn make_wibble(x) { internal.Wibble(x) } " ) .add_package_module( "package", "package/internal", "pub type Wibble(a, b) { Wibble(a) }" ), find_position_of("main").to_selection(), ); } #[test] fn add_type_annotations_uses_internal_name_for_same_package() { let src = " import thepackage/internal pub fn main() { internal.Constructor } "; assert_code_action!( ADD_ANNOTATION, TestProject::for_source(src) .add_module( "thepackage/internal", " pub type Internal { Constructor } " ) .add_module( "thepackage/external", " import thepackage/internal pub type External = internal.Internal " ), find_position_of("main").to_selection(), ); } #[test] fn add_omitted_labels_in_function_call() { assert_code_action!( ADD_OMITTED_LABELS, " pub fn main() { labelled(1, 2) } pub fn labelled(a a, b b) { todo } ", find_position_of("labelled").to_selection(), ); } #[test] fn add_omitted_labels_in_function_call_with_some_labels() { assert_code_action!( ADD_OMITTED_LABELS, " pub fn main() { labelled(1, 2) } pub fn labelled(a, b b) { todo } ", find_position_of("labelled").to_selection(), ); } #[test] fn add_omitted_labels_in_function_call_uses_shorthand_syntax() { assert_code_action!( ADD_OMITTED_LABELS, " pub fn main() { let a = 1 labelled(a, 2) } pub fn labelled(a a, b b) { todo } ", find_position_of("labelled").to_selection(), ); } #[test] fn add_omitted_labels_works_with_innermost_function_call() { assert_code_action!( ADD_OMITTED_LABELS, " pub fn main() { let a = 1 labelled(a, labelled(1, 2)) } pub fn labelled(a a, b b) { todo } ", find_position_of("labelled") .nth_occurrence(2) .to_selection(), ); } #[test] fn add_omitted_labels_works_with_constructors_calls() { assert_code_action!( ADD_OMITTED_LABELS, " pub fn main() { Labelled(1, 2) } pub type Labelled { Labelled(Int, b: Int) } ", find_position_of("Labelled").to_selection(), ); } #[test] fn add_omitted_labels_works_with_constructors_calls_with_some_labels_1() { assert_code_action!( ADD_OMITTED_LABELS, " pub fn main() { labelled(3, 1, b: 2) } pub fn labelled(a a, b b, c c) { todo } ", find_position_of("labelled").to_selection(), ); } #[test] fn add_omitted_labels_works_with_constructors_calls_with_some_labels() { assert_code_action!( ADD_OMITTED_LABELS, " pub fn main() { let a = 1 labelled(a, b: 2) } pub fn labelled(a a, b b) { todo } ", find_position_of("labelled").to_selection(), ); } #[test] fn add_omitted_labels_works_on_call_with_wrongly_placed_labels() { assert_code_action!( ADD_OMITTED_LABELS, " pub fn main() { labelled(3, b: 2, 1) } pub fn labelled(a a, b b, c c) { todo } ", find_position_of("labelled").to_selection(), ); } #[test] fn add_omitted_labels_does_not_work_on_call_with_wrong_labels_2() { assert_no_code_actions!( ADD_OMITTED_LABELS, " pub fn main() { labelled(3, 1, d: 2) } pub fn labelled(a a, b b, c c) { todo } ", find_position_of("labelled").to_selection(), ); } #[test] fn add_omitted_labels_does_not_pop_up_if_called_function_has_no_labels() { assert_no_code_actions!( ADD_OMITTED_LABELS, " pub fn main() { let a = 1 labelled(a, 2) } pub fn labelled(a, b) { todo } ", find_position_of("labelled").to_selection(), ); } #[test] fn add_omitted_labels_does_not_label_piped_argument() { assert_code_action!( ADD_OMITTED_LABELS, " pub fn main() { 1 |> labelled(2) } pub fn labelled(a a, b b) { todo } ", find_position_of("labelled").to_selection(), ); } #[test] fn add_omitted_labels_does_not_label_use() { assert_code_action!( ADD_OMITTED_LABELS, " pub fn main() { use <- labelled(1) todo } pub fn labelled(a a, b b) { todo } ", find_position_of("labelled").to_selection(), ); } #[test] fn extract_function() { assert_code_action!( EXTRACT_FUNCTION, " pub fn do_things(a, b) { let result = { let a = 10 + a let b = 10 + b a * b } result + 3 } ", find_position_of("{") .nth_occurrence(2) .select_until(find_position_of("}")) ); } #[test] fn extract_function_from_statements() { assert_code_action!( EXTRACT_FUNCTION, " pub fn do_things(a, b) { let a = 10 + a let b = 10 + b let result = a * b result + 3 } ", find_position_of("let").select_until(find_position_of("* b")) ); } #[test] fn extract_function_which_use_variables_defined_in_the_extracted_span() { assert_code_action!( EXTRACT_FUNCTION, " pub fn do_things(a, b) { let new_a = 10 + a let new_b = 10 + b let result = new_a * new_b result + 3 } ", find_position_of("let").select_until(find_position_of("* new_b")) ); } #[test] fn extract_function_which_use_variables_shadowed_in_an_inner_scope() { assert_code_action!( EXTRACT_FUNCTION, " pub fn do_things(a, b) { let first_part = { let a = a + 10 let b = b + 10 a * b } let result = first_part + a * b result + 3 } ", find_position_of("let").select_until(find_position_of("+ a * b")) ); } #[test] fn extract_function_which_uses_multiple_extracted_variables() { assert_code_action!( EXTRACT_FUNCTION, " pub fn do_things(a, b) { let wibble = a + b let wobble = a * b wobble / wibble } ", find_position_of("let").select_until(find_position_of("* b")) ); } #[test] fn extract_function_which_uses_no_extracted_variables() { assert_code_action!( EXTRACT_FUNCTION, " pub fn do_things(a, b) { let x = a + b echo x a } ", find_position_of("let").select_until(find_position_of("echo x")) ); } #[test] fn extract_function_which_uses_variable_in_guard() { assert_code_action!( EXTRACT_FUNCTION, " pub fn do_things(a, b) { let result = case Nil { _ if a > b -> 17 _ if a < b -> 12 _ -> panic } result % 4 } ", find_position_of("case").select_until(find_position_of("}")) ); } #[test] fn extract_function_which_uses_variable_in_bit_array_pattern() { assert_code_action!( EXTRACT_FUNCTION, " pub fn main() { let bits = todo let size = todo let segment = case bits { <> -> Ok(x) _ -> Error(Nil) } case segment { Ok(value) -> echo value Error(_) -> panic } } ", find_position_of("case").select_until(find_position_of("}")) ); } #[test] fn extract_function_which_uses_constant() { assert_code_action!( EXTRACT_FUNCTION, " const pi = 3.14 pub fn main() { let radius = 4.5 let circumference = radius *. pi *. 2.0 echo circumference } ", find_position_of("radius *.").select_until(find_position_of("2.0")) ); } #[test] fn extract_function_which_uses_constant_in_guard() { assert_code_action!( EXTRACT_FUNCTION, r#" const pi = 3.14 pub fn main() { let value = 3.15 let string = case value { 0.0 -> "Zero" 1.0 -> "One" _ if value == pi -> "PI" _ -> "Something else" } echo string } "#, find_position_of("case").select_until(find_position_of("}")) ); } #[test] fn no_code_action_to_extract_function_when_expression_is_not_fully_selected() { assert_no_code_actions!( EXTRACT_FUNCTION, r#" fn print(text: String) { todo } pub fn main() { let arguments = todo case arguments { ["help"] -> print("USAGE TEXT HERE") _ -> panic as "Invalid args" } } "#, find_position_of("print") .under_char('i') .select_until(find_position_of("TEXT")) ); } #[test] fn extract_function_when_name_already_in_scope() { assert_code_action!( EXTRACT_FUNCTION, r#" fn function() { todo } pub fn do_things(a, b) { let result = { let a = 10 + a let b = 10 + b a * b } result + 3 } "#, find_position_of("= {").select_until(find_position_of("}").nth_occurrence(2)) ); } #[test] fn extract_function_when_multiple_names_already_in_scope() { assert_code_action!( EXTRACT_FUNCTION, r#" fn function() { todo } fn function_2() { todo } fn function_3() { todo } fn function_4() { todo } pub fn do_things(a, b) { let result = { let a = 10 + a let b = 10 + b a * b } result + 3 } "#, find_position_of("= {").select_until(find_position_of("}").nth_occurrence(5)) ); } #[test] fn extract_function_partially_selected() { assert_code_action!( EXTRACT_FUNCTION, r#" pub fn main() { let a = 10 let b = 20 let c = a + b echo c } "#, find_position_of("a =").select_until(find_position_of("c =")) ); } #[test] fn selected_statements_do_not_select_outer_block() { // We want to make sure only the statements within the block are extracted, // and not the block itself. assert_code_action!( EXTRACT_FUNCTION, r#" pub fn main() { let c = { let a = 10 let b = 20 a + b } echo c } "#, find_position_of("let a").select_until(find_position_of("+ b")) ); } #[test] fn no_code_action_to_extract_when_multiple_functions_are_selected() { assert_no_code_actions!( EXTRACT_FUNCTION, r#" pub fn main() { let a = 10 a + 1 } pub fn other() { let b = 20 b * 2 } "#, find_position_of("let a").select_until(find_position_of("let b")) ); } #[test] fn extract_statements_in_tail_position() { assert_code_action!( EXTRACT_FUNCTION, r#" pub fn main() { let a = 1 let b = 2 let c = 3 let d = 4 a * b + c * d } "#, find_position_of("let c").select_until(find_position_of("* d")) ); } #[test] fn extract_use_in_tail_position() { assert_code_action!( EXTRACT_FUNCTION, r#" pub fn main() { use <- wibble 123 } fn wibble(f: fn() -> Int) -> Int { f() } "#, find_position_of("use").select_until(find_position_of("wibble")) ); } #[test] fn extract_use_in_tail_position_2() { assert_code_action!( EXTRACT_FUNCTION, r#" pub fn main() { use <- wibble use <- wobble 123 } fn wibble(f: fn() -> Float) -> Float { f() } fn wobble(f: fn() -> Int) -> Float { 1.1 } "#, find_position_of("use") .nth_occurrence(2) .select_until(find_position_of("wobble")) ); } #[test] fn extract_block_tail_position_3() { assert_code_action!( EXTRACT_FUNCTION, r#" pub fn main() { case 1 { _ -> { use <- wibble 123 } _ -> todo } } fn wibble(f: fn() -> Float) -> Float { f() } "#, find_position_of("{") .nth_occurrence(3) .select_until(find_position_of("}")) ); } #[test] fn extract_block_tail_position_4() { assert_code_action!( EXTRACT_FUNCTION, r#" pub fn main() { case 1 { _ -> { use <- wibble use <- wibble 123 } _ -> todo } } fn wibble(f: fn() -> Float) -> Float { f() } "#, find_position_of("{") .nth_occurrence(3) .select_until(find_position_of("}")) ); } // https://github.com/gleam-lang/gleam/issues/5036 #[test] fn generate_function_in_other_module_correctly_appends() { let src = "import module_breaker/another pub fn main() -> Nil { another.function() } "; assert_code_action!( GENERATE_FUNCTION, TestProject::for_source(src).add_module( "module_breaker/another", "pub fn useless() { Nil } " ), find_position_of("function").to_selection() ); } // https://github.com/gleam-lang/gleam/issues/4904 #[test] fn no_code_action_to_generate_function_on_unsupported_target() { assert_no_code_actions!( GENERATE_FUNCTION, r#" pub fn main() { wibble() } @external(javascript, "./ffi.mjs", "wibble") fn wibble() -> Nil "#, find_position_of("wibble").to_selection() ); } #[test] fn merge_case_branch() { assert_code_action!( MERGE_CASE_BRANCHES, r#"pub fn go(n: Int) { case n { 1 -> todo 2 -> todo _ -> todo } }"#, find_position_of("1").select_until(find_position_of("2")) ); } #[test] fn merge_case_branch_with_todo_keeps_the_non_todo_body() { assert_code_action!( MERGE_CASE_BRANCHES, r#"pub fn go(n: Int) { case n { 1 -> todo 2 -> n * 2 3 -> todo _ -> todo } }"#, find_position_of("1").select_until(find_position_of("3")) ); } #[test] fn merge_case_branch_with_todo_keeps_the_non_todo_body_1() { assert_code_action!( MERGE_CASE_BRANCHES, r#"pub fn go(n: Int) { case n { 1 -> todo 2 -> todo 3 -> n * 2 _ -> todo } }"#, find_position_of("1").select_until(find_position_of("3")) ); } #[test] fn merge_case_branch_with_todo_keeps_the_non_todo_body_2() { assert_code_action!( MERGE_CASE_BRANCHES, r#"pub fn go(n: Int) { case n { 1 -> n * 2 2 -> todo 3 -> todo _ -> todo } }"#, find_position_of("1").select_until(find_position_of("3")) ); } #[test] fn merge_case_branch_with_complex_bodies_1() { assert_code_action!( MERGE_CASE_BRANCHES, r#"pub fn go(n: Int) { case n { 1 -> Ok("one or two") 2 -> Ok("one or two") _ -> Error("neither one or two") } }"#, find_position_of("1").select_until(find_position_of("2")) ); } #[test] fn merge_case_branch_with_complex_bodies_2() { assert_code_action!( MERGE_CASE_BRANCHES, r#"pub fn go(n: Int) { case n { 1 -> n 2 -> n _ -> panic as "neither one nor two" } }"#, find_position_of("1").select_until(find_position_of("2")) ); } #[test] fn merge_case_branch_with_complex_bodies_3() { assert_code_action!( MERGE_CASE_BRANCHES, r#"pub fn go(n: Int) { case n { 1 -> go(n - 1) 2 -> go(n - 1) _ -> 10 } }"#, find_position_of("1").select_until(find_position_of("2")) ); } #[test] fn merge_case_branch_with_complex_bodies_4() { assert_code_action!( MERGE_CASE_BRANCHES, r#"pub fn go(n: Int) { case n { 1 -> { let a = go(n - 1) a * 10 } 2 -> { let a = go(n - 1) a * 10 } _ -> 10 } }"#, find_position_of("1").select_until(find_position_of("2")) ); } #[test] fn merge_case_branch_will_not_merge_branches_with_guards() { assert_no_code_actions!( MERGE_CASE_BRANCHES, r#"pub fn go(n: Int) { case n { 1 if True -> todo 2 -> todo _ -> todo } }"#, find_position_of("1").select_until(find_position_of("2")) ); } #[test] fn merge_case_branch_will_not_merge_branches_defining_different_variables() { assert_no_code_actions!( MERGE_CASE_BRANCHES, r#"pub fn go(result: Result(Int, Int)) { case result { Ok(value) -> todo Error(error) -> todo _ -> todo } }"#, find_position_of("Ok").select_until(find_position_of("error")) ); } #[test] fn merge_case_branch_can_merge_branches_defining_the_same_variables() { assert_code_action!( MERGE_CASE_BRANCHES, r#"pub fn go(result) { case result { [Ok(value), ..] -> todo [_, Error(value)] -> todo _ -> todo } }"#, find_position_of("Ok").select_until(find_position_of("todo").nth_occurrence(2)) ); } #[test] fn merge_case_branch_can_merge_multiple_branches() { assert_code_action!( MERGE_CASE_BRANCHES, r#"pub fn go(result) { case result { [_] -> 1 [Ok(value), ..] -> todo [_, Error(value)] -> todo [_, _, Error(value)] -> todo [_, _] -> 1 _ -> 2 } }"#, find_position_of("todo").select_until(find_position_of("todo").nth_occurrence(3)) ); } #[test] fn merge_case_branch_does_not_pop_up_with_a_single_selected_branch() { assert_no_code_actions!( MERGE_CASE_BRANCHES, r#"pub fn go(result) { case result { [] -> todo _ -> 2 } }"#, find_position_of("[]").to_selection() ); } #[test] fn merge_case_branch_works_with_existing_alternative_patterns() { assert_code_action!( MERGE_CASE_BRANCHES, r#"pub fn go(result) { case result { [] | [_, _, ..]-> todo [_] -> todo _ -> 2 } }"#, find_position_of("[]").select_until(find_position_of("[_]")) ); } #[test] fn merge_case_branch_does_not_merge_branches_with_variables_with_same_name_and_different_types() { assert_no_code_actions!( MERGE_CASE_BRANCHES, r#"pub fn go(result: Result(Int, String)) { case result { Ok(one) -> todo Error(one) -> todo } }"#, find_position_of("Ok").select_until(find_position_of("Error")) ); } #[test] fn annotate_all_top_level_definitions_dont_affect_local_vars() { assert_code_action!( ANNOTATE_TOP_LEVEL_DEFINITIONS, r#" pub const answer = 42 pub fn add_two(thing) { thing + 2 } pub fn add_one(thing) { let result = thing + 1 result } "#, find_position_of("fn").select_until(find_position_of("(")) ); } #[test] fn annotate_all_top_level_definitions_constant() { assert_code_action!( ANNOTATE_TOP_LEVEL_DEFINITIONS, r#" pub const answer = 42 pub fn add_two(thing) { thing + 2 } pub fn add_one(thing) { thing + 1 } "#, find_position_of("const").select_until(find_position_of("=")) ); } #[test] fn annotate_all_top_level_definitions_function() { assert_code_action!( ANNOTATE_TOP_LEVEL_DEFINITIONS, r#" pub fn add_two(thing) { thing + 2 } pub fn add_one(thing) { thing + 1 } "#, find_position_of("fn").select_until(find_position_of("(")) ); } #[test] fn annotate_all_top_level_definitions_already_annotated() { assert_no_code_actions!( ANNOTATE_TOP_LEVEL_DEFINITIONS, r#" pub const answer: Int = 42 pub fn add_two(thing: Int) -> Int { thing + 2 } pub fn add_one(thing: Int) -> Int { thing + 1 } "#, find_position_of("fn").select_until(find_position_of("(")) ); } #[test] fn annotate_all_top_level_definitions_inside_body() { assert_no_code_actions!( ANNOTATE_TOP_LEVEL_DEFINITIONS, r#" pub fn add_one(thing) { thing + 1 } "#, find_position_of("thing + 1").to_selection() ); } #[test] fn annotate_all_top_level_definitions_partially_annotated() { assert_code_action!( ANNOTATE_TOP_LEVEL_DEFINITIONS, r#" pub const answer: Int = 42 pub const another_answer = 43 pub fn add_two(thing) -> Int { thing + 2 } pub fn add_one(thing: Int) { thing + 1 } "#, find_position_of("fn").select_until(find_position_of("(")) ); } #[test] fn annotate_all_top_level_definitions_with_partially_annotated_generic_function() { assert_code_action!( ANNOTATE_TOP_LEVEL_DEFINITIONS, r#" pub fn wibble(a: a, b, c: c, d) { todo } "#, find_position_of("wibble").to_selection() ); } #[test] fn annotate_all_top_level_definitions_with_two_generic_functions() { assert_code_action!( ANNOTATE_TOP_LEVEL_DEFINITIONS, r#" fn wibble(one) { todo } fn wobble(other) { todo } "#, find_position_of("wobble").to_selection() ); } #[test] fn annotate_all_top_level_definitions_with_constant_and_generic_functions() { assert_code_action!( ANNOTATE_TOP_LEVEL_DEFINITIONS, r#" const answer = 42 fn wibble(one) { todo } fn wobble(other) { todo } "#, find_position_of("wobble").to_selection() ); } #[test] fn annotate_all_top_level_definitions_not_suggested_if_annotations_present() { assert_no_code_actions!( ANNOTATE_TOP_LEVEL_DEFINITIONS, r#" fn wibble(one: Int) -> Int { one } fn wobble(one) { wibble(one) } "#, find_position_of("wibble").to_selection() ); } // https://github.com/gleam-lang/gleam/issues/5273 #[test] fn extract_constant_doesnt_place_constant_below_documentation() { assert_code_action!( EXTRACT_CONSTANT, r#" /// Wibble does some wobbling pub fn wibble() { let x = "wobble" x } "#, find_position_of("x").to_selection() ); } // https://github.com/gleam-lang/gleam/issues/5273 #[test] fn extract_constant_doesnt_place_constant_below_large_documentation() { assert_code_action!( EXTRACT_CONSTANT, r#" /// Wibble does some wobbling /// Note that it doesn't perform wibbling pub fn wibble() { let x = "wobble" x } "#, find_position_of("x").to_selection() ); } #[test] fn add_missing_type_parameter_for_single_constructor() { assert_code_action!( ADD_MISSING_TYPE_PARAMETER, r#" type Wibble { Wibble(field: t) } "#, find_position_of("t").nth_occurrence(2).to_selection() ); } #[test] fn add_missing_type_parameter_to_exising_parameter() { assert_code_action!( ADD_MISSING_TYPE_PARAMETER, r#" type Wibble(t) { Wibble(field: t) Wobble(field: u) } "#, find_position_of("u").to_selection() ); } #[test] fn add_missing_type_parameter_preserves_comments() { assert_code_action!( ADD_MISSING_TYPE_PARAMETER, r#" type Wibble( // Comment 1 b, // Comment 2 ) { Wibble(a, b, c) } "#, find_position_of("c").to_selection() ); } #[test] fn add_missing_type_parameter_sorted_alphabetically() { assert_code_action!( ADD_MISSING_TYPE_PARAMETER, r#" type Wibble(b) { Wibble(c, b, a) } "#, find_position_of("a").to_selection() ); } #[test] fn add_missing_type_parameter_not_suggested_when_nothing_missing() { assert_no_code_actions!( ADD_MISSING_TYPE_PARAMETER, r#" type Wibble(t) { Wibble(field: t) } "#, find_position_of("t").nth_occurrence(2).to_selection() ); } #[test] fn add_missing_type_parameter_not_suggested_when_no_parameters() { assert_no_code_actions!( ADD_MISSING_TYPE_PARAMETER, r#" type Wibble { Wibble } "#, find_position_of("Wibble").nth_occurrence(2).to_selection() ); } #[test] fn add_missing_type_parameter_does_not_add_types_that_do_not_exist() { assert_no_code_actions!( ADD_MISSING_TYPE_PARAMETER, r#" type Wibble { Wibble(Wobble) } "#, find_position_of("Wibble").nth_occurrence(2).to_selection() ); } // https://github.com/gleam-lang/gleam/issues/5288 #[test] fn extract_anonymous_function_without_variable_capture_1() { assert_code_action!( EXTRACT_FUNCTION, " pub fn main() { let int_pow = fn(base, exp) { case exp { exp if exp < 0 -> 0 0 -> base exp -> int_pow(base * exp, exp - 1) } } } ", find_position_of("fn(").select_until(find_position_of("}").nth_occurrence(2)) ); } #[test] fn extract_anonymous_function_without_variable_capture_2() { let src = r#" import gleam/io import gleam/list pub fn main() { list.each(list.range(0, 10), fn(_) { io.println("wibble wobble") }) } "#; assert_code_action!( EXTRACT_FUNCTION, TestProject::for_source(src) .add_hex_module( "gleam/io", " pub fn println(string: String) -> Nil { todo } " ) .add_hex_module( "gleam/list", " pub fn each(list: List(a), f: fn(a) -> b) -> Nil { todo } pub fn range(from start: Int, to stop: Int) -> List(Int) { todo } " ), find_position_of("fn(").select_until(find_position_of("}")) ); } #[test] fn extract_unary_anonymous_function_with_variable_capture_1() { let src = " import gleam/list pub fn main() { let needle = 42 let haystack = [25, 81, 74, 42, 33] list.filter(haystack, fn(x) { x == needle }) } "; assert_code_action!( EXTRACT_FUNCTION, TestProject::for_source(src).add_hex_module( "gleam/list", " pub fn filter( list: List(a), keeping predicate: fn(a) -> Bool, ) -> List(a) { todo } " ), find_position_of("fn(").select_until(find_position_of("}")) ); } #[test] fn extract_unary_anonymous_function_with_variable_capture_2() { let src = " import gleam/list pub fn main() { let needle = 42 let haystack = [25, 81, 74, 42, 33] list.filter(haystack, fn(_) { needle == 42 }) } "; assert_code_action!( EXTRACT_FUNCTION, TestProject::for_source(src).add_hex_module( "gleam/list", " pub fn filter( list: List(a), keeping predicate: fn(a) -> Bool, ) -> List(a) { todo } " ), find_position_of("fn(").select_until(find_position_of("}")) ); } #[test] fn extract_anonymous_function_with_variable_capture_1() { assert_code_action!( EXTRACT_FUNCTION, " pub fn main() { let outer_scope = 3 let wibble = fn(a, b) { a + b + outer_scope } } ", find_position_of("fn(").select_until(find_position_of("}")) ); } #[test] fn extract_anonymous_function_with_variable_capture_2() { let src = " import gleam/list pub fn main() { let shorter = [1, 2, 3] let longer = [4, 5, 6, 7, 8] let offset = 5 list.map2(shorter, longer, fn(_left, right) { right + offset }) } "; assert_code_action!( EXTRACT_FUNCTION, TestProject::for_source(src).add_hex_module( "gleam/list", " pub fn map2( list1: List(a), list2: List(b), with fun: fn(a, b) -> c, ) -> List(c) { todo } " ), find_position_of("fn(").select_until(find_position_of("}")) ); } // https://github.com/gleam-lang/gleam/issues/5263 #[test] fn interpolate_string_allows_extracting_record_access_syntax() { assert_code_action!( INTERPOLATE_STRING, r#"pub fn main() { "wibble wobble.some_field woo" }"#, find_position_of("wobble").select_until(find_position_of("some_field ").under_last_char()), ); } // https://github.com/gleam-lang/gleam/issues/5299 #[test] fn generate_dynamic_decoder_produces_zero_values_for_prelude_and_stdlib_types() { let src = r#" import gleam/option import gleam/dict pub type Wobble { Wobble( bit_array: BitArray, int: Int, float: Float, bool: Bool, list: List(Int), string: String, nil: Nil, option: option.Option(String), dict: dict.Dict(Int, Bool), ) Dummy( a: Int, b: Int, c: Int, d: Int, e: Int, f: Int, g: Int, h: Int, i: Int, j: Int, ) } "#; assert_code_action!( GENERATE_DYNAMIC_DECODER, TestProject::for_source(src) .add_hex_module("gleam/option", "pub type Option(a) { Some(a) None }") .add_hex_module("gleam/dict", "pub type Dict(key, value)"), find_position_of("pub type Wobble {").select_until(find_position_of("}")) ); } #[test] fn interpolate_string_does_not_add_empty_string_right_at_the_start() { assert_code_action!( INTERPOLATE_STRING, r#"pub fn main() { "wibble wobble woo" }"#, find_position_of("wibble ").select_until(find_position_of("wibble ").under_last_char()), ); } #[test] fn generate_dynamic_decoder_produces_zero_values_for_user_defined_type_in_the_same_package_1() { let src = r#" import wibble.{type Wibble} pub type Wobble { Wobble(value: Wibble) Dummy(a: Int, b: Int) } "#; assert_code_action!( GENERATE_DYNAMIC_DECODER, TestProject::for_source(src).add_module("wibble", "pub type Wibble { Wibble }"), find_position_of("pub type Wobble {").select_until(find_position_of("}").nth_occurrence(2)) ); } #[test] fn interpolate_string_does_not_add_empty_string_right_at_the_end() { assert_code_action!( INTERPOLATE_STRING, r#"pub fn main() { "wibble wobble woo" }"#, find_position_of("woo\"").select_until(find_position_of("woo\"").under_last_char()), ); } #[test] fn generate_dynamic_decoder_produces_zero_values_for_user_defined_type_in_the_same_package_2() { let src = r#" import internal/wibble.{type Wibble} pub type Wobble { Wobble(value: Wibble) Dummy(a: Int, b: Int) } "#; assert_code_action!( GENERATE_DYNAMIC_DECODER, TestProject::for_source(src) .add_module( "internal/wibble", r#" import internal/nested_wibble.{type NestedWibble} pub type Wibble { Wibble(value: NestedWibble) } "# ) .add_module( "internal/nested_wibble", r#" pub type NestedWibble { NestedWibble } "# ), find_position_of("pub type Wobble {").select_until(find_position_of("}").nth_occurrence(2)) ); } #[test] fn generate_dynamic_decoder_produces_zero_values_for_user_defined_type_in_the_same_package_3() { let src = r#" import wibble.{type Wibble} pub type Wobble { Wobble(value: Wibble) Dummy(a: Int, b: Int) } "#; assert_code_action!( GENERATE_DYNAMIC_DECODER, TestProject::for_source(src) .add_module( "wibble", r#" import gleam/dict.{type Dict} pub type Wibble { Wibble(map: Dict(Int, Bool)) } "# ) .add_hex_module( "gleam/dict", r#" pub type Dict(key, value) pub fn new() -> Dict(k, v) { todo } "# ), find_position_of("pub type Wobble {").select_until(find_position_of("}").nth_occurrence(2)) ); } #[test] fn generate_dynamic_decoder_does_not_produce_zero_values_for_types_from_other_packages() { let src = r#" import wibble.{type Wibble} pub type Wobble { Wobble(value: Wibble) Dummy(a: Int, b: Wibble) } "#; assert_code_action!( GENERATE_DYNAMIC_DECODER, TestProject::for_source(src).add_hex_module("wibble", "pub type Wibble { Wibble }"), find_position_of("pub type Wobble {").select_until(find_position_of("}").nth_occurrence(2)) ); } #[test] fn generate_dynamic_decoder_skips_over_recursive_constructors_when_generating_zero_values() { assert_code_action!( GENERATE_DYNAMIC_DECODER, r#" pub type Wobble { Wobble(inside: Wobble) Dummy(a: Int, b: Int) } "#, find_position_of("pub type Wobble {").select_until(find_position_of("}")) ); } #[test] fn generate_dynamic_decoder_skips_over_mutually_recursive_constructors_when_generating_zero_values() { assert_code_action!( GENERATE_DYNAMIC_DECODER, r#" pub type Wobble { Wobble(inside: Wibble) DummyWobble(a: Int, b: Int) } pub type Wibble { Wibble(inside: Wobble) DummyWibble(a: Int, b: Int) } "#, find_position_of("pub type Wobble {").select_until(find_position_of("}")) ); } #[test] fn generate_dynamic_decoder_uses_smallest_possible_constructor_for_zero_value() { let src = r#" import wibble.{type Wibble} pub type Wobble { Wobble(impossible: Wobble) WibbleWobble(nope: Wibble) Dummy(a: Int, b: #(Float, Float)) } "#; assert_code_action!( GENERATE_DYNAMIC_DECODER, TestProject::for_source(src).add_hex_module("wibble", "pub type Wibble { Wibble }"), find_position_of("pub type Wobble {").select_until(find_position_of("}").nth_occurrence(2)) ); } #[test] fn generate_dynamic_decoder_generates_todo_for_zero_value_when_all_constructors_fail() { assert_code_action!( GENERATE_DYNAMIC_DECODER, r#" pub type Wobble { Wobble(nope: Wobble) Wibble(not: Int, again: Wobble) } "#, find_position_of("pub type Wobble {").select_until(find_position_of("}")) ); } #[test] fn generate_dynamic_decoder_uses_decode_success_for_nil() { assert_code_action!( GENERATE_DYNAMIC_DECODER, r#" pub type Nothing { No(val: Nil) Nope(val: #(Nil, Nil)) Nothing(val: List(Nil)) } "#, find_position_of("pub type Nothing {").select_until(find_position_of("}")) ); } #[test] fn generate_to_json_function_uses_json_null_for_nil() { let src = " import gleam/json import gleam/dict.{type Dict} pub type Nothing { Nope(val: Nil) Nothing(val1: List(Nil), val2: Dict(Int, Nil)) } "; assert_code_action!( GENERATE_TO_JSON_FUNCTION, TestProject::for_source(src) .add_package_module("gleam_json", "gleam/json", "pub type Json") .add_hex_module("gleam/dict", "pub type Dict(key, value)"), find_position_of("pub type Nothing {") .select_until(find_position_of("}").nth_occurrence(2)) ); } #[test] fn generate_to_json_function_ignores_nil_and_nil_tuple_fields_with_underscore() { let src = " import gleam/json import gleam/dict.{type Dict} pub type Nothing { No(val: #(Nil), another: #(Nil, #(Nil, Int))) Nope(val: List(#(Nil))) } "; assert_code_action!( GENERATE_TO_JSON_FUNCTION, TestProject::for_source(src) .add_package_module("gleam_json", "gleam/json", "pub type Json") .add_hex_module("gleam/dict", "pub type Dict(key, value)"), find_position_of("pub type Nothing {") .select_until(find_position_of("}").nth_occurrence(2)) ); } #[test] fn replace_underscore_with_function_return_type() { assert_code_action!( REPLACE_UNDERSCORE_WITH_TYPE, r#" pub fn wibble() -> _ { 12 } "#, find_position_of("_").to_selection() ); } #[test] fn replace_nested_underscore_with_generic_type() { assert_code_action!( REPLACE_UNDERSCORE_WITH_TYPE, r#" pub fn wibble() -> Result(a, _) { Ok(todo) } "#, find_position_of("_").to_selection() ); } #[test] fn replace_nested_underscore_with_function_return_type() { assert_code_action!( REPLACE_UNDERSCORE_WITH_TYPE, r#" pub fn wibble() -> Result(Result(_, Nil), Int) { Ok(Ok("hello")) } "#, find_position_of("_").to_selection() ); } #[test] fn replace_nested_underscore_in_let_annotation() { assert_code_action!( REPLACE_UNDERSCORE_WITH_TYPE, r#" pub fn wibble() { let a: Result(Result(_, Nil), Nil) = Ok(Ok("hello")) } "#, find_position_of("_").to_selection() ); } #[test] fn replace_underscore_in_let_annotation() { assert_code_action!( REPLACE_UNDERSCORE_WITH_TYPE, r#" pub fn wibble() { let a: _ = Ok(Ok("hello")) } "#, find_position_of("_").to_selection() ); } #[test] fn replace_nested_underscore_with_tuple_type() { assert_code_action!( REPLACE_UNDERSCORE_WITH_TYPE, r#" pub fn wibble() { let a: #(Int, _, Int) = #(1, "hello", 2) } "#, find_position_of("_").to_selection() ); } #[test] fn replace_underscore_in_function_argument() { assert_code_action!( REPLACE_UNDERSCORE_WITH_TYPE, r#" pub fn wibble(a: _) -> Int { a + 2 } "#, find_position_of("_").to_selection() ); } #[test] fn replace_underscore_in_fn_expr_argument() { assert_code_action!( REPLACE_UNDERSCORE_WITH_TYPE, r#" pub fn wibble() { fn(a: _) { a + 2 } } "#, find_position_of("_").to_selection() ); } ================================================ FILE: language-server/src/tests/compilation.rs ================================================ use crate::engine::Compilation; use super::*; #[test] fn compile_please() { let io = LanguageServerTestIO::new(); let mut engine = setup_engine(&io); let response = engine.compile_please(); assert!(response.result.is_ok()); assert!(response.warnings.is_empty()); assert_eq!(response.compilation, Compilation::Yes(vec![])); drop(engine); let actions = io.into_actions(); assert_eq!( actions, vec![ // new Action::DependencyDownloadingStarted, Action::DownloadDependencies, Action::DependencyDownloadingFinished, Action::LockBuild, Action::UnlockBuild, // compile_please Action::CompilationStarted, Action::LockBuild, Action::UnlockBuild, Action::CompilationFinished, ] ) } #[test] fn compile_error_in_src() { let io = LanguageServerTestIO::new(); let mut engine = setup_engine(&io); _ = io.src_module("app/error", "pub type Error {"); let response = engine.compile_please(); assert!(response.result.is_err()); assert!(response.warnings.is_empty()); assert_eq!(response.compilation, Compilation::Yes(vec![])); drop(engine); let actions = io.into_actions(); assert_eq!( actions, vec![ // new Action::DependencyDownloadingStarted, Action::DownloadDependencies, Action::DependencyDownloadingFinished, Action::LockBuild, Action::UnlockBuild, // compile_please Action::CompilationStarted, Action::LockBuild, Action::UnlockBuild, Action::CompilationFinished, ] ) } #[test] fn compile_error_in_test() { let io = LanguageServerTestIO::new(); let mut engine = setup_engine(&io); _ = io.test_module("app/error", "pub type Error {"); let response = engine.compile_please(); assert!(response.result.is_err()); assert!(response.warnings.is_empty()); assert_eq!(response.compilation, Compilation::Yes(vec![])); drop(engine); let actions = io.into_actions(); assert_eq!( actions, vec![ // new Action::DependencyDownloadingStarted, Action::DownloadDependencies, Action::DependencyDownloadingFinished, Action::LockBuild, Action::UnlockBuild, // compile_please Action::CompilationStarted, Action::LockBuild, Action::UnlockBuild, Action::CompilationFinished, ] ) } #[test] fn compile_error_in_dev() { let io = LanguageServerTestIO::new(); let mut engine = setup_engine(&io); _ = io.dev_module("app/error", "pub type Error {"); let response = engine.compile_please(); assert!(response.result.is_err()); assert!(response.warnings.is_empty()); assert_eq!(response.compilation, Compilation::Yes(vec![])); drop(engine); let actions = io.into_actions(); assert_eq!( actions, vec![ // new Action::DependencyDownloadingStarted, Action::DownloadDependencies, Action::DependencyDownloadingFinished, Action::LockBuild, Action::UnlockBuild, // compile_please Action::CompilationStarted, Action::LockBuild, Action::UnlockBuild, Action::CompilationFinished, ] ) } #[test] fn compile_recompile() { let io = LanguageServerTestIO::new(); let mut engine = setup_engine(&io); let path = io.src_module("app", "pub fn main() { 0 }"); // The first time it compiles. let response = engine.compile_please(); assert!(response.result.is_ok()); assert!(response.warnings.is_empty()); assert_eq!(response.compilation, Compilation::Yes(vec![path.clone()])); // The source file has been updated, so the file is compiled again. _ = io.src_module("app", "pub fn main() { 1 }"); let response = engine.compile_please(); assert!(response.result.is_ok()); assert!(response.warnings.is_empty()); assert_eq!(response.compilation, Compilation::Yes(vec![path])); // This time it does not compile the module again, instead using the // cache from the previous run. let response = engine.compile_please(); assert_eq!(response.result, Ok(())); assert!(response.warnings.is_empty()); assert_eq!(response.compilation, Compilation::Yes(vec![])); drop(engine); let actions = io.into_actions(); assert_eq!( actions, vec![ // new Action::DependencyDownloadingStarted, Action::DownloadDependencies, Action::DependencyDownloadingFinished, Action::LockBuild, Action::UnlockBuild, // compile_please Action::CompilationStarted, Action::LockBuild, Action::UnlockBuild, Action::CompilationFinished, // compile_please Action::CompilationStarted, Action::LockBuild, Action::UnlockBuild, Action::CompilationFinished, // compile_please Action::CompilationStarted, Action::LockBuild, Action::UnlockBuild, Action::CompilationFinished, ] ) } #[test] fn dep_compile_recompile() { let io = LanguageServerTestIO::new(); let mut engine = setup_engine(&io); add_path_dep(&mut engine, "mydep"); let path = io.path_dep_module("mydep", "moddy", "pub fn main() { 0 }"); // The first time it compiles. let response = engine.compile_please(); assert!(response.result.is_ok()); assert!(response.warnings.is_empty()); assert_eq!(response.compilation, Compilation::Yes(vec![path.clone()])); assert!(!engine.compiler.project_compiler.packages.is_empty()); // The source file has been updated, so the file is compiled again. _ = io.path_dep_module("mydep", "moddy", "pub fn main() { 1 }"); let response = engine.compile_please(); assert!(response.result.is_ok()); assert!(response.warnings.is_empty()); assert_eq!(response.compilation, Compilation::Yes(vec![path])); // This time it does not compile the module again, instead using the // cache from the previous run. let response = engine.compile_please(); assert!(response.result.is_ok()); assert!(response.warnings.is_empty()); assert_eq!(response.compilation, Compilation::Yes(vec![])); drop(engine); let actions = io.into_actions(); assert_eq!( actions, vec![ // new Action::DependencyDownloadingStarted, Action::DownloadDependencies, Action::DependencyDownloadingFinished, Action::LockBuild, Action::UnlockBuild, // compile_please Action::CompilationStarted, Action::LockBuild, Action::UnlockBuild, Action::CompilationFinished, // compile_please Action::CompilationStarted, Action::LockBuild, Action::UnlockBuild, Action::CompilationFinished, // compile_please Action::CompilationStarted, Action::LockBuild, Action::UnlockBuild, Action::CompilationFinished, ] ) } ================================================ FILE: language-server/src/tests/completion.rs ================================================ use insta::assert_debug_snapshot; use itertools::Itertools; use lsp_types::{CompletionItem, Position}; use super::*; pub fn show_complete(code: &str, position: Position) -> String { let mut str: String = "".into(); for (line_number, line) in code.lines().enumerate() { let same_line = line_number as u32 == position.line; if !same_line { str.push_str(line); } else { str.push_str(&line[0..position.character as usize]); str.push('|'); str.push_str(&line[position.character as usize..]); } str.push('\n'); } str } fn apply_completion(src: &str, completions: Vec, value: &str) -> String { let completion = completions .iter() .find(|c| c.label == value) .unwrap_or_else(|| panic!("no completion with value `{value}`")); let mut edits = vec![]; if let Some(lsp_types::CompletionTextEdit::Edit(edit)) = &completion.text_edit { edits.push(edit.clone()); } apply_code_edit(src, edits) } #[macro_export] macro_rules! assert_apply_completion { ($project:expr, $name:literal, $position:expr) => { let src = $project.src; let completions = completion($project, $position); let output = format!( "{}\n\n----- After applying completion -----\n{}", show_complete(src, $position), apply_completion(src, completions, $name) ); insta::assert_snapshot!(insta::internals::AutoName, output, src); }; } #[macro_export] macro_rules! assert_completion { ($project:expr) => { let src = $project.src; let result = completion_with_prefix($project, ""); let output = format!( "{}\n\n----- Completion content -----\n{}", show_complete(src, Position::new(0, 0)), format_completion_results(result) ); insta::assert_snapshot!(insta::internals::AutoName, output, src); }; ($project:expr, $position:expr) => { let src = $project.src; let result = completion($project, $position); let output = format!( "{}\n\n----- Completion content -----\n{}", show_complete(src, $position), format_completion_results(result) ); insta::assert_snapshot!(insta::internals::AutoName, output, src); }; } #[macro_export] macro_rules! assert_completion_with_prefix { ($project:expr, $prefix:expr) => { let src = $project.src; let result = completion_with_prefix($project, $prefix); let line = 1 + $prefix.lines().count(); let output = format!( "{}\n\n----- Completion content -----\n{}", show_complete(src, Position::new(line as u32, 0)), format_completion_results(result) ); insta::assert_snapshot!(insta::internals::AutoName, output, src); }; } fn format_completion_results(completions: Vec) -> EcoString { use std::fmt::Write; let mut buffer: EcoString = "".into(); for CompletionItem { label, label_details, kind, detail, documentation, deprecated, preselect, sort_text, filter_text, insert_text, insert_text_format, insert_text_mode, text_edit, additional_text_edits, command, commit_characters, data, tags, } in completions { assert!(deprecated.is_none()); assert!(preselect.is_none()); assert!(filter_text.is_none()); assert!(insert_text.is_none()); assert!(insert_text_format.is_none()); assert!(insert_text_mode.is_none()); assert!(command.is_none()); assert!(commit_characters.is_none()); assert!(data.is_none()); assert!(tags.is_none()); buffer.push_str(&label); if let Some(kind) = kind { write!(buffer, "\n kind: {kind:?}").unwrap(); } if let Some(detail) = detail { write!(buffer, "\n detail: {detail}").unwrap(); } if let Some(sort_text) = sort_text { write!(buffer, "\n sort: {sort_text}").unwrap(); } if let Some(label_details) = label_details { assert!(label_details.detail.is_none()); if let Some(desc) = label_details.description { write!(buffer, "\n desc: {desc}").unwrap(); } } if let Some(documentation) = documentation { let lsp_types::Documentation::MarkupContent(m) = documentation else { panic!("unexpected docs in test {documentation:?}"); }; match m.kind { lsp_types::MarkupKind::Markdown => (), lsp_types::MarkupKind::PlainText => { panic!("unexpected docs markup kind {:?}", m.kind) } }; write!(buffer, "\n docs: {:?}", m.value).unwrap(); } let edit = |buffer: &mut EcoString, e: lsp_types::TextEdit| { let a = e.range.start.line; let b = e.range.start.character; let c = e.range.end.line; let d = e.range.end.character; write!(buffer, "\n [{a}:{b}-{c}:{d}]: {:?}", e.new_text).unwrap(); }; if let Some(text_edit) = text_edit { let lsp_types::CompletionTextEdit::Edit(e) = text_edit else { panic!("unexpected text edit in test {text_edit:?}"); }; buffer.push_str("\n edits:"); edit(&mut buffer, e); } for e in additional_text_edits.unwrap_or_default() { edit(&mut buffer, e); } buffer.push('\n'); } buffer } fn completion(tester: TestProject<'_>, position: Position) -> Vec { tester.at(position, |engine, param, src| { let response = engine.completion(param, src); let mut completions = response.result.unwrap().unwrap_or_default(); completions.sort_by(|a, b| a.label.cmp(&b.label)); completions }) } fn completion_with_prefix(tester: TestProject<'_>, prefix: &str) -> Vec { let src = &format!("{}fn typing_in_here() {{\n 0\n}}\n {}", prefix, tester.src); let tester = TestProject { src, ..tester }; // Put the cursor inside the "typing_in_here" fn body. let line = 1 + prefix.lines().count(); completion(tester, Position::new(line as u32, 0)) .into_iter() .filter(|c| c.label != "typing_in_here") .collect_vec() } #[test] fn completions_for_outside_a_function() { let code = " pub fn main() { 0 }"; assert_completion!(TestProject::for_source(code), Position::new(0, 0)); } #[test] fn local_public_function() { let code = " pub fn main() { 0 }"; assert_completion!(TestProject::for_source(code)); } #[test] fn local_public_function_with_documentation() { let code = " /// Hello pub fn main() { 0 }"; assert_completion!(TestProject::for_source(code)); } #[test] fn local_public_enum() { let code = " pub type Direction { Left Right } "; assert_completion!(TestProject::for_source(code)); } #[test] fn local_public_record() { let code = " pub type Box { /// Hello Box(Int, Int, Float) } "; assert_completion!(TestProject::for_source(code)); } #[test] fn local_public_enum_with_documentation() { let code = " pub type Direction { /// Hello Left /// Goodbye Right } "; assert_completion!(TestProject::for_source(code)); } #[test] fn local_public_record_with_documentation() { let code = " pub type Box { Box(Int, Int, Float) } "; assert_completion!(TestProject::for_source(code)); } #[test] fn imported_module_function() { let code = " import dep "; let dep = " pub fn wobble() { Nil } "; assert_completion!(TestProject::for_source(code).add_module("dep", dep)); } #[test] fn importable_module_function() { let code = " "; let dep = " pub fn wobble() { Nil } "; assert_completion!(TestProject::for_source(code).add_module("dep", dep)); } #[test] fn importable_module_function_with_existing_imports() { let code = " //// Some module comments // Some other whitespace import dep2 "; let dep = " pub fn wobble() { Nil } "; let dep2 = " pub fn wobble() { Nil } "; assert_completion!( TestProject::for_source(code) .add_module("dep", dep) .add_module("dep2", dep2) ); } #[test] fn importable_module_function_from_deep_module() { let code = " "; let dep = " pub fn wobble() { Nil } "; assert_completion!(TestProject::for_source(code).add_module("a/b/dep", dep)); } #[test] fn completions_for_type_import_completions_without_brackets() { let src = "import dep."; let dep = " pub opaque type Wibble { Wibble(wibble: String, wobble: Int) } "; let position = Position::new(0, 11); let tester = TestProject::for_source("import dep").add_module("dep", dep); let mut io = LanguageServerTestIO::new(); let mut engine = tester.build_engine(&mut io); // pass a valid src to compile once _ = io.src_module("app", tester.src); let _ = engine.compile_please(); // update src to the one we want to test _ = io.src_module("app", src); let param = tester.build_path(position); let response = engine.completion(param, src.into()); let mut completions = response.result.unwrap().unwrap_or_default(); completions.sort_by(|a, b| a.label.cmp(&b.label)); let output = format!( "{}\n\n----- Completion content -----\n{}", show_complete(src, position), format_completion_results(completions) ); insta::assert_snapshot!(insta::internals::AutoName, output, src); } #[test] fn importable_adds_extra_new_line_if_no_imports() { let dep = "pub fn wobble() {\nNil\n}"; let code = ""; assert_completion!(TestProject::for_source(code).add_module("dep", dep)); } #[test] fn importable_adds_extra_new_line_if_import_exists_below_other_definitions() { let dep = "pub fn wobble() {\nNil\n}"; let code = "\nimport dep2\n"; // "code" goes after "fn typing_in_here() {}". assert_completion!( TestProject::for_source(code) .add_module("dep", dep) .add_module("dep2", "") ); } #[test] fn importable_does_not_add_extra_new_line_if_imports_exist() { let dep = "pub fn wobble() {\nNil\n}"; let prefix = "import wibble\n\n"; let code = ""; assert_completion_with_prefix!( TestProject::for_source(code) .add_module("dep", dep) .add_module("wibble", ""), prefix ); } #[test] fn importable_does_not_add_extra_new_line_if_newline_exists() { let dep = "pub fn wobble() {\nNil\n}"; let prefix = "\n"; let code = ""; assert_completion_with_prefix!( TestProject::for_source(code) .add_module("dep", dep) .add_module("wibble", ""), prefix ); } #[test] fn imported_public_enum() { let code = " import dep "; let dep = " pub type Direction { Left Right } "; assert_completion!(TestProject::for_source(code).add_module("dep", dep)); } #[test] fn imported_public_record() { let code = " import dep "; let dep = " pub type Box { Box(Int) } "; assert_completion!(TestProject::for_source(code).add_module("dep", dep)); } #[test] fn imported_unqualified_module_function() { let code = " import dep.{wobble} "; let dep = " pub fn wobble() { Nil } "; assert_completion!(TestProject::for_source(code).add_module("dep", dep)); } #[test] fn imported_unqualified_public_enum() { let code = " import dep.{Left} "; let dep = " pub type Direction { Left Right } "; assert_completion!(TestProject::for_source(code).add_module("dep", dep)); } #[test] fn imported_unqualified_public_record() { let code = " import dep.{Box} "; let dep = " pub type Box { Box(Int) } "; assert_completion!(TestProject::for_source(code).add_module("dep", dep)); } #[test] fn private_function() { let code = " fn private() { 1 } "; let dep = ""; assert_completion!(TestProject::for_source(code).add_module("dep", dep)); } #[test] fn private_type() { let code = " type Wibble { Wobble } "; let dep = ""; assert_completion!(TestProject::for_source(code).add_module("dep", dep)); } #[test] fn opaque_type() { let code = " pub opaque type Wibble { Wobble } "; let dep = ""; assert_completion!(TestProject::for_source(code).add_module("dep", dep)); } #[test] fn private_function_in_dep() { let code = "import dep"; let dep = " fn private() { 1 } "; assert_completion!(TestProject::for_source(code).add_module("dep", dep)); } #[test] fn private_type_in_dep() { let code = "import dep"; let dep = " type Wibble { Wobble } "; assert_completion!(TestProject::for_source(code).add_module("dep", dep)); } #[test] fn in_custom_type_definition() { let code = "import dep"; let dep = " type Wibble { Wobble } "; assert_completion!(TestProject::for_source(code).add_module("dep", dep)); } #[test] fn for_custom_type_definition() { let code = " pub type Wibble { Wobble }"; assert_completion!(TestProject::for_source(code), Position::new(2, 0)); } #[test] fn for_type_alias() { let code = " pub type Wibble = Result( String, String ) "; assert_completion!(TestProject::for_source(code), Position::new(2, 0)); } #[test] fn for_function_arguments() { let code = " pub fn wibble( _: String, ) -> Nil { Nil } "; assert_completion!(TestProject::for_source(code), Position::new(2, 0)); } #[test] fn imported_type() { let dep = " pub type Zoo = List(String) type Private = List(String) "; let code = "import dep pub fn wibble( _: String, ) -> Nil { Nil } "; assert_completion!( TestProject::for_source(code).add_module("dep", dep), Position::new(3, 0) ); } #[test] fn imported_type_cursor_after_dot() { let dep = " pub type Zoo = List(String) type Private = List(String) "; let code = "import dep pub fn wibble( _: dep.Zoo, ) -> Nil { Nil } "; assert_completion!( TestProject::for_source(code).add_module("dep", dep), Position::new(3, 12) ); } #[test] fn imported_type_cursor_after_dot_other_matching_modules() { let dep = " pub type Zoo = List(String) type Private = List(String) "; let dep2 = " pub type Zoo = List(String) type Private = List(String) "; let code = "import dep import dep2 pub fn wibble( _: dep.Zoo, ) -> Nil { Nil } "; assert_completion!( TestProject::for_source(code) .add_module("dep", dep) .add_module("dep2", dep2), Position::new(4, 12) ); } #[test] fn imported_type_cursor_after_dot_other_modules() { let dep = " pub type Zoo = List(String) type Private = List(String) "; let other = " pub type Zoo = List(String) type Private = List(String) "; let code = "import dep pub fn wibble( _: dep.Zoo, ) -> Nil { Nil } "; assert_completion!( TestProject::for_source(code) .add_module("dep", dep) .add_module("other", other), Position::new(3, 12) ); } #[test] fn imported_type_cursor_mid_phrase_other_modules() { let dep = " pub type Zoo = List(String) type Private = List(String) "; let other = " pub type Zoo = List(String) type Private = List(String) "; let code = "import dep pub fn wibble( _: dep.Zoo, ) -> Nil { Nil } "; assert_completion!( TestProject::for_source(code) .add_module("dep", dep) .add_module("other", other), Position::new(3, 8) ); } #[test] fn importable_type() { let dep = " pub type Zoo = List(String) type Private = List(String) "; let code = " pub fn wibble( _: String, ) -> Nil { Nil } "; assert_completion!( TestProject::for_source(code).add_module("dep", dep), Position::new(3, 0) ); } #[test] fn importable_type_with_existing_imports_at_top() { let dep = " pub type Zoo = List(String) type Private = List(String) "; let dep2 = " pub type Zoo = List(String) type Private = List(String) "; let code = "import dep2 pub fn wibble( _: String, ) -> Nil { Nil } "; assert_completion!( TestProject::for_source(code) .add_module("dep", dep) .add_module("dep2", dep2), Position::new(3, 0) ); } #[test] fn importable_type_with_existing_imports() { let dep = " pub type Zoo = List(String) type Private = List(String) "; let dep2 = " pub type Zoo = List(String) type Private = List(String) "; let code = " //// Some module comments // Some other whitespace import dep2 pub fn wibble( _: String, ) -> Nil { Nil } "; assert_completion!( TestProject::for_source(code) .add_module("dep", dep) .add_module("dep2", dep2), Position::new(7, 0) ); } #[test] fn importable_type_from_deep_module() { let dep = " pub type Zoo = List(String) type Private = List(String) "; let code = " pub fn wibble( _: String, ) -> Nil { Nil } "; assert_completion!( TestProject::for_source(code).add_module("a/b/dep", dep), Position::new(3, 0) ); } #[test] fn unqualified_imported_type() { let dep = " pub type Zoo = List(String) type Private = List(String) "; let code = "import dep.{type Zoo} pub fn wibble( _: String, ) -> Nil { Nil } "; assert_completion!( TestProject::for_source(code).add_module("dep", dep), Position::new(3, 0) ); } #[test] fn local_private_type() { let code = " type Zoo = Int pub fn wibble( x: String, ) -> String { \"ok\" } "; assert_completion!(TestProject::for_source(code), Position::new(4, 0)); } #[test] fn local_variable() { let code = " pub fn main(wibble: Int) { let wobble = 1 w let wabble = 2 } "; assert_completion!(TestProject::for_source(code), Position::new(3, 3)); } #[test] fn local_variable_anonymous_function() { let code = " pub fn main() { let add_one = fn(wibble: Int) { wibble + 1 } add_one(1) } "; assert_completion!(TestProject::for_source(code), Position::new(2, 40)); } #[test] fn local_variable_nested_anonymous_function() { let code = " pub fn main() { let add_one = fn(wibble: Int) { let wabble = 1 let add_two = fn(wobble: Int) { wobble + 2 } wibble + add_two(1) } add_one(1) } "; assert_completion!(TestProject::for_source(code), Position::new(4, 42)); } #[test] fn local_variable_ignore_anonymous_function_args() { let code = " pub fn main() { let add_one = fn(wibble: Int) { wibble + 1 } let wobble = 1 w } "; assert_completion!(TestProject::for_source(code), Position::new(4, 3)); } #[test] fn local_variable_ignore_anonymous_function_args_nested() { let code = " pub fn main() { let add_one = fn(wibble: Int) { let wabble = 1 let add_two = fn(wobble: Int) { wobble + 2 } wibble + add_two(1) } add_one(1) } "; assert_completion!(TestProject::for_source(code), Position::new(5, 10)); } #[test] fn local_variable_ignore_anonymous_function_returned() { let code = " pub fn main() { fn(wibble: Int) { let wabble = 1 let add_two = fn(wobble: Int) { wobble + 2 } wibble + add_two(1) } } "; assert_completion!(TestProject::for_source(code), Position::new(5, 10)); } #[test] fn local_variable_case_expression() { let code = " pub fn main() { case True { True as wibble -> { todo } False -> { todo } } } "; assert_completion!(TestProject::for_source(code), Position::new(3, 25)); } #[test] fn local_variable_inside_nested_exprs() { let code = r#" type Wibble { Wobble(List(#(Bool))) } fn wibble() { Wobble([#(!{ let wibble = True wibble })]) todo } "#; assert_completion!(TestProject::for_source(code), Position::new(5, 7)); } #[test] fn local_variable_pipe() { let code = " pub fn main() { let add_one = fn(wibble: Int) { wibble + 1 } let wobble = 1 wobble |> add_one } "; assert_completion!(TestProject::for_source(code), Position::new(4, 19)); } #[test] fn local_variable_pipe_with_args() { let code = " pub fn main() { let add_one = fn(wibble: Int, wobble: Int) { wibble + wobble } let wobble = 1 let wibble = 2 wobble |> add_one(1, wibble) } "; assert_completion!(TestProject::for_source(code), Position::new(5, 29)); } #[test] fn local_variable_function_call() { let code = " fn add_one(wibble: Int) -> Int { wibble + 1 } pub fn main() { let wobble = 1 add_one(wobble) } "; assert_completion!(TestProject::for_source(code), Position::new(7, 16)); } #[test] fn local_variable_ignored() { let code = " fn wibble() { let a = 1 let _b = 2 } "; assert_completion!(TestProject::for_source(code), Position::new(4, 0)); } #[test] fn local_variable_as() { let code = " fn wibble() { let b as c = 5 } "; assert_completion!(TestProject::for_source(code), Position::new(3, 0)); } #[test] fn local_variable_tuple() { let code = " fn wibble() { let assert #([d, e] as f, g) = #([0, 1], 2) } "; assert_completion!(TestProject::for_source(code), Position::new(3, 0)); } #[test] fn local_variable_bit_array() { let code = " fn wibble() { let assert <> as i = <<1:1>> } "; assert_completion!(TestProject::for_source(code), Position::new(3, 0)); } #[test] fn local_variable_string() { let code = r#" fn wibble() { let assert "a" <> j = "ab" } "#; assert_completion!(TestProject::for_source(code), Position::new(3, 0)); } #[test] fn local_variable_ignore_within_function() { let code = " fn main(a, b, z) { Nil } "; assert_completion!(TestProject::for_source(code), Position::new(1, 14)); } #[test] fn internal_values_from_root_package_are_in_the_completions() { let dep = r#" @external(erlang, "rand", "uniform") @internal pub fn random_float() -> Float @internal pub fn main() { 0 } @internal pub type Wibble { Wobble } @internal pub const wibble = 1 "#; assert_completion!(TestProject::for_source("import dep").add_module("dep", dep)); } #[test] fn internal_types_from_root_package_are_in_the_completions() { let code = "import dep pub fn wibble( _: String, ) -> Nil { Nil }"; let dep = r#" @internal pub type Alias = Int @internal pub type AnotherType { Constructor } "#; assert_completion!( TestProject::for_source(code).add_module("dep", dep), Position::new(3, 0) ); } #[test] fn internal_values_from_the_same_module_are_in_the_completions() { let code = r#" @external(erlang, "rand", "uniform") @internal pub fn random_float() -> Float @internal pub fn main() { 0 } @internal pub type Wibble { Wobble } @internal pub const wibble = 1 "#; assert_completion!(TestProject::for_source(code)); } #[test] fn internal_types_from_the_same_module_are_in_the_completions() { let code = " @internal pub type Alias = Result(Int, String) @internal pub type AnotherType { Wibble } "; assert_completion!(TestProject::for_source(code), Position::new(3, 0)); } #[test] fn internal_types_from_a_dependency_are_ignored() { let code = "import dep pub fn wibble( _: String, ) -> Nil { Nil }"; let dep = r#" @internal pub type Alias = Int @internal pub type AnotherType { Constructor } "#; assert_completion!( TestProject::for_source(code).add_dep_module("dep", dep), Position::new(3, 0) ); } #[test] fn internal_values_from_a_dependency_are_ignored() { let dep = r#" @external(erlang, "rand", "uniform") @internal pub fn random_float() -> Float @internal pub fn main() { 0 } @internal pub type Wibble { Wobble } @internal pub const wibble = 1 "#; assert_completion!(TestProject::for_source("import dep").add_dep_module("dep", dep)); } #[test] fn completions_for_an_import() { let code = "import dep pub fn main() { 0 }"; let dep = ""; assert_completion!( TestProject::for_source(code).add_module("dep", dep), Position::new(0, 10) ); } #[test] fn completions_for_an_import_no_test() { let code = "import gleam pub fn main() { 0 }"; let test = " import gleam pub fn main() { 0 } "; assert_completion!( TestProject::for_source(code).add_test_module("my_tests", test), Position::new(0, 10) ); } #[test] fn completions_for_an_import_while_in_test() { let code = "import gleam pub fn main() { 0 }"; let test = " import gleam pub fn main() { 0 } "; let test_helper = " pub fn test_helper() { 0 } "; let (mut engine, position_param) = TestProject::for_source(code) .add_test_module("my_test", test) .add_test_module("test_helper", test_helper) .positioned_with_io_in_test(Position::new(0, 10), "my_test"); let response = engine.completion(position_param, code.into()); let mut completions = response.result.unwrap().unwrap_or_default(); completions.sort_by(|a, b| a.label.cmp(&b.label)); assert_debug_snapshot!(completions,); } #[test] fn completions_for_an_import_while_in_dev() { let code = "import gleam pub fn main() { 0 }"; let dev_helper = " pub fn dev_helper() { 0 } "; let position = Position::new(0, 12); let (mut engine, position_param) = TestProject::for_source(code) .add_test_module("my_test", code) .add_dev_module("my_dev_code", code) .add_dev_module("dev_helper", dev_helper) .positioned_with_io_in_dev(position, "my_dev_code"); let response = engine.completion(position_param, code.into()); let mut completions = response.result.unwrap().unwrap_or_default(); completions.sort_by(|a, b| a.label.cmp(&b.label)); let output = format!( "{}\n\n----- Completion content -----\n{}", show_complete(code, position), format_completion_results(completions) ); insta::assert_snapshot!(insta::internals::AutoName, output, code); } #[test] fn completions_for_an_import_with_docs() { let code = "import gleam pub fn main() { 0 }"; let dep = "//// Some package //// documentation! pub fn main() { 1 } "; assert_completion!( TestProject::for_source(code).add_dep_module("dep", dep), Position::new(0, 10) ); } #[test] fn completions_for_an_import_from_dependency() { let code = "import gleam pub fn main() { 0 }"; let dep = ""; assert_completion!( TestProject::for_source(code).add_hex_module("example_module", dep), Position::new(0, 10) ); } #[test] fn completions_for_an_import_not_from_indirect_dependency() { let code = "import gleam pub fn main() { 0 }"; let dep = ""; assert_completion!( TestProject::for_source(code) .add_hex_module("example_module", dep) .add_indirect_hex_module("indirect_module", ""), Position::new(0, 10) ); } #[test] fn completions_for_an_import_not_from_dev_dependency() { let code = "import gleam pub fn main() { 0 }"; let dep = ""; assert_completion!( TestProject::for_source(code) .add_hex_module("example_module", dep) .add_dev_hex_module("indirect_module", ""), Position::new(0, 10) ); } #[test] fn completions_for_an_import_not_from_dev_dependency_in_test() { let code = "import gleam pub fn main() { 0 }"; let test = "import gleam pub fn main() { 0 } "; let dep = ""; let (mut engine, position_param) = TestProject::for_source(code) .add_test_module("my_test", test) .add_hex_module("example_module", dep) .add_dev_hex_module("indirect_module", "") .positioned_with_io_in_test(Position::new(0, 10), "my_test"); let response = engine.completion(position_param, code.into()); let mut completions = response.result.unwrap().unwrap_or_default(); completions.sort_by(|a, b| a.label.cmp(&b.label)); assert_debug_snapshot!(completions,); } #[test] fn completions_for_an_import_not_from_dev_dependency_in_dev() { let code = "import gleam pub fn main() { 0 }"; let dev = "import gleam pub fn main() { 0 } "; let dep = ""; let position = Position::new(0, 10); let (mut engine, position_param) = TestProject::for_source(code) .add_dev_module("my_dev_module", dev) .add_hex_module("example_module", dep) .add_dev_hex_module("indirect_module", "") .positioned_with_io_in_dev(position, "my_dev_module"); let response = engine.completion(position_param, code.into()); let mut completions = response.result.unwrap().unwrap_or_default(); completions.sort_by(|a, b| a.label.cmp(&b.label)); let output = format!( "{}\n\n----- Completion content -----\n{}", show_complete(dev, position), format_completion_results(completions) ); insta::assert_snapshot!(insta::internals::AutoName, output, dev); } #[test] fn completions_for_an_import_from_dependency_with_docs() { let code = "//// Main package //// documentation! import gleam pub fn main() { 0 }"; let dep = "//// Some package //// documentation! pub fn main() { 1 } "; assert_completion!( TestProject::for_source(code).add_hex_module("example_module", dep), Position::new(3, 10) ); } #[test] fn completions_for_an_import_start() { let code = "import gleam pub fn main() { 0 }"; let dep = ""; assert_completion!( TestProject::for_source(code).add_dep_module("dep", dep), Position::new(0, 0) ); } #[test] fn completions_for_an_import_preceeding_whitespace() { let code = " import gleam pub fn main() { 0 }"; let dep = ""; assert_completion!( TestProject::for_source(code).add_dep_module("dep", dep), Position::new(0, 2) ); } #[test] fn internal_modules_from_same_package_are_included() { let code = "import gleam pub fn main() { 0 }"; let internal_name = format!("{LSP_TEST_ROOT_PACKAGE_NAME}/internal"); assert_completion!( TestProject::for_source(code) // Not included .add_dep_module("dep/internal", "") // Included .add_module(&internal_name, ""), Position::new(0, 0) ); } #[test] fn completions_for_an_unqualified_import() { let code = " import dep.{} pub fn main() { 0 }"; let dep = "pub const wibble = \"wibble\" const wobble = \"wobble\" @internal pub const wabble = \"wabble\" pub fn myfun() { 0 } pub type Wibble = String "; assert_completion!( TestProject::for_source(code).add_module("dep", dep), Position::new(1, 12) ); } #[test] fn completions_for_an_unqualified_import_on_new_line() { let code = " import dep.{ wibble, } pub fn main() { 0 }"; let dep = "pub const wibble = \"wibble\" pub fn myfun() { 0 } pub type Wibble = String "; assert_completion!( TestProject::for_source(code).add_module("dep", dep), // putting cursor at beginning of line because some formatters // remove the empty whitespace in the test code. // Does also work with (3, 2) when empty spaces are not removed. Position::new(3, 0) ); } #[test] fn completions_for_an_unqualified_import_already_imported() { let code = " import dep.{wibble,wabble,type Wibble} pub fn main() { 0 }"; let dep = "pub const wibble = \"wibble\" const wobble = \"wobble\" @internal pub const wabble = \"wabble\" pub fn myfun() { 0 } pub type Wibble = String "; assert_completion!( TestProject::for_source(code).add_module("dep", dep), Position::new(1, 12) ); } #[test] fn completions_for_a_function_arg_annotation() { let code = " pub fn wibble( _: String, ) -> Nil { Nil } "; assert_completion!(TestProject::for_source(code), Position::new(2, 11)); } #[test] fn completions_for_a_function_return_annotation() { let code = " pub fn wibble( _: String, ) -> Nil { Nil } "; assert_completion!(TestProject::for_source(code), Position::new(3, 7)); } #[test] fn completions_for_a_var_annotation() { let code = " pub fn main() { let wibble: Int = 7 } "; assert_completion!(TestProject::for_source(code), Position::new(2, 16)); } #[test] fn completions_for_a_const_annotation() { let code = " const wibble: Int = 7 pub fn main() { let wibble: Int = 7 } "; assert_completion!(TestProject::for_source(code), Position::new(2, 16)); } #[test] fn ignore_completions_in_empty_comment() { // Reproducing issue #2161 let code = " pub fn main() { case 0 { // _ -> 1 } } "; // End of the comment (right after the last `/`) assert_eq!( completion(TestProject::for_source(code), Position::new(3, 6)), vec![], ); } #[test] fn ignore_completions_in_middle_of_comment() { // Reproducing issue #2161 let code = " pub fn main() { case 0 { // comment _ -> 1 } } "; // At `c` assert_eq!( completion(TestProject::for_source(code), Position::new(3, 7)), vec![], ); } #[test] fn ignore_completions_in_end_of_comment() { // Reproducing issue #2161 let code = " pub fn main() { case 0 { // comment _ -> 1 } } "; // End of the comment (after `t`) assert_eq!( completion(TestProject::for_source(code), Position::new(3, 14)), vec![], ); } #[test] fn ignore_completions_inside_empty_string() { let code = " pub fn main() { \"\" } "; assert_eq!( completion(TestProject::for_source(code), Position::new(2, 2)), vec![], ); } #[test] fn ignore_completions_inside_string() { let code = " pub fn main() { \"Ok()\" } "; assert_eq!( completion(TestProject::for_source(code), Position::new(2, 5)), vec![], ); } #[test] fn completions_for_record_access() { let code = " pub type Wibble { Wibble(wibble: Int, wobble: Int) Wobble(wabble: Int, wobble: Int) } fn fun() { let wibble = Wibble(1, 2) wibble.wobble } "; assert_completion!(TestProject::for_source(code), Position::new(8, 15)); } #[test] fn completions_for_private_record_access() { let code = " type Wibble { Wibble(wibble: Int, wobble: Int) Wobble(wabble: Int, wobble: Int) } fn fun() { let wibble = Wibble(1, 2) wibble.wobble } "; assert_completion!(TestProject::for_source(code), Position::new(8, 15)); } #[test] fn completions_for_record_access_known_variant() { let code = " type Wibble { Wibble(a: Int, b: Int, c: Int, d: Int) Wobble(z: Bool) } fn fun(some_wibble: Wibble) { case some_wibble { Wibble(..) as w -> w.a Wobble(..) -> panic } } "; assert_completion!(TestProject::for_source(code), Position::new(8, 26)); } #[test] fn completions_for_record_access_unknown_variant() { let code = " type Wibble { Wibble(a: Int, b: Int, c: Int, d: Int) Wobble(a: Int, z: Bool) } fn fun(some_wibble: Wibble) { some_wibble.a } "; assert_completion!(TestProject::for_source(code), Position::new(7, 15)); } #[test] fn completions_for_record_labels() { let code = " pub type Wibble { Wibble(wibble: String, wobble: Int) } fn fun() { // completion inside parens below includes labels let wibble = Wibble() } "; assert_completion!(TestProject::for_source(code), Position::new(6, 22)); } #[test] fn completions_for_imported_record_labels() { let code = " import dep fn fun() { // completion inside parens below includes labels let wibble = dep.Wibble() } "; let dep = " pub type Wibble { Wibble(wibble: String, wobble: Int) } "; assert_completion!( TestProject::for_source(code).add_dep_module("dep", dep), Position::new(4, 26) ); } #[test] fn no_completions_for_imported_internal_record_fields() { let code = " import dep fn fun(wibble: dep.Wibble) { wibble. // ^ We should see no completions here! } "; let dep = " @internal pub type Wibble { Wibble(wibble: String, wobble: Int) } "; assert_completion!( TestProject::for_source(code).add_dep_module("dep", dep), Position::new(4, 9) ); } #[test] fn completions_for_internal_record_fields_inside_the_same_module() { let code = " @internal pub type Wibble { Wibble(wibble: String, wobble: Int) } fn fun(wibble: Wibble) { wibble. // ^ We should see some completions here! } "; assert_completion!(TestProject::for_source(code), Position::new(7, 9)); } #[test] fn completions_for_imported_record_fields() { let code = " import dep fn fun(wibble: dep.Wibble) { wibble. // ^ We should see wibble and wobble completions here! } "; let dep = " pub type Wibble { Wibble(wibble: String, wobble: Int) } "; assert_completion!( TestProject::for_source(code).add_dep_module("dep", dep), Position::new(4, 9) ); } #[test] fn completions_for_function_labels() { let code = " fn wibble(wibble arg1: String, wobble arg2: String) { arg1 <> arg2 } fn fun() { // completion inside parens below includes labels let wibble = wibble() } "; assert_completion!(TestProject::for_source(code), Position::new(6, 22)); } #[test] fn completions_for_imported_function_labels() { let code = " import dep fn fun() { // completion inside parens below includes labels let wibble = dep.wibble() } "; let dep = " pub fn wibble(wibble arg1: String, wobble arg2: String) { arg1 <> arg2 } "; assert_completion!( TestProject::for_source(code).add_dep_module("dep", dep), Position::new(4, 26) ); } #[test] fn no_completion_inside_comment_that_is_more_than_three_lines() { let code = "import list // list. // list. fn fun() { // list. todo } "; let dep = "pub fn map() {}"; let completions = completion( TestProject::for_source(code).add_dep_module("list", dep), Position::new(5, 10), ); assert_eq!(completions, vec![],); } #[test] fn completions_for_prelude_values() { let code = " pub fn main() { let my_bool = T } "; assert_completion!(TestProject::for_source(code), Position::new(2, 17)); } #[test] fn variable_shadowing() { let code = " pub fn main() { let x = 1 let x = [1, 2] } "; assert_completion!(TestProject::for_source(code), Position::new(4, 0)); } #[test] fn argument_shadowing() { let code = " pub fn main(x: Int) { fn(x: Float) { } } "; assert_completion!(TestProject::for_source(code), Position::new(3, 0)); } #[test] fn argument_variable_shadowing() { let code = " pub fn main(x: Int) { let x = [1, 2] } "; assert_completion!(TestProject::for_source(code), Position::new(3, 0)); } // https://github.com/gleam-lang/gleam/issues/3833 #[test] fn autocomplete_doesnt_delete_the_piece_of_code_that_comes_after() { let code = " import list pub fn main(x: Int) { list.list.filter([1, 2, 3], todo) } "; let list = " pub fn filter(_, _) { [] } pub fn map(_, _) { [] } "; assert_apply_completion!( TestProject::for_source(code).add_dep_module("list", list), "list.map", Position::new(3, 7) ); } #[test] fn autocomplete_doesnt_delete_the_piece_of_code_that_comes_after_2() { let code = " import list pub fn main(x: Int) { list.mlist.filter([1, 2, 3], todo) } "; let list = " pub fn filter(_, _) { [] } pub fn map(_, _) { [] } "; assert_apply_completion!( TestProject::for_source(code).add_dep_module("list", list), "list.map", Position::new(3, 8) ); } #[test] fn case_subject() { let code = " pub fn main(something: Bool) { case so } "; assert_apply_completion!( TestProject::for_source(code), "something", Position::new(2, 9) ); } #[test] fn constant() { let code = " const hello = 10 const world = he "; assert_completion!(TestProject::for_source(code), Position::new(2, 16)); } #[test] fn constant_with_many_options() { let code = " import wibble.{Wobble} type Wibble { Wibble } const pi = 3.14159 fn some_function() { todo } const my_constant = a "; assert_completion!( TestProject::for_source(code).add_hex_module("wibble", "pub type Wibble { Wobble Wubble }"), Position::new(13, 21) ); } #[test] fn constant_with_module_select() { let code = " import wibble type Wibble { Wibble } const pi = 3.14159 fn some_function() { todo } const my_constant = wibble.W "; assert_completion!( TestProject::for_source(code).add_hex_module( "wibble", " pub type Wibble { Wobble Wubble } pub const some_constant = 1 pub fn some_function() { todo } " ), Position::new(13, 28) ); } #[test] fn labelled_arguments() { let code = " pub type Wibble { Wibble(wibble: Int, wobble: Float) } pub fn main() { Wibble(w) } "; assert_completion!(TestProject::for_source(code), Position::new(6, 10)); } #[test] fn labelled_arguments_with_existing_label() { // This should only suggest the `wobble:` label let code = " pub type Wibble { Wibble(wibble: Int, wobble: Float) } pub fn main() { Wibble(wibble: 10, w) } "; assert_completion!(TestProject::for_source(code), Position::new(6, 22)); } #[test] fn labelled_arguments_after_label() { // This should not suggest any labels, as `wibble: wibble:` is not valid syntax. let code = " pub type Wibble { Wibble(wibble: Int, wobble: Float) } pub fn main() { Wibble(wibble: w) } "; assert_completion!(TestProject::for_source(code), Position::new(6, 18)); } #[test] fn labelled_arguments_function_call() { let code = " pub fn divide(x: Int, by y: Int) { x / y } pub fn main() { divide(10, b) } "; assert_completion!(TestProject::for_source(code), Position::new(4, 14)); } #[test] fn labelled_arguments_from_different_module() { let code = " import wibble pub fn main() { wibble.divide(10, b) } "; assert_completion!( TestProject::for_source(code) .add_hex_module("wibble", "pub fn divide(x: Int, by y: Int) { x / y }"), Position::new(4, 21) ); } #[test] fn no_label_completions_in_nested_expression() { // Since we are completing inside a list, labels are no longer available let code = " pub type Wibble { Wibble(wibble: Int, wobble: Float) } pub fn main() { Wibble([w]) } "; assert_completion!(TestProject::for_source(code), Position::new(6, 11)); } // https://github.com/gleam-lang/gleam/issues/4625 #[test] fn no_variable_completions_before_declaration_in_block() { let code = " pub fn main() { { s let something = 10 } } "; assert_completion!(TestProject::for_source(code), Position::new(3, 5)); } // https://github.com/gleam-lang/gleam/issues/4625 #[test] fn no_variable_completions_before_declaration_in_anonymous_function() { let code = " pub fn main() { fn() { s let something = 10 } } "; assert_completion!(TestProject::for_source(code), Position::new(3, 5)); } // https://github.com/gleam-lang/gleam/issues/4625 #[test] fn no_variable_completions_after_block_scope() { let code = " pub fn main() { { let something = 10 } s } "; assert_completion!(TestProject::for_source(code), Position::new(5, 3)); } // https://github.com/gleam-lang/gleam/issues/4625 #[test] fn no_variable_completions_after_anonymous_function_scope() { let code = " pub fn main() { fn() { let something = 10 } s } "; assert_completion!(TestProject::for_source(code), Position::new(5, 3)); } // https://github.com/gleam-lang/gleam/issues/4625 #[test] fn no_variable_completions_after_case_scope() { let code = " pub fn main() { case todo { something -> Nil } s } "; assert_completion!(TestProject::for_source(code), Position::new(5, 3)); } // https://github.com/gleam-lang/gleam/issues/4625 #[test] fn no_variable_completions_after_case_clause_scope() { let code = " pub fn main() { case todo { something -> Nil something_else -> s } s } "; assert_completion!(TestProject::for_source(code), Position::new(4, 23)); } // https://github.com/gleam-lang/gleam/issues/4625 #[test] fn no_variable_completions_before_case_clause() { let code = " pub fn main() { case todo { something -> s something_else -> Nil } s } "; assert_completion!(TestProject::for_source(code), Position::new(3, 18)); } // https://github.com/gleam-lang/gleam/issues/4652 #[test] fn no_completions_in_constant_string() { let code = r#" const x = "io." "#; let completions = completion( TestProject::for_source(code).add_hex_module("gleam/io", "pub fn println() {todo}"), Position::new(1, 14), ); assert_eq!(completions, vec![],); } #[test] fn prefer_values_matching_expected_type() { let code = " pub fn main() -> Bool { let wibble = 123 let wubble = True let Wobble = 1.5 w } "; assert_completion!(TestProject::for_source(code), Position::new(5, 3)); } #[test] fn prefer_function_which_returns_expected_type() { let code = " pub fn main() -> Int { a } fn add(a, b) { a + b } fn sub(a, b) { a - b } fn addf(a, b) { a +. b } "; assert_completion!(TestProject::for_source(code), Position::new(2, 3)); } #[test] fn prefer_function_which_returns_expected_generic_type() { let code = " pub fn main() -> Result(Int, Nil) { let result = Ok(12) let result2 = Error(True) r } "; assert_completion!(TestProject::for_source(code), Position::new(4, 3)); } #[test] fn completion_for_type() { let dep = "pub type Wibble"; let code = "pub fn new() -> wibble.Wibble {}"; assert_completion!( TestProject::for_source(code).add_dep_module("wibble", dep), Position::new(0, 23) ); } #[test] fn completion_for_partially_correct_existing_module_select() { let dep = "pub fn filter() {}"; let code = " import gleam/list pub fn new() { list.fer // ^ cursor right after the 'f' } "; assert_completion!( TestProject::for_source(code).add_dep_module("gleam/list", dep), Position::new(4, 8) ); } #[test] fn complete_keyword_being_typed() { assert_apply_completion!( TestProject::for_source("pub fn main() { t }"), "todo", Position::new(0, 17) ); } #[test] fn complete_echo_keyword() { assert_apply_completion!( TestProject::for_source("pub fn main() { e wibble }"), "echo", Position::new(0, 17) ); } #[test] fn complete_panic_keyword() { assert_apply_completion!( TestProject::for_source("pub fn main() { wibble(p) }"), "panic", Position::new(0, 24) ); } #[test] fn do_not_show_completions_when_typing_a_number() { assert_completion!( TestProject::for_source( " pub fn main() { 2 } pub fn window_by_2() {} pub fn to_base_32() {} " ), Position::new(1, 17) ); } ================================================ FILE: language-server/src/tests/definition.rs ================================================ use lsp_types::{ GotoDefinitionParams, Location, Position, Range, Url, request::GotoTypeDefinitionParams, }; use super::*; fn definition(tester: &TestProject<'_>, position: Position) -> Option { tester.at(position, |engine, param, _| { let params = GotoDefinitionParams { text_document_position_params: param, work_done_progress_params: Default::default(), partial_result_params: Default::default(), }; let response = engine.goto_definition(params); response.result.unwrap() }) } fn pretty_definition(project: TestProject<'_>, position_finder: PositionFinder) -> String { let position = position_finder.find_position(project.src); let location = definition(&project, position).expect("a location to jump to"); jump_locations_to_string(project, position, vec![location]) } fn type_definition(tester: &TestProject<'_>, position: Position) -> Vec { tester.at(position, |engine, param, _| { let params = GotoTypeDefinitionParams { text_document_position_params: param, work_done_progress_params: Default::default(), partial_result_params: Default::default(), }; let response = engine.goto_type_definition(params); response.result.unwrap() }) } fn pretty_type_definition(project: TestProject<'_>, position_finder: PositionFinder) -> String { let position = position_finder.find_position(project.src); let location = type_definition(&project, position); format!( "Jumping to type definition\n\n{}", jump_locations_to_string(project, position, location) ) } fn jump_locations_to_string( project: TestProject<'_>, original_position: Position, locations: Vec, ) -> String { let src = hover::show_hover( project.src, Range { start: original_position, end: original_position, }, original_position, ); let destinations = locations .iter() .map(|location| { let pretty_destination = location .uri .path_segments() .expect("a location to jump to") // To make snapshots the same both on windows and unix systems we need // to discard windows' `C:` path segment at the beginning of a uri. .skip_while(|segment| *segment == "C:") .join("/"); let destination_code = hover::show_hover( project .src_from_module_url(&location.uri) .expect("a module to jump to"), location.range, location.range.start, ); format!( "----- Jumped to `{pretty_destination}` {destination_code}" ) }) .join("\n\n"); format!( "----- Jumping from `src/app.gleam` {src} {destinations}", ) } #[macro_export] macro_rules! assert_goto { ($src:literal, $position:expr) => { let project = TestProject::for_source($src); assert_goto!(project, $position); }; ($project:expr, $position:expr) => { let output = pretty_definition($project, $position); insta::assert_snapshot!(insta::internals::AutoName, output); }; } #[macro_export] macro_rules! assert_goto_type { ($src:literal, $position:expr) => { let project = TestProject::for_source($src); assert_goto_type!(project, $position); }; ($project:expr, $position:expr) => { let output = pretty_type_definition($project, $position); insta::assert_snapshot!(insta::internals::AutoName, output); }; } #[test] fn goto_type_definition_in_same_file() { assert_goto_type!( " pub type Wibble { Wibble } pub fn main() { let x = Wibble x }", find_position_of("x").nth_occurrence(2) ); } #[test] fn goto_type_definition_in_different_file_of_same_project() { let src = " import wibble.{type Wibble} pub fn main() { use_wibble(todo) } pub fn use_wibble(wibble: Wibble) { todo } "; assert_goto_type!( TestProject::for_source(src).add_module("wibble", "pub type Wibble"), find_position_of("todo") ); } #[test] fn goto_type_definition_in_different_file_of_dependency() { let src = " import wibble.{type Wibble} pub fn main() { use_wibble(todo) } pub fn use_wibble(wibble: Wibble) { todo } "; assert_goto_type!( TestProject::for_source(src).add_dep_module("wibble", "pub type Wibble"), find_position_of("todo") ); } #[test] fn goto_type_definition_can_jump_to_multiple_types() { let src = " import wibble.{type Wibble, Wibble} import box.{Box} pub fn main() { let a = Box(Wibble) } "; assert_goto_type!( TestProject::for_source(src) .add_dep_module("wibble", "pub type Wibble { Wibble }") .add_dep_module("box", "pub type Box(a) { Box(a) }"), find_position_of("let a") ); } #[test] fn goto_type_definition_can_jump_to_all_types_in_a_tuple() { let src = " import wibble.{type Wibble} import wobble.{type Wobble} import box.{type Box} pub fn main() { let a: #(Box(Wibble), Wobble) = todo } "; assert_goto_type!( TestProject::for_source(src) .add_dep_module("wibble", "pub type Wibble { Wibble }") .add_dep_module("wobble", "pub type Wobble { Wobble }") .add_dep_module("box", "pub type Box(a) { Box(a) }"), find_position_of("let a") ); } #[test] fn goto_type_definition_can_jump_to_all_types_in_a_function_type() { let src = " import wibble.{type Wibble} import wobble.{type Wobble} import box.{type Box} pub fn main() { let a = fn(wibble: Wibble) { box.Box(wobble.Wobble) } } "; assert_goto_type!( TestProject::for_source(src) .add_dep_module("wibble", "pub type Wibble { Wibble }") .add_dep_module("wobble", "pub type Wobble { Wobble }") .add_dep_module("box", "pub type Box(a) { Box(a) }"), find_position_of("let a") ); } #[test] fn goto_definition_local_variable() { assert_goto!( " pub fn main() { let x = 1 x }", find_position_of("x").nth_occurrence(2) ); } #[test] fn goto_definition_record_update() { assert_goto!( " pub type Wibble { Wibble(one: Int, two: Int) } pub fn main() { Wibble(..todo, one: 1) }", find_position_of("Wibble").nth_occurrence(3) ); } #[test] fn goto_definition_same_module_constants() { assert_goto!( " const x = 1 pub fn main() { x }", find_position_of("x").nth_occurrence(2) ); } #[test] fn goto_definition_same_module_functions() { assert_goto!( " fn add_2(x) { x + 2 } pub fn main() { add_2(1) }", find_position_of("add_2(1)") ); } #[test] fn goto_definition_same_module_records() { assert_goto!( " pub type Rec { Var1(Int) Var2(Int, Int) } pub fn main() { let a = Var1(1) let b = Var2(2, 3) }", find_position_of("Var1(1)") ); } #[test] fn goto_definition_imported_module_constants() { let code = " import example_module fn main() { example_module.my_num } "; assert_goto!( TestProject::for_source(code).add_module("example_module", "pub const my_num = 1"), find_position_of("my_num") ); } #[test] fn goto_definition_unqualified_imported_module_constants() { let code = " import example_module.{my_num} fn main() { my_num } "; assert_goto!( TestProject::for_source(code).add_module("example_module", "pub const my_num = 1"), find_position_of("my_num").nth_occurrence(2) ); } #[test] fn goto_definition_module_function_calls() { let code = " import example_module fn main() { example_module.my_fn } "; assert_goto!( TestProject::for_source(code).add_module("example_module", "pub fn my_fn() { Nil }"), find_position_of("my_fn") ); } #[test] fn goto_definition_imported_module_records() { let dep_src = " pub type Rec { Var1(Int) Var2(Int, Int) }"; let code = " import example_module fn main() { example_module.Var1(1) } "; assert_goto!( TestProject::for_source(code).add_module("example_module", dep_src), find_position_of("Var1(1)") ); } #[test] fn goto_definition_unqualified_imported_module_records() { let dep_src = " pub type Rec { Var1(Int) Var2(Int, Int) }"; let code = " import example_module.{Var1} fn main() { Var1(1) } "; assert_goto!( TestProject::for_source(code).add_module("example_module", dep_src), find_position_of("Var1(1)").under_char('a') ); } #[test] fn goto_definition_external_module_constants() { let code = " import example_module fn main() { example_module.my_num } "; assert_goto!( TestProject::for_source(code).add_hex_module("example_module", "pub const my_num = 1"), find_position_of("my_num").under_char('u') ); } #[test] fn goto_definition_external_module_function_calls() { let code = " import example_module fn main() { example_module.my_fn } "; assert_goto!( TestProject::for_source(code).add_hex_module("example_module", "pub fn my_fn() { Nil }"), find_position_of("my_fn") ); } #[test] fn goto_definition_external_module_function_calls_with_multiple_compiles() { let dep = "pub fn my_fn() { Nil }"; let code = " import example_module fn main() { example_module.my_fn } "; let (mut engine, position_param) = TestProject::for_source(code) .add_hex_module("example_module", dep) .positioned_with_io(Position::new(3, 20)); let params = GotoDefinitionParams { text_document_position_params: position_param.clone(), work_done_progress_params: Default::default(), partial_result_params: Default::default(), }; let response = engine.goto_definition(params.clone()); let response = response.result.unwrap(); assert_eq!( response, Some(Location { uri: Url::from_file_path(Utf8PathBuf::from(if cfg!(target_family = "windows") { r"\\?\C:\build\packages\hex\src\example_module.gleam" } else { "/build/packages/hex/src/example_module.gleam" })) .unwrap(), range: Range { start: Position { line: 0, character: 0 }, end: Position { line: 0, character: 14 } } }) ); engine.compiler.sources.clear(); let response = engine.compile_please(); assert!(response.result.is_ok()); let response = engine.goto_definition(params.clone()); let response = response.result.unwrap(); assert_eq!( response, Some(Location { uri: Url::from_file_path(Utf8PathBuf::from(if cfg!(target_family = "windows") { r"\\?\C:\build\packages\hex\src\example_module.gleam" } else { "/build/packages/hex/src/example_module.gleam" })) .unwrap(), range: Range { start: Position { line: 0, character: 0 }, end: Position { line: 0, character: 14 } } }) ) } #[test] fn goto_definition_path_module_function_calls_with_multiple_compiles() { let dep = "pub fn my_fn() { Nil }"; let code = " import example_module fn main() { example_module.my_fn } "; let (mut engine, position_param) = TestProject::for_source(code) .add_dep_module("example_module", dep) .positioned_with_io(Position::new(3, 20)); let params = GotoDefinitionParams { text_document_position_params: position_param.clone(), work_done_progress_params: Default::default(), partial_result_params: Default::default(), }; let response = engine.goto_definition(params.clone()); let response = response.result.unwrap(); assert_eq!( response, Some(Location { uri: Url::from_file_path(Utf8PathBuf::from(if cfg!(target_family = "windows") { r"\\?\C:\dep\src\example_module.gleam" } else { "/dep/src/example_module.gleam" })) .unwrap(), range: Range { start: Position { line: 0, character: 0 }, end: Position { line: 0, character: 14 } } }) ); engine.compiler.sources.clear(); let response = engine.compile_please(); assert!(response.result.is_ok()); let response = engine.goto_definition(params.clone()); let response = response.result.unwrap(); assert_eq!( response, Some(Location { uri: Url::from_file_path(Utf8PathBuf::from(if cfg!(target_family = "windows") { r"\\?\C:\dep\src\example_module.gleam" } else { "/dep/src/example_module.gleam" })) .unwrap(), range: Range { start: Position { line: 0, character: 0 }, end: Position { line: 0, character: 14 } } }) ) } #[test] fn goto_definition_external_module_records() { let hex_src = " pub type Rec { Var1(Int) Var2(Int, Int) } "; let code = " import example_module fn main() { example_module.Var1(1) } "; assert_goto!( TestProject::for_source(code).add_hex_module("example_module", hex_src), find_position_of("Var1(1)").under_char('r') ); } #[test] fn goto_definition_path_module_function_calls() { let code = " import example_module fn main() { example_module.my_fn } "; assert_goto!( TestProject::for_source(code).add_dep_module("example_module", "pub fn my_fn() { Nil }"), find_position_of("my_fn").under_char('y') ); } #[test] fn goto_definition_type() { assert_goto!( " pub type Rec { Var1(Int) Var2(Int, Int) } pub fn make_var() -> Rec { Var1(1) }", find_position_of("Rec").nth_occurrence(2) ); } #[test] fn goto_definition_type_in_module() { let hex_src = " pub type Rec { Var1(Int) Var2(Int, Int) } "; let code = " import example_module fn make_var() -> example_module.Rec { example_module.Var1(1) } "; assert_goto!( TestProject::for_source(code).add_hex_module("example_module", hex_src), find_position_of("Rec") ); } #[test] fn goto_definition_type_in_path_dep() { let dep = " pub type Rec { Var1(Int) Var2(Int, Int) } "; let code = " import example_module fn make_var() -> example_module.Rec { example_module.Var1(1) } "; assert_goto!( TestProject::for_source(code).add_dep_module("example_module", dep), find_position_of("Rec") ); } #[test] fn goto_definition_deep_type_in_module() { let hex_src = " pub type Wobble { Wobble(Int) } pub type Wibble(a) { Wibble(a) } pub type Wabble(a) { Wabble(a) } "; let code = " import example_module fn make_var() -> example_module.Wabble(example_module.Wibble(example_module.Wobble)) { example_module.Wabble(example_module.Wibble(example_module.Wobble(1))) } "; assert_goto!( TestProject::for_source(code).add_hex_module("example_module", hex_src), find_position_of("Wobble").under_char('o') ); } #[test] fn goto_definition_import() { let code = " import example_module fn main() { example_module.my_num } "; assert_goto!( TestProject::for_source(code).add_module("example_module", "pub const my_num = 1"), find_position_of("example_module").under_char('p') ); } #[test] fn goto_definition_import_aliased() { let code = " import example_module as example fn main() { example.my_num } "; assert_goto!( TestProject::for_source(code).add_module("example_module", "pub const my_num = 1"), find_position_of("example") .nth_occurrence(2) .under_char('x') ); } #[test] fn goto_definition_import_unqualified_value() { let code = " import example_module.{my_num} fn main() { my_num } "; assert_goto!( TestProject::for_source(code).add_module("example_module", "pub const my_num = 1"), find_position_of("my_num").under_char('_') ); } #[test] fn goto_definition_unqualified_function() { let code = " import wibble.{wobble} fn main() { wobble() } "; assert_goto!( TestProject::for_source(code).add_module("wibble", "pub fn wobble() {}"), find_position_of("wobble").nth_occurrence(2).under_char('o') ); } #[test] fn goto_definition_import_unqualified_type() { let code = " import example_module.{type MyType} fn main() -> MyType { 0 } "; assert_goto!( TestProject::for_source(code).add_module("example_module", "pub type MyType = Int"), find_position_of("MyType").under_char('T') ); } // https://github.com/gleam-lang/gleam/issues/3610 #[test] fn goto_definition_of_external_function_in_same_module() { let code = " @external(erlang, \"wibble\", \"wobble\") fn external_function() -> Nil fn main() { external_function() } "; assert_goto!( TestProject::for_source(code), find_position_of("external_function") .nth_occurrence(2) .under_char('l') ); } // https://github.com/gleam-lang/gleam/issues/3758 #[test] fn goto_definition_from_anonymous_function() { let code = " pub type Wibble pub fn main() { fn(w: Wibble) { todo } } "; assert_goto!( TestProject::for_source(code), find_position_of("w: Wibble").under_char('i') ); } #[test] fn goto_definition_module() { let code = " import wibble pub fn main() { wibble.wibble() } "; assert_goto!( TestProject::for_source(code).add_module("wibble", "pub fn wibble() {}"), find_position_of("wibble.").under_char('i') ); } #[test] fn goto_definition_constant() { assert_goto!( " const value = 25 const my_constant = value ", find_position_of("= value").under_char('a') ); } #[test] fn goto_definition_constant_record() { assert_goto!( " type Wibble { Wibble(Int) } const wibble = Wibble(10) ", find_position_of("Wibble(10)").under_char('l') ); } #[test] fn goto_definition_imported_constant() { let src = " import wibble const my_constant = wibble.value "; assert_goto!( TestProject::for_source(src).add_hex_module("wibble", "pub const value = 10"), find_position_of("value").under_char('v') ); } #[test] fn goto_definition_constant_imported_record() { let src = " import wibble const my_constant = wibble.Wibble(10) "; assert_goto!( TestProject::for_source(src).add_hex_module("wibble", "pub type Wibble { Wibble(Int) }"), find_position_of("Wibble(10)").under_char('W') ); } #[test] fn goto_definition_from_alternative_pattern() { assert_goto!( " type Wibble { Wibble Wobble } fn warble(wibble: Wibble) { case wibble { Wibble | Wobble -> 0 } } ", find_position_of("Wobble ->") ); } #[test] fn goto_definition_of_local_variable_from_guard() { assert_goto!( " pub fn main() { let wibble = True let wobble = False case wibble { True if wobble -> !wibble False if !wobble -> wibble _ -> wobble } } ", find_position_of("wobble").nth_occurrence(2).under_char('o') ); } #[test] fn goto_definition_of_record_from_guard() { assert_goto!( " type Wibble { Wibble Wobble } pub fn main() { let wibble = True let wobble = Wibble case wibble { True if wobble == Wibble -> !wibble False if wobble == Wobble -> wibble _ -> Wibble } } ", find_position_of("== Wibble").under_char('l') ); } #[test] fn goto_definition_of_module_select_from_guard() { let src = " import mod pub fn main() { let wibble = True case wibble { True if mod.wibble < 5 -> !wibble False if mod.wibble != 10 -> wibble _ -> mod.wibble + 1 } } "; assert_goto!( TestProject::for_source(src).add_module("mod", "pub const wibble = 10"), find_position_of("mod.wibble").under_char('w') ); } #[test] fn goto_definition_of_record_module_select_from_guard() { let src = " import mod pub fn main() { let wibble = True let wobble = mod.Wibble case wibble { True if wobble == mod.Wobble -> !wibble False if wobble == mod.Wibble -> wibble _ -> mod.Wibble } } "; assert_goto!( TestProject::for_source(src).add_module("mod", "pub type Wobble { Wibble Wobble }"), find_position_of("== mod.Wibble").under_char('i') ); } ================================================ FILE: language-server/src/tests/document_symbols.rs ================================================ use insta::assert_debug_snapshot; use lsp_types::{DocumentSymbol, DocumentSymbolParams}; use super::*; fn doc_symbols(tester: TestProject<'_>) -> Vec { tester.at(Position::default(), |engine, param, _| { let params = DocumentSymbolParams { text_document: param.text_document, work_done_progress_params: Default::default(), partial_result_params: Default::default(), }; let response = engine.document_symbol(params); response.result.unwrap() }) } #[test] fn doc_symbols_type_no_constructors() { let code = " pub type A"; assert_debug_snapshot!(doc_symbols(TestProject::for_source(code))) } #[test] fn doc_symbols_type_no_constructors_starting_at_documentation() { let code = " /// My type pub type A"; assert_debug_snapshot!(doc_symbols(TestProject::for_source(code))) } #[test] fn doc_symbols_type_no_constructors_starting_at_empty_doc() { let code = " // Some prior code... /// pub type A"; assert_debug_snapshot!(doc_symbols(TestProject::for_source(code))) } #[test] fn doc_symbols_type_constructor_no_args() { let code = " pub type B { C D /// E E }"; assert_debug_snapshot!(doc_symbols(TestProject::for_source(code))) } #[test] fn doc_symbols_type_constructor_pos_args() { let code = " pub type B { C(Int) /// D D(List(Int)) /// E E( Result(Int, Bool) ) }"; assert_debug_snapshot!(doc_symbols(TestProject::for_source(code))) } #[test] fn doc_symbols_type_constructor_labeled_args() { let code = " pub type B { C(argc: Int) /// D D(argd: List(Int)) /// E E( /// Arg arge: Result(Int, Bool) ) }"; assert_debug_snapshot!(doc_symbols(TestProject::for_source(code))) } #[test] fn doc_symbols_type_constructor_pos_and_labeled_args() { let code = " pub type B { C(Int, argc: Int) /// D D(Int, argd: List(Int)) /// E E( Int, /// Arg arge: Result(Int, Bool) ) }"; assert_debug_snapshot!(doc_symbols(TestProject::for_source(code))) } #[test] fn doc_symbols_type_alias() { let code = " /// DOC pub type FFF = Int pub type FFFF = List(Int)"; assert_debug_snapshot!(doc_symbols(TestProject::for_source(code))) } #[test] fn doc_symbols_function() { let code = " /// DOC pub fn super_func(a: Int) -> List(Int) { [a + 5] } pub fn super_func2(a: Int) -> List(Int) { [a + 5] }"; assert_debug_snapshot!(doc_symbols(TestProject::for_source(code))) } #[test] fn doc_symbols_constant() { let code = " /// DOC pub const my_const = 5 pub const my_const2 = [25]"; assert_debug_snapshot!(doc_symbols(TestProject::for_source(code))) } ================================================ FILE: language-server/src/tests/folding_range.rs ================================================ use lsp_types::{FoldingRange, FoldingRangeKind, FoldingRangeParams}; use super::*; fn folding_ranges(tester: TestProject<'_>) -> Vec { tester.at(Position::default(), |engine, param, _| { let params = FoldingRangeParams { text_document: param.text_document, work_done_progress_params: Default::default(), partial_result_params: Default::default(), }; let response = engine.folding_range(params); response.result.unwrap() }) } fn kind_name(kind: &Option) -> &'static str { match kind { Some(FoldingRangeKind::Imports) => "imports", Some(FoldingRangeKind::Comment) => "comment", Some(FoldingRangeKind::Region) => "region", None => "none", } } fn apply_fold(code: &str, range: &FoldingRange) -> String { let start = range.start_line as usize; let end = range.end_line as usize; let lines: Vec<&str> = code.lines().collect(); let mut output = vec![]; for (index, line) in lines.iter().enumerate() { if index < start || index > end { output.push((*line).to_string()); } else if index == start { output.push(format!("{line} ...")); } } output.join("\n") } fn pretty_folding_output(code: &str, ranges: &[FoldingRange]) -> String { let mut output = format!("----- Code -----\n{code}\n\n----- Ranges -----\n"); if ranges.is_empty() { output.push_str("(none)\n"); return output; } for (index, range) in ranges.iter().enumerate() { let line = format!( "{}. lines {}..{} kind: {}\n", index + 1, range.start_line, range.end_line, kind_name(&range.kind) ); output.push_str(&line); } for (index, range) in ranges.iter().enumerate() { let fold = apply_fold(code, range); let section = format!( "\n----- Fold {} (lines {}..{}) -----\n{fold}\n", index + 1, range.start_line, range.end_line ); output.push_str(§ion); } output } fn folding_snapshot_output(project: TestProject<'_>) -> String { let src = project.src; let ranges = folding_ranges(project); pretty_folding_output(src, &ranges) } macro_rules! assert_folding { ($src:literal $(,)?) => { assert_folding!(TestProject::for_source($src)); }; ($project:expr $(,)?) => {{ let project = $project; let src = project.src; let output = folding_snapshot_output(project); insta::assert_snapshot!(insta::internals::AutoName, output, src); }}; } macro_rules! assert_no_folding { ($src:literal $(,)?) => { assert_no_folding!(TestProject::for_source($src)); }; ($project:expr $(,)?) => {{ let ranges = folding_ranges($project); assert!( ranges.is_empty(), "Expected no folding ranges, got: {ranges:#?}" ); }}; } #[test] fn folds_multiline_function_body() { assert_folding!( "pub fn main() { let x = 1 x }" ); } #[test] fn does_not_fold_single_line_function_body() { assert_no_folding!("pub fn main() { 1 }"); } #[test] fn folds_import_block() { let code = "import one import two import three pub fn main() { 1 }"; assert_folding!( TestProject::for_source(code) .add_dep_module("one", "pub fn value() { 1 }") .add_dep_module("two", "pub fn value() { 2 }") .add_dep_module("three", "pub fn value() { 3 }"), ); } #[test] fn folds_separated_import_blocks() { let code = "import one import two import three import four pub fn main() { 1 }"; assert_folding!( TestProject::for_source(code) .add_dep_module("one", "pub fn value() { 1 }") .add_dep_module("two", "pub fn value() { 2 }") .add_dep_module("three", "pub fn value() { 3 }") .add_dep_module("four", "pub fn value() { 4 }"), ); } #[test] fn folds_multiline_custom_type() { assert_folding!( "pub type W { W(Int) }" ); } #[test] fn folds_multiline_constant() { assert_folding!( "pub const xs = [ 1, 2, ]" ); } #[test] fn folds_multiline_type_alias() { assert_folding!( "pub type Pair = #(Int, Int)" ); } #[test] fn does_not_fold_single_line_custom_type() { assert_no_folding!("pub type W { W(Int) }"); } #[test] fn does_not_fold_single_line_constant() { assert_no_folding!("pub const x = 1"); } #[test] fn does_not_fold_single_line_type_alias() { assert_no_folding!("pub type Pair = #(Int, Int)"); } #[test] fn folds_mixed_definitions_in_source_order() { let code = "import one import two pub type W { W(Int) } pub const xs = [ 1, 2, ] pub fn main() { 1 }"; assert_folding!( TestProject::for_source(code) .add_dep_module("one", "pub fn value() { 1 }") .add_dep_module("two", "pub fn value() { 2 }"), ); } #[test] fn folds_only_multiline_functions_in_source_order() { assert_folding!( "pub fn one() { 1 } pub fn two() { 2 } pub fn three() { 3 }" ); } ================================================ FILE: language-server/src/tests/hover.rs ================================================ use lsp_types::{Hover, HoverContents, HoverParams, MarkedString, Position, Range}; use super::*; fn hover(tester: TestProject<'_>, position: Position) -> Option { tester.at(position, |engine, param, _| { let params = HoverParams { text_document_position_params: param, work_done_progress_params: Default::default(), }; let response = engine.hover(params); response.result.unwrap() }) } pub fn show_hover(code: &str, range: Range, position: Position) -> String { let Range { start, end } = range; // When we display the over range the end character is always excluded! let end = Position::new(end.line, end.character); let mut buffer: String = "".into(); for (line_number, line) in code.lines().enumerate() { let mut underline: String = "".into(); let mut underline_empty = true; for (column_number, _) in line.chars().enumerate() { let current_position = Position::new(line_number as u32, column_number as u32); if current_position == position { underline_empty = false; underline.push('↑'); } else if start <= current_position && current_position < end { underline_empty = false; underline.push('▔'); } else { underline.push(' '); } } buffer.push_str(line); if !underline_empty { buffer.push('\n'); buffer.push_str(&underline); } buffer.push('\n'); } buffer } fn pretty_hover_contents(contents: HoverContents) -> String { let (kind, content) = match contents { HoverContents::Scalar(marked_string) => ("markdown", pretty_marked_string(marked_string)), HoverContents::Array(marked_strings) => ( "markdown array", marked_strings .into_iter() .map(pretty_marked_string) .join("\n\n"), ), HoverContents::Markup(lsp_types::MarkupContent { kind, value }) => match kind { lsp_types::MarkupKind::PlainText => ("plaintext", value), lsp_types::MarkupKind::Markdown => ("markdown", value), }, }; format!("----- Hover content ({kind}) -----\n{content}") } fn pretty_marked_string(marked_string: MarkedString) -> String { match marked_string { MarkedString::String(string) => string, MarkedString::LanguageString(lsp_types::LanguageString { language, value }) => { format!("```{language}\n{value}\n```") } } } #[macro_export] macro_rules! assert_hover { ($code:literal, $position:expr $(,)?) => { let project = TestProject::for_source($code); assert_hover!(project, $position); }; ($project:expr, $position:expr $(,)?) => { let src = $project.src; let position = $position.find_position(src); let result = hover($project, position).expect("no hover produced"); let pretty_hover = show_hover(src, result.range.expect("hover with no range"), position); let output = format!( "{}\n\n{}", pretty_hover, pretty_hover_contents(result.contents) ); insta::assert_snapshot!(insta::internals::AutoName, output, src); }; } #[test] fn hover_function_definition() { assert_hover!( " fn add_2(x) { x + 2 } ", find_position_of("add_2") ); } #[test] fn hover_local_function() { assert_hover!( " fn my_fn() { Nil } fn main() { my_fn } ", find_position_of("my_fn").under_char('y').nth_occurrence(2) ); } // https://github.com/gleam-lang/gleam/issues/2654 #[test] fn hover_local_function_in_pipe() { assert_hover!( " fn add1(num: Int) -> Int { num + 1 } pub fn main() { add1(1) 1 |> add1 |> add1 |> add1 } ", find_position_of("add1") .with_char_offset(1) .nth_occurrence(2) ); } // https://github.com/gleam-lang/gleam/issues/2654 #[test] fn hover_local_function_in_pipe_1() { assert_hover!( " fn add1(num: Int) -> Int { num + 1 } pub fn main() { add1(1) 1 |> add1 |> add1 |> add1 } ", find_position_of("add1") .with_char_offset(2) .nth_occurrence(3) ); } // https://github.com/gleam-lang/gleam/issues/2654 #[test] fn hover_local_function_in_pipe_2() { assert_hover!( " fn add1(num: Int) -> Int { num + 1 } pub fn main() { add1(1) 1 |> add1 |> add1 |> add1 } ", find_position_of("add1") .with_char_offset(2) .nth_occurrence(4) ); } // https://github.com/gleam-lang/gleam/issues/2654 #[test] fn hover_local_function_in_pipe_3() { assert_hover!( " fn add1(num: Int) -> Int { num + 1 } pub fn main() { add1(1) 1 |> add1 |> add1 |> add1 } ", find_position_of("add1") .with_char_offset(2) .nth_occurrence(5) ); } #[test] fn hover_imported_function() { let code = " import example_module fn main() { example_module.my_fn } "; assert_hover!( TestProject::for_source(code).add_module("example_module", "pub fn my_fn() { Nil }"), find_position_of("my_fn").under_char('_'), ); } #[test] fn hover_external_imported_function() { let code = " import example_module fn main() { example_module.my_fn } "; assert_hover!( TestProject::for_source(code).add_hex_module("example_module", "pub fn my_fn() { Nil }"), find_position_of("my_fn").under_char('_'), ); } #[test] fn hover_external_imported_unqualified_function() { let code = " import example_module.{my_fn} fn main() { my_fn } "; assert_hover!( TestProject::for_source(code).add_hex_module("example_module", "pub fn my_fn() { Nil }"), find_position_of("my_fn").under_char('f').nth_occurrence(2), ); } #[test] fn hover_external_imported_function_renamed_module() { let code = " import example_module as renamed_module fn main() { renamed_module.my_fn } "; assert_hover!( TestProject::for_source(code).add_hex_module("example_module", "pub fn my_fn() { Nil }"), find_position_of("my_fn").under_char('f'), ); } #[test] fn hover_external_unqualified_imported_function_renamed_module() { let code = " import example_module.{my_fn} as renamed_module fn main() { my_fn } "; assert_hover!( TestProject::for_source(code).add_hex_module("example_module", "pub fn my_fn() { Nil }"), find_position_of("my_fn").under_char('_').nth_occurrence(2), ); } #[test] fn hover_external_imported_function_nested_module() { // Example of HexDocs link with nested modules: https://hexdocs.pm/lustre/lustre/element/svg.html let code = " import my/nested/example_module fn main() { example_module.my_fn } "; assert_hover!( TestProject::for_source(code) .add_hex_module("my/nested/example_module", "pub fn my_fn() { Nil }"), find_position_of("my_fn").under_char('f'), ); } #[test] fn hover_external_imported_ffi_renamed_function() { let code = r#" import example_module fn main() { example_module.my_fn } "#; let hex_module = r#" @external(erlang, "my_mod_ffi", "renamed_fn") pub fn my_fn() -> Nil "#; assert_hover!( TestProject::for_source(code).add_hex_module("example_module", hex_module,), find_position_of("my_fn").under_char('f'), ); } #[test] fn hover_external_imported_constants() { let code = " import example_module fn main() { example_module.my_const } "; assert_hover!( TestProject::for_source(code).add_hex_module("example_module", "pub const my_const = 42"), find_position_of("my_const").under_char('_'), ); } #[test] fn hover_external_imported_unqualified_constants() { let code = " import example_module.{my_const} fn main() { my_const } "; assert_hover!( TestProject::for_source(code).add_hex_module("example_module", "pub const my_const = 42"), find_position_of("my_const") .under_char('c') .nth_occurrence(2), ); } #[test] fn hover_external_value_with_two_modules_same_name() { let code = " import a/example_module as _ import b/example_module fn main() { example_module.my_const } "; assert_hover!( TestProject::for_source(code) .add_hex_module("a/example_module", "pub const my_const = 42") .add_hex_module("b/example_module", "pub const my_const = 42"), find_position_of("my_const").under_char('c'), ); } #[test] fn hover_external_function_with_another_value_same_name() { let code = " import a/example_module.{my_const as discarded} import b/example_module.{my_const} as _ fn main() { my_const } "; assert_hover!( TestProject::for_source(code) .add_hex_module("a/example_module", "pub const my_const = 42") .add_hex_module("b/example_module", "pub const my_const = 42"), find_position_of("my_const") .under_char('o') .nth_occurrence(3), ); } #[test] fn hover_function_definition_with_docs() { assert_hover!( " /// Exciting documentation /// Maybe even multiple lines fn append(x, y) { x <> y } ", find_position_of("append") ); } #[test] fn hover_function_argument() { assert_hover!( " /// Exciting documentation /// Maybe even multiple lines fn append(x, y) { x <> y } ", find_position_of("append(x, y)").under_char('x') ); } #[test] fn hover_function_body() { let code = " /// Exciting documentation /// Maybe even multiple lines fn append(x, y) { x <> y } "; assert_eq!( hover(TestProject::for_source(code), Position::new(4, 1)), None ); } #[test] fn hover_expressions_in_function_body() { assert_hover!( " fn append(x, y) { x <> y } ", find_position_of("x").nth_occurrence(2) ); } #[test] fn hover_module_constant() { assert_hover!( " /// Exciting documentation /// Maybe even multiple lines const one = 1 ", find_position_of("one") ); } #[test] fn hover_variable_in_use_expression() { assert_hover!( " fn b(fun: fn(Int) -> String) { fun(42) } fn do_stuff() { let c = \"done\" use a <- b c } ", find_position_of("use a").under_last_char() ); } #[test] fn hover_variable_in_use_expression_1() { assert_hover!( " fn b(fun: fn(Int) -> String) { fun(42) } fn do_stuff() { let c = \"done\" use a <- b c } ", find_position_of("b").nth_occurrence(2) ); } #[test] fn hover_variable_in_use_expression_2() { assert_hover!( " fn b(fun: fn(Int) -> String) { fun(42) } fn do_stuff() { let c = \"done\" use a <- b c } ", find_position_of("c").nth_occurrence(2) ); } #[test] fn hover_function_arg_annotation_2() { assert_hover!( " /// Exciting documentation /// Maybe even multiple lines fn append(x: String, y: String) -> String { x <> y } ", find_position_of("String").under_char('n') ); } #[test] fn hover_function_return_annotation() { assert_hover!( " /// Exciting documentation /// Maybe even multiple lines fn append(x: String, y: String) -> String { x <> y } ", find_position_of("String").under_char('n').nth_occurrence(3) ); } #[test] fn hover_function_return_annotation_with_tuple() { assert_hover!( " /// Exciting documentation /// Maybe even multiple lines fn append(x: String, y: String) -> #(String, String) { #(x, y) } ", find_position_of("String").under_char('r').nth_occurrence(3) ); } #[test] fn hover_module_constant_annotation() { assert_hover!( " /// Exciting documentation /// Maybe even multiple lines const one: Int = 1 ", find_position_of("Int").under_last_char() ); } #[test] fn hover_type_constructor_annotation() { assert_hover!( " type Wibble { Wibble(arg: String) } ", find_position_of("String").under_char('n') ); } #[test] fn hover_type_alias_annotation() { assert_hover!("type Wibble = Int", find_position_of("Int").under_char('n')); } #[test] fn hover_assignment_annotation() { assert_hover!( " fn wibble() { let wobble: Int = 7 wobble } ", find_position_of("Int").under_last_char() ); } #[test] fn hover_function_arg_annotation_with_documentation() { assert_hover!( " /// Exciting documentation /// Maybe even multiple lines type Wibble { Wibble(arg: String) } fn identity(x: Wibble) -> Wibble { x } ", find_position_of("Wibble") .under_last_char() .nth_occurrence(3) ); } #[test] fn hover_import_unqualified_value() { let code = " import example_module.{my_num} fn main() { my_num } "; assert_hover!( TestProject::for_source(code).add_module( "example_module", " /// Exciting documentation /// Maybe even multiple lines pub const my_num = 1" ), find_position_of("my_num").under_char('n') ); } #[test] fn hover_import_unqualified_value_from_hex() { let code = " import example_module.{my_num} fn main() { my_num } "; assert_hover!( TestProject::for_source(code).add_hex_module( "example_module", " /// Exciting documentation /// Maybe even multiple lines pub const my_num = 1" ), find_position_of("my_num").under_char('n') ); } #[test] fn hover_import_unqualified_type() { let code = " import example_module.{type MyType, MyType} fn main() -> MyType { MyType } "; assert_hover!( TestProject::for_source(code).add_module( "example_module", " /// Exciting documentation /// Maybe even multiple lines pub type MyType { MyType }" ), find_position_of("MyType").under_last_char() ); } #[test] fn hover_works_even_for_invalid_code() { assert_hover!( " fn invalid() { 1 + Nil } fn valid() { Nil } ", find_position_of("fn valid").under_char('v') ); } #[test] fn hover_for_pattern_spread_ignoring_all_fields() { assert_hover!( " pub type Model { Model( Int, Float, label1: Int, label2: String, ) } pub fn main() { case todo { Model(..) -> todo } } ", find_position_of("..") ); } #[test] fn hover_for_pattern_spread_ignoring_some_fields() { assert_hover!( " pub type Model { Model( Int, Float, label1: Int, label2: String, ) } pub fn main() { case todo { Model(_, label1: _, ..) -> todo } } ", find_position_of("..").under_last_char() ); } #[test] fn hover_for_pattern_spread_ignoring_all_positional_fields() { assert_hover!( " pub type Model { Model( Int, Float, label1: Int, label2: String, ) } pub fn main() { case todo { Model(_, _, _, ..) -> todo } } ", find_position_of("..") ); } #[test] fn hover_label_shorthand_in_call_arg() { assert_hover!( " fn wibble(arg1 arg1: Int, arg2 arg2: Bool) { Nil } fn main() { let arg1 = 1 let arg2 = True wibble(arg2:, arg1:) } ", find_position_of("arg2:").nth_occurrence(2) ); } #[test] fn hover_label_shorthand_in_pattern_call_arg() { assert_hover!( " pub type Wibble { Wibble(arg1: Int, arg2: Bool) } pub fn main() { case todo { Wibble(arg2:, ..) -> todo } } ", find_position_of("arg2:") .nth_occurrence(2) .under_last_char() ); } #[test] fn hover_label_shorthand_in_pattern_call_arg_2() { assert_hover!( " pub type Wibble { Wibble(arg1: Int, arg2: Bool) } pub fn main() { let Wibble(arg2:, ..) = todo } ", find_position_of("arg2:").nth_occurrence(2).under_char('r') ); } #[test] fn hover_contextual_type() { let code = " import wibble/wobble const value = wobble.Wobble "; assert_hover!( TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wobble }"), find_position_of("value").under_char('v') ); } #[test] fn hover_contextual_type_aliased_module() { let code = " import wibble/wobble as wubble const value = wubble.Wobble "; assert_hover!( TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wobble }"), find_position_of("value").under_char('v') ); } #[test] fn hover_contextual_type_unqualified() { let code = " import wibble/wobble.{type Wibble} const value = wobble.Wobble "; assert_hover!( TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wobble }"), find_position_of("value").under_char('v') ); } #[test] fn hover_contextual_type_unqualified_aliased() { let code = " import wibble/wobble.{type Wibble as Wobble} const value = wobble.Wobble "; assert_hover!( TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wobble }"), find_position_of("value").under_char('v') ); } #[test] fn hover_contextual_type_aliased() { let code = " import wibble/wobble type Local = wobble.Wibble const value = wobble.Wobble "; assert_hover!( TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wobble }"), find_position_of("value").under_char('v') ); } #[test] fn hover_contextual_type_function() { let code = " import wibble/wobble type MyInt = Int fn func(value: wobble.Wibble) -> MyInt { 1 } "; assert_hover!( TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wobble }"), find_position_of("func").under_char('f') ); } #[test] fn hover_contextual_type_unqualified_import() { let code = " import wibble/wobble.{type Wibble as Wobble, Wobble} "; assert_hover!( TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wobble }"), find_position_of("Wobble}").under_char('W') ); } #[test] fn hover_contextual_type_pattern() { let code = " import wibble/wobble.{Wibble, Wobble, Wubble} pub fn cycle(wibble: wobble.Wibble) { case wibble { Wibble -> Wobble Wobble -> Wubble Wubble -> Wibble } } "; assert_hover!( TestProject::for_source(code) .add_hex_module("wibble/wobble", "pub type Wibble { Wibble Wobble Wubble }"), find_position_of("Wubble ->").under_char('u') ); } #[test] fn hover_contextual_type_pattern_spread() { let code = " import wibble/wobble.{type Wibble as Wobble} type Thing { Thing(id: Int, value: Wobble) } pub fn main(thing: Thing) { case thing { Thing(id: 0, ..) -> 12 _ -> 14 } } "; assert_hover!( TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wibble }"), find_position_of("..").under_char('.') ); } #[test] fn hover_contextual_type_expression() { let code = " import wibble/wobble pub fn main() { let wibble = wobble.Wibble } "; assert_hover!( TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wibble }"), find_position_of(".Wibble").under_char('l') ); } #[test] fn hover_contextual_type_arg() { let code = " import wibble/wobble fn do_things(wibble: wobble.Wibble) { wibble } "; assert_hover!( TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wibble }"), find_position_of("wibble:").under_char('w') ); } #[test] fn hover_print_type_variable_names() { let code = " fn main(value: Result(ok, error)) { let v = value v } "; assert_hover!( TestProject::for_source(code), find_position_of("let v").under_char('v') ); } #[test] fn hover_print_unbound_type_variable_names() { let code = " fn make_ok(value: some_type) { let result = Ok(value) result } "; assert_hover!( TestProject::for_source(code), find_position_of("result =").under_char('s') ); } #[test] fn hover_print_unbound_type_variable_name_without_conflicts() { let code = " fn make_ok(value: a) { let result = Ok(value) result } "; assert_hover!( TestProject::for_source(code), find_position_of("result =").under_char('s') ); } #[test] fn hover_print_imported_alias() { let code = " import aliases.{type Aliased} const thing: Aliased = 10 "; assert_hover!( TestProject::for_source(code).add_hex_module("aliases", "pub type Aliased = Int"), find_position_of("thing").under_char('g') ); } #[test] fn hover_prelude_type() { let code = " const number = 100 "; assert_hover!( TestProject::for_source(code), find_position_of("number").under_char('b') ); } #[test] fn hover_shadowed_prelude_type() { let code = " type Int { Int } const number = 100 "; assert_hover!( TestProject::for_source(code), find_position_of("number").under_char('b') ); } #[test] fn hover_shadowed_prelude_type_imported() { let code = " import numbers.{type Int} const number = 100 "; assert_hover!( TestProject::for_source(code).add_hex_module("numbers", "pub type Int"), find_position_of("number =").under_char('b') ); } #[test] fn hover_contextual_type_annotation() { let code = " import wibble/wobble fn make_wibble() -> wobble.Wibble { wobble.Wibble } "; assert_hover!( TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wibble }"), find_position_of("-> wobble.Wibble").under_char('i') ); } #[test] fn hover_contextual_type_annotation_prelude() { let code = " fn add_one(a: Int) -> Int { a + 1 } "; assert_hover!( TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wibble }"), find_position_of("-> Int").under_char('I') ); } #[test] fn hover_contextual_type_annotation_unqualified() { let code = " import wibble/wobble.{type Wibble} fn main(wibble: Wibble) { wibble } "; assert_hover!( TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wibble }"), find_position_of(": Wibble").under_char('W') ); } #[test] fn hover_contextual_type_annotation_unqualified_aliased() { let code = " import wibble/wobble.{type Wibble as Wubble} fn main(wibble: Wubble) { wibble } "; assert_hover!( TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wibble }"), find_position_of(": Wubble").under_char('W') ); } #[test] fn hover_contextual_type_annotation_aliased_module() { let code = " import wibble/wobble as wubble fn main(wibble: wubble.Wibble) { wibble } "; assert_hover!( TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wibble }"), find_position_of(": wubble.Wibble").under_char('W') ); } #[test] fn hover_contextual_type_annotation_aliased() { let code = " import wibble/wobble type Wubble = wobble.Wibble fn main(wibble: Wubble) { wibble } "; assert_hover!( TestProject::for_source(code).add_hex_module("wibble/wobble", "pub type Wibble { Wibble }"), find_position_of(": Wubble").under_char('e') ); } #[test] fn hover_print_underlying_for_alias_with_parameters() { let code = " type LocalResult = Result(String, Int) fn do_thing() -> LocalResult { Error(1) } "; assert_hover!( TestProject::for_source(code), find_position_of("do_thing").under_char('d') ); } #[test] fn hover_print_alias_when_parameters_match() { let code = " type MyResult(a, b) = Result(a, b) fn do_thing() -> MyResult(Int, Int) { Error(1) } "; assert_hover!( TestProject::for_source(code), find_position_of("do_thing").under_char('d') ); } #[test] fn hover_print_underlying_for_imported_alias() { let code = " import alias.{type A} fn wibble() -> Result(Int, String) { todo } "; assert_hover!( TestProject::for_source(code).add_hex_module("alias", "pub type A = Result(Int, String)"), find_position_of("wibble").under_char('l') ); } #[test] fn hover_print_aliased_imported_generic_type() { let code = " import gleam/option.{type Option as Maybe} const none: Maybe(Int) = option.None "; assert_hover!( TestProject::for_source(code) .add_hex_module("gleam/option", "pub type Option(a) { None Some(a) }"), find_position_of("none").under_char('e') ); } #[test] fn hover_print_qualified_prelude_type_when_shadowed_by_alias() { let code = " type Result = #(Bool, String) const ok = Ok(10) "; assert_hover!( TestProject::for_source(code), find_position_of("ok").under_char('k') ); } #[test] fn hover_print_qualified_prelude_type_when_shadowed_by_imported_alias() { let code = " import alias.{type Bool} const value = True "; assert_hover!( TestProject::for_source(code).add_hex_module("alias", "pub type Bool = #(Int, Int)"), find_position_of("value").under_char('v') ); } // https://github.com/gleam-lang/gleam/issues/3761 #[test] fn hover_over_block_in_list_spread() { let code = " pub fn main() { [1, 2, ..{ let x = 1 [x] }] } "; assert_hover!(TestProject::for_source(code), find_position_of("x")); } // https://github.com/gleam-lang/gleam/issues/3758 #[test] fn hover_for_anonymous_function_annotation() { let code = " /// An example type. pub type Wibble pub fn main() { fn(w: Wibble) { todo } } "; assert_hover!( TestProject::for_source(code), find_position_of("w: Wibble").under_char('b') ); } #[test] fn hover_for_label_in_pattern() { let code = " type Wibble { Wibble(wibble: Int, wobble: Int) } pub fn main() { let Wibble(wibble: _, wobble: _) = todo todo } "; assert_hover!( TestProject::for_source(code), find_position_of("wibble: _").under_char('l') ); } #[test] fn hover_for_label_in_expression() { let code = " fn add(wibble a, wobble b) { a + b } pub fn main() { add(wibble: 1, wobble: 2) } "; assert_hover!( TestProject::for_source(code), find_position_of("wibble:").under_char('i') ); } #[test] fn hover_for_pattern_in_use() { let code = " type Wibble { Wibble(Int, Float) } pub fn main() { use Wibble(int, float) <- todo todo } "; assert_hover!( TestProject::for_source(code), find_position_of("int").under_char('i') ); } #[test] fn hover_for_annotation_in_use() { let code = " pub fn main() { use something: Int <- todo todo } "; assert_hover!( TestProject::for_source(code), find_position_of("Int").under_char('n') ); } #[test] fn hover_on_pipe_with_invalid_step() { assert_hover!( " pub fn main() { [1, 2, 3] |> map(wibble) |> filter(fn(value) { value }) } fn map(list: List(a), fun: fn(a) -> b) -> List(b) {} fn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) {} ", find_position_of("[") ); } #[test] fn hover_on_pipe_with_invalid_step_1() { assert_hover!( " pub fn main() { [1, 2, 3] |> map(wibble) |> filter(fn(value) { value }) } fn map(list: List(a), fun: fn(a) -> b) -> List(b) {} fn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) {} ", find_position_of("1") ); } #[test] fn hover_on_pipe_with_invalid_step_2() { assert_hover!( " pub fn main() { [1, 2, 3] |> map(wibble) |> filter(fn(value) { value }) } fn map(list: List(a), fun: fn(a) -> b) -> List(b) {} fn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) {} ", find_position_of("map") ); } #[test] fn hover_on_pipe_with_invalid_step_3() { assert_hover!( " pub fn main() { [1, 2, 3] |> map(wibble) |> filter(fn(value) { value }) } fn map(list: List(a), fun: fn(a) -> b) -> List(b) {} fn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) {} ", find_position_of("wibble") ); } #[test] fn hover_on_pipe_with_invalid_step_4() { assert_hover!( " pub fn main() { [1, 2, 3] |> map(wibble) |> filter(fn(value) { value }) } fn map(list: List(a), fun: fn(a) -> b) -> List(b) {} fn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) {} ", find_position_of("filter") ); } #[test] fn hover_on_pipe_with_invalid_step_5() { assert_hover!( " pub fn main() { [1, 2, 3] |> map(wibble) |> filter(fn(value) { value }) } fn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo } fn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) { todo } ", find_position_of("fn(value)") ); } #[test] fn hover_on_pipe_with_invalid_step_6() { assert_hover!( " pub fn main() { [1, 2, 3] |> wibble |> filter(fn(value) { value }) } fn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) { todo } ", find_position_of("wibble") ); } #[test] fn hover_on_pipe_with_invalid_step_8() { assert_hover!( " pub fn main() { [1, 2, 3] |> wibble |> filter(fn(value) { value }) } fn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) { todo } ", find_position_of("fn(value)") ); } #[test] fn hover_over_module_name() { let src = " import wibble pub fn main() { wibble.wibble() } "; assert_hover!( TestProject::for_source(src).add_hex_module( "wibble", " //// This is the wibble module. //// Here is some documentation about it. //// This module does stuff pub fn wibble() { todo } " ), find_position_of("wibble.") ); } #[test] fn hover_over_module_with_path() { let src = " import wibble/wobble pub fn main() { wobble.wibble() } "; assert_hover!( TestProject::for_source(src).add_hex_module( "wibble/wobble", " //// The module documentation pub fn wibble() { todo } " ), find_position_of("wobble.") ); } #[test] fn hover_over_module_name_in_annotation() { let src = " import wibble pub fn main(w: wibble.Wibble) { todo } "; assert_hover!( TestProject::for_source(src).add_hex_module( "wibble", " //// This is the wibble module. //// Here is some documentation about it. //// This module does stuff pub type Wibble " ), find_position_of("wibble.") ); } #[test] fn hover_over_imported_module() { let src = " import wibble "; assert_hover!( TestProject::for_source(src).add_hex_module( "wibble", " //// This is the wibble module. //// Here is some documentation about it. //// This module does stuff " ), find_position_of("wibble") ); } #[test] fn no_hexdocs_link_when_hovering_over_local_module() { let src = " import wibble "; assert_hover!( TestProject::for_source(src).add_module( "wibble", " //// This is the wibble module. //// Here is some documentation about it. //// This module does stuff " ), find_position_of("wibble") ); } #[test] fn hover_for_constant_int() { assert_hover!( " const ten = 10 ", find_position_of("10") ); } #[test] fn hover_for_constant_float() { assert_hover!( " const pi = 3.14 ", find_position_of("3.14") ); } #[test] fn hover_for_constant_string() { assert_hover!( r#" const message = "Hello!" "#, find_position_of("!") ); } #[test] fn hover_for_constant_other_constant() { assert_hover!( " const constant1 = 10 const constant2 = constant1 ", find_position_of("= constant1").under_char('s') ); } #[test] fn hover_for_constant_record() { assert_hover!( " type Wibble { Wibble(Int) } const w = Wibble(10) ", find_position_of("Wibble(10)").under_char('i') ); } #[test] fn hover_for_constant_tuple() { assert_hover!( " const tuple = #(1, 3.5, False) ", find_position_of("#(") ); } #[test] fn hover_for_constant_tuple_element() { assert_hover!( " const tuple = #(1, 3.5, False) ", find_position_of("False") ); } #[test] fn hover_for_constant_list() { assert_hover!( " const numbers = [2, 4, 6, 8] ", find_position_of("[") ); } #[test] fn hover_for_constant_list_element() { assert_hover!( " const numbers = [2, 4, 6, 8] ", find_position_of("4") ); } #[test] fn hover_for_constant_string_concatenation() { assert_hover!( r#" const name = "Bob" const message = "Hello " <> name "#, find_position_of("<>") ); } #[test] fn hover_for_constant_string_concatenation_side() { assert_hover!( r#" const name = "Bob" const message = "Hello " <> name "#, find_position_of("<> name").under_char('n') ); } #[test] fn hover_for_constant_bit_array() { assert_hover!( " const bits = <<1:2, 3:4>> ", find_position_of(",") ); } #[test] fn hover_for_constant_bit_array_segment() { assert_hover!( " const bits = <<1:2, 3:4>> ", find_position_of("1") ); } #[test] fn hover_for_constant_bit_array_segment_option() { assert_hover!( " const bits = <<1:size(2), 3:4>> ", find_position_of("2") ); } #[test] fn hover_for_nested_constant() { assert_hover!( " type Wibble { Wibble Wobble(BitArray) } const value = #(1, 2, [Wibble, Wobble(<<1, 2, 3>>), Wibble]) ", find_position_of("3") ); } #[test] fn record_field_documentation() { assert_hover!( " pub type Wibble { Wibble( /// This is some documentation about the wibble field. wibble: Int ) } pub fn wibble(w: Wibble) { w.wibble } ", find_position_of("w.wibble").under_char('l') ); } #[test] fn no_documentation_for_shared_record_field() { assert_hover!( " pub type Wibble { Wibble( /// This is some documentation about the wibble field. wibble: Int ) Wobble( /// Here's some documentation explaining a field of Wobble wibble: Int ) } pub fn wibble(w: Wibble) { w.wibble } ", find_position_of("w.wibble").under_char('l') ); } #[test] fn documentation_for_shared_record_field_when_variant_is_known() { assert_hover!( " pub type Wibble { Wibble( /// This is some documentation about the wibble field. wibble: Int ) Wobble( /// This won't show up because it's a Wibble variant wibble: Int ) } pub fn wibble(w: Wibble) { let assert Wibble(..) = w w.wibble } ", find_position_of("w.wibble").under_char('l') ); } #[test] fn hover_for_string_prefix_pattern() { assert_hover!( " pub fn main() { case \"wibble\" { \"wib\" as alias <> suffix -> alias <> suffix other -> other } } ", find_position_of("<>") ); } #[test] fn hover_for_string_prefix_pattern_prefix_alias() { assert_hover!( " pub fn main() { case \"wibble\" { \"wib\" as alias <> suffix -> alias <> suffix other -> other } } ", find_position_of("alias").nth_occurrence(1) ); } #[test] fn hover_for_string_prefix_pattern_suffix_variable() { assert_hover!( " pub fn main() { case \"wibble\" { \"wib\" as alias <> suffix -> alias <> suffix other -> other } } ", find_position_of("suffix").nth_occurrence(1) ); } #[test] fn hover_for_string_prefix_pattern_prefix_alias_alternative_definition() { assert_hover!( " pub fn main() { case \"wibble\" { \"wib\" as prefix <> rest | \"wob\" as prefix <> rest -> prefix <> rest other -> other } } ", find_position_of("prefix").nth_occurrence(2) ); } #[test] fn hover_for_string_prefix_pattern_suffix_variable_alternative_definition() { assert_hover!( " pub fn main() { case \"wibble\" { \"wib\" <> rest | \"wob\" <> rest -> rest other -> other } } ", find_position_of("rest").nth_occurrence(2) ); } #[test] fn hover_for_string_prefix_pattern_suffix_variable_discard() { assert_hover!( " pub fn main() { case \"wibble\" { \"wib\" <> _discard -> Nil other -> Nil } } ", find_position_of("_discard") ); } #[test] fn hover_for_custom_type() { assert_hover!( "/// Exciting documentation /// Maybe even multiple lines type Wibble { /// Some more exciting documentation Wibble(String) /// The most exciting documentation Wobble(arg: Int) }", find_position_of("Wibble") ); } #[test] fn hover_type_constructor_with_no_fields() { assert_hover!( " /// Exciting documentation /// Maybe even multiple lines type Wibble { /// Some more exciting documentation Wibble } ", find_position_of("Wibble").nth_occurrence(2) ); } #[test] fn hover_type_constructor_with_label() { assert_hover!( " /// Exciting documentation /// Maybe even multiple lines type Wibble { /// Some more exciting documentation Wibble(arg: String) /// The most exciting documentation Wobble(Int) } ", find_position_of("Wibble").nth_occurrence(2) ); } #[test] fn hover_type_constructor_with_no_label() { assert_hover!( " /// Exciting documentation /// Maybe even multiple lines type Wibble { /// Some more exciting documentation Wibble(arg: String) /// The most exciting documentation Wobble(Int) } ", find_position_of("Wobble") ); } #[test] fn hover_for_local_variable_from_guard() { assert_hover!( " pub fn main() { let wibble = True let wobble = False case wibble { True if wobble -> !wibble False if !wobble -> wibble _ -> wobble } } ", find_position_of("wobble").nth_occurrence(2).under_char('o') ); } #[test] fn hover_for_record_from_guard() { assert_hover!( " type Wibble { Wibble Wobble } pub fn main() { let wibble = True let wobble = Wibble case wibble { True if wobble == Wibble -> !wibble False if wobble == Wobble -> wibble _ -> Wibble } } ", find_position_of("== Wibble").under_char('l') ); } #[test] fn hover_for_module_select_from_guard() { let src = " import mod pub fn main() { let wibble = True case wibble { True if mod.wibble < 5 -> !wibble False if mod.wibble != 10 -> wibble _ -> mod.wibble + 1 } } "; assert_hover!( TestProject::for_source(src).add_module("mod", "pub const wibble = 10"), find_position_of("mod.wibble").under_char('w') ); } #[test] fn hover_for_record_module_select_from_guard() { let src = " import mod pub fn main() { let wibble = True let wobble = mod.Wibble case wibble { True if wobble == mod.Wobble -> !wibble False if wobble == mod.Wibble -> wibble _ -> mod.Wibble } } "; assert_hover!( TestProject::for_source(src).add_module("mod", "pub type Wobble { Wibble Wobble }"), find_position_of("== mod.Wibble").under_char('i') ); } #[test] fn hover_for_module_select_pattern() { let src = " import mod pub fn go(x: mod.Wibble) { case x { mod.Wibble -> 1 } } "; assert_hover!( TestProject::for_source(src).add_module("mod", "pub type Wibble { Wibble }"), find_position_of("mod.Wibble") .under_char('W') .nth_occurrence(2) ); } #[test] fn hover_for_invalid_record_update_1() { assert_hover!( " type Wibble { Wibble(a: Int, b: Bool) } pub fn go(wibble: Wibble) { Wibble(..wibble, a: True, b: 1) } ", find_position_of("True") ); } #[test] fn hover_for_invalid_record_update_2() { assert_hover!( " type Wibble { Wibble(a: Int, b: Bool) } pub fn go(wibble: Wibble) { Wibble(..wibble, a: True, b: 1) } ", find_position_of("1") ); } #[test] fn hover_for_invalid_record_update_3() { assert_hover!( " type Wibble { Wibble(a: Int, b: Bool) } pub fn go(wibble: Wibble) { Wibble(..wibble, a: True, b: 1) } ", find_position_of("Wibble").nth_occurrence(4) ); } ================================================ FILE: language-server/src/tests/reference.rs ================================================ use std::collections::HashMap; use lsp_types::{ PartialResultParams, Position, Range, ReferenceContext, ReferenceParams, TextDocumentPositionParams, WorkDoneProgressParams, }; use super::{TestProject, find_position_of}; fn find_references( tester: &TestProject<'_>, position: Position, ) -> Option>> { let locations = tester.at(position, |engine, params, _| { let params = ReferenceParams { text_document_position: TextDocumentPositionParams { text_document: params.text_document, position, }, work_done_progress_params: WorkDoneProgressParams::default(), partial_result_params: PartialResultParams::default(), context: ReferenceContext { include_declaration: true, }, }; engine.find_references(params).result.unwrap() })?; let mut references: HashMap> = HashMap::new(); for location in locations { let module_name = tester .module_name_from_url(&location.uri) .expect("Valid uri"); _ = references .entry(module_name) .or_default() .push(location.range); } Some(references) } fn show_references(code: &str, position: Option, ranges: &[Range]) -> String { let mut buffer = String::new(); for (line_number, line) in code.lines().enumerate() { let mut underline = String::new(); let mut underline_empty = true; let line_number = line_number as u32; for (column_number, _) in line.chars().enumerate() { let current_position = Position::new(line_number, column_number as u32); // Check if any range covers this specific character let is_in_range = ranges .iter() .any(|range| range.start <= current_position && current_position < range.end); if Some(current_position) == position { underline_empty = false; underline.push('↑'); } else if is_in_range { underline_empty = false; underline.push('▔'); } else { underline.push(' '); } } buffer.push_str(line); if !underline_empty { buffer.push('\n'); buffer.push_str(&underline); } buffer.push('\n'); } buffer } macro_rules! assert_references { ($code:literal, $position:expr $(,)?) => { assert_references!(TestProject::for_source($code), $position); }; (($module_name:literal, $module_src:literal), $code:literal, $position:expr $(,)?) => { assert_references!( TestProject::for_source($code).add_module($module_name, $module_src), $position ); }; ($project:expr, $position:expr $(,)?) => { let project = $project; let src = project.src; let position = $position.find_position(src); let result = find_references(&project, position).expect("References not found"); let mut output = String::new(); for (name, src) in project.root_package_modules.iter() { output.push_str(&format!( "-- {name}.gleam\n{}\n\n", show_references(src, None, result.get(*name).unwrap_or(&Vec::new())) )); } output.push_str(&format!( "-- app.gleam\n{}", show_references( src, Some(position), result.get("app").unwrap_or(&Vec::new()) ) )); insta::assert_snapshot!(insta::internals::AutoName, output, src); }; } macro_rules! assert_no_references { ($code:literal, $position:expr $(,)?) => { let project = TestProject::for_source($code); assert_no_references!(&project, $position); }; ($project:expr, $position:expr $(,)?) => { let src = $project.src; let position = $position.find_position(src); let result = find_references($project, position); assert_eq!(result, None); }; } #[test] fn references_for_local_variable() { assert_references!( " pub fn main() { let wibble = 10 let wobble = wibble + 1 wibble + wobble } ", find_position_of("wibble").nth_occurrence(2), ); } #[test] fn references_for_local_variable_from_definition() { assert_references!( " pub fn main() { let wibble = 10 let wobble = wibble + 1 wibble + wobble } ", find_position_of("wibble"), ); } #[test] fn references_for_private_function() { assert_references!( " fn wibble() { wibble() } pub fn main() { let _ = wibble() wibble() + 4 } fn wobble() { wibble() || wobble() } ", find_position_of("wibble"), ); } #[test] fn references_for_private_function_from_reference() { assert_references!( " fn wibble() { wibble() } pub fn main() { let _ = wibble() wibble() + 4 } fn wobble() { wibble() || wobble() } ", find_position_of("wibble").nth_occurrence(2), ); } #[test] fn references_for_public_function() { assert_references!( ( "mod", " import app.{wibble} fn wobble() { app.wibble() } fn other() { wibble() } " ), " pub fn wibble() { wibble() } ", find_position_of("wibble").nth_occurrence(2), ); } #[test] fn references_for_function_from_qualified_reference() { assert_references!( ( "mod", " pub fn wibble() { wibble() } " ), " import mod pub fn main() { let value = mod.wibble() mod.wibble() value } ", find_position_of("wibble"), ); } #[test] fn references_for_function_from_unqualified_reference() { assert_references!( ( "mod", " pub fn wibble() { wibble() } " ), " import mod.{wibble} pub fn main() { let value = wibble() mod.wibble() value } ", find_position_of("wibble()"), ); } #[test] fn references_for_private_constant() { assert_references!( " const wibble = 10 pub fn main() { let _ = wibble wibble + 4 } fn wobble() { wibble + wobble() } ", find_position_of("wibble"), ); } #[test] fn references_for_private_constant_from_reference() { assert_references!( " const wibble = 10 pub fn main() { let _ = wibble wibble + 4 } fn wobble() { wibble + wobble() } ", find_position_of("wibble").nth_occurrence(2), ); } #[test] fn references_for_public_constant() { assert_references!( ( "mod", " import app.{wibble} fn wobble() { app.wibble } fn other() { wibble } " ), " pub const wibble = 10 pub fn main() { wibble } ", find_position_of("wibble").nth_occurrence(2), ); } #[test] fn references_for_constant_from_qualified_reference() { assert_references!( ( "mod", " pub const wibble = 10 fn wobble() { wibble } " ), " import mod pub fn main() { let value = mod.wibble mod.wibble + value } ", find_position_of("wibble"), ); } #[test] fn references_for_constant_from_unqualified_reference() { assert_references!( ( "mod", " pub const wibble = 10 fn wobble() { wibble } " ), " import mod.{wibble} pub fn main() { let value = mod.wibble wibble + value } ", find_position_of("wibble +"), ); } #[test] fn references_for_private_type_variant() { assert_references!( " type Wibble { Wibble } fn main() { let _ = Wibble Wibble } fn wobble() { Wibble wobble() } ", find_position_of("Wibble }"), ); } #[test] fn references_for_private_type_variant_from_reference() { assert_references!( " type Wibble { Wibble } fn main() { let _ = Wibble Wibble } fn wobble() { Wibble wobble() } ", find_position_of(" = Wibble").under_char('W'), ); } #[test] fn references_for_public_type_variant() { assert_references!( ( "mod", " import app.{Wibble} fn wobble() { app.Wibble } fn other() { Wibble } " ), " pub type Wibble { Wibble } pub fn main() { Wibble } ", find_position_of("Wibble }"), ); } #[test] fn references_for_type_variant_from_qualified_reference() { assert_references!( ( "mod", " pub type Wibble { Wibble } fn wobble() { Wibble } " ), " import mod pub fn main() { let value = mod.Wibble mod.Wibble value } ", find_position_of("Wibble"), ); } #[test] fn references_for_type_variant_from_unqualified_reference() { assert_references!( ( "mod", " pub type Wibble { Wibble } fn wobble() { Wibble } " ), " import mod.{Wibble} pub fn main() { let value = mod.Wibble Wibble } ", find_position_of("Wibble").nth_occurrence(3), ); } #[test] fn no_references_for_keyword() { assert_no_references!( " pub fn wibble() { todo } ", find_position_of("fn") ); } #[test] fn references_for_aliased_value() { assert_references!( ( "mod", " import app.{Wibble as Wobble} fn wobble() { Wobble } " ), " pub type Wibble { Wibble } pub fn main() { Wibble } ", find_position_of("Wibble").nth_occurrence(2), ); } #[test] fn references_for_aliased_const() { assert_references!( ( "mod", " import app.{wibble as other} fn wobble() { other } " ), " pub const wibble = 123 pub fn main() { wibble } ", find_position_of("wibble").nth_occurrence(2), ); } #[test] fn references_for_aliased_function() { assert_references!( ( "mod", " import app.{wibble as other} fn wobble() { other() } " ), " pub fn wibble() { 123 } pub fn main() { wibble() } ", find_position_of("wibble").nth_occurrence(2), ); } #[test] fn references_for_private_type() { assert_references!( " type Wibble { Wibble } fn main() -> Wibble { todo } fn wobble(w: Wibble) { todo } ", find_position_of("Wibble"), ); } #[test] fn references_for_private_type_from_reference() { assert_references!( " type Wibble { Wibble } fn main() -> Wibble { todo } fn wobble(w: Wibble) { todo } ", find_position_of("-> Wibble").under_char('W'), ); } #[test] fn references_for_public_type() { assert_references!( ( "mod", " import app.{type Wibble} fn wobble() -> Wibble { todo } fn other(w: app.Wibble) { todo } " ), " pub type Wibble { Wibble } pub fn main() -> Wibble { todo } ", find_position_of("Wibble"), ); } #[test] fn references_for_type_from_qualified_reference() { assert_references!( ( "mod", " pub type Wibble { Wibble } fn wobble() -> Wibble { todo } " ), " import mod pub fn main() -> mod.Wibble { let _: mod.Wibble = todo } ", find_position_of("Wibble"), ); } #[test] fn references_for_type_from_unqualified_reference() { assert_references!( ( "mod", " pub type Wibble { Wibble } fn wobble() -> Wibble { todo } " ), " import mod.{type Wibble} pub fn main() -> Wibble { let _: mod.Wibble = todo } ", find_position_of("Wibble").nth_occurrence(2), ); } #[test] fn references_for_aliased_type() { assert_references!( ( "mod", " import app.{type Wibble as Wobble} fn wobble() -> Wobble { todo } fn other(w: app.Wibble) { todo } " ), " pub type Wibble { Wibble } pub fn main() -> Wibble { todo } ", find_position_of("-> Wibble").under_char('W'), ); } #[test] fn references_for_type_from_let_annotation() { assert_references!( ( "mod", " pub type Wibble { Wibble } fn wobble() -> Wibble { todo } " ), " import mod.{type Wibble} pub fn main() -> Wibble { let _: mod.Wibble = todo } ", find_position_of("mod.Wibble").under_char('W'), ); } #[test] fn references_for_prefix_string_suffix_variable_in_case() { assert_references!( " pub fn main() -> String { let wibble = \"1-wibble\" let rest = case wibble { \"1\" <> rest -> rest other -> other } rest } ", find_position_of("rest").nth_occurrence(2) ); } #[test] fn references_for_prefix_string_suffix_variable_in_case_triggered_from_usage() { assert_references!( " pub fn main() -> String { let wibble = \"1-wibble\" let rest = case wibble { \"1\" <> rest -> rest other -> other } rest } ", find_position_of("rest").nth_occurrence(3) ); } #[test] fn references_for_prefix_string_suffix_variable_with_alternative_definition_in_case() { assert_references!( " pub fn main() -> String { let wibble = \"1-wibble\" let rest = case wibble { \"1\" <> rest | \"2\" <> rest -> rest other -> other } rest } ", find_position_of("rest").nth_occurrence(2), ); } #[test] fn references_for_prefix_string_suffix_variable_with_alternative_definition_triggered_from_second_pattern() { assert_references!( " pub fn main() -> String { let wibble = \"1-wibble\" let rest = case wibble { \"1\" <> rest | \"2\" <> rest -> rest other -> other } rest } ", find_position_of("rest").nth_occurrence(3), ); } #[test] fn references_for_prefix_string_suffix_variable_with_alternative_definition_triggered_from_usage() { assert_references!( " pub fn main() -> String { let wibble = \"1-wibble\" let rest = case wibble { \"1\" <> rest | \"2\" <> rest -> rest other -> other } rest } ", find_position_of("rest").nth_occurrence(4), ); } #[test] fn references_for_prefix_string_suffix_variable_in_let_assert() { assert_references!( " pub fn main() -> String { let assert \"1\" <> rest = \"1-wibble\" rest } ", find_position_of("rest").nth_occurrence(1), ); } #[test] fn references_for_prefix_string_suffix_variable_in_let_assert_triggered_from_usage() { assert_references!( " pub fn main() -> String { let assert \"1\" <> rest = \"1-wibble\" rest } ", find_position_of("rest").nth_occurrence(2), ); } #[test] fn references_for_prefix_string_alias_in_case() { assert_references!( " pub fn main() -> String { let wibble = \"1-wibble\" let digit = case wibble { \"1\" as digit <> _rest -> digit other -> other } digit } ", find_position_of("digit").nth_occurrence(2) ); } #[test] fn references_for_prefix_string_alias_in_case_triggered_from_usage() { assert_references!( " pub fn main() -> String { let wibble = \"1-wibble\" let digit = case wibble { \"1\" as digit <> _rest -> digit other -> other } digit } ", find_position_of("digit").nth_occurrence(3) ); } #[test] fn references_for_prefix_string_alias_with_alternative_definitions_in_case() { assert_references!( " pub fn main() -> String { let wibble = \"1-wibble\" let digit = case wibble { \"1\" as digit <> _rest | \"2\" as digit <> _rest -> digit other -> other } digit } ", find_position_of("digit").nth_occurrence(2) ); } #[test] fn references_for_prefix_string_alias_with_alternative_definitions_triggered_from_second_pattern() { assert_references!( " pub fn main() -> String { let wibble = \"1-wibble\" let digit = case wibble { \"1\" as digit <> _rest | \"2\" as digit <> _rest -> digit other -> other } digit } ", find_position_of("digit").nth_occurrence(3) ); } #[test] fn references_for_prefix_string_alias_with_alternative_definitions_triggered_from_usage() { assert_references!( " pub fn main() -> String { let wibble = \"1-wibble\" let digit = case wibble { \"1\" as digit <> _rest | \"2\" as digit <> _rest -> digit other -> other } digit } ", find_position_of("digit").nth_occurrence(4) ); } #[test] fn references_for_prefix_string_alias_in_let_assert() { assert_references!( " pub fn main() -> String { let assert \"1\" as digit <> _rest = \"1-wibble\" digit } ", find_position_of("digit").nth_occurrence(1) ); } #[test] fn references_for_prefix_string_alias_in_let_assert_triggered_from_usage() { assert_references!( " pub fn main() -> String { let assert \"1\" as digit <> _rest = \"1-wibble\" digit } ", find_position_of("digit").nth_occurrence(2) ); } #[test] fn references_for_prefix_string_suffix_variable_nested_in_tuple() { assert_references!( " fn main() { case #(\"1-wibble\", 0) { #(\"1\" <> rest, _) -> rest _ -> \"\" } } ", find_position_of("rest").nth_occurrence(1) ); } #[test] fn references_for_prefix_string_alias_used_in_guard() { assert_references!( " fn main() { case \"1-wibble\" { \"1\" as digit <> _rest if digit == \"1\" -> digit _ -> \"\" } } ", find_position_of("digit").nth_occurrence(1) ); } #[test] fn references_for_prefix_string_suffix_used_in_guard() { assert_references!( " fn main() { case \"1-wibble\" { \"1\" <> rest if rest == \"-wibble\" -> rest _ -> \"\" } } ", find_position_of("rest").nth_occurrence(1) ); } #[test] fn references_for_prefix_string_suffix_shadowing_outer_variable() { assert_references!( " fn main() { let rest = \"outer\" case \"1-wibble\" { \"1\" <> rest -> rest _ -> rest } } ", find_position_of("rest").nth_occurrence(2) ); } #[test] fn references_for_prefix_string_alias_and_suffix_complex_guard() { assert_references!( " fn main() { case \"1-wibble\" { \"1\" as digit <> rest if digit == \"1\" && rest == \"-wibble\" -> #(digit, rest) _ -> #(\"\", \"\") } } ", find_position_of("digit").nth_occurrence(1) ); } #[test] fn references_for_local_variable_from_guard() { assert_references!( " pub fn main() { let wibble = True let wobble = False case wibble { True if wobble -> !wibble False if !wobble -> wibble _ -> wobble } } ", find_position_of("wobble").nth_occurrence(2).under_char('o') ); } #[test] fn references_for_module_select_from_guard() { assert_references!( ("mod", "pub const wibble = 10"), " import mod pub fn main() { let wibble = True case wibble { True if mod.wibble < 5 -> !wibble False if mod.wibble != 10 -> wibble _ -> mod.wibble + 1 } } ", find_position_of("mod.wibble").under_char('w') ); } ================================================ FILE: language-server/src/tests/rename.rs ================================================ use std::collections::HashMap; use lsp_types::{ Position, Range, RenameParams, TextDocumentPositionParams, Url, WorkDoneProgressParams, }; use super::{TestProject, find_position_of, hover}; /// Returns the rename range and edit to apply if the rename is valid and can be /// carried out. /// However if the rename produces an error response from the language server, /// the error message is returned. fn rename( tester: &TestProject<'_>, new_name: &str, position: Position, ) -> Result, String> { let prepare_rename_response = tester.at(position, |engine, params, _| { let params = TextDocumentPositionParams { text_document: params.text_document, position, }; engine.prepare_rename(params).result.unwrap() }); let range = match prepare_rename_response { Some(lsp_types::PrepareRenameResponse::Range(range)) => range, Some(lsp_types::PrepareRenameResponse::RangeWithPlaceholder { range, .. }) => range, _ => return Ok(None), }; let outcome = tester.at(position, |engine, params, _| { let params = RenameParams { text_document_position: TextDocumentPositionParams { text_document: params.text_document, position, }, new_name: new_name.to_string(), work_done_progress_params: WorkDoneProgressParams::default(), }; engine.rename(params).result.unwrap() }); match outcome { Ok(Some(edit)) => Ok(Some((range, edit))), Ok(None) => Ok(None), Err(error) => Err(error.message), } } fn apply_rename( tester: &TestProject<'_>, new_name: &str, position: Position, ) -> (Range, HashMap) { let (range, edit) = rename(tester, new_name, position) .expect("Rename failed") .expect("No rename produced"); let changes = edit.changes.expect("No text edit found"); (range, apply_code_edit(tester, changes)) } fn apply_code_edit( tester: &TestProject<'_>, changes: HashMap>, ) -> HashMap { let mut modules = HashMap::new(); for (uri, change) in changes { let module_name = tester.module_name_from_url(&uri).expect("Valid uri"); let module_code = tester.src_from_module_url(&uri).expect("Module exists"); _ = modules.insert(module_name, super::apply_code_edit(module_code, change)); } modules } macro_rules! assert_rename { ($code:literal, $new_name:literal, $position:expr $(,)?) => { assert_rename!(TestProject::for_source($code), $new_name, $position); }; (($module_name:literal, $module_src:literal), $code:literal, $new_name:literal, $position:expr $(,)?) => { assert_rename!( TestProject::for_source($code).add_module($module_name, $module_src), $new_name, $position ); }; ($project:expr, $new_name:literal, $position:expr $(,)?) => { let project = $project; let src = project.src; let position = $position.find_position(src); let (range, result) = apply_rename(&project, $new_name, position); let mut output = String::from("----- BEFORE RENAME\n"); for (name, src) in project.root_package_modules.iter() { output.push_str(&format!("-- {name}.gleam\n{src}\n\n")); } output.push_str(&format!( "-- app.gleam\n{}\n\n----- AFTER RENAME\n", hover::show_hover(src, range, range.start) )); for (name, src) in project.root_package_modules.iter() { output.push_str(&format!( "-- {name}.gleam\n{}\n\n", result .get(*name) .map(|string| string.as_str()) .unwrap_or(*src) )); } output.push_str(&format!( "-- app.gleam\n{}", result .get("app") .map(|string| string.as_str()) .unwrap_or(src) )); insta::assert_snapshot!(insta::internals::AutoName, output, src); }; } macro_rules! assert_no_rename { ($code:literal, $new_name:literal, $position:expr $(,)?) => { let project = TestProject::for_source($code); assert_no_rename!(&project, $new_name, $position); }; ($project:expr, $new_name:literal, $position:expr $(,)?) => { let src = $project.src; let position = $position.find_position(src); let result = rename($project, $new_name, position); assert_eq!(result, Ok(None)); }; } macro_rules! assert_rename_error { ($code:literal, $new_name:literal, $position:expr $(,)?) => { let project = TestProject::for_source($code); assert_rename_error!(&project, $new_name, $position); }; ($project:expr, $new_name:literal, $position:expr $(,)?) => { let src = $project.src; let position = $position.find_position(src); let error = rename($project, $new_name, position).unwrap_err(); let snapshot = format!("Error response message:\n\n{error}"); insta::assert_snapshot!(insta::internals::AutoName, snapshot, src); }; } #[test] fn rename_local_variable() { assert_rename!( " pub fn main() { let wibble = 10 wibble } ", "wobble", find_position_of("wibble").nth_occurrence(2), ); } #[test] fn rename_shadowed_local_variable() { assert_rename!( " pub fn main() { let wibble = 10 let wibble = wibble / 2 wibble } ", "wobble", find_position_of("wibble /"), ); } #[test] fn rename_shadowing_local_variable() { assert_rename!( " pub fn main() { let wibble = 10 let wibble = wibble / 2 wibble } ", "wobble", find_position_of("wibble").nth_occurrence(4), ); } #[test] fn rename_local_variable_record_access() { assert_rename!( " type Wibble { Wibble(wibble: Int) } pub fn main() { let wibble = Wibble(wibble: 1) wibble.wibble } ", "wobble", find_position_of("wibble."), ); } #[test] fn rename_local_variable_guard_clause() { assert_rename!( " pub fn main() { let wibble = True case Nil { Nil if wibble -> todo _ -> panic } wibble || False } ", "wobble", find_position_of("wibble ||"), ); } #[test] fn rename_local_variable_from_definition() { assert_rename!( " pub fn main() { let wibble = 10 let wobble = wibble + 1 wobble - wibble } ", "some_value", find_position_of("wibble =") ); } #[test] fn rename_local_variable_from_definition_nested_pattern() { assert_rename!( " pub fn main() { let assert Ok([_, wibble, ..]) = Error(12) wibble } ", "second_element", find_position_of("wibble,") ); } #[test] fn rename_local_variable_assignment_pattern() { assert_rename!( " pub fn main() { let assert Error(12 as something) = Error(12) something } ", "the_error", find_position_of("something").nth_occurrence(2) ); } #[test] fn rename_local_variable_from_definition_assignment_pattern() { assert_rename!( " pub fn main() { let assert Error(12 as something) = Error(12) something } ", "the_error", find_position_of("something)") ); } #[test] fn rename_local_variable_argument() { assert_rename!( " pub fn add(first_number: Int, x: Int) -> Int { x + first_number } ", "second_number", find_position_of("x +") ); } #[test] fn rename_local_variable_argument_from_definition() { assert_rename!( " pub fn wibble(wibble: Float) { wibble /. 0.3 } ", "wobble", find_position_of("wibble:") ); } #[test] fn rename_local_variable_label_shorthand() { assert_rename!( " type Wibble { Wibble(wibble: Int) } pub fn main() { let Wibble(wibble:) = todo wibble + 1 } ", "wobble", find_position_of("wibble +") ); } #[test] fn rename_local_variable_label_shorthand_from_definition() { assert_rename!( " type Wibble { Wibble(wibble: Int) } pub fn main() { let Wibble(wibble:) = todo wibble + 1 } ", "wobble", find_position_of("wibble:)") ); } #[test] fn rename_local_variable_from_label_shorthand() { assert_rename!( " type Wibble { Wibble(wibble: Int) } pub fn main() { let wibble = todo Wibble(wibble:) } ", "wobble", find_position_of("wibble:)") ); } #[test] fn rename_local_variable_in_bit_array_pattern() { assert_rename!( " pub fn starts_with(bits: BitArray, prefix: BitArray) -> Bool { let prefix_size = bit_size(prefix) case bits { <> if pref == prefix -> True _ -> False } } ", "size_of_prefix", find_position_of("prefix_size =") ); } #[test] fn rename_local_variable_from_bit_array_pattern() { assert_rename!( " pub fn starts_with(bits: BitArray, prefix: BitArray) -> Bool { let prefix_size = bit_size(prefix) case bits { <> if pref == prefix -> True _ -> False } } ", "size_of_prefix", find_position_of("prefix_size)") ); } #[test] fn no_rename_keyword() { assert_no_rename!( " pub fn main() {} ", "wibble", find_position_of("fn"), ); } #[test] fn no_rename_invalid_name() { assert_rename_error!( " pub fn main() { let wibble = 10 wibble } ", "Not_AValid_Name", find_position_of("wibble").nth_occurrence(2) ); } #[test] fn rename_function_from_definition() { assert_rename!( ( "mod", " import app fn wibble() { app.something() } " ), " pub fn something() { something() } fn something_else() { something() } ", "some_function", find_position_of("something") ); } #[test] fn rename_function_from_reference() { assert_rename!( ( "mod", " import app fn wibble() { app.something() } " ), " pub fn something() { something() } fn something_else() { something() } ", "some_function", find_position_of("something").nth_occurrence(2) ); } #[test] fn rename_function_from_qualified_reference() { assert_rename!( ( "mod", " pub fn wibble() { wibble() } " ), " import mod pub fn main() { mod.wibble() } ", "some_function", find_position_of("wibble") ); } #[test] fn rename_function_from_unqualified_reference() { assert_rename!( ( "mod", " pub fn wibble() { wibble() } " ), " import mod.{wibble} pub fn main() { wibble() mod.wibble() } ", "some_function", find_position_of("wibble(") ); } #[test] fn rename_aliased_function() { assert_rename!( ( "mod", " import app.{something as something_else} fn wibble() { something_else() } " ), " pub fn something() { something() } fn something_else() { something() } ", "some_function", find_position_of("something") ); } #[test] fn rename_function_shadowing_module() { let src = " import gleam/list pub fn list() { [] } pub fn main() { list.map(todo, todo) } "; assert_rename!( TestProject::for_source(src).add_hex_module("gleam/list", "pub fn map(_, _) {}"), "empty_list", find_position_of("list()") ); } #[test] fn rename_function_shadowed_by_field_access() { assert_rename!( ( "mod", " import app type App { App(something: Int) } pub fn main() { let app = App(10) app.something } " ), " pub fn something() { todo } ", "function", find_position_of("something") ); } #[test] fn no_rename_function_with_invalid_name() { assert_rename_error!( " pub fn main() { let wibble = 10 wibble } ", "Not_AValid_Name", find_position_of("main") ); } #[test] fn no_rename_function_from_other_package() { let src = " import wibble pub fn main() { wibble.wobble() } "; assert_no_rename!( &TestProject::for_source(src).add_hex_module("wibble", "pub fn wobble() { todo }"), "something", find_position_of("wobble") ); } #[test] fn rename_constant_from_definition() { assert_rename!( ( "mod", " import app fn wibble() { app.something } " ), " pub const something = 10 pub fn main() { something + { 4 * something } } ", "ten", find_position_of("something") ); } #[test] fn rename_constant_from_reference() { assert_rename!( ( "mod", " import app fn wibble() { app.something } " ), " pub const something = 10 pub fn main() { something + { 4 * something } } ", "ten", find_position_of("something").nth_occurrence(2) ); } #[test] fn rename_constant_from_qualified_reference() { assert_rename!( ( "mod", " pub const something = 10 fn wibble() { something } " ), " import mod pub fn main() { mod.something } ", "ten", find_position_of("something") ); } #[test] fn rename_constant_from_unqualified_reference() { assert_rename!( ( "mod", " pub const something = 10 fn wibble() { something } " ), " import mod.{something} pub fn main() { something + mod.something } ", "ten", find_position_of("something +") ); } #[test] fn rename_aliased_constant() { assert_rename!( ( "mod", " import app.{something as some_constant} fn wibble() { some_constant } " ), " pub const something = 10 pub fn main() { something + { 4 * something } } ", "ten", find_position_of("something") ); } #[test] fn rename_constant_shadowing_module() { let src = " import gleam/list const list = [] pub fn main() { list.map(todo, todo) } "; assert_rename!( TestProject::for_source(src).add_hex_module("gleam/list", "pub fn map(_, _) {}"), "empty_list", find_position_of("list =") ); } #[test] fn rename_constant_shadowed_by_field_access() { assert_rename!( ( "mod", " import app type App { App(something: Int) } pub fn main() { let app = App(10) app.something } " ), " pub const something = 10 ", "constant", find_position_of("something") ); } #[test] fn no_rename_constant_with_invalid_name() { assert_rename_error!( " const value = 10 ", "Ten", find_position_of("value") ); } #[test] fn no_rename_constant_from_other_package() { let src = " import wibble pub fn main() { wibble.wobble } "; assert_no_rename!( &TestProject::for_source(src).add_hex_module("wibble", "pub const wobble = 2"), "something", find_position_of("wobble") ); } #[test] fn rename_type_variant_from_definition() { assert_rename!( ( "mod", " import app fn wibble() { app.Constructor(4) } " ), " pub type Wibble { Constructor(Int) } pub fn main() { Constructor(10) } ", "Wibble", find_position_of("Constructor(Int") ); } #[test] fn rename_type_variant_from_reference() { assert_rename!( ( "mod", " import app fn wibble() { app.Constructor(4) } " ), " pub type Wibble { Constructor(Int) } pub fn main() { Constructor(10) } ", "Wibble", find_position_of("Constructor(10") ); } #[test] fn rename_type_variant_from_qualified_reference() { assert_rename!( ( "mod", " pub type Wibble { Constructor(Int) } fn wibble() { Constructor(42) } " ), " import mod pub fn main() { mod.Constructor } ", "Variant", find_position_of("Constructor") ); } #[test] fn rename_type_variant_from_unqualified_reference() { assert_rename!( ( "mod", " pub type Wibble { Constructor(Int) } fn wibble() { Constructor(81) } " ), " import mod.{Constructor} pub fn main() { #(Constructor(75), mod.Constructor(57)) } ", "Number", find_position_of("Constructor(75") ); } #[test] fn rename_aliased_type_variant() { assert_rename!( ( "mod", " import app.{Constructor as ValueConstructor} fn wibble() { ValueConstructor(172) } " ), " pub type Wibble { Constructor(Int) } pub fn main() { Constructor(42) } ", "MakeAWibble", find_position_of("Constructor") ); } #[test] fn no_rename_type_variant_with_invalid_name() { assert_rename_error!( " pub type Wibble { Constructor(Int) } ", "name_in_snake_case", find_position_of("Constructor") ); } #[test] fn rename_custom_type_variant_pattern() { assert_rename!( " pub type Type { X Y } pub fn main(t) { case t { X -> 0 Y -> 0 } } ", "Renamed", find_position_of("X") ); } #[test] fn rename_imported_custom_type_variant_pattern() { assert_rename!( ( "other", " import app pub fn main(t) { case t { app.X -> 0 app.Y -> 0 } } " ), " pub type Type { X Y } ", "Renamed", find_position_of("X") ); } #[test] fn rename_imported_unqualified_custom_type_variant_pattern() { assert_rename!( ( "other", " import app.{X, Y} pub fn main(t) { case t { X -> 0 Y -> 0 } } " ), " pub type Type { X Y } ", "Renamed", find_position_of("X") ); } #[test] fn rename_type_variant_pattern_with_arguments() { assert_rename!( " pub type Wibble { Wibble(Int) Wobble(Float) } fn wibble() { case Wibble(10) { Wibble(20) -> todo Wibble(_) -> panic } } ", "Variant", find_position_of("Wibble(10)") ); } #[test] fn rename_type_variant_from_pattern() { assert_rename!( " pub type Type { X Y } pub fn main(t) { case t { X -> 0 Y -> 0 } } ", "Renamed", find_position_of("X ->") ); } #[test] fn no_rename_type_variant_from_other_package() { let src = " import wibble pub fn main() { wibble.Wibble(10) } "; assert_no_rename!( &TestProject::for_source(src).add_hex_module("wibble", "pub type Wibble { Wibble(Int) }"), "Constructor", find_position_of("Wibble") ); } #[test] fn rename_value_in_nested_module() { assert_rename!( ( "sub/mod", " pub fn wibble() { wibble() } " ), " import sub/mod pub fn main() { mod.wibble() } ", "some_function", find_position_of("wibble") ); } #[test] fn rename_value_in_aliased_module() { assert_rename!( ( "mod", " pub fn wibble() { wibble() } " ), " import mod as the_module pub fn main() { the_module.wibble() } ", "some_function", find_position_of("wibble") ); } #[test] fn rename_aliased_value() { assert_rename!( ( "mod", " import app.{Wibble as Wobble} fn wobble() { Wobble } " ), " pub type Wibble { Wibble } pub fn main() { Wibble } ", "Wubble", find_position_of("Wibble }") ); } #[test] fn rename_type_from_definition() { assert_rename!( ( "mod", " import app fn wibble() -> app.Wibble { todo } " ), " pub type Wibble { Constructor } pub fn main(w: Wibble) -> Wibble { todo } ", "SomeType", find_position_of("Wibble") ); } #[test] fn rename_type_from_reference() { assert_rename!( ( "mod", " import app fn wibble() -> app.Wibble { todo } " ), " pub type Wibble { Constructor } pub fn main(w: Wibble) -> Wibble { todo } ", "SomeType", find_position_of("Wibble").nth_occurrence(2) ); } #[test] fn rename_type_from_qualified_reference() { assert_rename!( ( "mod", " pub type Wibble { Constructor } fn wibble(w: Wibble) -> Wibble { todo } " ), " import mod pub fn main(w: mod.Wibble) -> mod.Wibble { todo } ", "SomeType", find_position_of("Wibble") ); } #[test] fn rename_type_from_unqualified_reference() { assert_rename!( ( "mod", " pub type Wibble { Constructor } fn wibble(w: Wibble) -> Wibble { todo } " ), " import mod.{type Wibble} pub fn main(w: Wibble) -> mod.Wibble { todo } ", "SomeType", find_position_of("Wibble)") ); } #[test] fn rename_aliased_type() { assert_rename!( ( "mod", " import app.{type Wibble as Wobble} fn wibble() -> Wobble { todo } " ), " pub type Wibble { Constructor } pub fn main(w: Wibble) -> Wibble { todo } ", "SomeType", find_position_of("Wibble") ); } #[test] fn no_rename_type_with_invalid_name() { assert_rename_error!( " type Wibble { Wobble } ", "a_type_name", find_position_of("Wibble") ); } #[test] fn no_rename_type_from_other_package() { let src = " import wibble pub fn main() -> wibble.Wibble { todo } "; assert_no_rename!( &TestProject::for_source(src).add_hex_module("wibble", "pub type Wibble { Wibble }"), "SomeType", find_position_of("Wibble") ); } // https://github.com/gleam-lang/gleam/issues/4372 #[test] fn rename_type_referenced_in_variant_constructor_argument() { assert_rename!( ( "mod", " import app pub type Wobble { Wobble(w: app.Wibble) } " ), " pub type Wibble { Wibble } pub fn main() { let wibble = Wibble } ", "SomeType", find_position_of("Wibble") ); } // https://github.com/gleam-lang/gleam/issues/4372 #[test] fn rename_type_from_variant_constructor_argument() { assert_rename!( ( "mod", " pub type Wibble { Wibble } pub fn main() { let wibble = Wibble } " ), " import mod pub type Wobble { Wobble(w: mod.Wibble) } ", "SomeType", find_position_of("Wibble") ); } // https://github.com/gleam-lang/gleam/issues/4553 #[test] fn rename_local_variable_with_label_shorthand() { assert_rename!( " pub type Wibble { Wibble(first: Int, second: Int) } pub fn main() { let second = 2 Wibble(first: 1, second:) } ", "something", find_position_of("second =") ); } // https://github.com/gleam-lang/gleam/issues/4748 #[test] fn rename_alternative_pattern() { assert_rename!( " pub fn main(x) { case x { #(wibble, [wobble]) | #(wobble, [wibble, _]) | #(_, [wibble, wobble, ..]) -> wibble + wobble _ -> 0 } } ", "new_name", find_position_of("wibble") ); } // https://github.com/gleam-lang/gleam/issues/5091 #[test] fn rename_alternative_pattern_aliases() { assert_rename!( " pub fn main(x) { case x { [] as list | [_] as list -> list _ -> [] } } ", "new_name", find_position_of("list") ); } #[test] fn rename_alternative_pattern_aliases_from_alternative() { assert_rename!( " pub fn main(x) { case x { [] as list | [_] as list -> list _ -> [] } } ", "new_name", find_position_of("list").nth_occurrence(2) ); } #[test] fn rename_alternative_pattern_aliases_from_usage() { assert_rename!( " pub fn main(x) { case x { [] as list | [_] as list -> list _ -> [] } } ", "new_name", find_position_of("list").nth_occurrence(3) ); } #[test] fn rename_alternative_pattern_alias_and_variable_1() { assert_rename!( " pub fn main(x) { case x { [] as list | [_, ..list] -> list _ -> [] } } ", "new_name", find_position_of("list").nth_occurrence(1) ); } #[test] fn rename_alternative_pattern_alias_and_variable_2() { assert_rename!( " pub fn main(x) { case x { [] as list | [_, ..list] -> list _ -> [] } } ", "new_name", find_position_of("list").nth_occurrence(2) ); } #[test] fn rename_alternative_pattern_alias_and_variable_3() { assert_rename!( " pub fn main(x) { case x { [_, ..list] | [] as list -> list _ -> [] } } ", "new_name", find_position_of("list").nth_occurrence(1) ); } #[test] fn rename_alternative_pattern_alias_and_variable_4() { assert_rename!( " pub fn main(x) { case x { [_, ..list] | [] as list -> list _ -> [] } } ", "new_name", find_position_of("list").nth_occurrence(2) ); } #[test] fn rename_alternative_pattern_from_usage() { assert_rename!( " pub fn main(x) { case x { #(wibble, [wobble]) | #(wobble, [wibble, _]) | #(_, [wibble, wobble, ..]) -> wibble + wobble _ -> 0 } } ", "new_name", find_position_of("wibble +") ); } // https://github.com/gleam-lang/gleam/issues/4605 #[test] fn rename_prelude_value() { assert_rename!( " pub fn main() { Ok(10) } ", "Success", find_position_of("Ok") ); } #[test] fn rename_prelude_type() { assert_rename!( " pub fn main() -> Result(Int, Nil) { Ok(10) } ", "SuccessOrFailure", find_position_of("Result") ); } #[test] fn rename_variable_with_alternative_pattern_with_same_name() { assert_rename!( " pub fn main(x) { let some_var = 10 case x { #(some_var, []) | #(_, [some_var]) -> some_var _ -> 0 } some_var } ", "new_name", find_position_of("some_var") ); } #[test] fn rename_prelude_value_with_prelude_already_imported() { assert_rename!( " import gleam pub fn main() { Ok(gleam.Error(10)) } ", "Success", find_position_of("Ok") ); } #[test] fn rename_prelude_value_with_prelude_import_with_empty_braces() { assert_rename!( " import gleam.{} pub fn main() { Ok(gleam.Error(10)) } ", "Success", find_position_of("Ok") ); } #[test] fn rename_prelude_value_with_other_prelude_value_imported() { assert_rename!( " import gleam.{Error} pub fn main() { Ok(Error(10)) } ", "Success", find_position_of("Ok") ); } #[test] fn rename_prelude_type_with_prelude_value_imported_with_trailing_comma() { assert_rename!( " import gleam.{Error,} pub fn main() -> Result(Int, Nil) { Error(10) } ", "OkOrError", find_position_of("Result") ); } #[test] fn rename_prelude_value_with_other_module_imported() { assert_rename!( ("something", "pub type Something"), " import something pub fn main() { Ok(10) } ", "Success", find_position_of("Ok") ); } #[test] fn rename_module_access_in_clause_guard() { assert_rename!( ( "wibble", " import app pub fn main() { case app.something { thing if thing == app.something -> True _ -> False } } " ), " pub const something = 10 ", "new_name", find_position_of("something") ); } #[test] fn rename_variable_used_in_record_update() { assert_rename!( " type Wibble { Wibble(a: Int, b: Int, c: Int) } fn wibble(wibble: Wibble) { Wibble(..wibble, c: 1) } ", "value", find_position_of("wibble:") ); } //https://github.com/gleam-lang/gleam/issues/4941 #[test] fn rename_external_function() { assert_rename!( r#" pub fn main() { wibble() } @external(erlang, "a", "a") fn wibble() -> Nil "#, "new_name", find_position_of("wibble").nth_occurrence(2) ); } #[test] fn rename_external_javascript_function_with_pure_gleam_fallback() { assert_rename!( r#" pub fn main() { wibble() } @external(javascript, "a", "a") fn wibble() -> Nil { Nil } "#, "new_name", find_position_of("wibble").nth_occurrence(2) ); } #[test] fn rename_nested_aliased_pattern() { assert_rename!( r#" pub fn go(x) { case x { [[nested, ..] as wibble, ..] -> todo _ -> todo } } "#, "new_name", find_position_of("nested") ); } #[test] fn rename_module_from_import() { assert_rename!( TestProject::for_source("import option") .add_module("option", "pub type Option(a) { Some(a) None }"), "opt", find_position_of("option"), ); } #[test] fn rename_works_when_error_is_present() { assert_rename!( r#" fn wibble() { "test string" } pub fn main() { 1 + "1" echo wibble() } "#, "wobble", find_position_of("wibble") ); } #[test] fn rename_module_from_import_with_alias() { assert_rename!( TestProject::for_source("import option as opt") .add_module("option", "pub type Option(a) { Some(a) None }"), "o", find_position_of("opt"), ); } #[test] fn reanem_module_from_import_with_unqualified_values() { assert_rename!( TestProject::for_source("import option . { Some, None }") .add_module("option", "pub type Option(a) { Some(a) None }"), "opt", find_position_of("option"), ); } #[test] fn rename_module_from_import_with_unqualified_value_and_alias() { assert_rename!( TestProject::for_source("import option . {Some, None} as opt") .add_module("option", "pub type Option(a) { Some(a) None }"), "o", find_position_of("opt"), ); } #[test] fn rename_module_from_import_namespaced() { assert_rename!( TestProject::for_source("import std / option ") .add_module("std/option", "pub type Option(a) { Some(a) None }"), "opt", find_position_of("option"), ); } #[test] fn rename_module_from_import_namespaced_with_alias() { assert_rename!( TestProject::for_source("import std / option as opt") .add_module("std/option", "pub type Option(a) { Some(a) None }"), "o", find_position_of("opt"), ); } #[test] fn rename_module_from_import_namespaced_with_unqualified_values() { assert_rename!( TestProject::for_source("import std / option . { Some, None }") .add_module("std/option", "pub type Option(a) { Some(a) None }"), "opt", find_position_of("option"), ); } #[test] fn rename_module_from_import_namespaced_with_unqualified_value_and_alias() { assert_rename!( TestProject::for_source("import std / option . { Some, None } as opt") .add_module("std/option", "pub type Option(a) { Some(a) None }"), "o", find_position_of("opt"), ); } #[test] fn rename_module_from_import_with_alias_to_original_name() { assert_rename!( TestProject::for_source("import option as opt") .add_module("option", "pub type Option(a) { Some(a) None }"), "option", find_position_of("opt"), ); } #[test] fn rename_module_from_variant_in_expression() { let src = r#" import option pub fn main() { echo option . None } "#; assert_rename!( TestProject::for_source(src).add_module("option", "pub type Option(a) { Some(a) None }"), "opt", find_position_of("option").nth_occurrence(2) ); } #[test] fn rename_prefix_string_suffix_variable_in_case() { assert_rename!( " fn main() -> String { let wibble = \"1-wibble\" case wibble { \"1\" <> rest -> rest other -> other } } ", "new_name", find_position_of("rest").nth_occurrence(1) ); } #[test] fn rename_module_from_constant_in_expression() { let src = r#" import maths pub fn main() { echo maths . pi } "#; assert_rename!( TestProject::for_source(src).add_module("maths", "pub const pi = 3.14"), "m", find_position_of("maths").nth_occurrence(2), ); } #[test] fn rename_module_from_variant_in_const() { let src = r#" import option const x = option .None "#; assert_rename!( TestProject::for_source(src).add_module("option", "pub type Option(a) { Some(a) None }"), "opt", find_position_of("option").nth_occurrence(2), ); } #[test] fn rename_module_from_constant_in_const() { let src = r#" import maths const x = maths . pi "#; assert_rename!( TestProject::for_source(src).add_module("maths", "pub const pi = 3.14"), "m", find_position_of("maths").nth_occurrence(2), ); } #[test] fn rename_module_from_variant_in_pattern() { let src = r#" import option pub fn is_some(option) { case option { option. Some(_) -> True option .None -> False } } "#; assert_rename!( TestProject::for_source(src).add_module("option", "pub type Option(a) { Some(a) None }"), "opt", find_position_of("option").nth_occurrence(4), ); } #[test] fn rename_prefix_string_suffix_variable_in_case_triggered_from_usage() { assert_rename!( " fn main() -> String { let wibble = \"1-wibble\" case wibble { \"1\" <> rest -> rest other -> other } } ", "new_name", find_position_of("rest").nth_occurrence(2) ); } #[test] fn rename_module_from_variant_in_clause_guard() { let src = r#" import option pub fn count_none(list) { case list { [option, ..rest] if option == option . None -> 1 + count_none(rest) [_, ..rest] -> count_none(rest) [] -> 0 } } "#; assert_rename!( TestProject::for_source(src).add_module("option", "pub type Option(a) { Some(a) None }"), "opt", find_position_of("option").nth_occurrence(4), ); } #[test] fn rename_prefix_string_suffix_variable_with_alternative_definition_in_case() { assert_rename!( " fn main() -> String { let wibble = \"1-wibble\" case wibble { \"1\" <> rest | \"2\" <> rest -> rest other -> other } } ", "new_name", find_position_of("rest").nth_occurrence(1), ); } #[test] fn rename_module_from_constant_in_clause_guard() { let src = r#" import maths pub fn count_pi(list) { case list { [number, ..rest] if number == maths . pi -> 1 + count_pi(rest) [_, ..rest] -> count_pi(rest) [] -> 0 } } "#; assert_rename!( TestProject::for_source(src).add_module("maths", "pub const pi = 3.14"), "m", find_position_of("maths").nth_occurrence(2), ); } #[test] fn rename_prefix_string_suffix_variable_with_alternative_definition_triggered_from_second_pattern() { assert_rename!( " fn main() -> String { let wibble = \"1-wibble\" case wibble { \"1\" <> rest | \"2\" <> rest -> rest other -> other } } ", "new_name", find_position_of("rest").nth_occurrence(2), ); } #[test] fn rename_module_from_type_in_custom_type() { let src = r#" import option type Value(a) { Value(option.Option(a)) } "#; assert_rename!( TestProject::for_source(src).add_module("option", "pub type Option(a) { Some(a) None }"), "opt", find_position_of("option").nth_occurrence(2), ); } #[test] fn rename_module_from_type_in_type_alias() { let src = r#" import option type Option(a) = option.Option(a) "#; assert_rename!( TestProject::for_source(src).add_module("option", "pub type Option(a) { Some(a) None }"), "opt", find_position_of("option").nth_occurrence(2), ); } #[test] fn rename_module_from_type_in_annotation() { let src = r#" import option const x: option.Option(Int) = option.Some(1) "#; assert_rename!( TestProject::for_source(src).add_module("option", "pub type Option(a) { Some(a) None }"), "opt", find_position_of("option").nth_occurrence(2), ); } #[test] fn rename_module_from_function_call() { let src = r#" import option pub fn main() { option.is_some(option.Some(1)) } "#; let option_module = r#" pub type Option(a) { Some(a) None } pub fn is_some(option: Option(a)) -> Bool { case option { Some(_) -> True None -> False } } "#; assert_rename!( TestProject::for_source(src).add_module("option", option_module), "opt", find_position_of("option").nth_occurrence(2), ); } #[test] fn rename_prefix_string_suffix_variable_in_let_assert() { assert_rename!( " fn main() -> String { let assert \"1\" <> rest = \"1-wibble\" rest } ", "new_name", find_position_of("rest").nth_occurrence(1) ); } #[test] fn rename_prefix_string_suffix_variable_in_let_assert_triggered_from_usage() { assert_rename!( " fn main() -> String { let assert \"1\" <> rest = \"1-wibble\" rest } ", "new_name", find_position_of("rest").nth_occurrence(2) ); } #[test] fn rename_prefix_string_alias_in_case() { assert_rename!( " fn main() -> String { let wibble = \"1-wibble\" case wibble { \"1\" as digit <> rest -> digit <> rest other -> other } } ", "new_name", find_position_of("digit").nth_occurrence(1) ); } #[test] fn rename_prefix_string_alias_in_case_triggered_from_usage() { assert_rename!( " fn main() -> String { let wibble = \"1-wibble\" case wibble { \"1\" as digit <> rest -> digit <> rest other -> other } } ", "new_name", find_position_of("digit").nth_occurrence(2) ); } #[test] fn rename_prefix_string_alias_with_alternative_definitions_in_case() { assert_rename!( " fn main() -> String { let wibble = \"1-wibble\" case wibble { \"1\" as digit <> rest | \"2\" as digit <> rest -> digit <> rest other -> other } } ", "new_name", find_position_of("digit").nth_occurrence(1) ); } #[test] fn rename_prefix_string_alias_with_alternative_definitions_triggered_from_second_pattern() { assert_rename!( " fn main() -> String { let wibble = \"1-wibble\" case wibble { \"1\" as digit <> rest | \"2\" as digit <> rest -> digit <> rest other -> other } } ", "new_name", find_position_of("digit").nth_occurrence(2) ); } #[test] fn rename_prefix_string_alias_in_let_assert() { assert_rename!( " fn main() -> String { let assert \"1\" as digit <> rest = \"1-wibble\" digit } ", "new_name", find_position_of("digit").nth_occurrence(1) ); } #[test] fn rename_prefix_string_alias_in_let_assert_triggered_from_usage() { assert_rename!( " fn main() -> String { let assert \"1\" as digit <> rest = \"1-wibble\" digit } ", "new_name", find_position_of("digit").nth_occurrence(2) ); } #[test] fn rename_prefix_string_suffix_variable_nested_in_tuple() { assert_rename!( " fn main() { case #(\"1-wibble\", 0) { #(\"1\" <> rest, _) -> rest _ -> \"\" } } ", "new_name", find_position_of("rest").nth_occurrence(1) ); } #[test] fn rename_prefix_string_alias_used_in_guard() { assert_rename!( " fn main() { case \"1-wibble\" { \"1\" as digit <> _rest if digit == \"1\" -> digit _ -> \"\" } } ", "new_name", find_position_of("digit").nth_occurrence(1) ); } #[test] fn rename_prefix_string_suffix_used_in_guard() { assert_rename!( " fn main() { case \"1-wibble\" { \"1\" <> rest if rest == \"-wibble\" -> rest _ -> \"\" } } ", "new_name", find_position_of("rest").nth_occurrence(1) ); } #[test] fn rename_prefix_string_suffix_shadowing_outer_variable() { assert_rename!( " fn main() { let rest = \"outer\" case \"1-wibble\" { \"1\" <> rest -> rest _ -> rest } } ", "new_name", find_position_of("rest").nth_occurrence(2) ); } #[test] fn rename_prefix_string_alias_and_suffix_complex_guard() { assert_rename!( " fn main() { case \"1-wibble\" { \"1\" as digit <> rest if digit == \"1\" && rest == \"-wibble\" -> #(digit, rest) _ -> #(\"\", \"\") } } ", "new_name", find_position_of("digit").nth_occurrence(1) ); } #[test] fn rename_module_from_alias_use() { let src = r#" import maths as m pub fn main() { echo m .pi } "#; assert_rename!( TestProject::for_source(src).add_module("maths", "pub const pi = 3.14"), "mth", find_position_of("m").nth_occurrence(5) ); } #[test] fn rename_local_variable_from_guard() { assert_rename!( " pub fn main() { let wibble = True let wobble = False case wibble { True if wobble -> !wibble False if !wobble -> wibble _ -> wobble } } ", "something_else", find_position_of("wobble").nth_occurrence(2).under_char('o') ); } #[test] fn alias_imported_module_from_guard() { assert_rename!( ("mod", "pub const wibble = 10"), " import mod pub fn main() { let wibble = True case wibble { True if mod.wibble < 5 -> !wibble False if mod.wibble != 10 -> wibble _ -> mod.wibble + 1 } } ", "module", find_position_of("mod.wibble").under_char('m') ); } #[test] fn rename_module_select_from_guard() { assert_rename!( ("mod", "pub const wibble = 10"), " import mod pub fn main() { let wibble = True case wibble { True if mod.wibble < 5 -> !wibble False if mod.wibble != 10 -> wibble _ -> mod.wibble + 1 } } ", "ten", find_position_of("mod.wibble").under_char('w') ); } ================================================ FILE: language-server/src/tests/router.rs ================================================ use std::time::SystemTime; use gleam_core::{Error, io::FileSystemWriter, paths::ProjectPaths}; use crate::{files::FileSystemProxy, tests::Action}; use super::LanguageServerTestIO; type Router = crate::router::Router; #[test] fn recompile_after_no_changes_does_not_redownload_dependencies() { let paths = ProjectPaths::new("/app".into()); let (io, mut router) = set_up_minimal_router(&paths); assert_eq!( compile(&mut router, &paths), Ok(()), "Pre-condition: Initial compile should succeed" ); { let mut actions = io.actions.lock().unwrap(); assert!( actions.contains(&Action::DownloadDependencies), "Expectation: Initial compile should download dependencies" ); actions.clear(); } assert_eq!( compile(&mut router, &paths), Ok(()), "Recompile should succeed" ); { let actions = io.actions.lock().unwrap(); assert!( !actions.contains(&Action::DownloadDependencies), "Recompile should not re-download dependencies" ); } } #[test] fn deleting_build_dir_redownloads_dependencies() { let paths = ProjectPaths::new("/app".into()); let (io, mut router) = set_up_minimal_router(&paths); _ = compile(&mut router, &paths); io.actions.lock().unwrap().clear(); io.delete_directory(&paths.build_directory()).unwrap(); assert_eq!( compile(&mut router, &paths), Ok(()), "Compile after deleting build directory should succeed" ); { let actions = io.actions.lock().unwrap(); assert!( actions.contains(&Action::DownloadDependencies), "Compile after deleting build directory should re-download dependencies" ); } } #[test] fn changing_config_redownloads_dependencies() { let paths = ProjectPaths::new("/app".into()); let (io, mut router) = set_up_minimal_router(&paths); _ = compile(&mut router, &paths); io.actions.lock().unwrap().clear(); let toml = r#"name = "wobble" version = "1.0.0""#; io.write(&paths.root_config(), toml).unwrap(); io.io .try_set_modification_time(&paths.root_config(), SystemTime::now()) .unwrap(); assert_eq!( compile(&mut router, &paths), Ok(()), "Compile after changing gleam.toml should succeed" ); { let actions = io.actions.lock().unwrap(); assert!( actions.contains(&Action::DownloadDependencies), "Compile after changing gleam.toml should re-download dependencies" ); } } fn compile(router: &mut Router, paths: &ProjectPaths) -> Result<(), Error> { router .project_for_path(paths.root().into()) .unwrap() .unwrap() .engine .compile_please() .result } fn set_up_minimal_router(paths: &ProjectPaths) -> (LanguageServerTestIO, Router) { let io = LanguageServerTestIO::new(); let router = Router::new(io.clone(), FileSystemProxy::new(io.clone())); let toml = r#"name = "wibble" version = "1.0.0""#; io.write(&paths.root_config(), toml).unwrap(); (io, router) } ================================================ FILE: language-server/src/tests/signature_help.rs ================================================ use super::*; use lsp_types::{ ParameterInformation, ParameterLabel, SignatureHelp, SignatureHelpParams, SignatureInformation, }; fn signature_help(tester: TestProject<'_>, position: Position) -> Option { tester.at(position, |engine, param, _| { let params = SignatureHelpParams { context: None, text_document_position_params: param, work_done_progress_params: Default::default(), }; let response = engine.signature_help(params); response.result.unwrap() }) } fn pretty_signature_help(signature_help: SignatureHelp) -> String { let SignatureHelp { signatures, active_signature, active_parameter, } = signature_help; let SignatureInformation { label, documentation, parameters, active_parameter: _, } = signatures .get(active_signature.expect("an active signature") as usize) .expect("an active signature"); let parameters = parameters .as_ref() .expect("no signature help for function with no parameters"); let documentation = match documentation { Some(d) => format!("Documentation:\n{d:#?}"), None => "No documentation".to_string(), }; let label = match active_parameter { None => label.to_string(), Some(i) => match parameters.get(i as usize) { None => label.to_string(), Some(ParameterInformation { label: ParameterLabel::LabelOffsets([start, end]), .. }) => { let spaces = " ".repeat(*start as usize); let underlined = "▔".repeat((end - start) as usize); format!("{label}\n{spaces}{underlined}") } Some(_) => panic!("unexpected response"), }, }; format!("{label}\n\n{documentation}") } #[macro_export] macro_rules! assert_signature_help { ($code:literal, $position:expr $(,)?) => { let project = TestProject::for_source($code); assert_signature_help!(project, $position); }; ($project:expr, $position:expr $(,)?) => { let src = $project.src; let position = $position.find_position(src); let result = signature_help($project, position).expect("no signature help produced"); let pretty_hover = hover::show_hover( src, lsp_types::Range { start: Position { character: 1, line: 1, }, end: Position { character: 0, line: 0, }, }, position, ); let output = format!( "{}\n\n----- Signature help -----\n{}", pretty_hover, pretty_signature_help(result) ); insta::assert_snapshot!(insta::internals::AutoName, output, src); }; } #[macro_export] macro_rules! assert_no_signature_help { ($code:literal, $position:expr $(,)?) => { let project = TestProject::for_source($code); assert_no_signature_help!(project, $position); }; ($project:expr, $position:expr $(,)?) => { let src = $project.src; let position = $position.find_position(src); let result = signature_help($project, position); match result { Some(_) => panic!("Expected no signature help"), None => (), } }; } #[test] pub fn help_for_calling_local_variable_first_arg() { assert_signature_help!( r#" pub fn main() { let wibble = fn(a: Int, b: String) { 1.0 } wibble() } "#, find_position_of("wibble()").under_last_char() ); } #[test] pub fn help_for_calling_local_variable_last_arg() { assert_signature_help!( r#" pub fn main() { let wibble = fn(a: Int, b: String) { 1.0 } wibble(1,) } "#, find_position_of("wibble(1,)").under_last_char() ); } #[test] pub fn help_for_calling_local_variable_with_module_function() { assert_signature_help!( r#" pub fn wibble(a: Int, b: String) { 1.0 } pub fn main() { let wobble = fn(a: Int, b: String) { 1.0 } wobble(1,) } "#, find_position_of("wobble(1,)").under_last_char() ); } #[test] pub fn help_for_calling_module_function() { assert_signature_help!( r#" pub fn wibble(a: Int, b: String) { 1.0 } pub fn main() { wibble() } "#, find_position_of("wibble()").under_last_char() ); } #[test] pub fn help_for_calling_module_constant_referencing_function() { assert_signature_help!( r#" pub fn wibble(a: Int, b: String) { 1.0 } const wobble = wibble pub fn main() { wobble() } "#, find_position_of("wobble()").under_last_char() ); } #[test] pub fn help_for_calling_local_variable_referencing_constant_referencing_function() { assert_signature_help!( r#" pub fn wibble(a: Int, b: String) { 1.0 } const wobble = wibble pub fn main() { let woo = wobble woo() } "#, find_position_of("woo()").under_last_char() ); } #[test] pub fn help_still_shows_up_even_if_an_argument_has_the_wrong_type() { assert_signature_help!( r#" pub fn wibble(a: Int, b: String) { 1.0 } pub fn main() { wibble("wrong",) } "#, find_position_of("wibble(\"wrong\",)").under_last_char() ); } #[test] pub fn help_shows_documentation_for_local_function() { assert_signature_help!( r#" /// Some doc! pub fn wibble(a: Int, b: String) { 1.0 } pub fn main() { wibble() } "#, find_position_of("wibble()").under_last_char() ); } #[test] pub fn help_shows_documentation_for_imported_function() { let code = r#" import example pub fn main() { example.example_fn() } "#; assert_signature_help!( TestProject::for_source(code).add_module( "example", "/// Some doc! pub fn example_fn(a: Int, b: String) { Nil }" ), find_position_of("example_fn()").under_last_char() ); } #[test] pub fn help_for_unqualified_call() { let code = r#" import example.{example_fn} pub fn main() { example_fn() } "#; assert_signature_help!( TestProject::for_source(code) .add_module("example", "pub fn example_fn(a: Int, b: String) { Nil }"), find_position_of("example_fn()").under_last_char() ); } #[test] pub fn help_for_aliased_unqualified_call() { let code = r#" import example.{example_fn as wibble} pub fn main() { wibble() } "#; assert_signature_help!( TestProject::for_source(code) .add_module("example", "pub fn example_fn(a: Int, b: String) { Nil }"), find_position_of("wibble()").under_last_char() ); } #[test] pub fn help_for_qualified_call() { let code = r#" import example pub fn main() { example.example_fn() } "#; assert_signature_help!( TestProject::for_source(code) .add_module("example", "pub fn example_fn(a: Int, b: String) { Nil }"), find_position_of("example_fn()").under_last_char() ); } #[test] pub fn help_for_aliased_qualified_call() { let code = r#" import example as wibble pub fn main() { wibble.example_fn() } "#; assert_signature_help!( TestProject::for_source(code) .add_module("example", "pub fn example_fn(a: Int, b: String) { Nil }"), find_position_of("example_fn()").under_last_char() ); } #[test] pub fn help_shows_labels() { assert_signature_help!( r#" pub fn wibble(a: Int, b b: Int, c c: String) { 1.0 } pub fn main() { wibble() } "#, find_position_of("wibble()").under_last_char() ); } #[test] pub fn help_shows_labelled_argument_after_all_unlabelled() { assert_signature_help!( r#" pub fn wibble(a: Int, b b: Int, c c: String) { 1.0 } pub fn main() { wibble(1,) } "#, find_position_of("wibble(1,)").under_last_char() ); } #[test] pub fn help_shows_first_missing_labelled_argument_if_out_of_order() { assert_signature_help!( r#" pub fn wibble(a a: Int, b b: Int, c c: String) { 1.0 } pub fn main() { wibble(c: "c",) } "#, find_position_of("wibble(c: \"c\",)").under_last_char() ); } #[test] pub fn help_for_piped_imported_function_starts_from_second_argument() { let code = r#" import example pub fn main() { 1 |> example.example_fn() } "#; assert_signature_help!( TestProject::for_source(code) .add_module("example", "pub fn example_fn(a: Int, b: String) { Nil }"), find_position_of("example_fn()").under_last_char() ); } #[test] pub fn help_for_piped_function_starts_from_second_argument() { assert_signature_help!( r#" pub fn wibble(a a: Int, b b: Int, c c: String) { 1.0 } pub fn main() { 1 |> wibble() } "#, find_position_of("wibble()").under_last_char() ); } #[test] pub fn help_for_use_function_call_starts_from_first_argument() { assert_signature_help!( r#" pub fn wibble(a: Int, b: Int, c: fn() -> Int) { 1.0 } pub fn main() { use <- wibble() } "#, find_position_of("wibble()").under_last_char() ); } #[test] pub fn help_for_use_function_call_uses_precise_types_when_missing_some_arguments() { assert_signature_help!( r#" pub fn guard(a: Bool, b: a, c: fn() -> a) { 1.0 } pub fn main() { use <- guard(True,) } "#, find_position_of("guard(True,)").under_last_char() ); } #[test] pub fn help_for_use_function_shows_next_unlabelled_argument() { assert_signature_help!( r#" pub fn guard(a a: Bool, b b: a, c c: fn() -> a) { 1.0 } pub fn main() { use <- guard(b: 1,) } "#, find_position_of("guard(b: 1,)").under_last_char() ); } #[test] pub fn help_does_not_come_up_for_function_that_does_not_exist() { assert_no_signature_help!( r#" pub fn main() { use <- to_be_or_not_to_be() } "#, find_position_of("to_be_or_not_to_be()").under_last_char() ); } #[test] // Regression introduced by 4112682cdb5d5b0bb6d1defc6cde849b6a6f65ab. pub fn help_with_labelled_constructor() { assert_signature_help!( r#" pub type Pokemon { Pokemon(name: String, types: List(String), moves: List(String)) } pub fn main() { Pokemon(name: "Jirachi",) } "#, find_position_of(r#"Pokemon(name: "Jirachi",)"#).under_last_char() ); } #[test] pub fn help_for_use_function_call_uses_generic_names_when_missing_all_arguments() { assert_signature_help!( r#" pub fn wibble(x: something, y: fn() -> something, z: anything) { Nil } pub fn main() { wibble( ) } "#, find_position_of("wibble( ").under_last_char() ); } #[test] pub fn help_for_use_function_call_uses_concrete_types_when_possible_or_generic_names_when_unbound() { assert_signature_help!( r#" pub fn wibble(x: something, y: fn() -> something, z: anything) { Nil } pub fn main() { wibble(1, ) } "#, find_position_of("wibble(1, )").under_last_char() ); } #[test] pub fn help_for_use_function_call_uses_concrete_types_when_late_ubound() { assert_signature_help!( r#" fn identity(x: a) -> a { x } fn main() { let a = Ok(10) identity( a) } "#, find_position_of("identity( ").under_last_char() ); } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_annotation_triggers_on_empty_space_before_function_curly_brace.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() { 1 }" --- ----- BEFORE ACTION pub fn main() { 1 } ↑ ----- AFTER ACTION pub fn main() -> Int { 1 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_annotation_triggers_on_function_curly_brace.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() { 1 }" --- ----- BEFORE ACTION pub fn main() { 1 } ↑ ----- AFTER ACTION pub fn main() -> Int { 1 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_correct_type_annotation_for_non_variable_use.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nfn usable(f) {\n f(#(1, 2))\n}\n\npub fn main() {\n use #(a, b) <- usable\n a + b\n}\n" --- ----- BEFORE ACTION fn usable(f) { f(#(1, 2)) } pub fn main() { use #(a, b) <- usable ▔▔▔▔▔▔▔▔▔↑ a + b } ----- AFTER ACTION fn usable(f) { f(#(1, 2)) } pub fn main() { use #(a, b): #(Int, Int) <- usable a + b } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_missing_patterns_adds_a_discard_for_opaque_type.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\nimport wibble\n\npub fn main(w: wibble.Wibble) {\n case w {}\n}\n" --- ----- BEFORE ACTION import wibble pub fn main(w: wibble.Wibble) { case w {} ↑ } ----- AFTER ACTION import wibble pub fn main(w: wibble.Wibble) { case w { _ -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_missing_patterns_adds_a_discard_for_opaque_type_1.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\nimport wibble\n\npub type Type {\n Type(wibble: wibble.Wibble, list: List(Int))\n}\n\npub fn main(thing: Type) {\n case thing {}\n}\n" --- ----- BEFORE ACTION import wibble pub type Type { Type(wibble: wibble.Wibble, list: List(Int)) } pub fn main(thing: Type) { case thing {} ↑ } ----- AFTER ACTION import wibble pub type Type { Type(wibble: wibble.Wibble, list: List(Int)) } pub fn main(thing: Type) { case thing { Type(wibble:, list:) -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_missing_patterns_adds_a_discard_for_opaque_type_2.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\nimport wibble\n\npub type Type {\n Type(wibble.Wibble)\n}\n\npub fn main(thing: Type) {\n case thing {}\n}\n" --- ----- BEFORE ACTION import wibble pub type Type { Type(wibble.Wibble) } pub fn main(thing: Type) { case thing {} ↑ } ----- AFTER ACTION import wibble pub type Type { Type(wibble.Wibble) } pub fn main(thing: Type) { case thing { Type(_) -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_missing_patterns_adds_patterns_for_internal_type_inside_same_module_where_it_is_defined.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\n@internal\npub type Wibble {\n Wibble(Int)\n Wobble(String)\n}\n\npub fn main(thing: Wibble) {\n case thing {}\n}\n" --- ----- BEFORE ACTION @internal pub type Wibble { Wibble(Int) Wobble(String) } pub fn main(thing: Wibble) { case thing {} ↑ } ----- AFTER ACTION @internal pub type Wibble { Wibble(Int) Wobble(String) } pub fn main(thing: Wibble) { case thing { Wibble(_) -> todo Wobble(_) -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_missing_patterns_bool.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main(bool: Bool) {\n case bool {}\n}\n" --- ----- BEFORE ACTION pub fn main(bool: Bool) { case bool {} ▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main(bool: Bool) { case bool { True -> todo False -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_missing_patterns_custom_type.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\ntype Wibble {\n Wibble\n Wobble\n Wubble\n}\n\npub fn main(wibble: Wibble) {\n case wibble {\n Wobble -> Nil\n }\n}\n" --- ----- BEFORE ACTION type Wibble { Wibble Wobble Wubble } pub fn main(wibble: Wibble) { case wibble { ▔▔▔▔▔↑ Wobble -> Nil } } ----- AFTER ACTION type Wibble { Wibble Wobble Wubble } pub fn main(wibble: Wibble) { case wibble { Wobble -> Nil Wibble -> todo Wubble -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_missing_patterns_infinite.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let value = 3\n case value {\n 1 -> \"one\"\n 2 -> \"two\"\n 3 -> \"three\"\n }\n}\n" --- ----- BEFORE ACTION pub fn main() { let value = 3 case value { ▔▔▔▔▔↑ 1 -> "one" 2 -> "two" 3 -> "three" } } ----- AFTER ACTION pub fn main() { let value = 3 case value { 1 -> "one" 2 -> "two" 3 -> "three" _ -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_missing_patterns_inline.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main(a: Bool) {\n let value = case a {}\n}\n" --- ----- BEFORE ACTION pub fn main(a: Bool) { let value = case a {} ▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main(a: Bool) { let value = case a { True -> todo False -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_missing_patterns_list.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let list = [1, 2, 3]\n case list {\n [a, b, c, 4 as d] -> d\n }\n}\n" --- ----- BEFORE ACTION pub fn main() { let list = [1, 2, 3] case list { ▔▔▔▔▔↑ [a, b, c, 4 as d] -> d } } ----- AFTER ACTION pub fn main() { let list = [1, 2, 3] case list { [a, b, c, 4 as d] -> d [] -> todo [_] -> todo [_, _] -> todo [_, _, _] -> todo [_, _, _, _] -> todo [_, _, _, _, _, ..] -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_missing_patterns_multi.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main(a: Bool) {\n let b = 1\n case a, b {\n\n }\n}\n" --- ----- BEFORE ACTION pub fn main(a: Bool) { let b = 1 case a, b { ▔▔▔▔▔▔▔▔↑ } } ----- AFTER ACTION pub fn main(a: Bool) { let b = 1 case a, b { True, _ -> todo False, _ -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_missing_patterns_multibyte_grapheme.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\n// ä\nfn wibble() {\n case True {}\n}\n" --- ----- BEFORE ACTION // ä fn wibble() { case True {} ▔▔▔▔▔↑ } ----- AFTER ACTION // ä fn wibble() { case True { True -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_missing_patterns_opaque_type.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport mod\n\npub fn main(w: mod.Wibble) {\n case w {}\n}\n" --- ----- BEFORE ACTION import mod pub fn main(w: mod.Wibble) { case w {} ↑ } ----- AFTER ACTION import mod pub fn main(w: mod.Wibble) { case w { _ -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_missing_patterns_tuple.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main(two_at_once: #(Bool, Result(Int, Nil))) {\n case two_at_once {\n #(False, Error(_)) -> Nil\n }\n}\n" --- ----- BEFORE ACTION pub fn main(two_at_once: #(Bool, Result(Int, Nil))) { case two_at_once { ▔▔▔▔▔↑ #(False, Error(_)) -> Nil } } ----- AFTER ACTION pub fn main(two_at_once: #(Bool, Result(Int, Nil))) { case two_at_once { #(False, Error(_)) -> Nil #(False, Ok(_)) -> todo #(True, _) -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_missing_patterns_with_labels.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble {\n Wibble(integer: Int, float: Float)\n Wobble(string: String, bool: Bool)\n}\n\npub fn main(w: Wibble) {\n case w {}\n}\n" --- ----- BEFORE ACTION pub type Wibble { Wibble(integer: Int, float: Float) Wobble(string: String, bool: Bool) } pub fn main(w: Wibble) { case w {} ▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub type Wibble { Wibble(integer: Int, float: Float) Wobble(string: String, bool: Bool) } pub fn main(w: Wibble) { case w { Wibble(integer:, float:) -> todo Wobble(string:, bool:) -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_missing_type_parameter_for_single_constructor.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\ntype Wibble {\n Wibble(field: t)\n}\n" --- ----- BEFORE ACTION type Wibble { Wibble(field: t) ↑ } ----- AFTER ACTION type Wibble(t) { Wibble(field: t) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_missing_type_parameter_preserves_comments.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\ntype Wibble(\n // Comment 1\n b,\n // Comment 2\n) {\n Wibble(a, b, c)\n}\n" --- ----- BEFORE ACTION type Wibble( // Comment 1 b, // Comment 2 ) { Wibble(a, b, c) ↑ } ----- AFTER ACTION type Wibble( // Comment 1 b, // Comment 2 a, c) { Wibble(a, b, c) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_missing_type_parameter_sorted_alphabetically.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\ntype Wibble(b) {\n Wibble(c, b, a)\n}\n" --- ----- BEFORE ACTION type Wibble(b) { Wibble(c, b, a) ↑ } ----- AFTER ACTION type Wibble(b, a, c) { Wibble(c, b, a) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_missing_type_parameter_to_exising_parameter.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\ntype Wibble(t) {\n Wibble(field: t)\n Wobble(field: u)\n}\n" --- ----- BEFORE ACTION type Wibble(t) { Wibble(field: t) Wobble(field: u) ↑ } ----- AFTER ACTION type Wibble(t, u) { Wibble(field: t) Wobble(field: u) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_multiple_annotations.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub const my_constant = 20\n\npub fn add_my_constant(value) {\n let result = value + my_constant\n result\n}\n" --- ----- BEFORE ACTION pub const my_constant = 20 ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ pub fn add_my_constant(value) { ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ let result = value + my_constant ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ result ▔▔▔▔▔▔▔▔ } ↑ ----- AFTER ACTION pub const my_constant: Int = 20 pub fn add_my_constant(value: Int) -> Int { let result: Int = value + my_constant result } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_omitted_labels_does_not_label_piped_argument.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n 1 |> labelled(2)\n}\n\npub fn labelled(a a, b b) { todo }\n " --- ----- BEFORE ACTION pub fn main() { 1 |> labelled(2) ↑ } pub fn labelled(a a, b b) { todo } ----- AFTER ACTION pub fn main() { 1 |> labelled(b: 2) } pub fn labelled(a a, b b) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_omitted_labels_does_not_label_use.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n use <- labelled(1)\n todo\n}\n\npub fn labelled(a a, b b) { todo }\n " --- ----- BEFORE ACTION pub fn main() { use <- labelled(1) ↑ todo } pub fn labelled(a a, b b) { todo } ----- AFTER ACTION pub fn main() { use <- labelled(a: 1) todo } pub fn labelled(a a, b b) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_omitted_labels_in_function_call.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n labelled(1, 2)\n}\n\npub fn labelled(a a, b b) { todo }\n " --- ----- BEFORE ACTION pub fn main() { labelled(1, 2) ↑ } pub fn labelled(a a, b b) { todo } ----- AFTER ACTION pub fn main() { labelled(a: 1, b: 2) } pub fn labelled(a a, b b) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_omitted_labels_in_function_call_uses_shorthand_syntax.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let a = 1\n labelled(a, 2)\n}\n\npub fn labelled(a a, b b) { todo }\n " --- ----- BEFORE ACTION pub fn main() { let a = 1 labelled(a, 2) ↑ } pub fn labelled(a a, b b) { todo } ----- AFTER ACTION pub fn main() { let a = 1 labelled(a:, b: 2) } pub fn labelled(a a, b b) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_omitted_labels_in_function_call_with_some_labels.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n labelled(1, 2)\n}\n\npub fn labelled(a, b b) { todo }\n " --- ----- BEFORE ACTION pub fn main() { labelled(1, 2) ↑ } pub fn labelled(a, b b) { todo } ----- AFTER ACTION pub fn main() { labelled(1, b: 2) } pub fn labelled(a, b b) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_omitted_labels_works_on_call_with_wrongly_placed_labels.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n labelled(3, b: 2, 1)\n}\n\npub fn labelled(a a, b b, c c) { todo }\n " --- ----- BEFORE ACTION pub fn main() { labelled(3, b: 2, 1) ↑ } pub fn labelled(a a, b b, c c) { todo } ----- AFTER ACTION pub fn main() { labelled(a: 3, b: 2, c: 1) } pub fn labelled(a a, b b, c c) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_omitted_labels_works_with_constructors_calls.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n Labelled(1, 2)\n}\n\npub type Labelled {\n Labelled(Int, b: Int)\n}\n " --- ----- BEFORE ACTION pub fn main() { Labelled(1, 2) ↑ } pub type Labelled { Labelled(Int, b: Int) } ----- AFTER ACTION pub fn main() { Labelled(1, b: 2) } pub type Labelled { Labelled(Int, b: Int) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_omitted_labels_works_with_constructors_calls_with_some_labels.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let a = 1\n labelled(a, b: 2)\n}\n\npub fn labelled(a a, b b) { todo }\n " --- ----- BEFORE ACTION pub fn main() { let a = 1 labelled(a, b: 2) ↑ } pub fn labelled(a a, b b) { todo } ----- AFTER ACTION pub fn main() { let a = 1 labelled(a:, b: 2) } pub fn labelled(a a, b b) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_omitted_labels_works_with_constructors_calls_with_some_labels_1.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n labelled(3, 1, b: 2)\n}\n\npub fn labelled(a a, b b, c c) { todo }\n " --- ----- BEFORE ACTION pub fn main() { labelled(3, 1, b: 2) ↑ } pub fn labelled(a a, b b, c c) { todo } ----- AFTER ACTION pub fn main() { labelled(a: 3, c: 1, b: 2) } pub fn labelled(a a, b b, c c) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_omitted_labels_works_with_innermost_function_call.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let a = 1\n labelled(a, labelled(1, 2))\n}\n\npub fn labelled(a a, b b) { todo }\n " --- ----- BEFORE ACTION pub fn main() { let a = 1 labelled(a, labelled(1, 2)) ↑ } pub fn labelled(a a, b b) { todo } ----- AFTER ACTION pub fn main() { let a = 1 labelled(a, labelled(a: 1, b: 2)) } pub fn labelled(a a, b b) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_type_annotations_public_alias_to_internal_generic_type.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport package\n\npub fn main() {\n package.make_wibble(10)\n}\n" --- ----- BEFORE ACTION import package pub fn main() { ↑ package.make_wibble(10) } ----- AFTER ACTION import package pub fn main() -> package.Wibble(Int, a) { package.make_wibble(10) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_type_annotations_public_alias_to_internal_type.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport package\n\npub fn main() {\n package.make_wibble()\n}\n" --- ----- BEFORE ACTION import package pub fn main() { ↑ package.make_wibble() } ----- AFTER ACTION import package pub fn main() -> package.Wibble { package.make_wibble() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_type_annotations_public_alias_to_internal_type_aliased_module.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport package as pkg\n\npub fn main() {\n pkg.make_wibble()\n}\n" --- ----- BEFORE ACTION import package as pkg pub fn main() { ↑ pkg.make_wibble() } ----- AFTER ACTION import package as pkg pub fn main() -> pkg.Wibble { pkg.make_wibble() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__add_type_annotations_uses_internal_name_for_same_package.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport thepackage/internal\n\npub fn main() {\n internal.Constructor\n}\n" --- ----- BEFORE ACTION import thepackage/internal pub fn main() { ↑ internal.Constructor } ----- AFTER ACTION import thepackage/internal pub fn main() -> internal.Internal { internal.Constructor } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__adding_annotations_correctly_prints_type_variables.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn map_result(input, function) {\n case input {\n Ok(value) -> Ok(function(value))\n Error(error) -> Error(error)\n }\n}\n" --- ----- BEFORE ACTION pub fn map_result(input, function) { ▔▔▔▔▔▔▔▔▔▔▔▔▔↑ case input { Ok(value) -> Ok(function(value)) Error(error) -> Error(error) } } ----- AFTER ACTION pub fn map_result(input: Result(a, b), function: fn(a) -> c) -> Result(c, b) { case input { Ok(value) -> Ok(function(value)) Error(error) -> Error(error) } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__adding_annotations_prints_contextual_types.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type IntAlias = Int\n\npub fn main() {\n let value = 20\n}\n" --- ----- BEFORE ACTION pub type IntAlias = Int pub fn main() { let value = 20 ▔▔▔▔↑ } ----- AFTER ACTION pub type IntAlias = Int pub fn main() { let value: IntAlias = 20 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__adding_annotations_prints_contextual_types2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Result\n\npub fn main() {\n let value = Ok(12)\n}\n" --- ----- BEFORE ACTION pub type Result pub fn main() { let value = Ok(12) ▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub type Result pub fn main() { let value: gleam.Result(Int, a) = Ok(12) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__adding_annotations_prints_contextual_types3.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport wibble\n\npub fn main() {\n let value = wibble.Wibble\n}\n" --- ----- BEFORE ACTION import wibble pub fn main() { let value = wibble.Wibble ▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION import wibble pub fn main() { let value: wibble.Wibble = wibble.Wibble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__adding_annotations_prints_contextual_types4.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport wibble as wobble\n\npub fn main() {\n let value = wobble.Wibble\n}\n" --- ----- BEFORE ACTION import wibble as wobble pub fn main() { let value = wobble.Wibble ▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION import wibble as wobble pub fn main() { let value: wobble.Wibble = wobble.Wibble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__adding_annotations_prints_contextual_types5.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport wibble.{type Wibble as Wobble}\n\npub fn main() {\n let value = wibble.Wibble\n}\n" --- ----- BEFORE ACTION import wibble.{type Wibble as Wobble} pub fn main() { let value = wibble.Wibble ▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION import wibble.{type Wibble as Wobble} pub fn main() { let value: Wobble = wibble.Wibble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__adding_annotations_prints_type_variable_names.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn do_generic_things(a: type_a, b: type_b) {\n let a_value = a\n let b_value = b\n let other_value = a_value\n}\n" --- ----- BEFORE ACTION pub fn do_generic_things(a: type_a, b: type_b) { let a_value = a ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ let b_value = b ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ let other_value = a_value ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ } ↑ ----- AFTER ACTION pub fn do_generic_things(a: type_a, b: type_b) { let a_value: type_a = a let b_value: type_b = b let other_value: type_a = a_value } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__allow_further_pattern_matching_on_asserted_list.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(x) {\n let assert [first, ..] = [Ok(Nil), ..todo]\n todo\n}\n" --- ----- BEFORE ACTION pub fn main(x) { let assert [first, ..] = [Ok(Nil), ..todo] ↑ todo } ----- AFTER ACTION pub fn main(x) { let assert [first, ..] = [Ok(Nil), ..todo] case first { Ok(value) -> todo Error(value) -> todo } todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__allow_further_pattern_matching_on_asserted_result.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(x) {\n let assert Ok(one) = Ok(Error(Nil))\n}\n" --- ----- BEFORE ACTION pub fn main(x) { let assert Ok(one) = Ok(Error(Nil)) ↑ } ----- AFTER ACTION pub fn main(x) { let assert Ok(one) = Ok(Error(Nil)) case one { Ok(value) -> todo Error(value) -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__allow_further_pattern_matching_on_let_record_destructuring.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(x) {\n let Wibble(field:) = Wibble(Ok(Nil))\n}\n\npub type Wibble { Wibble(field: Result(Nil, String)) }\n" --- ----- BEFORE ACTION pub fn main(x) { let Wibble(field:) = Wibble(Ok(Nil)) ↑ } pub type Wibble { Wibble(field: Result(Nil, String)) } ----- AFTER ACTION pub fn main(x) { let Wibble(field:) = Wibble(Ok(Nil)) case field { Ok(value) -> todo Error(value) -> todo } } pub type Wibble { Wibble(field: Result(Nil, String)) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__allow_further_pattern_matching_on_let_tuple_destructuring.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(x) {\n let #(one, other) = #(Ok(1), Error(Nil))\n}\n" --- ----- BEFORE ACTION pub fn main(x) { let #(one, other) = #(Ok(1), Error(Nil)) ↑ } ----- AFTER ACTION pub fn main(x) { let #(one, other) = #(Ok(1), Error(Nil)) case one { Ok(value) -> todo Error(value) -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__annotate_all_top_level_definitions_constant.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub const answer = 42\n\npub fn add_two(thing) {\n thing + 2\n}\n\npub fn add_one(thing) {\n thing + 1\n}\n" --- ----- BEFORE ACTION pub const answer = 42 ▔▔▔▔▔▔▔▔▔▔▔▔▔↑ pub fn add_two(thing) { thing + 2 } pub fn add_one(thing) { thing + 1 } ----- AFTER ACTION pub const answer: Int = 42 pub fn add_two(thing: Int) -> Int { thing + 2 } pub fn add_one(thing: Int) -> Int { thing + 1 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__annotate_all_top_level_definitions_dont_affect_local_vars.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs assertion_line: 11175 expression: "\npub const answer = 42\n\npub fn add_two(thing) {\n thing + 2\n}\n\npub fn add_one(thing) {\n let result = thing + 1\n result\n}\n" snapshot_kind: text --- ----- BEFORE ACTION pub const answer = 42 pub fn add_two(thing) { ▔▔▔▔▔▔▔▔▔▔↑ thing + 2 } pub fn add_one(thing) { let result = thing + 1 result } ----- AFTER ACTION pub const answer: Int = 42 pub fn add_two(thing: Int) -> Int { thing + 2 } pub fn add_one(thing: Int) -> Int { let result = thing + 1 result } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__annotate_all_top_level_definitions_function.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn add_two(thing) {\n thing + 2\n}\n\npub fn add_one(thing) {\n thing + 1\n}\n" --- ----- BEFORE ACTION pub fn add_two(thing) { ▔▔▔▔▔▔▔▔▔▔↑ thing + 2 } pub fn add_one(thing) { thing + 1 } ----- AFTER ACTION pub fn add_two(thing: Int) -> Int { thing + 2 } pub fn add_one(thing: Int) -> Int { thing + 1 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__annotate_all_top_level_definitions_partially_annotated.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub const answer: Int = 42\npub const another_answer = 43\n\npub fn add_two(thing) -> Int {\n thing + 2\n}\n\npub fn add_one(thing: Int) {\n thing + 1\n}\n" --- ----- BEFORE ACTION pub const answer: Int = 42 pub const another_answer = 43 pub fn add_two(thing) -> Int { ▔▔▔▔▔▔▔▔▔▔↑ thing + 2 } pub fn add_one(thing: Int) { thing + 1 } ----- AFTER ACTION pub const answer: Int = 42 pub const another_answer: Int = 43 pub fn add_two(thing: Int) -> Int { thing + 2 } pub fn add_one(thing: Int) -> Int { thing + 1 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__annotate_all_top_level_definitions_with_constant_and_generic_functions.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nconst answer = 42\n\nfn wibble(one) { todo }\n\nfn wobble(other) { todo }\n" --- ----- BEFORE ACTION const answer = 42 fn wibble(one) { todo } fn wobble(other) { todo } ↑ ----- AFTER ACTION const answer: Int = 42 fn wibble(one: a) -> b { todo } fn wobble(other: a) -> b { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__annotate_all_top_level_definitions_with_partially_annotated_generic_function.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn wibble(a: a, b, c: c, d) {\n todo\n}\n" --- ----- BEFORE ACTION pub fn wibble(a: a, b, c: c, d) { ↑ todo } ----- AFTER ACTION pub fn wibble(a: a, b: b, c: c, d: d) -> e { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__annotate_all_top_level_definitions_with_two_generic_functions.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nfn wibble(one) { todo }\n\nfn wobble(other) { todo }\n" --- ----- BEFORE ACTION fn wibble(one) { todo } fn wobble(other) { todo } ↑ ----- AFTER ACTION fn wibble(one: a) -> b { todo } fn wobble(other: a) -> b { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__annotate_anonymous_function.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn add_curry(a) {\n fn(b) { a + b }\n}\n" --- ----- BEFORE ACTION pub fn add_curry(a) { fn(b) { a + b } ▔▔▔↑ } ----- AFTER ACTION pub fn add_curry(a) { fn(b: Int) -> Int { a + b } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__annotate_anonymous_function_with_annotated_return_type.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn add_curry(a) {\n fn(b) -> Int { a + b }\n}\n" --- ----- BEFORE ACTION pub fn add_curry(a) { fn(b) -> Int { a + b } ▔▔▔↑ } ----- AFTER ACTION pub fn add_curry(a) { fn(b: Int) -> Int { a + b } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__annotate_anonymous_function_with_partially_annotated_parameters.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n fn(a, b: Int, c) { a + b + c }\n}\n" --- ----- BEFORE ACTION pub fn main() { fn(a, b: Int, c) { a + b + c } ▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { fn(a: Int, b: Int, c: Int) -> Int { a + b + c } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__annotate_constant.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub const my_constant = 20\n" --- ----- BEFORE ACTION pub const my_constant = 20 ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ ----- AFTER ACTION pub const my_constant: Int = 20 ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__annotate_function.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn add_one(thing) {\n thing + 1\n}\n" --- ----- BEFORE ACTION pub fn add_one(thing) { ▔▔▔▔▔▔▔▔▔▔↑ thing + 1 } ----- AFTER ACTION pub fn add_one(thing: Int) -> Int { thing + 1 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__annotate_function_with_annotated_return_type.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn add_one(thing) -> Int {\n thing + 1\n}\n" --- ----- BEFORE ACTION pub fn add_one(thing) -> Int { ▔▔▔▔▔▔▔▔▔▔↑ thing + 1 } ----- AFTER ACTION pub fn add_one(thing: Int) -> Int { thing + 1 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__annotate_function_with_partially_annotated_parameters.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn add(a: Float, b) -> Float {\n a +. b\n}\n" --- ----- BEFORE ACTION pub fn add(a: Float, b) -> Float { ▔▔▔▔▔▔↑ a +. b } ----- AFTER ACTION pub fn add(a: Float, b: Float) -> Float { a +. b } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__annotate_local_variable.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let my_value = 10\n}\n" --- ----- BEFORE ACTION pub fn main() { let my_value = 10 ▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let my_value: Int = 10 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__annotate_local_variable_let_assert.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn fallible() -> Result(Int, Nil) {\n todo\n}\n\npub fn main() {\n let assert Ok(value) = fallible()\n}\n" --- ----- BEFORE ACTION pub fn fallible() -> Result(Int, Nil) { todo } pub fn main() { let assert Ok(value) = fallible() ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn fallible() -> Result(Int, Nil) { todo } pub fn main() { let assert Ok(value): Result(Int, Nil) = fallible() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__annotate_local_variable_with_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\ntype Wibble {\n Wibble(a: Int, b: Int, c: Int)\n}\n\npub fn main() {\n let Wibble(a, b, c) = Wibble(1, 2, 3)\n}\n" --- ----- BEFORE ACTION type Wibble { Wibble(a: Int, b: Int, c: Int) } pub fn main() { let Wibble(a, b, c) = Wibble(1, 2, 3) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION type Wibble { Wibble(a: Int, b: Int, c: Int) } pub fn main() { let Wibble(a, b, c): Wibble = Wibble(1, 2, 3) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__annotate_local_variable_with_pattern2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main(values) {\n let #(left, right) = values\n}\n" --- ----- BEFORE ACTION pub fn main(values) { let #(left, right) = values ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main(values) { let #(left, right): #(a, b) = values } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__annotate_nested_local_variable.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let a = {\n let b = 10\n b + 1\n }\n}\n" --- ----- BEFORE ACTION pub fn main() { let a = { let b = 10 ▔▔▔▔↑ b + 1 } } ----- AFTER ACTION pub fn main() { let a = { let b: Int = 10 b + 1 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__annotate_use.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn wibble(wobble: fn(Int, Int) -> Int) {\n wobble(1, 2)\n}\n\npub fn main() {\n use a, b <- wibble\n a + b\n}\n" --- ----- BEFORE ACTION pub fn wibble(wobble: fn(Int, Int) -> Int) { wobble(1, 2) } pub fn main() { use a, b <- wibble ▔▔▔▔▔▔▔▔▔↑ a + b } ----- AFTER ACTION pub fn wibble(wobble: fn(Int, Int) -> Int) { wobble(1, 2) } pub fn main() { use a: Int, b: Int <- wibble a + b } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__annotate_use_with_partially_annotated_parameters.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn wibble(wobble: fn(Int, Int) -> Int) {\n wobble(1, 2)\n}\n\npub fn main() {\n use a: Int, b <- wibble\n a + b\n}\n" --- ----- BEFORE ACTION pub fn wibble(wobble: fn(Int, Int) -> Int) { wobble(1, 2) } pub fn main() { use a: Int, b <- wibble ▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ a + b } ----- AFTER ACTION pub fn wibble(wobble: fn(Int, Int) -> Int) { wobble(1, 2) } pub fn main() { use a: Int, b: Int <- wibble a + b } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__assign_unused_result.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let x = 1\n Ok(x)\n Nil\n}\n" --- ----- BEFORE ACTION pub fn main() { let x = 1 Ok(x) ▔▔↑ Nil } ----- AFTER ACTION pub fn main() { let x = 1 let _ = Ok(x) Nil } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__assign_unused_result_in_block.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n {\n let x = 1\n Ok(x)\n Nil\n }\n Nil\n}\n" --- ----- BEFORE ACTION pub fn main() { { let x = 1 Ok(x) ▔▔↑ Nil } Nil } ----- AFTER ACTION pub fn main() { { let x = 1 let _ = Ok(x) Nil } Nil } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__assign_unused_result_on_block_end.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n {\n let x = 1\n Ok(x)\n Ok(x)\n }\n Nil\n}\n" --- ----- BEFORE ACTION pub fn main() { { let x = 1 Ok(x) Ok(x) } ↑ Nil } ----- AFTER ACTION pub fn main() { let _ = { let x = 1 Ok(x) Ok(x) } Nil } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__assign_unused_result_on_block_start.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n {\n let x = 1\n Ok(x)\n Ok(x)\n }\n Nil\n}\n" --- ----- BEFORE ACTION pub fn main() { { ↑ let x = 1 Ok(x) Ok(x) } Nil } ----- AFTER ACTION pub fn main() { let _ = { let x = 1 Ok(x) Ok(x) } Nil } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__assign_unused_result_only_first_action.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let x = 1\n Ok(x)\n Ok(x)\n Nil\n}\n" --- ----- BEFORE ACTION pub fn main() { let x = 1 Ok(x) ▔▔↑ Ok(x) Nil } ----- AFTER ACTION pub fn main() { let x = 1 let _ = Ok(x) Ok(x) Nil } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__collapse_nested_case.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(x) {\n case x {\n Ok(var) -> case var {\n 1 -> 2\n 2 -> 4\n _ -> -1\n }\n _ -> todo\n }\n}" --- ----- BEFORE ACTION pub fn main(x) { case x { Ok(var) -> case var { ↑ 1 -> 2 2 -> 4 _ -> -1 } _ -> todo } } ----- AFTER ACTION pub fn main(x) { case x { Ok(1) -> 2 Ok(2) -> 4 Ok(_) -> -1 _ -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__collapse_nested_case_aliases_variable_if_it_is_used.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(x) {\n case x {\n [first, ..rest] ->\n case first {\n 1 | 2 -> first\n 3 | 4 | 5 -> 5\n _ -> 0\n }\n\n [] -> -1\n }\n}\n" --- ----- BEFORE ACTION pub fn main(x) { case x { [first, ..rest] -> ↑ case first { 1 | 2 -> first 3 | 4 | 5 -> 5 _ -> 0 } [] -> -1 } } ----- AFTER ACTION pub fn main(x) { case x { [1 as first, ..rest] | [2 as first, ..rest] -> first [3, ..rest] | [4, ..rest] | [5, ..rest] -> 5 [_, ..rest] -> 0 [] -> -1 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__collapse_nested_case_combines_inner_and_outer_guards.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(x) {\n case x {\n [first, ..rest] if False ->\n case first {\n 1 if False -> 1.1\n _ if True -> 0.0 *. 10.0\n _ -> 0.0\n }\n\n [] -> 1.1\n }\n}\n" --- ----- BEFORE ACTION pub fn main(x) { case x { [first, ..rest] if False -> ↑ case first { 1 if False -> 1.1 _ if True -> 0.0 *. 10.0 _ -> 0.0 } [] -> 1.1 } } ----- AFTER ACTION pub fn main(x) { case x { [1, ..rest] if False && False -> 1.1 [_, ..rest] if False && True -> 0.0 *. 10.0 [_, ..rest] if False -> 0.0 [] -> 1.1 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__collapse_nested_case_combines_inner_and_outer_guards_and_adds_parentheses_when_needed.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(x) {\n case x {\n [first, ..rest] if False || True ->\n case first {\n 1 if False && True -> 1.1\n _ if True || False -> 0.0 *. 10.0\n _ -> 0.0\n }\n\n [] -> 1.1\n }\n}\n" --- ----- BEFORE ACTION pub fn main(x) { case x { [first, ..rest] if False || True -> ↑ case first { 1 if False && True -> 1.1 _ if True || False -> 0.0 *. 10.0 _ -> 0.0 } [] -> 1.1 } } ----- AFTER ACTION pub fn main(x) { case x { [1, ..rest] if { False || True } && False && True -> 1.1 [_, ..rest] if { False || True } && { True || False } -> 0.0 *. 10.0 [_, ..rest] if False || True -> 0.0 [] -> 1.1 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__collapse_nested_case_combines_list_with_tail.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(elems: List(List(Int))) {\n case elems {\n [] -> 0\n [_, ..tail] ->\n case tail {\n [] -> -1\n [[1]] -> 1\n [_, [3, 4]] -> 3\n [_, _, [5, 6, 7], _] -> 5\n tail_list -> {\n echo tail_list\n -1\n }\n }\n _ -> -1\n }\n}" --- ----- BEFORE ACTION pub fn main(elems: List(List(Int))) { case elems { [] -> 0 [_, ..tail] -> ↑ case tail { [] -> -1 [[1]] -> 1 [_, [3, 4]] -> 3 [_, _, [5, 6, 7], _] -> 5 tail_list -> { echo tail_list -1 } } _ -> -1 } } ----- AFTER ACTION pub fn main(elems: List(List(Int))) { case elems { [] -> 0 [_, ] -> -1 [_, [1]] -> 1 [_, _, [3, 4]] -> 3 [_, _, _, [5, 6, 7], _] -> 5 [_, ..tail_list] -> { echo tail_list -1 } _ -> -1 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__collapse_nested_case_combines_list_with_unformatted_tail.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(elems: List(String)) {\n case elems {\n [first, .. rest_of_list] ->\n case rest_of_list {\n [] -> 0\n [\"first\"] -> 1\n [_, \"second\"] -> 2\n [_, \"second\", \"third\", _] -> 4\n _ -> -1\n }\n _ -> -1\n }\n}" --- ----- BEFORE ACTION pub fn main(elems: List(String)) { case elems { [first, .. rest_of_list] -> ↑ case rest_of_list { [] -> 0 ["first"] -> 1 [_, "second"] -> 2 [_, "second", "third", _] -> 4 _ -> -1 } _ -> -1 } } ----- AFTER ACTION pub fn main(elems: List(String)) { case elems { [first, ] -> 0 [first, "first"] -> 1 [first, _, "second"] -> 2 [first, _, "second", "third", _] -> 4 [first, _] -> -1 _ -> -1 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__collapse_nested_case_does_not_ignore_inner_guards.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(x) {\n case x {\n [first, ..rest] ->\n case first {\n 1 -> 1.1\n _ if True -> 0.0 *. 10.0\n _ -> 0.0\n }\n\n [] -> 1.1\n }\n}\n" --- ----- BEFORE ACTION pub fn main(x) { case x { [first, ..rest] -> ↑ case first { 1 -> 1.1 _ if True -> 0.0 *. 10.0 _ -> 0.0 } [] -> 1.1 } } ----- AFTER ACTION pub fn main(x) { case x { [1, ..rest] -> 1.1 [_, ..rest] if True -> 0.0 *. 10.0 [_, ..rest] -> 0.0 [] -> 1.1 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__collapse_nested_case_does_not_ignore_outer_guards.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(x) {\n case x {\n [first, ..rest] if True ->\n case first {\n 1 -> 1.1\n _ -> 0.0 *. 10.0\n }\n\n [] -> 1.1\n }\n}\n" --- ----- BEFORE ACTION pub fn main(x) { case x { [first, ..rest] if True -> ↑ case first { 1 -> 1.1 _ -> 0.0 *. 10.0 } [] -> 1.1 } } ----- AFTER ACTION pub fn main(x) { case x { [1, ..rest] if True -> 1.1 [_, ..rest] if True -> 0.0 *. 10.0 [] -> 1.1 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__collapse_nested_case_does_not_remove_labels.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(x) {\n case x {\n Wibble(field2:, field: wibble) ->\n case wibble {\n 1 -> 2\n 2 -> 4\n _ -> -1\n }\n\n Wobble -> todo\n }\n}\n\npub type Wibble {\n Wibble(field: Int, field2: String)\n Wobble\n}\n" --- ----- BEFORE ACTION pub fn main(x) { case x { Wibble(field2:, field: wibble) -> ↑ case wibble { 1 -> 2 2 -> 4 _ -> -1 } Wobble -> todo } } pub type Wibble { Wibble(field: Int, field2: String) Wobble } ----- AFTER ACTION pub fn main(x) { case x { Wibble(field2:, field: 1) -> 2 Wibble(field2:, field: 2) -> 4 Wibble(field2:, field: _) -> -1 Wobble -> todo } } pub type Wibble { Wibble(field: Int, field2: String) Wobble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__collapse_nested_case_does_not_remove_labels_with_shorthand_syntax.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(x) {\n case x {\n Wibble(field2:, field:) ->\n case field {\n 1 -> 2\n 2 -> 4\n _ -> -1\n }\n\n Wobble -> todo\n }\n}\n\npub type Wibble {\n Wibble(field: Int, field2: String)\n Wobble\n}\n" --- ----- BEFORE ACTION pub fn main(x) { case x { Wibble(field2:, field:) -> ↑ case field { 1 -> 2 2 -> 4 _ -> -1 } Wobble -> todo } } pub type Wibble { Wibble(field: Int, field2: String) Wobble } ----- AFTER ACTION pub fn main(x) { case x { Wibble(field2:, field: 1) -> 2 Wibble(field2:, field: 2) -> 4 Wibble(field2:, field: _) -> -1 Wobble -> todo } } pub type Wibble { Wibble(field: Int, field2: String) Wobble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__collapse_nested_case_works_with_alternative_patterns.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(x) {\n case x {\n [first, ..rest] ->\n case first {\n 1 | 2 -> True\n 3 | 4 | 5 -> False\n _ -> False\n }\n\n [] -> True\n }\n}\n" --- ----- BEFORE ACTION pub fn main(x) { case x { [first, ..rest] -> ↑ case first { 1 | 2 -> True 3 | 4 | 5 -> False _ -> False } [] -> True } } ----- AFTER ACTION pub fn main(x) { case x { [1, ..rest] | [2, ..rest] -> True [3, ..rest] | [4, ..rest] | [5, ..rest] -> False [_, ..rest] -> False [] -> True } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__collapse_nested_case_works_with_blocks.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(x) {\n case x {\n Ok(var) -> {\n case var {\n 1 -> 2\n 2 -> 4\n _ -> -1\n }\n }\n _ -> todo\n }\n}" --- ----- BEFORE ACTION pub fn main(x) { case x { Ok(var) -> { ↑ case var { 1 -> 2 2 -> 4 _ -> -1 } } _ -> todo } } ----- AFTER ACTION pub fn main(x) { case x { Ok(1) -> 2 Ok(2) -> 4 Ok(_) -> -1 _ -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__collapse_nested_case_works_with_patterns_defining_multiple_variables.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(x) {\n case x {\n Wibble(var, var2) ->\n case var {\n 1 -> 2\n 2 -> 4\n _ -> -1\n }\n\n Wobble -> todo\n }\n}\n\npub type Wibble {\n Wibble(Int, String)\n Wobble\n}\n" --- ----- BEFORE ACTION pub fn main(x) { case x { Wibble(var, var2) -> ↑ case var { 1 -> 2 2 -> 4 _ -> -1 } Wobble -> todo } } pub type Wibble { Wibble(Int, String) Wobble } ----- AFTER ACTION pub fn main(x) { case x { Wibble(1, var2) -> 2 Wibble(2, var2) -> 4 Wibble(_, var2) -> -1 Wobble -> todo } } pub type Wibble { Wibble(Int, String) Wobble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_assert_custom_type_with_label_shorthands_to_case.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble { Wibble(arg: Int, arg2: Float) }\npub fn main() {\n let assert Wibble(arg2:, ..) = Wibble(arg: 1, arg2: 1.0)\n}\n" --- ----- BEFORE ACTION pub type Wibble { Wibble(arg: Int, arg2: Float) } pub fn main() { let assert Wibble(arg2:, ..) = Wibble(arg: 1, arg2: 1.0) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub type Wibble { Wibble(arg: Int, arg2: Float) } pub fn main() { let arg2 = case Wibble(arg: 1, arg2: 1.0) { Wibble(arg2:, ..) -> arg2 _ -> panic } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_assert_result_to_case.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let assert Ok(value) = Ok(1)\n}" --- ----- BEFORE ACTION pub fn main() { let assert Ok(value) = Ok(1) ▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let value = case Ok(1) { Ok(value) -> value _ -> panic } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_from_use_expression_with_empty_parens.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n use <- wibble()\n todo\n todo\n}\n\nfn wibble(f) {\n f()\n}\n" --- ----- BEFORE ACTION pub fn main() { use <- wibble() ↑ todo todo } fn wibble(f) { f() } ----- AFTER ACTION pub fn main() { wibble(fn() { todo todo }) } fn wibble(f) { f() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_from_use_expression_with_multiple_patterns.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n use a, b <- wibble(1, 2)\n todo\n todo\n}\n\nfn wibble(n, m, f) {\n f(1, 2)\n}\n" --- ----- BEFORE ACTION pub fn main() { use a, b <- wibble(1, 2) ↑ todo todo } fn wibble(n, m, f) { f(1, 2) } ----- AFTER ACTION pub fn main() { wibble(1, 2, fn(a, b) { todo todo }) } fn wibble(n, m, f) { f(1, 2) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_from_use_expression_with_no_parens.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n use <- wibble\n todo\n todo\n}\n\nfn wibble(f) {\n f()\n}\n" --- ----- BEFORE ACTION pub fn main() { use <- wibble ▔▔▔▔▔▔▔↑ todo todo } fn wibble(f) { f() } ----- AFTER ACTION pub fn main() { wibble(fn() { todo todo }) } fn wibble(f) { f() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_from_use_expression_with_parens_and_other_args.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n use <- wibble(1, 2)\n todo\n todo\n}\n\nfn wibble(n, m, f) {\n f()\n}\n" --- ----- BEFORE ACTION pub fn main() { use <- wibble(1, 2) ▔▔▔▔▔▔▔↑ todo todo } fn wibble(n, m, f) { f() } ----- AFTER ACTION pub fn main() { wibble(1, 2, fn() { todo todo }) } fn wibble(n, m, f) { f() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_from_use_expression_with_single_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n use a <- wibble(1, 2)\n todo\n todo\n}\n\nfn wibble(n, m, f) {\n f(1)\n}\n" --- ----- BEFORE ACTION pub fn main() { use a <- wibble(1, 2) ↑ todo todo } fn wibble(n, m, f) { f(1) } ----- AFTER ACTION pub fn main() { wibble(1, 2, fn(a) { todo todo }) } fn wibble(n, m, f) { f(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_from_use_expression_with_type_annotations.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n use a: Int, b: Int <- wibble(1, 2)\n todo\n}\n\nfn wibble(n, m, f) {\n f(1, 2)\n}\n" --- ----- BEFORE ACTION pub fn main() { use a: Int, b: Int <- wibble(1, 2) ▔▔▔↑ todo } fn wibble(n, m, f) { f(1, 2) } ----- AFTER ACTION pub fn main() { wibble(1, 2, fn(a: Int, b: Int) { todo }) } fn wibble(n, m, f) { f(1, 2) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_from_use_multiline_with_no_trailing_comma.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n use a, b <- wibble(\n 1,\n 2\n )\n todo\n}\n\nfn wibble(n, m, f) {\n f(todo, todo)\n}\n" --- ----- BEFORE ACTION pub fn main() { use a, b <- wibble( ▔▔▔↑ 1, 2 ) todo } fn wibble(n, m, f) { f(todo, todo) } ----- AFTER ACTION pub fn main() { wibble( 1, 2 , fn(a, b) { todo }) } fn wibble(n, m, f) { f(todo, todo) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_from_use_with_labels.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n use a <- wibble(one: 1, two: 2)\n todo\n}\n\nfn wibble(one _, two _, three f) {\n f(1)\n}\n" --- ----- BEFORE ACTION pub fn main() { use a <- wibble(one: 1, two: 2) ↑ todo } fn wibble(one _, two _, three f) { f(1) } ----- AFTER ACTION pub fn main() { wibble(one: 1, two: 2, three: fn(a) { todo }) } fn wibble(one _, two _, three f) { f(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_from_use_with_labels_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n use a <- wibble(1, two: 2)\n todo\n}\n\nfn wibble(one _, two _, three f) {\n f(1)\n}\n" --- ----- BEFORE ACTION pub fn main() { use a <- wibble(1, two: 2) ↑ todo } fn wibble(one _, two _, three f) { f(1) } ----- AFTER ACTION pub fn main() { wibble(1, two: 2, three: fn(a) { todo }) } fn wibble(one _, two _, three f) { f(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_from_use_with_labels_3.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n use a <- wibble(1, three: 3)\n todo\n}\n\nfn wibble(one _, two f, three _) {\n f(1)\n}\n" --- ----- BEFORE ACTION pub fn main() { use a <- wibble(1, three: 3) ↑ todo } fn wibble(one _, two f, three _) { f(1) } ----- AFTER ACTION pub fn main() { wibble(1, three: 3, two: fn(a) { todo }) } fn wibble(one _, two f, three _) { f(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_from_use_with_labels_4.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n use a <- wibble(two: 2, three: 3)\n todo\n}\n\nfn wibble(one f, two _, three _) {\n f(1)\n}\n" --- ----- BEFORE ACTION pub fn main() { use a <- wibble(two: 2, three: 3) ↑ todo } fn wibble(one f, two _, three _) { f(1) } ----- AFTER ACTION pub fn main() { wibble(two: 2, three: 3, one: fn(a) { todo }) } fn wibble(one f, two _, three _) { f(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_from_use_with_trailing_comma.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n use a, b <- wibble(1, 2,)\n todo\n}\n\nfn wibble(n, m, f) {\n f(todo, todo)\n}\n" --- ----- BEFORE ACTION pub fn main() { use a, b <- wibble(1, 2,) ▔▔▔↑ todo } fn wibble(n, m, f) { f(todo, todo) } ----- AFTER ACTION pub fn main() { wibble(1, 2, fn(a, b) { todo }) } fn wibble(n, m, f) { f(todo, todo) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_from_use_with_trailing_comma_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n use a, b <- wibble(\n 1,\n 2,\n )\n todo\n}\n\nfn wibble(n, m, f) {\n f(todo, todo)\n}\n" --- ----- BEFORE ACTION pub fn main() { use a, b <- wibble( ▔▔▔↑ 1, 2, ) todo } fn wibble(n, m, f) { f(todo, todo) } ----- AFTER ACTION pub fn main() { wibble( 1, 2, fn(a, b) { todo }) } fn wibble(n, m, f) { f(todo, todo) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_from_use_with_trailing_comma_and_label.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n use a, b <- wibble(\n 1,\n wibble: 2,\n )\n todo\n}\n\nfn wibble(n, wibble m, wobble f) {\n f(todo, todo)\n}\n" --- ----- BEFORE ACTION pub fn main() { use a, b <- wibble( ▔▔▔↑ 1, wibble: 2, ) todo } fn wibble(n, wibble m, wobble f) { f(todo, todo) } ----- AFTER ACTION pub fn main() { wibble( 1, wibble: 2, wobble: fn(a, b) { todo }) } fn wibble(n, wibble m, wobble f) { f(todo, todo) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_inner_let_assert_to_case.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let assert [wibble] = {\n let assert Ok(wobble) = {\n Ok(1)\n }\n [wobble]\n }\n}" --- ----- BEFORE ACTION pub fn main() { let assert [wibble] = { let assert Ok(wobble) = { ↑ Ok(1) } [wobble] } } ----- AFTER ACTION pub fn main() { let assert [wibble] = { let wobble = case { Ok(1) } { Ok(wobble) -> wobble _ -> panic } [wobble] } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_let_assert_alias_to_case.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let assert 10 as ten = 10\n}" --- ----- BEFORE ACTION pub fn main() { let assert 10 as ten = 10 ▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let ten = case 10 { 10 as ten -> ten _ -> panic } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_let_assert_bit_array_to_case.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let assert <> = <<73, 98>>\n}" --- ----- BEFORE ACTION pub fn main() { let assert <> = <<73, 98>> ▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let #(bits1, bits2) = case <<73, 98>> { <> -> #(bits1, bits2) _ -> panic } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_let_assert_string_prefix_pattern_alias_to_case.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let assert \"123\" as one_two_three <> rest = \"123456\"\n}" --- ----- BEFORE ACTION pub fn main() { let assert "123" as one_two_three <> rest = "123456" ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let #(one_two_three, rest) = case "123456" { "123" as one_two_three <> rest -> #(one_two_three, rest) _ -> panic } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_let_assert_string_prefix_to_case.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let assert \"_\" <> thing = \"_Hello\"\n}" --- ----- BEFORE ACTION pub fn main() { let assert "_" <> thing = "_Hello" ↑ } ----- AFTER ACTION pub fn main() { let thing = case "_Hello" { "_" <> thing -> thing _ -> panic } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_let_assert_to_case_discard.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let assert [_elem] = [6]\n}" --- ----- BEFORE ACTION pub fn main() { let assert [_elem] = [6] ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let _ = case [6] { [_elem] -> Nil _ -> panic } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_let_assert_to_case_indented.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n {\n let assert Ok(value) = Ok(1)\n }\n}" --- ----- BEFORE ACTION pub fn main() { { let assert Ok(value) = Ok(1) ↑ } } ----- AFTER ACTION pub fn main() { { let value = case Ok(1) { Ok(value) -> value _ -> panic } } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_let_assert_to_case_multi_variables.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let assert [var1, var2, _var3, var4] = [1, 2, 3, 4]\n}" --- ----- BEFORE ACTION pub fn main() { let assert [var1, var2, _var3, var4] = [1, 2, 3, 4] ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let #(var1, var2, var4) = case [1, 2, 3, 4] { [var1, var2, _var3, var4] -> #(var1, var2, var4) _ -> panic } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_let_assert_to_case_no_variables.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let assert [] = []\n}" --- ----- BEFORE ACTION pub fn main() { let assert [] = [] ↑ } ----- AFTER ACTION pub fn main() { let _ = case [] { [] -> Nil _ -> panic } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_let_assert_tuple_to_case.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let assert #(first, 10, third) = #(5, 10, 15)\n}\n" --- ----- BEFORE ACTION pub fn main() { let assert #(first, 10, third) = #(5, 10, 15) ↑ } ----- AFTER ACTION pub fn main() { let #(first, third) = case #(5, 10, 15) { #(first, 10, third) -> #(first, third) _ -> panic } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_let_assert_with_message_to_case.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn expect(value, message) {\n let assert Ok(inner) = value as message\n inner\n}\n" --- ----- BEFORE ACTION pub fn expect(value, message) { let assert Ok(inner) = value as message ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ inner } ----- AFTER ACTION pub fn expect(value, message) { let inner = case value { Ok(inner) -> inner _ -> panic as message } inner } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_outer_let_assert_to_case.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let assert [wibble] = {\n let assert Ok(wobble) = {\n Ok(1)\n }\n [wobble]\n }\n}" --- ----- BEFORE ACTION pub fn main() { let assert [wibble] = { ▔▔▔▔▔▔▔↑ let assert Ok(wobble) = { Ok(1) } [wobble] } } ----- AFTER ACTION pub fn main() { let wibble = case { let assert Ok(wobble) = { Ok(1) } [wobble] } { [wibble] -> wibble _ -> panic } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_function_call_always_inlines_the_first_step.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n [1, 2, 3]\n |> map(todo)\n |> filter(todo)\n}\n\nfn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo }\nfn filter(list: List(a), fun: fn(a) -> Bool) -> List(b) { todo }\n" --- ----- BEFORE ACTION pub fn main() { [1, 2, 3] ▔▔▔▔▔▔▔▔▔ |> map(todo) ▔▔▔▔▔↑ |> filter(todo) } fn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo } fn filter(list: List(a), fun: fn(a) -> Bool) -> List(b) { todo } ----- AFTER ACTION pub fn main() { map([1, 2, 3], todo) |> filter(todo) } fn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo } fn filter(list: List(a), fun: fn(a) -> Bool) -> List(b) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_function_call_works_when_piping_a_module_select.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport wibble\n\npub fn main() {\n wibble.wobble |> woo(_)\n}\n\nfn woo(n) { todo }\n" --- ----- BEFORE ACTION import wibble pub fn main() { wibble.wobble |> woo(_) ↑ } fn woo(n) { todo } ----- AFTER ACTION import wibble pub fn main() { woo(wibble.wobble) } fn woo(n) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_function_call_works_when_piping_an_invalid_module_select.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble.wobble |> woo(_)\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble.wobble |> woo(_) ↑ } ----- AFTER ACTION pub fn main() { woo(wibble.wobble) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_function_call_works_with_argument_in_first_position.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n [1, 2, 3]\n |> map(todo)\n}\n\nfn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo }\n" --- ----- BEFORE ACTION pub fn main() { [1, 2, 3] |> map(todo) ↑ } fn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo } ----- AFTER ACTION pub fn main() { map([1, 2, 3], todo) } fn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_function_call_works_with_argument_in_first_position_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n [1, 2, 3] |> wibble\n}\n\nfn wibble(a) { todo }\n" --- ----- BEFORE ACTION pub fn main() { [1, 2, 3] |> wibble ↑ } fn wibble(a) { todo } ----- AFTER ACTION pub fn main() { wibble([1, 2, 3]) } fn wibble(a) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_function_call_works_with_argument_in_first_position_3.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n [1, 2, 3] |> wibble()\n}\n\nfn wibble(a) { todo }\n" --- ----- BEFORE ACTION pub fn main() { [1, 2, 3] |> wibble() ↑ } fn wibble(a) { todo } ----- AFTER ACTION pub fn main() { wibble([1, 2, 3]) } fn wibble(a) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_function_call_works_with_argument_in_first_position_4.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n [1, 2, 3] |> wibble.wobble\n}\n" --- ----- BEFORE ACTION pub fn main() { [1, 2, 3] |> wibble.wobble ↑ } ----- AFTER ACTION pub fn main() { wibble.wobble([1, 2, 3]) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_function_call_works_with_echo.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble.wobble |> echo\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble.wobble |> echo ↑ } ----- AFTER ACTION pub fn main() { echo wibble.wobble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_function_call_works_with_function_producing_another_function.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n 1 |> wibble(2)\n}\n\nfn wibble(c) -> fn(a) -> Nil {\n fn(_) { Nil }\n}\n" --- ----- BEFORE ACTION pub fn main() { 1 |> wibble(2) ↑ } fn wibble(c) -> fn(a) -> Nil { fn(_) { Nil } } ----- AFTER ACTION pub fn main() { wibble(2)(1) } fn wibble(c) -> fn(a) -> Nil { fn(_) { Nil } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_function_call_works_with_hole_in_first_position.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n [1, 2, 3]\n |> map(_, todo)\n}\n\nfn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo }\n" --- ----- BEFORE ACTION pub fn main() { [1, 2, 3] ↑ |> map(_, todo) } fn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo } ----- AFTER ACTION pub fn main() { map([1, 2, 3], todo) } fn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_function_call_works_with_hole_not_in_first_position.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n fn(a) { todo }\n |> map([1, 2, 3], _)\n}\n\nfn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo }\n" --- ----- BEFORE ACTION pub fn main() { fn(a) { todo } ▔▔▔▔▔▔▔▔▔▔▔▔▔▔ |> map([1, 2, 3], _) ▔▔▔▔▔↑ } fn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo } ----- AFTER ACTION pub fn main() { map([1, 2, 3], fn(a) { todo }) } fn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_function_call_works_with_labelled_argument.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n [1, 2, 3] |> wibble(wobble: _, woo:)\n}\n" --- ----- BEFORE ACTION pub fn main() { [1, 2, 3] |> wibble(wobble: _, woo:) ↑ } ----- AFTER ACTION pub fn main() { wibble(wobble: [1, 2, 3], woo:) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_function_call_works_with_labelled_argument_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n [1, 2, 3] |> wibble(wobble:, woo: _)\n}\n" --- ----- BEFORE ACTION pub fn main() { [1, 2, 3] |> wibble(wobble:, woo: _) ↑ } ----- AFTER ACTION pub fn main() { wibble(wobble:, woo: [1, 2, 3]) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_pipe_on_first_step_of_pipeline.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(wobble, woo) |> wobble\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble(wobble, woo) |> wobble ↑ } ----- AFTER ACTION pub fn main() { wobble |> wibble(woo) |> wobble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_pipe_pipes_the_outermost_argument.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub fn main() {\n wibble(wobble(woo))\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble(wobble(woo)) ↑ } ----- AFTER ACTION pub fn main() { wibble(woo |> wobble) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_pipe_when_first_arg_is_a_pipe_itself.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(wobble |> woo, waa)\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble(wobble |> woo, waa) ↑ } ----- AFTER ACTION pub fn main() { wobble |> woo |> wibble(waa) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_pipe_with_bool_operator_adds_braces.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(wobble != woo, waa)\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble(wobble != woo, waa) ↑ } ----- AFTER ACTION pub fn main() { { wobble != woo } |> wibble(waa) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_pipe_with_comparison_adds_braces.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(1.0 >=. 0.0, waa)\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble(1.0 >=. 0.0, waa) ↑ } ----- AFTER ACTION pub fn main() { { 1.0 >=. 0.0 } |> wibble(waa) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_pipe_with_complex_binop_adds_braces.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nfn bug() {\n bool.guard(1 == 2 || 2 == 3, Nil, fn() { Nil })\n}\n" --- ----- BEFORE ACTION fn bug() { bool.guard(1 == 2 || 2 == 3, Nil, fn() { Nil }) ↑ } ----- AFTER ACTION fn bug() { { 1 == 2 || 2 == 3 } |> bool.guard(Nil, fn() { Nil }) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_pipe_with_function_call_on_first_argument.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(wobble, woo)\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble(wobble, woo) ↑ } ----- AFTER ACTION pub fn main() { wobble |> wibble(woo) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_pipe_with_function_call_on_function_name_extracts_first_argument.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(wobble, woo)\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble(wobble, woo) ↑ } ----- AFTER ACTION pub fn main() { wobble |> wibble(woo) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_pipe_with_function_call_on_second_argument.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(wobble, woo)\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble(wobble, woo) ↑ } ----- AFTER ACTION pub fn main() { woo |> wibble(wobble, _) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_pipe_with_function_call_with_labelled_arguments_inserts_hole.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(wobble: 1, woo: 2)\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble(wobble: 1, woo: 2) ↑ } ----- AFTER ACTION pub fn main() { 1 |> wibble(wobble: _, woo: 2) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_pipe_with_function_call_with_labelled_arguments_inserts_hole_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(wobble: 1, woo: 2)\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble(wobble: 1, woo: 2) ↑ } ----- AFTER ACTION pub fn main() { 2 |> wibble(wobble: 1, woo: _) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_pipe_with_function_call_with_shorthand_labelled_argument_inserts_hole.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(wobble:, woo:)\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble(wobble:, woo:) ↑ } ----- AFTER ACTION pub fn main() { wobble |> wibble(wobble: _, woo:) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_pipe_with_function_call_with_shorthand_labelled_argument_inserts_hole_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(wobble:, woo:)\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble(wobble:, woo:) ↑ } ----- AFTER ACTION pub fn main() { woo |> wibble(wobble:, woo: _) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_pipe_with_function_returning_other_function.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(wobble)(woo)\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble(wobble)(woo) ↑ } ----- AFTER ACTION pub fn main() { woo |> wibble(wobble) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_pipe_with_nested_calls_picks_the_innermost_one.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\nfn bug() {\n wibble(Wobble(\n field: woo(other_call(last))\n ))\n}\n" --- ----- BEFORE ACTION fn bug() { wibble(Wobble( field: woo(other_call(last)) ↑ )) } ----- AFTER ACTION fn bug() { wibble(Wobble( field: other_call(last) |> woo )) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_pipe_with_string_concat_adds_braces.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(wobble <> woo, waa)\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble(wobble <> woo, waa) ↑ } ----- AFTER ACTION pub fn main() { { wobble <> woo } |> wibble(waa) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_pipe_with_sum_adds_no_braces.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(1 + 1, waa)\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble(1 + 1, waa) ↑ } ----- AFTER ACTION pub fn main() { 1 + 1 |> wibble(waa) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_pipe_works_in_anonymous_function_inside_a_pipeline.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble |> wobble(fn() { woo(1) })\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble |> wobble(fn() { woo(1) }) ↑ } ----- AFTER ACTION pub fn main() { wibble |> wobble(fn() { 1 |> woo }) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_pipe_works_in_final_step_of_a_pipeline.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble |> wobble(woo(1))\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble |> wobble(woo(1)) ↑ } ----- AFTER ACTION pub fn main() { wibble |> wobble(1 |> woo) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__convert_to_pipe_works_inside_body_of_use.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n use <- wibble(wobble)\n woo(123)\n}\n" --- ----- BEFORE ACTION pub fn main() { use <- wibble(wobble) woo(123) ↑ } ----- AFTER ACTION pub fn main() { use <- wibble(wobble) 123 |> woo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__desugar_nested_use_expressions_picks_inner_under_cursor.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n use a, b <- wibble(1, 2)\n use a, b <- wibble(a, b)\n todo\n}\n\nfn wibble(n, m, f) {\n f(1, 2)\n}\n" --- ----- BEFORE ACTION pub fn main() { use a, b <- wibble(1, 2) use a, b <- wibble(a, b) ↑ todo } fn wibble(n, m, f) { f(1, 2) } ----- AFTER ACTION pub fn main() { use a, b <- wibble(1, 2) wibble(a, b, fn(a, b) { todo }) } fn wibble(n, m, f) { f(1, 2) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__desugar_nested_use_expressions_picks_inner_under_cursor_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n use a, b <- wibble(1, 2)\n use a, b <- wibble(a, b)\n todo\n}\n\nfn wibble(n, m, f) {\n f(1, 2)\n}\n" --- ----- BEFORE ACTION pub fn main() { use a, b <- wibble(1, 2) ▔▔▔↑ use a, b <- wibble(a, b) todo } fn wibble(n, m, f) { f(1, 2) } ----- AFTER ACTION pub fn main() { wibble(1, 2, fn(a, b) { use a, b <- wibble(a, b) todo }) } fn wibble(n, m, f) { f(1, 2) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__different_annotations_create_compatible_type_variables.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn do_generic_things(a, b) {\n let a_value = a\n let b_value = b\n let other_value = a_value\n}\n" --- ----- BEFORE ACTION pub fn do_generic_things(a, b) { let a_value = a ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ let b_value = b ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ let other_value = a_value ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ } ↑ ----- AFTER ACTION pub fn do_generic_things(a, b) { let a_value: a = a let b_value: b = b let other_value: a = a_value } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__expand_function_capture.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n wibble(_, 1)\n}" --- ----- BEFORE ACTION pub fn main() { wibble(_, 1) ↑ } ----- AFTER ACTION pub fn main() { fn(value) { wibble(value, 1) } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__expand_function_capture_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n wibble(1, _)\n}" --- ----- BEFORE ACTION pub fn main() { wibble(1, _) ↑ } ----- AFTER ACTION pub fn main() { fn(value) { wibble(1, value) } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__expand_function_capture_does_not_shadow_variables.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let value = 1\n let value_2 = 2\n wibble(value, _, value_2)\n}" --- ----- BEFORE ACTION pub fn main() { let value = 1 let value_2 = 2 wibble(value, _, value_2) ↑ } ----- AFTER ACTION pub fn main() { let value = 1 let value_2 = 2 fn(value_3) { wibble(value, value_3, value_2) } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__expand_function_capture_picks_a_name_based_on_the_type_of_the_hole.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n [1, 2, 3]\n |> map(add(_, 1))\n}\n\npub fn map(l: List(a), f: fn(a) -> b) -> List(b) { todo }\npub fn add(n, m) { n + m }\n" --- ----- BEFORE ACTION pub fn main() { [1, 2, 3] |> map(add(_, 1)) ↑ } pub fn map(l: List(a), f: fn(a) -> b) -> List(b) { todo } pub fn add(n, m) { n + m } ----- AFTER ACTION pub fn main() { [1, 2, 3] |> map(fn(int) { add(int, 1) }) } pub fn map(l: List(a), f: fn(a) -> b) -> List(b) { todo } pub fn add(n, m) { n + m } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_anonymous_function_with_variable_capture_1.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub fn main() {\n let outer_scope = 3\n let wibble = fn(a, b) {\n a + b + outer_scope\n }\n}\n " --- ----- BEFORE ACTION pub fn main() { let outer_scope = 3 let wibble = fn(a, b) { ▔▔▔▔▔▔▔▔▔▔ a + b + outer_scope ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ } ▔▔↑ } ----- AFTER ACTION pub fn main() { let outer_scope = 3 let wibble = fn(a, b) { function(a, b, outer_scope) } } fn function(a: Int, b: Int, outer_scope: Int) -> Int { a + b + outer_scope } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_anonymous_function_with_variable_capture_2.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\nimport gleam/list\n\npub fn main() {\n let shorter = [1, 2, 3]\n let longer = [4, 5, 6, 7, 8]\n let offset = 5\n list.map2(shorter, longer, fn(_left, right) { right + offset })\n}\n " --- ----- BEFORE ACTION import gleam/list pub fn main() { let shorter = [1, 2, 3] let longer = [4, 5, 6, 7, 8] let offset = 5 list.map2(shorter, longer, fn(_left, right) { right + offset }) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION import gleam/list pub fn main() { let shorter = [1, 2, 3] let longer = [4, 5, 6, 7, 8] let offset = 5 list.map2(shorter, longer, fn(_left, right) { function(right, offset) }) } fn function(right: Int, offset: Int) -> Int { right + offset } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_anonymous_function_without_variable_capture_1.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub fn main() {\n let int_pow = fn(base, exp) {\n case exp {\n exp if exp < 0 -> 0\n 0 -> base\n exp -> int_pow(base * exp, exp - 1)\n }\n }\n}\n " --- ----- BEFORE ACTION pub fn main() { let int_pow = fn(base, exp) { ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ case exp { ▔▔▔▔▔▔▔▔▔▔▔▔▔▔ exp if exp < 0 -> 0 ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ 0 -> base ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ exp -> int_pow(base * exp, exp - 1) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ } ▔▔▔▔▔ } ▔▔↑ } ----- AFTER ACTION pub fn main() { let int_pow = function } fn function(base: Int, exp: Int) -> Int { case exp { exp if exp < 0 -> 0 0 -> base exp -> int_pow(base * exp, exp - 1) } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_anonymous_function_without_variable_capture_2.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\nimport gleam/io\nimport gleam/list\n\npub fn main() {\n list.each(list.range(0, 10), fn(_) { io.println(\"wibble wobble\") })\n}\n " --- ----- BEFORE ACTION import gleam/io import gleam/list pub fn main() { list.each(list.range(0, 10), fn(_) { io.println("wibble wobble") }) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION import gleam/io import gleam/list pub fn main() { list.each(list.range(0, 10), function) } fn function(_int: Int) -> Nil { io.println("wibble wobble") } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_block_tail_position_3.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n case 1 {\n _ -> {\n use <- wibble\n 123\n }\n _ -> todo\n }\n}\n\nfn wibble(f: fn() -> Float) -> Float { f() }\n" --- ----- BEFORE ACTION pub fn main() { case 1 { _ -> { ▔ use <- wibble ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ 123 ▔▔▔▔▔▔▔▔▔ } ▔▔▔▔↑ _ -> todo } } fn wibble(f: fn() -> Float) -> Float { f() } ----- AFTER ACTION pub fn main() { case 1 { _ -> function() _ -> todo } } fn function() -> Float { use <- wibble 123 } fn wibble(f: fn() -> Float) -> Float { f() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_block_tail_position_4.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n case 1 {\n _ -> {\n use <- wibble\n use <- wibble\n 123\n }\n _ -> todo\n }\n}\n\nfn wibble(f: fn() -> Float) -> Float { f() }\n" --- ----- BEFORE ACTION pub fn main() { case 1 { _ -> { ▔ use <- wibble ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ use <- wibble ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ 123 ▔▔▔▔▔▔▔▔▔ } ▔▔▔▔↑ _ -> todo } } fn wibble(f: fn() -> Float) -> Float { f() } ----- AFTER ACTION pub fn main() { case 1 { _ -> function() _ -> todo } } fn function() -> Float { use <- wibble use <- wibble 123 } fn wibble(f: fn() -> Float) -> Float { f() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_declaration_with_proper_indentation.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let fahrenheit = {\n let degrees = 64\n degrees\n }\n fahrenheit\n}\n" --- ----- BEFORE ACTION pub fn main() { let fahrenheit = { let degrees = 64 ▔▔▔▔▔▔▔▔▔▔↑ degrees } fahrenheit } ----- AFTER ACTION const degrees = 64 pub fn main() { let fahrenheit = { degrees } fahrenheit } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_doesnt_place_constant_below_documentation.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\n/// Wibble does some wobbling\npub fn wibble() {\n let x = \"wobble\"\n x\n}\n" --- ----- BEFORE ACTION /// Wibble does some wobbling pub fn wibble() { let x = "wobble" ↑ x } ----- AFTER ACTION const x = "wobble" /// Wibble does some wobbling pub fn wibble() { x } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_doesnt_place_constant_below_large_documentation.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\n/// Wibble does some wobbling\n/// Note that it doesn't perform wibbling\npub fn wibble() {\n let x = \"wobble\"\n x\n}\n" --- ----- BEFORE ACTION /// Wibble does some wobbling /// Note that it doesn't perform wibbling pub fn wibble() { let x = "wobble" ↑ x } ----- AFTER ACTION const x = "wobble" /// Wibble does some wobbling /// Note that it doesn't perform wibbling pub fn wibble() { x } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_call_argument_with_bit_array.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "import gleam/io\n\npub fn main() {\n io.debug(<<3:size(8)>>)\n}" --- ----- BEFORE ACTION import gleam/io pub fn main() { io.debug(<<3:size(8)>>) ▔↑ } ----- AFTER ACTION import gleam/io const bit_array = <<3:size(8)>> pub fn main() { io.debug(bit_array) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_call_argument_with_float.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "import gleam/float\n\npub fn main() {\n float.ceiling(1.9998)\n}" --- ----- BEFORE ACTION import gleam/float pub fn main() { float.ceiling(1.9998) ▔▔▔▔▔↑ } ----- AFTER ACTION import gleam/float const float = 1.9998 pub fn main() { float.ceiling(float) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_call_argument_with_int.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "import gleam/list\n\npub fn main() {\n list.sample([4, 5, 6], 2)\n}" --- ----- BEFORE ACTION import gleam/list pub fn main() { list.sample([4, 5, 6], 2) ↑ } ----- AFTER ACTION import gleam/list const int = 2 pub fn main() { list.sample([4, 5, 6], int) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_call_argument_with_list.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "import gleam/io\n\npub fn main() {\n io.debug([\"constant\", \"another constant\"])\n}" --- ----- BEFORE ACTION import gleam/io pub fn main() { io.debug(["constant", "another constant"]) ↑ } ----- AFTER ACTION import gleam/io const strings = ["constant", "another constant"] pub fn main() { io.debug(strings) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_call_argument_with_nested_inside.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "import gleam/list\n\npub fn main() {\n list.unzip([#(1, 2), #(3, 4)])\n}" --- ----- BEFORE ACTION import gleam/list pub fn main() { list.unzip([#(1, 2), #(3, 4)]) ▔↑ } ----- AFTER ACTION import gleam/list const value = #(1, 2) pub fn main() { list.unzip([value, #(3, 4)]) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_call_argument_with_nested_outside.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "import gleam/list\n\npub fn main() {\n list.unzip([#(1, 2), #(3, 4)])\n}" --- ----- BEFORE ACTION import gleam/list pub fn main() { list.unzip([#(1, 2), #(3, 4)]) ↑ } ----- AFTER ACTION import gleam/list const values = [#(1, 2), #(3, 4)] pub fn main() { list.unzip(values) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_call_argument_with_string.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "import gleam/io\n\npub fn main() {\n io.print(\"constant\")\n}" --- ----- BEFORE ACTION import gleam/io pub fn main() { io.print("constant") ↑ } ----- AFTER ACTION import gleam/io const string = "constant" pub fn main() { io.print(string) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_call_argument_with_tuple.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "import gleam/io\n\npub fn main() {\n io.debug(#(1, 2, 3))\n}" --- ----- BEFORE ACTION import gleam/io pub fn main() { io.debug(#(1, 2, 3)) ▔↑ } ----- AFTER ACTION import gleam/io const value = #(1, 2, 3) pub fn main() { io.debug(value) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_declaration_of_bin_op.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let twelve = \"1\" <> \"2\"\n}" --- ----- BEFORE ACTION pub fn main() { let twelve = "1" <> "2" ↑ } ----- AFTER ACTION const string = "1" <> "2" pub fn main() { let twelve = string } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_declaration_of_bit_array.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let b = <<\"arr\":utf32>>\n}" --- ----- BEFORE ACTION pub fn main() { let b = <<"arr":utf32>> ▔▔▔▔▔▔↑ } ----- AFTER ACTION const bit_array = <<"arr":utf32>> pub fn main() { let b = bit_array } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_declaration_of_float.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let c = 3.1415\n}" --- ----- BEFORE ACTION pub fn main() { let c = 3.1415 ▔▔▔▔▔↑ } ----- AFTER ACTION const float = 3.1415 pub fn main() { let c = float } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_declaration_of_int.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let c = 125\n}" --- ----- BEFORE ACTION pub fn main() { let c = 125 ▔▔↑ } ----- AFTER ACTION const int = 125 pub fn main() { let c = int } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_declaration_of_list.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let c = [3.1415, 0.33333333]\n}" --- ----- BEFORE ACTION pub fn main() { let c = [3.1415, 0.33333333] ↑ } ----- AFTER ACTION const floats = [3.1415, 0.33333333] pub fn main() { let c = floats } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_declaration_of_nested_inside.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let c = #([1, 2, 3], [3, 2, 1])\n}" --- ----- BEFORE ACTION pub fn main() { let c = #([1, 2, 3], [3, 2, 1]) ↑ } ----- AFTER ACTION const ints = [1, 2, 3] pub fn main() { let c = #(ints, [3, 2, 1]) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_declaration_of_nested_outside.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let c = #([1, 2, 3], [3, 2, 1])\n}" --- ----- BEFORE ACTION pub fn main() { let c = #([1, 2, 3], [3, 2, 1]) ▔↑ } ----- AFTER ACTION const value = #([1, 2, 3], [3, 2, 1]) pub fn main() { let c = value } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_declaration_of_string.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let c = \"constant\"\n}" --- ----- BEFORE ACTION pub fn main() { let c = "constant" ↑ } ----- AFTER ACTION const string = "constant" pub fn main() { let c = string } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_declaration_of_tuple.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let #(one, two, three) = #(\"one\", \"two\", \"three\")\n}" --- ----- BEFORE ACTION pub fn main() { let #(one, two, three) = #("one", "two", "three") ▔↑ } ----- AFTER ACTION const value = #("one", "two", "three") pub fn main() { let #(one, two, three) = value } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_inside_block.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let fahrenheit = {\n let degrees = 64 + 32\n degrees\n }\n}" --- ----- BEFORE ACTION pub fn main() { let fahrenheit = { let degrees = 64 + 32 ↑ degrees } } ----- AFTER ACTION const int = 32 pub fn main() { let fahrenheit = { let degrees = 64 + int degrees } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_inside_case.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(result) {\n case result {\n Ok(value) -> value + 1\n Error(_) -> panic\n }\n}" --- ----- BEFORE ACTION pub fn main(result) { case result { Ok(value) -> value + 1 ↑ Error(_) -> panic } } ----- AFTER ACTION const int = 1 pub fn main(result) { case result { Ok(value) -> value + int Error(_) -> panic } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_inside_use_1.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n use x <- result.try(todo)\n Ok(123)\n}" --- ----- BEFORE ACTION pub fn main() { use x <- result.try(todo) Ok(123) ↑ } ----- AFTER ACTION const result = Ok(123) pub fn main() { use x <- result.try(todo) result } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_inside_use_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "const number = 123\n\npub fn main() {\n use x <- result.try(todo)\n Ok(number)\n}" --- ----- BEFORE ACTION const number = 123 pub fn main() { use x <- result.try(todo) Ok(number) ↑ } ----- AFTER ACTION const number = 123 const result = Ok(number) pub fn main() { use x <- result.try(todo) result } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_list_containing_constant.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "const something = \"something\"\n\npub fn main() {\n let c = [\"constant\", something]\n}" --- ----- BEFORE ACTION const something = "something" pub fn main() { let c = ["constant", something] ↑ } ----- AFTER ACTION const something = "something" const strings = ["constant", something] pub fn main() { let c = strings } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_literal_within_list.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let c = [\"constant\", todo]\n}" --- ----- BEFORE ACTION pub fn main() { let c = ["constant", todo] ↑ } ----- AFTER ACTION const string = "constant" pub fn main() { let c = [string, todo] } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_literal_within_tuple.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let c = #(0.333334, todo)\n}" --- ----- BEFORE ACTION pub fn main() { let c = #(0.333334, todo) ▔▔▔▔▔▔▔↑ } ----- AFTER ACTION const float = 0.333334 pub fn main() { let c = #(float, todo) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_nested_inside_in_expr.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n [#(\"a\", 0), #(\"b\", 1), #(\"a\", 2)]\n |> key_filter(\"a\")\n}" --- ----- BEFORE ACTION pub fn main() { [#("a", 0), #("b", 1), #("a", 2)] ▔↑ |> key_filter("a") } ----- AFTER ACTION const value = #("a", 0) pub fn main() { [value, #("b", 1), #("a", 2)] |> key_filter("a") } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_nested_outside_in_expr.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n [#(\"a\", 0), #(\"b\", 1), #(\"a\", 2)]\n |> key_filter(\"a\")\n}" --- ----- BEFORE ACTION pub fn main() { [#("a", 0), #("b", 1), #("a", 2)] ↑ |> key_filter("a") } ----- AFTER ACTION const values = [#("a", 0), #("b", 1), #("a", 2)] pub fn main() { values |> key_filter("a") } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_nil.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let x = Nil\n x\n}\n" --- ----- BEFORE ACTION pub fn main() { let x = Nil ▔▔▔▔↑ x } ----- AFTER ACTION const x = Nil pub fn main() { x } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_non_record_variant_1.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub type Auth {\n Verified\n Unverified\n}\n\npub fn main() {\n let a = Unverified\n let a = verify(something, a)\n\n a\n}\n" --- ----- BEFORE ACTION pub type Auth { Verified Unverified } pub fn main() { let a = Unverified ▔▔▔▔▔▔▔▔▔↑ let a = verify(something, a) a } ----- AFTER ACTION pub type Auth { Verified Unverified } const auth = Unverified pub fn main() { let a = auth let a = verify(something, a) a } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_non_record_variant_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub type Auth {\n Verified\n Unverified\n}\n\npub fn main() {\n let a = verify(something, Unverified)\n\n a\n}\n" --- ----- BEFORE ACTION pub type Auth { Verified Unverified } pub fn main() { let a = verify(something, Unverified) ▔▔▔▔▔▔▔▔▔↑ a } ----- AFTER ACTION pub type Auth { Verified Unverified } const auth = Unverified pub fn main() { let a = verify(something, auth) a } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_record_variant_1.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub type Auth {\n Verified(String)\n Unverified\n}\n\npub fn main() {\n let u = Verified(\"User\")\n let v = verify(something, u)\n\n v\n}" --- ----- BEFORE ACTION pub type Auth { Verified(String) Unverified } pub fn main() { let u = Verified("User") ▔▔▔▔↑ let v = verify(something, u) v } ----- AFTER ACTION pub type Auth { Verified(String) Unverified } const u = Verified("User") pub fn main() { let v = verify(something, u) v } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_record_variant_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub type Auth {\n Verified(Int)\n Unverified\n}\n\nconst auth = True\n\nconst id = 1234\n\npub fn main() {\n let v = verify(auth, Verified(id))\n\n v\n}" --- ----- BEFORE ACTION pub type Auth { Verified(Int) Unverified } const auth = True const id = 1234 pub fn main() { let v = verify(auth, Verified(id)) ▔▔▔▔▔▔▔▔↑ v } ----- AFTER ACTION pub type Auth { Verified(Int) Unverified } const auth = True const id = 1234 const auth_2 = Verified(id) pub fn main() { let v = verify(auth, auth_2) v } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_return_of_float.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n 0.25\n}" --- ----- BEFORE ACTION pub fn main() { 0.25 ▔▔▔↑ } ----- AFTER ACTION const float = 0.25 pub fn main() { float } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_return_of_int.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n 100\n}" --- ----- BEFORE ACTION pub fn main() { 100 ▔▔↑ } ----- AFTER ACTION const int = 100 pub fn main() { int } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_return_of_list.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n [1, 2, 3, 4]\n}" --- ----- BEFORE ACTION pub fn main() { [1, 2, 3, 4] ↑ } ----- AFTER ACTION const ints = [1, 2, 3, 4] pub fn main() { ints } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_return_of_nested_outside.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n [#(0.25, 0.75), #(0.5, 1.5)]\n}" --- ----- BEFORE ACTION pub fn main() { [#(0.25, 0.75), #(0.5, 1.5)] ▔↑ } ----- AFTER ACTION const value = #(0.25, 0.75) pub fn main() { [value, #(0.5, 1.5)] } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_return_of_string.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n \"constant\"\n}" --- ----- BEFORE ACTION pub fn main() { "constant" ↑ } ----- AFTER ACTION const string = "constant" pub fn main() { string } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_return_of_tuple.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n #(0.25, 0.75)\n}" --- ----- BEFORE ACTION pub fn main() { #(0.25, 0.75) ▔↑ } ----- AFTER ACTION const value = #(0.25, 0.75) pub fn main() { value } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_taken_name_by_constant.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "const ints = [1, 2]\n\npub fn main() {\n [5, 50]\n}" --- ----- BEFORE ACTION const ints = [1, 2] pub fn main() { [5, 50] ↑ } ----- AFTER ACTION const ints = [1, 2] const ints_2 = [5, 50] pub fn main() { ints_2 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_taken_name_by_function.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "fn floats() {\n [1.0, 2.0]\n}\n\npub fn main() {\n [0.25, 0.75]\n}" --- ----- BEFORE ACTION fn floats() { [1.0, 2.0] } pub fn main() { [0.25, 0.75] ↑ } ----- AFTER ACTION fn floats() { [1.0, 2.0] } const floats_2 = [0.25, 0.75] pub fn main() { floats_2 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_tuple_containing_constant.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "const something = \"something\"\n\npub fn main() {\n let c = #(0.333334, something)\n}" --- ----- BEFORE ACTION const something = "something" pub fn main() { let c = #(0.333334, something) ▔↑ } ----- AFTER ACTION const something = "something" const value = #(0.333334, something) pub fn main() { let c = value } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_whole_declaration_of_bin_op.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "import gleam/io\n\npub fn main() {\n let twelve = \"1\" <> \"2\"\n io.print(twelve)\n}" --- ----- BEFORE ACTION import gleam/io pub fn main() { let twelve = "1" <> "2" ▔▔▔▔▔▔▔▔▔↑ io.print(twelve) } ----- AFTER ACTION import gleam/io const twelve = "1" <> "2" pub fn main() { io.print(twelve) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_whole_declaration_of_bit_array.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "import gleam/io\n\nconst n = 24\n\npub fn main() {\n let bits = <<8080:size(n)>>\n bits\n}" --- ----- BEFORE ACTION import gleam/io const n = 24 pub fn main() { let bits = <<8080:size(n)>> ▔▔▔▔▔▔▔↑ bits } ----- AFTER ACTION import gleam/io const n = 24 const bits = <<8080:size(n)>> pub fn main() { bits } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_whole_declaration_of_float.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "import gleam/io\n\npub fn main() {\n let c = 3.1415\n io.debug(c)\n}" --- ----- BEFORE ACTION import gleam/io pub fn main() { let c = 3.1415 ▔▔▔▔↑ io.debug(c) } ----- AFTER ACTION import gleam/io const c = 3.1415 pub fn main() { io.debug(c) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_whole_declaration_of_int.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "import gleam/io\n\npub fn main() {\n let c = 125\n io.debug(c)\n}" --- ----- BEFORE ACTION import gleam/io pub fn main() { let c = 125 ▔▔▔▔↑ io.debug(c) } ----- AFTER ACTION import gleam/io const c = 125 pub fn main() { io.debug(c) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_whole_declaration_of_list.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "import gleam/io\n\npub fn main() {\n let c = [3.1415, 0.33333333]\n io.debug(c)\n}" --- ----- BEFORE ACTION import gleam/io pub fn main() { let c = [3.1415, 0.33333333] ▔▔▔▔↑ io.debug(c) } ----- AFTER ACTION import gleam/io const c = [3.1415, 0.33333333] pub fn main() { io.debug(c) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_whole_declaration_of_nested.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "import gleam/io\n\npub fn main() {\n let c = #([1, 2, 3], [3, 2, 1])\n io.debug(c)\n}" --- ----- BEFORE ACTION import gleam/io pub fn main() { let c = #([1, 2, 3], [3, 2, 1]) ▔▔▔▔↑ io.debug(c) } ----- AFTER ACTION import gleam/io const c = #([1, 2, 3], [3, 2, 1]) pub fn main() { io.debug(c) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_whole_declaration_of_string.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "import gleam/io\n\npub fn main() {\n let c = \"constant\"\n io.debug(c)\n}" --- ----- BEFORE ACTION import gleam/io pub fn main() { let c = "constant" ▔▔▔▔↑ io.debug(c) } ----- AFTER ACTION import gleam/io const c = "constant" pub fn main() { io.debug(c) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_from_whole_declaration_of_tuple.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "import gleam/io\n\npub fn main() {\n let c = #(\"one\", \"two\", \"three\")\n io.debug(c)\n}" --- ----- BEFORE ACTION import gleam/io pub fn main() { let c = #("one", "two", "three") ▔▔▔▔↑ io.debug(c) } ----- AFTER ACTION import gleam/io const c = #("one", "two", "three") pub fn main() { io.debug(c) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_in_correct_position_1.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nfn first() {\n 1\n}\n\nfn second() {\n 2\n}\n\nfn third() {\n 3\n}\n" --- ----- BEFORE ACTION fn first() { 1 ↑ } fn second() { 2 } fn third() { 3 } ----- AFTER ACTION const int = 1 fn first() { int } fn second() { 2 } fn third() { 3 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_in_correct_position_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nfn first() {\n 1\n}\n\nfn second() {\n 2\n}\n\nfn third() {\n 3\n}\n" --- ----- BEFORE ACTION fn first() { 1 } fn second() { 2 ↑ } fn third() { 3 } ----- AFTER ACTION fn first() { 1 } const int = 2 fn second() { int } fn third() { 3 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_constant_in_correct_position_3.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nfn first() {\n 1\n}\n\nfn second() {\n 2\n}\n\nfn third() {\n 3\n}\n" --- ----- BEFORE ACTION fn first() { 1 } fn second() { 2 } fn third() { 3 ↑ } ----- AFTER ACTION fn first() { 1 } fn second() { 2 } const int = 3 fn third() { int } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_function.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn do_things(a, b) {\n let result = {\n let a = 10 + a\n let b = 10 + b\n a * b\n }\n result + 3\n}\n" --- ----- BEFORE ACTION pub fn do_things(a, b) { let result = { ▔ let a = 10 + a ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ let b = 10 + b ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ a * b ▔▔▔▔▔▔▔▔▔ } ▔▔↑ result + 3 } ----- AFTER ACTION pub fn do_things(a, b) { let result = function(a, b) result + 3 } fn function(a: Int, b: Int) -> Int { let a = 10 + a let b = 10 + b a * b } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_function_from_statements.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn do_things(a, b) {\n let a = 10 + a\n let b = 10 + b\n let result = a * b\n result + 3\n}\n" --- ----- BEFORE ACTION pub fn do_things(a, b) { let a = 10 + a ▔▔▔▔▔▔▔▔▔▔▔▔▔▔ let b = 10 + b ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ let result = a * b ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ result + 3 } ----- AFTER ACTION pub fn do_things(a, b) { let result = function(a, b) result + 3 } fn function(a: Int, b: Int) -> Int { let a = 10 + a let b = 10 + b let result = a * b result } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_function_partially_selected.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let a = 10\n let b = 20\n let c = a + b\n\n echo c\n}\n" --- ----- BEFORE ACTION pub fn main() { let a = 10 ▔▔▔▔▔▔ let b = 20 ▔▔▔▔▔▔▔▔▔▔▔▔ let c = a + b ▔▔▔▔▔▔↑ echo c } ----- AFTER ACTION pub fn main() { let c = function() echo c } fn function() -> Int { let a = 10 let b = 20 let c = a + b c } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_function_when_multiple_names_already_in_scope.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nfn function() { todo }\nfn function_2() { todo }\nfn function_3() { todo }\nfn function_4() { todo }\n\npub fn do_things(a, b) {\n let result = {\n let a = 10 + a\n let b = 10 + b\n a * b\n }\n result + 3\n}\n" --- ----- BEFORE ACTION fn function() { todo } fn function_2() { todo } fn function_3() { todo } fn function_4() { todo } pub fn do_things(a, b) { let result = { ▔▔▔ let a = 10 + a ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ let b = 10 + b ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ a * b ▔▔▔▔▔▔▔▔▔ } ▔▔↑ result + 3 } ----- AFTER ACTION fn function() { todo } fn function_2() { todo } fn function_3() { todo } fn function_4() { todo } pub fn do_things(a, b) { let result = function_5(a, b) result + 3 } fn function_5(a: Int, b: Int) -> Int { let a = 10 + a let b = 10 + b a * b } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_function_when_name_already_in_scope.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nfn function() { todo }\n\npub fn do_things(a, b) {\n let result = {\n let a = 10 + a\n let b = 10 + b\n a * b\n }\n result + 3\n}\n" --- ----- BEFORE ACTION fn function() { todo } pub fn do_things(a, b) { let result = { ▔▔▔ let a = 10 + a ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ let b = 10 + b ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ a * b ▔▔▔▔▔▔▔▔▔ } ▔▔↑ result + 3 } ----- AFTER ACTION fn function() { todo } pub fn do_things(a, b) { let result = function_2(a, b) result + 3 } fn function_2(a: Int, b: Int) -> Int { let a = 10 + a let b = 10 + b a * b } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_function_which_use_variables_defined_in_the_extracted_span.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn do_things(a, b) {\n let new_a = 10 + a\n let new_b = 10 + b\n let result = new_a * new_b\n result + 3\n}\n" --- ----- BEFORE ACTION pub fn do_things(a, b) { let new_a = 10 + a ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ let new_b = 10 + b ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ let result = new_a * new_b ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ result + 3 } ----- AFTER ACTION pub fn do_things(a, b) { let result = function(a, b) result + 3 } fn function(a: Int, b: Int) -> Int { let new_a = 10 + a let new_b = 10 + b let result = new_a * new_b result } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_function_which_use_variables_shadowed_in_an_inner_scope.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn do_things(a, b) {\n let first_part = {\n let a = a + 10\n let b = b + 10\n a * b\n }\n let result = first_part + a * b\n result + 3\n}\n" --- ----- BEFORE ACTION pub fn do_things(a, b) { let first_part = { ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ let a = a + 10 ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ let b = b + 10 ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ a * b ▔▔▔▔▔▔▔▔▔ } ▔▔▔ let result = first_part + a * b ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ result + 3 } ----- AFTER ACTION pub fn do_things(a, b) { let result = function(a, b) result + 3 } fn function(a: Int, b: Int) -> Int { let first_part = { let a = a + 10 let b = b + 10 a * b } let result = first_part + a * b result } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_function_which_uses_constant.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nconst pi = 3.14\n\npub fn main() {\n let radius = 4.5\n\n let circumference = radius *. pi *. 2.0\n\n echo circumference\n}\n" --- ----- BEFORE ACTION const pi = 3.14 pub fn main() { let radius = 4.5 let circumference = radius *. pi *. 2.0 ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ echo circumference } ----- AFTER ACTION const pi = 3.14 pub fn main() { let radius = 4.5 let circumference = function(radius) echo circumference } fn function(radius: Float) -> Float { radius *. pi *. 2.0 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_function_which_uses_constant_in_guard.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nconst pi = 3.14\n\npub fn main() {\n let value = 3.15\n\n let string = case value {\n 0.0 -> \"Zero\"\n 1.0 -> \"One\"\n _ if value == pi -> \"PI\"\n _ -> \"Something else\"\n }\n\n echo string\n}\n" --- ----- BEFORE ACTION const pi = 3.14 pub fn main() { let value = 3.15 let string = case value { ▔▔▔▔▔▔▔▔▔▔▔▔ 0.0 -> "Zero" ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ 1.0 -> "One" ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ _ if value == pi -> "PI" ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ _ -> "Something else" ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ } ▔▔↑ echo string } ----- AFTER ACTION const pi = 3.14 pub fn main() { let value = 3.15 let string = function(value) echo string } fn function(value: Float) -> String { case value { 0.0 -> "Zero" 1.0 -> "One" _ if value == pi -> "PI" _ -> "Something else" } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_function_which_uses_multiple_extracted_variables.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn do_things(a, b) {\n let wibble = a + b\n let wobble = a * b\n wobble / wibble\n}\n" --- ----- BEFORE ACTION pub fn do_things(a, b) { let wibble = a + b ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ let wobble = a * b ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ wobble / wibble } ----- AFTER ACTION pub fn do_things(a, b) { let #(wobble, wibble) = function(a, b) wobble / wibble } fn function(a: Int, b: Int) -> #(Int, Int) { let wibble = a + b let wobble = a * b #(wobble, wibble) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_function_which_uses_no_extracted_variables.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn do_things(a, b) {\n let x = a + b\n echo x\n a\n}\n" --- ----- BEFORE ACTION pub fn do_things(a, b) { let x = a + b ▔▔▔▔▔▔▔▔▔▔▔▔▔ echo x ▔▔↑ a } ----- AFTER ACTION pub fn do_things(a, b) { function(a, b) a } fn function(a: Int, b: Int) -> Nil { let x = a + b echo x Nil } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_function_which_uses_variable_in_bit_array_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let bits = todo\n let size = todo\n\n let segment = case bits {\n <> -> Ok(x)\n _ -> Error(Nil)\n }\n\n case segment {\n Ok(value) -> echo value\n Error(_) -> panic\n }\n}\n" --- ----- BEFORE ACTION pub fn main() { let bits = todo let size = todo let segment = case bits { ▔▔▔▔▔▔▔▔▔▔▔ <> -> Ok(x) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ _ -> Error(Nil) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ } ▔▔↑ case segment { Ok(value) -> echo value Error(_) -> panic } } ----- AFTER ACTION pub fn main() { let bits = todo let size = todo let segment = function(bits, size) case segment { Ok(value) -> echo value Error(_) -> panic } } fn function(bits: BitArray, size: Int) -> Result(Int, Nil) { case bits { <> -> Ok(x) _ -> Error(Nil) } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_function_which_uses_variable_in_guard.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn do_things(a, b) {\n let result = case Nil {\n _ if a > b -> 17\n _ if a < b -> 12\n _ -> panic\n }\n\n result % 4\n}\n" --- ----- BEFORE ACTION pub fn do_things(a, b) { let result = case Nil { ▔▔▔▔▔▔▔▔▔▔ _ if a > b -> 17 ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ _ if a < b -> 12 ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ _ -> panic ▔▔▔▔▔▔▔▔▔▔▔▔▔▔ } ▔▔↑ result % 4 } ----- AFTER ACTION pub fn do_things(a, b) { let result = function(a, b) result % 4 } fn function(a: Int, b: Int) -> Int { case Nil { _ if a > b -> 17 _ if a < b -> 12 _ -> panic } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_statements_in_tail_position.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let a = 1\n let b = 2\n let c = 3\n let d = 4\n a * b + c * d\n}\n" --- ----- BEFORE ACTION pub fn main() { let a = 1 let b = 2 let c = 3 ▔▔▔▔▔▔▔▔▔ let d = 4 ▔▔▔▔▔▔▔▔▔▔▔ a * b + c * d ▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let a = 1 let b = 2 function(a, b) } fn function(a: Int, b: Int) -> Int { let c = 3 let d = 4 a * b + c * d } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_unary_anonymous_function_with_variable_capture_1.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\nimport gleam/list\n\npub fn main() {\n let needle = 42\n let haystack = [25, 81, 74, 42, 33]\n list.filter(haystack, fn(x) { x == needle })\n}\n " --- ----- BEFORE ACTION import gleam/list pub fn main() { let needle = 42 let haystack = [25, 81, 74, 42, 33] list.filter(haystack, fn(x) { x == needle }) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION import gleam/list pub fn main() { let needle = 42 let haystack = [25, 81, 74, 42, 33] list.filter(haystack, function(_, needle)) } fn function(x: Int, needle: Int) -> Bool { x == needle } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_unary_anonymous_function_with_variable_capture_2.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\nimport gleam/list\n\npub fn main() {\n let needle = 42\n let haystack = [25, 81, 74, 42, 33]\n list.filter(haystack, fn(_) { needle == 42 })\n}\n " --- ----- BEFORE ACTION import gleam/list pub fn main() { let needle = 42 let haystack = [25, 81, 74, 42, 33] list.filter(haystack, fn(_) { needle == 42 }) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION import gleam/list pub fn main() { let needle = 42 let haystack = [25, 81, 74, 42, 33] list.filter(haystack, function(_, needle)) } fn function(_int: Int, needle: Int) -> Bool { needle == 42 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_use_in_tail_position.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n use <- wibble\n 123\n}\n\nfn wibble(f: fn() -> Int) -> Int { f() }\n" --- ----- BEFORE ACTION pub fn main() { use <- wibble ▔▔▔▔▔▔▔↑ 123 } fn wibble(f: fn() -> Int) -> Int { f() } ----- AFTER ACTION pub fn main() { function() } fn function() -> Int { use <- wibble 123 } fn wibble(f: fn() -> Int) -> Int { f() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_use_in_tail_position_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n use <- wibble\n use <- wobble\n 123\n}\n\nfn wibble(f: fn() -> Float) -> Float { f() }\nfn wobble(f: fn() -> Int) -> Float { 1.1 }\n" --- ----- BEFORE ACTION pub fn main() { use <- wibble use <- wobble ▔▔▔▔▔▔▔↑ 123 } fn wibble(f: fn() -> Float) -> Float { f() } fn wobble(f: fn() -> Int) -> Float { 1.1 } ----- AFTER ACTION pub fn main() { use <- wibble function() } fn function() -> Float { use <- wobble 123 } fn wibble(f: fn() -> Float) -> Float { f() } fn wobble(f: fn() -> Int) -> Float { 1.1 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n list.map([1, 2, 3], int.add(1, _))\n}" --- ----- BEFORE ACTION pub fn main() { list.map([1, 2, 3], int.add(1, _)) ▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let ints = [1, 2, 3] list.map(ints, int.add(1, _)) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n map([1, 2, 3], add(1, _))\n}\npub fn add(n, m) { todo }\npub fn map(l, f) { todo }\n" --- ----- BEFORE ACTION pub fn main() { map([1, 2, 3], add(1, _)) ↑ } pub fn add(n, m) { todo } pub fn map(l, f) { todo } ----- AFTER ACTION pub fn main() { let value = add(1, _) map([1, 2, 3], value) } pub fn add(n, m) { todo } pub fn map(l, f) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_3.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n list.map([1, 2, 3], todo, todo)\n}" --- ----- BEFORE ACTION pub fn main() { list.map([1, 2, 3], todo, todo) ▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let value = todo list.map([1, 2, 3], todo, value) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_after_nested_anonymous_function.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let f = fn() {\n let x = 1 + 2\n let ff = fn() {\n let y = x + 3\n let z = y + x\n z\n }\n let z = x * 4\n z\n }\n let y = 5 + 6\n f()\n}" --- ----- BEFORE ACTION pub fn main() { let f = fn() { let x = 1 + 2 let ff = fn() { let y = x + 3 let z = y + x z } let z = x * 4 z } let y = 5 + 6 ↑ f() } ----- AFTER ACTION pub fn main() { let f = fn() { let x = 1 + 2 let ff = fn() { let y = x + 3 let z = y + x z } let z = x * 4 z } let int = 6 let y = 5 + int f() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_and_dont_shadow_existing_variable_in_argument.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "fn wibble(a, b) {\n a + b\n}\n\nfn main() {\n let int = 1\n wibble(int, 2)\n}" --- ----- BEFORE ACTION fn wibble(a, b) { a + b } fn main() { let int = 1 wibble(int, 2) ↑ } ----- AFTER ACTION fn wibble(a, b) { a + b } fn main() { let int = 1 let int_2 = 2 wibble(int, int_2) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_and_dont_shadow_existing_variable_in_operator.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "import gleam/int\nimport random_import as int_2\n\nconst int_3 = 3\n\nfn int_4() { 4 }\n\nfn isolated_scope() {\n let int_6 = 6\n int_6 + 1\n}\n\npub fn main() {\n let int_5 = 5\n let result = int_5 + 6\n result\n}\n" --- ----- BEFORE ACTION import gleam/int import random_import as int_2 const int_3 = 3 fn int_4() { 4 } fn isolated_scope() { let int_6 = 6 int_6 + 1 } pub fn main() { let int_5 = 5 let result = int_5 + 6 ↑ result } ----- AFTER ACTION import gleam/int import random_import as int_2 const int_3 = 3 fn int_4() { 4 } fn isolated_scope() { let int_6 = 6 int_6 + 1 } pub fn main() { let int_5 = 5 let int_6 = 6 let result = int_5 + int_6 result } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_does_not_shadow_name_in_same_block.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub fn main() {\n let result = {\n let int = 1\n wibble(11)\n }\n result\n}" --- ----- BEFORE ACTION pub fn main() { let result = { let int = 1 wibble(11) ↑ } result } ----- AFTER ACTION pub fn main() { let result = { let int = 1 let int_2 = 11 wibble(int_2) } result } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_does_not_shadow_name_in_same_branch.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub fn main(x: Result(_, _)) {\n case x {\n Ok(_) -> {\n let int = 1\n wibble(11)\n }\n Error(_) -> {\n panic\n }\n }\n}\n" --- ----- BEFORE ACTION pub fn main(x: Result(_, _)) { case x { Ok(_) -> { let int = 1 wibble(11) ↑ } Error(_) -> { panic } } } ----- AFTER ACTION pub fn main(x: Result(_, _)) { case x { Ok(_) -> { let int = 1 let int_2 = 11 wibble(int_2) } Error(_) -> { panic } } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_does_not_shadow_names_in_anonymous_function.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub fn main() {\n let fun = fn() {\n let int = 1\n wibble(11)\n }\n fun()\n}\n" --- ----- BEFORE ACTION pub fn main() { let fun = fn() { let int = 1 wibble(11) ↑ } fun() } ----- AFTER ACTION pub fn main() { let fun = fn() { let int = 1 let int_2 = 11 wibble(int_2) } fun() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_from_arg_in_nested_function_called_in_pipeline.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let result =\n [1, 2, 3]\n |> map(add(_, 1))\n |> map(subtract(_, 9))\n\n result\n}\npub fn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo }\npub fn add(a: Int, b: Int) -> Int { todo }\npub fn subtract(a: Int, b: Int) -> Int { todo }\n" --- ----- BEFORE ACTION pub fn main() { let result = [1, 2, 3] |> map(add(_, 1)) |> map(subtract(_, 9)) ↑ result } pub fn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo } pub fn add(a: Int, b: Int) -> Int { todo } pub fn subtract(a: Int, b: Int) -> Int { todo } ----- AFTER ACTION pub fn main() { let int = 9 let result = [1, 2, 3] |> map(add(_, 1)) |> map(subtract(_, int)) result } pub fn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo } pub fn add(a: Int, b: Int) -> Int { todo } pub fn subtract(a: Int, b: Int) -> Int { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_from_arg_in_pipelined_call.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let adder = add\n let x = [4, 5, 6] |> map2([1, 2, 3], adder)\n x\n}\npub fn map2(list1: List(a), list2: List(b), fun: fn(a, b) -> c) -> List(c) { todo }\npub fn add(a: Int, b: Int) -> Int { todo }\n" --- ----- BEFORE ACTION pub fn main() { let adder = add let x = [4, 5, 6] |> map2([1, 2, 3], adder) ↑ x } pub fn map2(list1: List(a), list2: List(b), fun: fn(a, b) -> c) -> List(c) { todo } pub fn add(a: Int, b: Int) -> Int { todo } ----- AFTER ACTION pub fn main() { let adder = add let ints = [1, 2, 3] let x = [4, 5, 6] |> map2(ints, adder) x } pub fn map2(list1: List(a), list2: List(b), fun: fn(a, b) -> c) -> List(c) { todo } pub fn add(a: Int, b: Int) -> Int { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_from_arg_in_pipelined_call_of_function_to_capture.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n fn(total, item) { total + item }\n |> fold(with: _, from: 0, over: [1, 2, 3])\n}\npub fn fold(over l: List(a), from i: t, with f: fn(t, a) -> t) -> acc { todo }\n" --- ----- BEFORE ACTION pub fn main() { fn(total, item) { total + item } |> fold(with: _, from: 0, over: [1, 2, 3]) ↑ } pub fn fold(over l: List(a), from i: t, with f: fn(t, a) -> t) -> acc { todo } ----- AFTER ACTION pub fn main() { let value = fold(with: _, from: 0, over: [1, 2, 3]) fn(total, item) { total + item } |> value } pub fn fold(over l: List(a), from i: t, with f: fn(t, a) -> t) -> acc { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_from_arg_in_pipelined_call_to_capture.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let adder = add\n let x = adder |> reduce([1, 2, 3], _)\n x\n}\npub fn reduce(list: List(a), fun: fn(a, a) -> a) -> Result(a, Nil) { todo }\npub fn add(a: Int, b: Int) -> Int { todo }\n" --- ----- BEFORE ACTION pub fn main() { let adder = add let x = adder |> reduce([1, 2, 3], _) ↑ x } pub fn reduce(list: List(a), fun: fn(a, a) -> a) -> Result(a, Nil) { todo } pub fn add(a: Int, b: Int) -> Int { todo } ----- AFTER ACTION pub fn main() { let adder = add let ints = [1, 2, 3] let x = adder |> reduce(ints, _) x } pub fn reduce(list: List(a), fun: fn(a, a) -> a) -> Result(a, Nil) { todo } pub fn add(a: Int, b: Int) -> Int { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_from_capture_arguments_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n int.add(11, _)\n}" --- ----- BEFORE ACTION pub fn main() { int.add(11, _) ↑ } ----- AFTER ACTION pub fn main() { let int = 11 int.add(int, _) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_ignores_names_in_anonymous_functions.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub fn main() {\n let fun = fn() {\n let int = 1\n int + 2\n }\n\n wibble(11)\n}\n" --- ----- BEFORE ACTION pub fn main() { let fun = fn() { let int = 1 int + 2 } wibble(11) ↑ } ----- AFTER ACTION pub fn main() { let fun = fn() { let int = 1 int + 2 } let int = 11 wibble(int) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_ignores_names_in_other_blocks.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub fn main() {\n {\n let int = 1\n int + 2\n }\n wibble(11)\n}\n" --- ----- BEFORE ACTION pub fn main() { { let int = 1 int + 2 } wibble(11) ↑ } ----- AFTER ACTION pub fn main() { { let int = 1 int + 2 } let int = 11 wibble(int) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_ignores_names_in_other_branches.snap ================================================ --- source: language-server/src/tests/action.rs expression: "pub fn main(x: Result(_, _)) {\n case x {\n Ok(_) -> {\n let int = 1\n int + 2\n }\n Error(_) -> wibble(11)\n }\n\n}" --- ----- BEFORE ACTION pub fn main(x: Result(_, _)) { case x { Ok(_) -> { let int = 1 int + 2 } Error(_) -> wibble(11) ↑ } } ----- AFTER ACTION pub fn main(x: Result(_, _)) { case x { Ok(_) -> { let int = 1 int + 2 } Error(_) -> { let int = 11 wibble(int) } } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_ignores_names_in_other_branches_2.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub fn main(x: Result(_, _)) {\n case x {\n Ok(_) -> {\n let int = 1\n int + 2\n }\n Error(_) -> {\n wibble(11)\n }\n }\n}\n" --- ----- BEFORE ACTION pub fn main(x: Result(_, _)) { case x { Ok(_) -> { let int = 1 int + 2 } Error(_) -> { wibble(11) ↑ } } } ----- AFTER ACTION pub fn main(x: Result(_, _)) { case x { Ok(_) -> { let int = 1 int + 2 } Error(_) -> { let int = 11 wibble(int) } } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_in_anonymous_fn_in_argument.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "fn map(value, fn_over_value) { todo }\n\npub fn main() {\n 1\n |> Ok\n |> map(fn(value) { value + 2 })\n}" --- ----- BEFORE ACTION fn map(value, fn_over_value) { todo } pub fn main() { 1 |> Ok |> map(fn(value) { value + 2 }) ↑ } ----- AFTER ACTION fn map(value, fn_over_value) { todo } pub fn main() { 1 |> Ok |> map(fn(value) { let int = 2 value + int }) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_in_block.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n {\n todo\n wibble([1, 2, 3])\n todo\n }\n}" --- ----- BEFORE ACTION pub fn main() { { todo wibble([1, 2, 3]) ▔▔▔↑ todo } } ----- AFTER ACTION pub fn main() { { todo let ints = [1, 2, 3] wibble(ints) todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_in_case_branch.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n case wibble {\n _ -> [1, 2, 3]\n }\n}" --- ----- BEFORE ACTION pub fn main() { case wibble { _ -> [1, 2, 3] ↑ } } ----- AFTER ACTION pub fn main() { case wibble { _ -> { let ints = [1, 2, 3] ints } } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_in_case_branch_from_second_arg.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n case todo {\n Ok(_) -> #(Ok(1), Error(\"s\"))\n Error(_) -> panic\n }\n}" --- ----- BEFORE ACTION pub fn main() { case todo { Ok(_) -> #(Ok(1), Error("s")) ↑ Error(_) -> panic } } ----- AFTER ACTION pub fn main() { case todo { Ok(_) -> { let result = Error("s") #(Ok(1), result) } Error(_) -> panic } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_in_case_branch_using_var.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n case todo {\n Ok(value) -> 2 * value + 1\n Error(_) -> panic\n }\n}" --- ----- BEFORE ACTION pub fn main() { case todo { Ok(value) -> 2 * value + 1 ▔▔▔▔↑ Error(_) -> panic } } ----- AFTER ACTION pub fn main() { case todo { Ok(value) -> { let int = 2 * value int + 1 } Error(_) -> panic } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_in_double_nested_anonymous_function.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let f = fn() {\n let x = 1 + 2\n let ff = fn() {\n let y = x + 3\n let z = y + x\n z\n }\n let z = x * 4\n z\n }\n let y = 5 + 6\n f()\n}" --- ----- BEFORE ACTION pub fn main() { let f = fn() { let x = 1 + 2 let ff = fn() { let y = x + 3 ↑ let z = y + x z } let z = x * 4 z } let y = 5 + 6 f() } ----- AFTER ACTION pub fn main() { let f = fn() { let x = 1 + 2 let ff = fn() { let int = 3 let y = x + int let z = y + x z } let z = x * 4 z } let y = 5 + 6 f() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_in_multiline_case_subject_branch.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n case\n list.map(\n [1, 2, 3],\n int.add(1, _)\n )\n {\n _ -> todo\n }\n}" --- ----- BEFORE ACTION pub fn main() { case list.map( [1, 2, 3], ↑ int.add(1, _) ) { _ -> todo } } ----- AFTER ACTION pub fn main() { let ints = [1, 2, 3] case list.map( ints, int.add(1, _) ) { _ -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_in_multiline_use.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n use <- wibble(\n [1, 2, 3]\n )\n todo\n}" --- ----- BEFORE ACTION pub fn main() { use <- wibble( [1, 2, 3] ↑ ) todo } ----- AFTER ACTION pub fn main() { let ints = [1, 2, 3] use <- wibble( ints ) todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_in_nested_anonymous_function.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let f = fn() {\n let x = 1 + 2\n let ff = fn() {\n let y = x + 3\n let z = y + x\n z\n }\n let z = x * 4\n z\n }\n let y = 5 + 6\n f()\n}" --- ----- BEFORE ACTION pub fn main() { let f = fn() { let x = 1 + 2 let ff = fn() { let y = x + 3 let z = y + x z } let z = x * 4 ↑ z } let y = 5 + 6 f() } ----- AFTER ACTION pub fn main() { let f = fn() { let x = 1 + 2 let ff = fn() { let y = x + 3 let z = y + x z } let int = 4 let z = x * int z } let y = 5 + 6 f() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_in_use.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n use <- wibble([1, 2, 3])\n todo\n}" --- ----- BEFORE ACTION pub fn main() { use <- wibble([1, 2, 3]) ↑ todo } ----- AFTER ACTION pub fn main() { let ints = [1, 2, 3] use <- wibble(ints) todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_inside_multiline_function_call.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n list.map(\n [1, 2, 3],\n int.add(1, _),\n )\n}" --- ----- BEFORE ACTION pub fn main() { list.map( [1, 2, 3], ↑ int.add(1, _), ) } ----- AFTER ACTION pub fn main() { let ints = [1, 2, 3] list.map( ints, int.add(1, _), ) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_inside_use_body.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n use <- wibble(todo)\n list.map([1, 2, 3], int.add(1, _))\n todo\n}" --- ----- BEFORE ACTION pub fn main() { use <- wibble(todo) list.map([1, 2, 3], int.add(1, _)) ↑ todo } ----- AFTER ACTION pub fn main() { use <- wibble(todo) let ints = [1, 2, 3] list.map(ints, int.add(1, _)) todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_starting_pipeline_steps.snap ================================================ --- source: language-server/src/tests/action.rs assertion_line: 9619 expression: "fn map(value, fn_over_value) { todo }\n\npub fn main() {\n 1\n |> Ok\n |> map(fn(value) { value + 2 })\n}" snapshot_kind: text --- ----- BEFORE ACTION fn map(value, fn_over_value) { todo } pub fn main() { 1 ▔ |> Ok ▔▔▔▔▔↑ |> map(fn(value) { value + 2 }) } ----- AFTER ACTION fn map(value, fn_over_value) { todo } pub fn main() { let result = 1 |> Ok result |> map(fn(value) { value + 2 }) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__extract_variable_with_list_with_plural_name_does_not_add_another_s.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble([Names, Names])\n}\n\npub type Names {\n Names\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble([Names, Names]) ↑ } pub type Names { Names } ----- AFTER ACTION pub fn main() { let names = [Names, Names] wibble(names) } pub type Names { Names } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_in_labelled_args_selects_innermost_function.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(\n wibble()\n )\n}\n\npub fn wibble(arg1 arg1, arg2 arg2) { Nil }\n " --- ----- BEFORE ACTION pub fn main() { wibble( wibble() ↑ ) } pub fn wibble(arg1 arg1, arg2 arg2) { Nil } ----- AFTER ACTION pub fn main() { wibble( wibble(arg1: todo, arg2: todo) ) } pub fn wibble(arg1 arg1, arg2 arg2) { Nil } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_in_labelled_args_with_some_arguments_already_supplied.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(1,)\n}\n\npub fn wibble(arg1 arg1, arg2 arg2) { Nil }\n " --- ----- BEFORE ACTION pub fn main() { wibble(1,) ↑ } pub fn wibble(arg1 arg1, arg2 arg2) { Nil } ----- AFTER ACTION pub fn main() { wibble(1,arg2: todo) } pub fn wibble(arg1 arg1, arg2 arg2) { Nil } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_in_labelled_args_with_some_arguments_already_supplied_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(arg2: 1)\n}\n\npub fn wibble(arg1 arg1, arg2 arg2) { Nil }\n " --- ----- BEFORE ACTION pub fn main() { wibble(arg2: 1) ↑ } pub fn wibble(arg1 arg1, arg2 arg2) { Nil } ----- AFTER ACTION pub fn main() { wibble(arg2: 1, arg1: todo) } pub fn wibble(arg1 arg1, arg2 arg2) { Nil } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_in_labelled_args_with_some_arguments_already_supplied_3.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(1, arg3: 2)\n}\n\npub fn wibble(arg1 arg1, arg2 arg2, arg3 arg3) { Nil }\n " --- ----- BEFORE ACTION pub fn main() { wibble(1, arg3: 2) ↑ } pub fn wibble(arg1 arg1, arg2 arg2, arg3 arg3) { Nil } ----- AFTER ACTION pub fn main() { wibble(1, arg3: 2, arg2: todo) } pub fn wibble(arg1 arg1, arg2 arg2, arg3 arg3) { Nil } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_in_labelled_args_works_with_pattern_and_no_parentheses.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let assert Ok(Wibble) = Wibble(1, \"2\")\n}\n\npub type Wibble { Wibble(arg1: Int, arg2: String) }\n " --- ----- BEFORE ACTION pub fn main() { let assert Ok(Wibble) = Wibble(1, "2") ▔▔▔▔▔↑ } pub type Wibble { Wibble(arg1: Int, arg2: String) } ----- AFTER ACTION pub fn main() { let assert Ok(Wibble(arg1:, arg2:)) = Wibble(1, "2") } pub type Wibble { Wibble(arg1: Int, arg2: String) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_in_labelled_args_works_with_pattern_and_parentheses.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let assert Ok(Wibble()) = Wibble(1, \"2\")\n}\n\npub type Wibble { Wibble(arg1: Int, arg2: String) }\n " --- ----- BEFORE ACTION pub fn main() { let assert Ok(Wibble()) = Wibble(1, "2") ▔▔▔▔▔↑ } pub type Wibble { Wibble(arg1: Int, arg2: String) } ----- AFTER ACTION pub fn main() { let assert Ok(Wibble(arg1:, arg2:)) = Wibble(1, "2") } pub type Wibble { Wibble(arg1: Int, arg2: String) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_in_labelled_args_works_with_pattern_and_parentheses_with_spaces.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let assert Ok(Wibble ()) = Wibble(1, \"2\")\n}\n\npub type Wibble { Wibble(arg1: Int, arg2: String) }\n " --- ----- BEFORE ACTION pub fn main() { let assert Ok(Wibble ()) = Wibble(1, "2") ▔▔▔▔▔↑ } pub type Wibble { Wibble(arg1: Int, arg2: String) } ----- AFTER ACTION pub fn main() { let assert Ok(Wibble (arg1:, arg2:)) = Wibble(1, "2") } pub type Wibble { Wibble(arg1: Int, arg2: String) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_in_labelled_args_works_with_pipes.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n 1 |> wibble()\n}\n\npub fn wibble(arg1 arg1, arg2 arg2) { Nil }\n " --- ----- BEFORE ACTION pub fn main() { 1 |> wibble() ↑ } pub fn wibble(arg1 arg1, arg2 arg2) { Nil } ----- AFTER ACTION pub fn main() { 1 |> wibble(arg2: todo) } pub fn wibble(arg1 arg1, arg2 arg2) { Nil } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_in_labelled_args_works_with_pipes_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n 1 |> wibble()\n}\n\npub fn wibble(not_labelled, arg1 arg1, arg2 arg2) { Nil }\n " --- ----- BEFORE ACTION pub fn main() { 1 |> wibble() ↑ } pub fn wibble(not_labelled, arg1 arg1, arg2 arg2) { Nil } ----- AFTER ACTION pub fn main() { 1 |> wibble(arg1: todo, arg2: todo) } pub fn wibble(not_labelled, arg1 arg1, arg2 arg2) { Nil } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_in_labelled_args_works_with_record_constructor.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n Wibble()\n}\n\npub type Wibble { Wibble(arg1: Int, arg2: String) }\n " --- ----- BEFORE ACTION pub fn main() { Wibble() ▔▔▔▔▔▔▔↑ } pub type Wibble { Wibble(arg1: Int, arg2: String) } ----- AFTER ACTION pub fn main() { Wibble(arg1: todo, arg2: todo) } pub type Wibble { Wibble(arg1: Int, arg2: String) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_in_labelled_args_works_with_regular_function.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble()\n}\n\npub fn wibble(arg1 arg1, arg2 arg2) { Nil }\n " --- ----- BEFORE ACTION pub fn main() { wibble() ↑ } pub fn wibble(arg1 arg1, arg2 arg2) { Nil } ----- AFTER ACTION pub fn main() { wibble(arg1: todo, arg2: todo) } pub fn wibble(arg1 arg1, arg2 arg2) { Nil } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_in_labelled_args_works_with_use.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n use <- wibble()\n todo\n}\n\npub fn wibble(arg1 arg1, arg2 arg2) { Nil }\n " --- ----- BEFORE ACTION pub fn main() { use <- wibble() ▔▔▔▔▔▔▔↑ todo } pub fn wibble(arg1 arg1, arg2 arg2) { Nil } ----- AFTER ACTION pub fn main() { use <- wibble(arg1: todo) todo } pub fn wibble(arg1 arg1, arg2 arg2) { Nil } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_in_labelled_args_works_with_use_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n use <- wibble(arg1: 1)\n todo\n}\n\npub fn wibble(arg1 arg1, arg2 arg2, arg3 arg3) { Nil }\n " --- ----- BEFORE ACTION pub fn main() { use <- wibble(arg1: 1) ▔▔▔▔▔▔▔▔▔▔↑ todo } pub fn wibble(arg1 arg1, arg2 arg2, arg3 arg3) { Nil } ----- AFTER ACTION pub fn main() { use <- wibble(arg1: 1, arg2: todo) todo } pub fn wibble(arg1 arg1, arg2 arg2, arg3 arg3) { Nil } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_in_labelled_args_works_with_use_3.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n use <- wibble(arg2: 2)\n todo\n}\n\npub fn wibble(arg1 arg1, arg2 arg2, arg3 arg3) { Nil }\n " --- ----- BEFORE ACTION pub fn main() { use <- wibble(arg2: 2) ▔▔▔▔▔▔▔▔▔▔↑ todo } pub fn wibble(arg1 arg1, arg2 arg2, arg3 arg3) { Nil } ----- AFTER ACTION pub fn main() { use <- wibble(arg2: 2, arg1: todo) todo } pub fn wibble(arg1 arg1, arg2 arg2, arg3 arg3) { Nil } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_labels_all_fields_have_matching_variables.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub type Player {\n Player(name: String, age: Int, team: String)\n}\n\npub fn main() {\n let name = \"Priya\"\n let age = 25\n let team = \"BLU\"\n Player()\n}\n" --- ----- BEFORE ACTION pub type Player { Player(name: String, age: Int, team: String) } pub fn main() { let name = "Priya" let age = 25 let team = "BLU" Player() ↑ } ----- AFTER ACTION pub type Player { Player(name: String, age: Int, team: String) } pub fn main() { let name = "Priya" let age = 25 let team = "BLU" Player(name:, age:, team:) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_labels_falls_back_to_todo_when_type_does_not_match.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub type Player {\n Player(name: String, team: String)\n}\n\npub fn main() {\n let name = 1\n Player(team: \"BLU\")\n}\n" --- ----- BEFORE ACTION pub type Player { Player(name: String, team: String) } pub fn main() { let name = 1 Player(team: "BLU") ↑ } ----- AFTER ACTION pub type Player { Player(name: String, team: String) } pub fn main() { let name = 1 Player(team: "BLU", name: todo) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_labels_generic_type_matching.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub type Container(a) {\n Container(value: a, label: String)\n}\n\npub fn main() {\n let value = 42\n let label = \"test\"\n Container()\n}\n" --- ----- BEFORE ACTION pub type Container(a) { Container(value: a, label: String) } pub fn main() { let value = 42 let label = "test" Container() ↑ } ----- AFTER ACTION pub type Container(a) { Container(value: a, label: String) } pub fn main() { let value = 42 let label = "test" Container(value:, label:) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_labels_ignores_underscore_prefixed_variables.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub type Player {\n Player(name: String, team: String)\n}\n\npub fn main() {\n let _name = \"Priya\"\n Player(team: \"BLU\")\n}\n" --- ----- BEFORE ACTION pub type Player { Player(name: String, team: String) } pub fn main() { let _name = "Priya" Player(team: "BLU") ↑ } ----- AFTER ACTION pub type Player { Player(name: String, team: String) } pub fn main() { let _name = "Priya" Player(team: "BLU", name: todo) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_labels_ignores_variable_defined_after_call.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub type Player {\n Player(name: String, team: String)\n}\n\npub fn main() {\n Player(team: \"BLU\")\n let name = \"Priya\"\n}\n" --- ----- BEFORE ACTION pub type Player { Player(name: String, team: String) } pub fn main() { Player(team: "BLU") ↑ let name = "Priya" } ----- AFTER ACTION pub type Player { Player(name: String, team: String) } pub fn main() { Player(team: "BLU", name: todo) let name = "Priya" } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_labels_inside_anonymous_function.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub type Player {\n Player(name: String, team: String)\n}\n\npub fn main() {\n let name = \"Outer\"\n let callback = fn() {\n let name = \"Inner\"\n Player(team: \"BLU\")\n }\n}\n" --- ----- BEFORE ACTION pub type Player { Player(name: String, team: String) } pub fn main() { let name = "Outer" let callback = fn() { let name = "Inner" Player(team: "BLU") ↑ } } ----- AFTER ACTION pub type Player { Player(name: String, team: String) } pub fn main() { let name = "Outer" let callback = fn() { let name = "Inner" Player(team: "BLU", name:) } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_labels_inside_assignment_with_same_name_as_field.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub type Player {\n Player(name: String, team: String)\n}\n\npub fn main() {\n let name = Player(team: \"BLU\")\n}\n" --- ----- BEFORE ACTION pub type Player { Player(name: String, team: String) } pub fn main() { let name = Player(team: "BLU") ↑ } ----- AFTER ACTION pub type Player { Player(name: String, team: String) } pub fn main() { let name = Player(team: "BLU", name: todo) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_labels_multiple_fields_some_matching.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub type Player {\n Player(name: String, age: Int, team: String)\n}\n\npub fn main() {\n let name = \"Priya\"\n let age = \"not an int\"\n Player()\n}\n" --- ----- BEFORE ACTION pub type Player { Player(name: String, age: Int, team: String) } pub fn main() { let name = "Priya" let age = "not an int" Player() ↑ } ----- AFTER ACTION pub type Player { Player(name: String, age: Int, team: String) } pub fn main() { let name = "Priya" let age = "not an int" Player(name:, age: todo, team: todo) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_labels_nested_pattern_constructor.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble {\n Wibble(a: Int, b: Float, c: String)\n Wobble(d: Bool, e: BitArray, f: List(Result(String, Nil)))\n}\n\npub fn main() {\n case todo {\n #([Wobble()], 2, 3) -> todo\n _ -> todo\n }\n}\n" --- ----- BEFORE ACTION pub type Wibble { Wibble(a: Int, b: Float, c: String) Wobble(d: Bool, e: BitArray, f: List(Result(String, Nil))) } pub fn main() { case todo { #([Wobble()], 2, 3) -> todo ↑ _ -> todo } } ----- AFTER ACTION pub type Wibble { Wibble(a: Int, b: Float, c: String) Wobble(d: Bool, e: BitArray, f: List(Result(String, Nil))) } pub fn main() { case todo { #([Wobble(d:, e:, f:)], 2, 3) -> todo _ -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_labels_pattern_constructor.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble {\n Wibble(a: Int, b: Float, c: String)\n Wobble(d: Bool, e: BitArray, f: List(Result(String, Nil)))\n}\n\npub fn main(w: Wibble) {\n case w {\n Wibble(..) -> todo\n Wobble() -> todo\n }\n}\n" --- ----- BEFORE ACTION pub type Wibble { Wibble(a: Int, b: Float, c: String) Wobble(d: Bool, e: BitArray, f: List(Result(String, Nil))) } pub fn main(w: Wibble) { case w { Wibble(..) -> todo Wobble() -> todo ↑ } } ----- AFTER ACTION pub type Wibble { Wibble(a: Int, b: Float, c: String) Wobble(d: Bool, e: BitArray, f: List(Result(String, Nil))) } pub fn main(w: Wibble) { case w { Wibble(..) -> todo Wobble(d:, e:, f:) -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_labels_pattern_constructor_let_assignment.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble {\n Wibble(a: Int, b: Float, c: String)\n}\n\npub fn main() {\n let Wibble() = todo\n}\n" --- ----- BEFORE ACTION pub type Wibble { Wibble(a: Int, b: Float, c: String) } pub fn main() { let Wibble() = todo ↑ } ----- AFTER ACTION pub type Wibble { Wibble(a: Int, b: Float, c: String) } pub fn main() { let Wibble(a:, b:, c:) = todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_labels_pattern_constructor_with_some_labels.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble {\n Wibble(a: Int, b: Float, c: String)\n Wobble(d: Bool, e: BitArray, f: List(Result(String, Nil)))\n}\n\npub fn main(w: Wibble) {\n case w {\n Wobble(e: <<>>) -> todo\n _ -> todo\n }\n}\n" --- ----- BEFORE ACTION pub type Wibble { Wibble(a: Int, b: Float, c: String) Wobble(d: Bool, e: BitArray, f: List(Result(String, Nil))) } pub fn main(w: Wibble) { case w { Wobble(e: <<>>) -> todo ↑ _ -> todo } } ----- AFTER ACTION pub type Wibble { Wibble(a: Int, b: Float, c: String) Wobble(d: Bool, e: BitArray, f: List(Result(String, Nil))) } pub fn main(w: Wibble) { case w { Wobble(e: <<>>, d:, f:) -> todo _ -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_labels_uses_function_argument_in_scope.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub type Player {\n Player(name: String, team: String)\n}\n\npub fn create_player(name: String) {\n Player(team: \"BLU\")\n}\n" --- ----- BEFORE ACTION pub type Player { Player(name: String, team: String) } pub fn create_player(name: String) { Player(team: "BLU") ↑ } ----- AFTER ACTION pub type Player { Player(name: String, team: String) } pub fn create_player(name: String) { Player(team: "BLU", name:) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_labels_uses_variable_in_scope_with_matching_type.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub type Player {\n Player(name: String, team: String)\n}\n\npub fn main() {\n let name = \"Priya\"\n Player(team: \"BLU\")\n}\n" --- ----- BEFORE ACTION pub type Player { Player(name: String, team: String) } pub fn main() { let name = "Priya" Player(team: "BLU") ↑ } ----- AFTER ACTION pub type Player { Player(name: String, team: String) } pub fn main() { let name = "Priya" Player(team: "BLU", name:) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_labels_variable_from_outer_scope_not_shadowed.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub type Player {\n Player(name: String, team: String)\n}\n\npub fn main() {\n let name = \"Outer\"\n let callback = fn() {\n Player(team: \"BLU\")\n }\n}\n" --- ----- BEFORE ACTION pub type Player { Player(name: String, team: String) } pub fn main() { let name = "Outer" let callback = fn() { Player(team: "BLU") ↑ } } ----- AFTER ACTION pub type Player { Player(name: String, team: String) } pub fn main() { let name = "Outer" let callback = fn() { Player(team: "BLU", name:) } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_labels_variable_in_scope_from_case_pattern.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub type Player {\n Player(name: String, team: String)\n}\n\npub fn main(result: Result(String, Nil)) {\n case result {\n Ok(name) -> Player(team: \"BLU\")\n Error(_) -> Player(team: \"RED\", name: \"Unknown\")\n }\n}\n" --- ----- BEFORE ACTION pub type Player { Player(name: String, team: String) } pub fn main(result: Result(String, Nil)) { case result { Ok(name) -> Player(team: "BLU") ↑ Error(_) -> Player(team: "RED", name: "Unknown") } } ----- AFTER ACTION pub type Player { Player(name: String, team: String) } pub fn main(result: Result(String, Nil)) { case result { Ok(name) -> Player(team: "BLU", name:) Error(_) -> Player(team: "RED", name: "Unknown") } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_labels_variable_out_of_scope_in_block.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub type Player {\n Player(name: String, team: String)\n}\n\npub fn main() {\n {\n let name = \"Priya\"\n }\n Player(team: \"BLU\")\n}\n" --- ----- BEFORE ACTION pub type Player { Player(name: String, team: String) } pub fn main() { { let name = "Priya" } Player(team: "BLU") ↑ } ----- AFTER ACTION pub type Player { Player(name: String, team: String) } pub fn main() { { let name = "Priya" } Player(team: "BLU", name: todo) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_unused_fields_with_all_ignored_fields.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble { Wibble(Int, label1: String, label2: Int) }\n\npub fn main() {\n let Wibble(..) = todo\n}" --- ----- BEFORE ACTION pub type Wibble { Wibble(Int, label1: String, label2: Int) } pub fn main() { let Wibble(..) = todo ↑ } ----- AFTER ACTION pub type Wibble { Wibble(Int, label1: String, label2: Int) } pub fn main() { let Wibble(int, label1:, label2:) = todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_unused_fields_with_all_positional_fields.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble { Wibble(Int, String) }\n\npub fn main() {\n let Wibble(..) = todo\n}" --- ----- BEFORE ACTION pub type Wibble { Wibble(Int, String) } pub fn main() { let Wibble(..) = todo ↑ } ----- AFTER ACTION pub type Wibble { Wibble(Int, String) } pub fn main() { let Wibble(int, string) = todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_unused_fields_with_ignored_fields_never_calls_a_positional_arg_as_a_labelled_one.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble { Wibble(Int, int: Int) }\n\npub fn main() {\n let Wibble(..) = todo\n}" --- ----- BEFORE ACTION pub type Wibble { Wibble(Int, int: Int) } pub fn main() { let Wibble(..) = todo ↑ } ----- AFTER ACTION pub type Wibble { Wibble(Int, int: Int) } pub fn main() { let Wibble(int_2, int:) = todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_unused_fields_with_ignored_labelled_fields.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble { Wibble(Int, label1: String, label2: Int) }\n\npub fn main() {\n let Wibble(_, ..) = todo\n}" --- ----- BEFORE ACTION pub type Wibble { Wibble(Int, label1: String, label2: Int) } pub fn main() { let Wibble(_, ..) = todo ↑ } ----- AFTER ACTION pub type Wibble { Wibble(Int, label1: String, label2: Int) } pub fn main() { let Wibble(_, label1:, label2:) = todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_unused_fields_with_ignored_mixed_fields.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble { Wibble(Int, String, label1: String, label2: Int) }\n\npub fn main() {\n let Wibble(_, label2:, ..) = todo\n}" --- ----- BEFORE ACTION pub type Wibble { Wibble(Int, String, label1: String, label2: Int) } pub fn main() { let Wibble(_, label2:, ..) = todo ↑ } ----- AFTER ACTION pub type Wibble { Wibble(Int, String, label1: String, label2: Int) } pub fn main() { let Wibble(_, string, label2:, label1:) = todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fill_unused_fields_with_ignored_positional_fields.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble { Wibble(Int, label1: String, label2: Int) }\n\npub fn main() {\n let Wibble(label1:, label2:, ..) = todo\n}" --- ----- BEFORE ACTION pub type Wibble { Wibble(Int, label1: String, label2: Int) } pub fn main() { let Wibble(label1:, label2:, ..) = todo ↑ } ----- AFTER ACTION pub type Wibble { Wibble(Int, label1: String, label2: Int) } pub fn main() { let Wibble(int, label1:, label2:) = todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fix_float_operator_on_ints.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n 1 >=. 2\n}\n" --- ----- BEFORE ACTION pub fn main() { 1 >=. 2 ↑ } ----- AFTER ACTION pub fn main() { 1 >= 2 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fix_float_operator_on_ints_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n 1 -. 2\n}\n" --- ----- BEFORE ACTION pub fn main() { 1 -. 2 ▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { 1 - 2 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fix_float_operator_on_ints_3.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n 1 *. wobble()\n}\n\nfn wobble() { 3 }\n" --- ----- BEFORE ACTION pub fn main() { 1 *. wobble() ↑ } fn wobble() { 3 } ----- AFTER ACTION pub fn main() { 1 * wobble() } fn wobble() { 3 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fix_int_operator_on_floats.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n 1.0 >= 2.3\n}\n" --- ----- BEFORE ACTION pub fn main() { 1.0 >= 2.3 ↑ } ----- AFTER ACTION pub fn main() { 1.0 >=. 2.3 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fix_int_operator_on_floats_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n 1.12 - 2.0\n}\n" --- ----- BEFORE ACTION pub fn main() { 1.12 - 2.0 ▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { 1.12 -. 2.0 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fix_int_operator_on_floats_3.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n 1.3 * wobble()\n}\n\nfn wobble() { 3.2 }\n" --- ----- BEFORE ACTION pub fn main() { 1.3 * wobble() ↑ } fn wobble() { 3.2 } ----- AFTER ACTION pub fn main() { 1.3 *. wobble() } fn wobble() { 3.2 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fix_plus_operator_on_strings.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n \"hello, \" + name()\n}\n\nfn name() { \"Jak\" }\n" --- ----- BEFORE ACTION pub fn main() { "hello, " + name() ▔▔▔▔▔▔▔▔▔▔▔↑ } fn name() { "Jak" } ----- AFTER ACTION pub fn main() { "hello, " <> name() } fn name() { "Jak" } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fix_truncated_segment_1.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n <<1, 257, 259:size(1)>>\n}" --- ----- BEFORE ACTION pub fn main() { <<1, 257, 259:size(1)>> ↑ } ----- AFTER ACTION pub fn main() { <<1, 1, 259:size(1)>> } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__fix_truncated_segment_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n <<1, 1024:size(10)>>\n}" --- ----- BEFORE ACTION pub fn main() { <<1, 1024:size(10)>> ↑ } ----- AFTER ACTION pub fn main() { <<1, 0:size(10)>> } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_dynamic_decoder.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Person {\n Person(name: String, age: Int, height: Float, is_cool: Bool, brain: BitArray)\n}\n" --- ----- BEFORE ACTION pub type Person { ↑ Person(name: String, age: Int, height: Float, is_cool: Bool, brain: BitArray) } ----- AFTER ACTION import gleam/dynamic/decode pub type Person { Person(name: String, age: Int, height: Float, is_cool: Bool, brain: BitArray) } fn person_decoder() -> decode.Decoder(Person) { use name <- decode.field("name", decode.string) use age <- decode.field("age", decode.int) use height <- decode.field("height", decode.float) use is_cool <- decode.field("is_cool", decode.bool) use brain <- decode.field("brain", decode.bit_array) decode.success(Person(name:, age:, height:, is_cool:, brain:)) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_dynamic_decoder_already_imported_module.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport gleam/dynamic/decode as dyn_dec\n\npub type Wibble {\n Wibble(a: Int, b: Float, c: String)\n}\n" --- ----- BEFORE ACTION import gleam/dynamic/decode as dyn_dec pub type Wibble { ↑ Wibble(a: Int, b: Float, c: String) } ----- AFTER ACTION import gleam/dynamic/decode as dyn_dec pub type Wibble { Wibble(a: Int, b: Float, c: String) } fn wibble_decoder() -> dyn_dec.Decoder(Wibble) { use a <- dyn_dec.field("a", dyn_dec.int) use b <- dyn_dec.field("b", dyn_dec.float) use c <- dyn_dec.field("c", dyn_dec.string) dyn_dec.success(Wibble(a:, b:, c:)) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_dynamic_decoder_complex_types.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport gleam/option\nimport gleam/dynamic\nimport gleam/dict\n\npub type Something\n\npub type Wibble(value) {\n Wibble(\n maybe: option.Option(Something),\n map: dict.Dict(String, List(value)),\n unknown: List(dynamic.Dynamic),\n )\n}\n" --- ----- BEFORE ACTION import gleam/option import gleam/dynamic import gleam/dict pub type Something pub type Wibble(value) { ↑ Wibble( maybe: option.Option(Something), map: dict.Dict(String, List(value)), unknown: List(dynamic.Dynamic), ) } ----- AFTER ACTION import gleam/dynamic/decode import gleam/option import gleam/dynamic import gleam/dict pub type Something pub type Wibble(value) { Wibble( maybe: option.Option(Something), map: dict.Dict(String, List(value)), unknown: List(dynamic.Dynamic), ) } fn wibble_decoder() -> decode.Decoder(Wibble(value)) { use maybe <- decode.field("maybe", decode.optional(todo as "Decoder for Something")) use map <- decode.field("map", decode.dict(decode.string, decode.list(todo as "Decoder for value"))) use unknown <- decode.field("unknown", decode.list(decode.dynamic)) decode.success(Wibble(maybe:, map:, unknown:)) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_dynamic_decoder_does_not_produce_zero_values_for_types_from_other_packages.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\nimport wibble.{type Wibble}\n\npub type Wobble {\n Wobble(value: Wibble)\n Dummy(a: Int, b: Wibble)\n}\n " --- ----- BEFORE ACTION import wibble.{type Wibble} pub type Wobble { ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Wobble(value: Wibble) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Dummy(a: Int, b: Wibble) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ } ↑ ----- AFTER ACTION import gleam/dynamic/decode import wibble.{type Wibble} pub type Wobble { Wobble(value: Wibble) Dummy(a: Int, b: Wibble) } fn wobble_decoder() -> decode.Decoder(Wobble) { use variant <- decode.field("type", decode.string) case variant { "wobble" -> { use value <- decode.field("value", todo as "Decoder for Wibble") decode.success(Wobble(value:)) } "dummy" -> { use a <- decode.field("a", decode.int) use b <- decode.field("b", todo as "Decoder for Wibble") decode.success(Dummy(a:, b:)) } _ -> decode.failure(todo as "Zero value for Wobble", "Wobble") } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_dynamic_decoder_for_multi_variant_type.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub type Wibble {\n Wibble(wibble: Int, next: Wibble)\n Wobble(wobble: Float, text: String, values: List(Bool))\n}\n" --- ----- BEFORE ACTION pub type Wibble { ↑ Wibble(wibble: Int, next: Wibble) Wobble(wobble: Float, text: String, values: List(Bool)) } ----- AFTER ACTION import gleam/dynamic/decode pub type Wibble { Wibble(wibble: Int, next: Wibble) Wobble(wobble: Float, text: String, values: List(Bool)) } fn wibble_decoder() -> decode.Decoder(Wibble) { use variant <- decode.field("type", decode.string) case variant { "wibble" -> { use wibble <- decode.field("wibble", decode.int) use next <- decode.field("next", wibble_decoder()) decode.success(Wibble(wibble:, next:)) } "wobble" -> { use wobble <- decode.field("wobble", decode.float) use text <- decode.field("text", decode.string) use values <- decode.field("values", decode.list(decode.bool)) decode.success(Wobble(wobble:, text:, values:)) } _ -> decode.failure(Wobble(wobble: 0.0, text: "", values: []), "Wibble") } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_dynamic_decoder_for_multi_variant_type_multi_word_name.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub type Wibble {\n OneTwo(wibble: Int, next: Wibble)\n ThreeFour(wobble: Float, text: String, values: List(Bool))\n FiveSixSeven(one_two: Int)\n}\n" --- ----- BEFORE ACTION pub type Wibble { ↑ OneTwo(wibble: Int, next: Wibble) ThreeFour(wobble: Float, text: String, values: List(Bool)) FiveSixSeven(one_two: Int) } ----- AFTER ACTION import gleam/dynamic/decode pub type Wibble { OneTwo(wibble: Int, next: Wibble) ThreeFour(wobble: Float, text: String, values: List(Bool)) FiveSixSeven(one_two: Int) } fn wibble_decoder() -> decode.Decoder(Wibble) { use variant <- decode.field("type", decode.string) case variant { "one_two" -> { use wibble <- decode.field("wibble", decode.int) use next <- decode.field("next", wibble_decoder()) decode.success(OneTwo(wibble:, next:)) } "three_four" -> { use wobble <- decode.field("wobble", decode.float) use text <- decode.field("text", decode.string) use values <- decode.field("values", decode.list(decode.bool)) decode.success(ThreeFour(wobble:, text:, values:)) } "five_six_seven" -> { use one_two <- decode.field("one_two", decode.int) decode.success(FiveSixSeven(one_two:)) } _ -> decode.failure(FiveSixSeven(one_two: 0), "Wibble") } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_dynamic_decoder_for_variant_with_no_fields.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub type Wibble {\n Wibble\n}\n" --- ----- BEFORE ACTION pub type Wibble { ↑ Wibble } ----- AFTER ACTION import gleam/dynamic/decode pub type Wibble { Wibble } fn wibble_decoder() -> decode.Decoder(Wibble) { use variant <- decode.then(decode.string) case variant { "wibble" -> decode.success(Wibble) _ -> decode.failure(Wibble, "Wibble") } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_dynamic_decoder_for_variants_with_mixed_fields.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub type Wibble {\n Wibble\n Wobble(field: String, field2: Int)\n}\n" --- ----- BEFORE ACTION pub type Wibble { ↑ Wibble Wobble(field: String, field2: Int) } ----- AFTER ACTION import gleam/dynamic/decode pub type Wibble { Wibble Wobble(field: String, field2: Int) } fn wibble_decoder() -> decode.Decoder(Wibble) { use variant <- decode.field("type", decode.string) case variant { "wibble" -> decode.success(Wibble) "wobble" -> { use field <- decode.field("field", decode.string) use field2 <- decode.field("field2", decode.int) decode.success(Wobble(field:, field2:)) } _ -> decode.failure(Wibble, "Wibble") } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_dynamic_decoder_for_variants_with_no_fields.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub type Wibble {\n Wibble\n Wobble\n Woo\n}\n" --- ----- BEFORE ACTION pub type Wibble { ↑ Wibble Wobble Woo } ----- AFTER ACTION import gleam/dynamic/decode pub type Wibble { Wibble Wobble Woo } fn wibble_decoder() -> decode.Decoder(Wibble) { use variant <- decode.then(decode.string) case variant { "wibble" -> decode.success(Wibble) "wobble" -> decode.success(Wobble) "woo" -> decode.success(Woo) _ -> decode.failure(Wibble, "Wibble") } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_dynamic_decoder_generates_todo_for_zero_value_when_all_constructors_fail.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub type Wobble {\n Wobble(nope: Wobble)\n Wibble(not: Int, again: Wobble)\n}\n " --- ----- BEFORE ACTION pub type Wobble { ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Wobble(nope: Wobble) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Wibble(not: Int, again: Wobble) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ } ↑ ----- AFTER ACTION import gleam/dynamic/decode pub type Wobble { Wobble(nope: Wobble) Wibble(not: Int, again: Wobble) } fn wobble_decoder() -> decode.Decoder(Wobble) { use variant <- decode.field("type", decode.string) case variant { "wobble" -> { use nope <- decode.field("nope", wobble_decoder()) decode.success(Wobble(nope:)) } "wibble" -> { use not <- decode.field("not", decode.int) use again <- decode.field("again", wobble_decoder()) decode.success(Wibble(not:, again:)) } _ -> decode.failure(todo as "Zero value for Wobble", "Wobble") } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_dynamic_decoder_produces_zero_values_for_prelude_and_stdlib_types.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\nimport gleam/option\nimport gleam/dict\n\npub type Wobble {\n Wobble(\n bit_array: BitArray,\n int: Int,\n float: Float,\n bool: Bool,\n list: List(Int),\n string: String,\n nil: Nil,\n option: option.Option(String),\n dict: dict.Dict(Int, Bool),\n )\n Dummy(\n a: Int,\n b: Int,\n c: Int,\n d: Int,\n e: Int,\n f: Int,\n g: Int,\n h: Int,\n i: Int,\n j: Int,\n )\n}\n " --- ----- BEFORE ACTION import gleam/option import gleam/dict pub type Wobble { ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Wobble( ▔▔▔▔▔▔▔▔▔ bit_array: BitArray, ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ int: Int, ▔▔▔▔▔▔▔▔▔▔▔▔▔ float: Float, ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ bool: Bool, ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ list: List(Int), ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ string: String, ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ nil: Nil, ▔▔▔▔▔▔▔▔▔▔▔▔▔ option: option.Option(String), ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ dict: dict.Dict(Int, Bool), ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ) ▔▔▔ Dummy( ▔▔▔▔▔▔▔▔ a: Int, ▔▔▔▔▔▔▔▔▔▔▔ b: Int, ▔▔▔▔▔▔▔▔▔▔▔ c: Int, ▔▔▔▔▔▔▔▔▔▔▔ d: Int, ▔▔▔▔▔▔▔▔▔▔▔ e: Int, ▔▔▔▔▔▔▔▔▔▔▔ f: Int, ▔▔▔▔▔▔▔▔▔▔▔ g: Int, ▔▔▔▔▔▔▔▔▔▔▔ h: Int, ▔▔▔▔▔▔▔▔▔▔▔ i: Int, ▔▔▔▔▔▔▔▔▔▔▔ j: Int, ▔▔▔▔▔▔▔▔▔▔▔ ) ▔▔▔ } ↑ ----- AFTER ACTION import gleam/dynamic/decode import gleam/option import gleam/dict pub type Wobble { Wobble( bit_array: BitArray, int: Int, float: Float, bool: Bool, list: List(Int), string: String, nil: Nil, option: option.Option(String), dict: dict.Dict(Int, Bool), ) Dummy( a: Int, b: Int, c: Int, d: Int, e: Int, f: Int, g: Int, h: Int, i: Int, j: Int, ) } fn wobble_decoder() -> decode.Decoder(Wobble) { use variant <- decode.field("type", decode.string) case variant { "wobble" -> { use bit_array <- decode.field("bit_array", decode.bit_array) use int <- decode.field("int", decode.int) use float <- decode.field("float", decode.float) use bool <- decode.field("bool", decode.bool) use list <- decode.field("list", decode.list(decode.int)) use string <- decode.field("string", decode.string) use nil <- decode.field("nil", decode.success(Nil)) use option <- decode.field("option", decode.optional(decode.string)) use dict <- decode.field("dict", decode.dict(decode.int, decode.bool)) decode.success(Wobble(bit_array:, int:, float:, bool:, list:, string:, nil:, option:, dict:)) } "dummy" -> { use a <- decode.field("a", decode.int) use b <- decode.field("b", decode.int) use c <- decode.field("c", decode.int) use d <- decode.field("d", decode.int) use e <- decode.field("e", decode.int) use f <- decode.field("f", decode.int) use g <- decode.field("g", decode.int) use h <- decode.field("h", decode.int) use i <- decode.field("i", decode.int) use j <- decode.field("j", decode.int) decode.success(Dummy(a:, b:, c:, d:, e:, f:, g:, h:, i:, j:)) } _ -> decode.failure(Wobble(bit_array: <<>>, int: 0, float: 0.0, bool: False, list: [], string: "", nil: Nil, option: option.None, dict: dict.new()), "Wobble") } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_dynamic_decoder_produces_zero_values_for_user_defined_type_in_the_same_package_1.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\nimport wibble.{type Wibble}\n\npub type Wobble {\n Wobble(value: Wibble)\n Dummy(a: Int, b: Int)\n}\n " --- ----- BEFORE ACTION import wibble.{type Wibble} pub type Wobble { ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Wobble(value: Wibble) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Dummy(a: Int, b: Int) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ } ↑ ----- AFTER ACTION import gleam/dynamic/decode import wibble.{type Wibble} pub type Wobble { Wobble(value: Wibble) Dummy(a: Int, b: Int) } fn wobble_decoder() -> decode.Decoder(Wobble) { use variant <- decode.field("type", decode.string) case variant { "wobble" -> { use value <- decode.field("value", todo as "Decoder for Wibble") decode.success(Wobble(value:)) } "dummy" -> { use a <- decode.field("a", decode.int) use b <- decode.field("b", decode.int) decode.success(Dummy(a:, b:)) } _ -> decode.failure(Wobble(value: wibble.Wibble), "Wobble") } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_dynamic_decoder_produces_zero_values_for_user_defined_type_in_the_same_package_2.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\nimport internal/wibble.{type Wibble}\n\npub type Wobble {\n Wobble(value: Wibble)\n Dummy(a: Int, b: Int)\n}\n " --- ----- BEFORE ACTION import internal/wibble.{type Wibble} pub type Wobble { ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Wobble(value: Wibble) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Dummy(a: Int, b: Int) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ } ↑ ----- AFTER ACTION import internal/nested_wibble import gleam/dynamic/decode import internal/wibble.{type Wibble} pub type Wobble { Wobble(value: Wibble) Dummy(a: Int, b: Int) } fn wobble_decoder() -> decode.Decoder(Wobble) { use variant <- decode.field("type", decode.string) case variant { "wobble" -> { use value <- decode.field("value", todo as "Decoder for Wibble") decode.success(Wobble(value:)) } "dummy" -> { use a <- decode.field("a", decode.int) use b <- decode.field("b", decode.int) decode.success(Dummy(a:, b:)) } _ -> decode.failure(Wobble(value: wibble.Wibble(value: nested_wibble.NestedWibble)), "Wobble") } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_dynamic_decoder_produces_zero_values_for_user_defined_type_in_the_same_package_3.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\nimport wibble.{type Wibble}\n\npub type Wobble {\n Wobble(value: Wibble)\n Dummy(a: Int, b: Int)\n}\n " --- ----- BEFORE ACTION import wibble.{type Wibble} pub type Wobble { ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Wobble(value: Wibble) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Dummy(a: Int, b: Int) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ } ↑ ----- AFTER ACTION import gleam/dict import gleam/dynamic/decode import wibble.{type Wibble} pub type Wobble { Wobble(value: Wibble) Dummy(a: Int, b: Int) } fn wobble_decoder() -> decode.Decoder(Wobble) { use variant <- decode.field("type", decode.string) case variant { "wobble" -> { use value <- decode.field("value", todo as "Decoder for Wibble") decode.success(Wobble(value:)) } "dummy" -> { use a <- decode.field("a", decode.int) use b <- decode.field("b", decode.int) decode.success(Dummy(a:, b:)) } _ -> decode.failure(Wobble(value: wibble.Wibble(map: dict.new())), "Wobble") } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_dynamic_decoder_recursive_type.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport gleam/option\n\npub type LinkedList {\n LinkedList(value: Int, next: option.Option(LinkedList))\n}\n" --- ----- BEFORE ACTION import gleam/option pub type LinkedList { ↑ LinkedList(value: Int, next: option.Option(LinkedList)) } ----- AFTER ACTION import gleam/dynamic/decode import gleam/option pub type LinkedList { LinkedList(value: Int, next: option.Option(LinkedList)) } fn linked_list_decoder() -> decode.Decoder(LinkedList) { use value <- decode.field("value", decode.int) use next <- decode.field("next", decode.optional(linked_list_decoder())) decode.success(LinkedList(value:, next:)) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_dynamic_decoder_skips_over_mutually_recursive_constructors_when_generating_zero_values.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub type Wobble {\n Wobble(inside: Wibble)\n DummyWobble(a: Int, b: Int)\n}\n\npub type Wibble {\n Wibble(inside: Wobble)\n DummyWibble(a: Int, b: Int)\n}\n " --- ----- BEFORE ACTION pub type Wobble { ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Wobble(inside: Wibble) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ DummyWobble(a: Int, b: Int) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ } ↑ pub type Wibble { Wibble(inside: Wobble) DummyWibble(a: Int, b: Int) } ----- AFTER ACTION import gleam/dynamic/decode pub type Wobble { Wobble(inside: Wibble) DummyWobble(a: Int, b: Int) } fn wobble_decoder() -> decode.Decoder(Wobble) { use variant <- decode.field("type", decode.string) case variant { "wobble" -> { use inside <- decode.field("inside", todo as "Decoder for Wibble") decode.success(Wobble(inside:)) } "dummy_wobble" -> { use a <- decode.field("a", decode.int) use b <- decode.field("b", decode.int) decode.success(DummyWobble(a:, b:)) } _ -> decode.failure(Wobble(inside: DummyWibble(a: 0, b: 0)), "Wobble") } } pub type Wibble { Wibble(inside: Wobble) DummyWibble(a: Int, b: Int) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_dynamic_decoder_skips_over_recursive_constructors_when_generating_zero_values.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub type Wobble {\n Wobble(inside: Wobble)\n Dummy(a: Int, b: Int)\n}\n " --- ----- BEFORE ACTION pub type Wobble { ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Wobble(inside: Wobble) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Dummy(a: Int, b: Int) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ } ↑ ----- AFTER ACTION import gleam/dynamic/decode pub type Wobble { Wobble(inside: Wobble) Dummy(a: Int, b: Int) } fn wobble_decoder() -> decode.Decoder(Wobble) { use variant <- decode.field("type", decode.string) case variant { "wobble" -> { use inside <- decode.field("inside", wobble_decoder()) decode.success(Wobble(inside:)) } "dummy" -> { use a <- decode.field("a", decode.int) use b <- decode.field("b", decode.int) decode.success(Dummy(a:, b:)) } _ -> decode.failure(Dummy(a: 0, b: 0), "Wobble") } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_dynamic_decoder_tuple.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble {\n Wibble(tuple: #(Int, Float, #(String, Bool)))\n}\n" --- ----- BEFORE ACTION pub type Wibble { ↑ Wibble(tuple: #(Int, Float, #(String, Bool))) } ----- AFTER ACTION import gleam/dynamic/decode pub type Wibble { Wibble(tuple: #(Int, Float, #(String, Bool))) } fn wibble_decoder() -> decode.Decoder(Wibble) { use tuple <- decode.field("tuple", { use a <- decode.field(0, decode.int) use b <- decode.field(1, decode.float) use c <- decode.field(2, { use a <- decode.field(0, decode.string) use b <- decode.field(1, decode.bool) decode.success(#(a, b)) }) decode.success(#(a, b, c)) }) decode.success(Wibble(tuple:)) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_dynamic_decoder_uses_decode_success_for_nil.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub type Nothing {\n No(val: Nil)\n Nope(val: #(Nil, Nil))\n Nothing(val: List(Nil))\n}\n " --- ----- BEFORE ACTION pub type Nothing { ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ No(val: Nil) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Nope(val: #(Nil, Nil)) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Nothing(val: List(Nil)) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ } ↑ ----- AFTER ACTION import gleam/dynamic/decode pub type Nothing { No(val: Nil) Nope(val: #(Nil, Nil)) Nothing(val: List(Nil)) } fn nothing_decoder() -> decode.Decoder(Nothing) { use variant <- decode.field("type", decode.string) case variant { "no" -> { use val <- decode.field("val", decode.success(Nil)) decode.success(No(val:)) } "nope" -> { use val <- decode.field("val", { use a <- decode.field(0, decode.success(Nil)) use b <- decode.field(1, decode.success(Nil)) decode.success(#(a, b)) }) decode.success(Nope(val:)) } "nothing" -> { use val <- decode.field("val", decode.list(decode.success(Nil))) decode.success(Nothing(val:)) } _ -> decode.failure(No(val: Nil), "Nothing") } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_dynamic_decoder_uses_smallest_possible_constructor_for_zero_value.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\nimport wibble.{type Wibble}\n\npub type Wobble {\n Wobble(impossible: Wobble)\n WibbleWobble(nope: Wibble)\n Dummy(a: Int, b: #(Float, Float))\n}\n " --- ----- BEFORE ACTION import wibble.{type Wibble} pub type Wobble { ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Wobble(impossible: Wobble) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ WibbleWobble(nope: Wibble) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Dummy(a: Int, b: #(Float, Float)) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ } ↑ ----- AFTER ACTION import gleam/dynamic/decode import wibble.{type Wibble} pub type Wobble { Wobble(impossible: Wobble) WibbleWobble(nope: Wibble) Dummy(a: Int, b: #(Float, Float)) } fn wobble_decoder() -> decode.Decoder(Wobble) { use variant <- decode.field("type", decode.string) case variant { "wobble" -> { use impossible <- decode.field("impossible", wobble_decoder()) decode.success(Wobble(impossible:)) } "wibble_wobble" -> { use nope <- decode.field("nope", todo as "Decoder for Wibble") decode.success(WibbleWobble(nope:)) } "dummy" -> { use a <- decode.field("a", decode.int) use b <- decode.field("b", { use a <- decode.field(0, decode.float) use b <- decode.field(1, decode.float) decode.success(#(a, b)) }) decode.success(Dummy(a:, b:)) } _ -> decode.failure(Dummy(a: 0, b: #(0.0, 0.0)), "Wobble") } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_function_arguments_with_labels_and_variables_uses_different_names.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let list = [2, 4, 5]\n let value = 1\n find(each: value, in: list)\n}\n" --- ----- BEFORE ACTION pub fn main() { let list = [2, 4, 5] let value = 1 find(each: value, in: list) ↑ } ----- AFTER ACTION pub fn main() { let list = [2, 4, 5] let value = 1 find(each: value, in: list) } fn find(each value: Int, in list: List(Int)) -> a { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_function_arguments_with_same_name_get_renamed.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let wibble = 10\n wubble(wibble, wibble)\n}\n" --- ----- BEFORE ACTION pub fn main() { let wibble = 10 wubble(wibble, wibble) ↑ } ----- AFTER ACTION pub fn main() { let wibble = 10 wubble(wibble, wibble) } fn wubble(wibble: Int, wibble_2: Int) -> a { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_function_capture.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\nfn map(list: List(a), f: fn(a) -> b) -> List(b) {\n todo\n}\n\npub fn main() {\n map([1, 2, 3], add(_, 1))\n}\n" snapshot_kind: text --- ----- BEFORE ACTION fn map(list: List(a), f: fn(a) -> b) -> List(b) { todo } pub fn main() { map([1, 2, 3], add(_, 1)) ↑ } ----- AFTER ACTION fn map(list: List(a), f: fn(a) -> b) -> List(b) { todo } pub fn main() { map([1, 2, 3], add(_, 1)) } fn add(int: Int, int_2: Int) -> b { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_function_generates_argument_names_from_labels.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n add(1, addend: 10)\n}\n" --- ----- BEFORE ACTION pub fn main() { add(1, addend: 10) ↑ } ----- AFTER ACTION pub fn main() { add(1, addend: 10) } fn add(int: Int, addend addend: Int) -> a { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_function_generates_argument_names_from_variables.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let wibble = 10\n let wobble = 20\n\n wubble(wibble, wobble, 14)\n}\n" --- ----- BEFORE ACTION pub fn main() { let wibble = 10 let wobble = 20 wubble(wibble, wobble, 14) ↑ } ----- AFTER ACTION pub fn main() { let wibble = 10 let wobble = 20 wubble(wibble, wobble, 14) } fn wubble(wibble: Int, wobble: Int, int: Int) -> a { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_function_in_other_module.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport wibble\n\npub fn main() {\n wibble.wibble()\n wibble.wobble()\n}\n" --- ----- BEFORE ACTION import wibble pub fn main() { wibble.wibble() wibble.wobble() ↑ } ----- AFTER ACTION // --- Edits applied to module 'wibble' pub fn wibble() {} pub fn wobble() -> a { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_function_in_other_module_correctly_appends.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "import module_breaker/another\n\npub fn main() -> Nil {\n another.function()\n}\n" --- ----- BEFORE ACTION import module_breaker/another pub fn main() -> Nil { another.function() ↑ } ----- AFTER ACTION // --- Edits applied to module 'module_breaker/another' pub fn useless() { Nil } pub fn function() -> Nil { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_function_labels_and_arguments_can_share_the_same_name.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let wibble = 10\n wubble(wibble, wibble: 14)\n}\n" --- ----- BEFORE ACTION pub fn main() { let wibble = 10 wubble(wibble, wibble: 14) ↑ } ----- AFTER ACTION pub fn main() { let wibble = 10 wubble(wibble, wibble: 14) } fn wubble(wibble: Int, wibble wibble_2: Int) -> a { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_function_picks_argument_name_based_on_record_access.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type User {\n User(id: Int, name: String)\n}\n\npub fn go(user: User) {\n authenticate(user.id, user.name)\n}\n" --- ----- BEFORE ACTION pub type User { User(id: Int, name: String) } pub fn go(user: User) { authenticate(user.id, user.name) ↑ } ----- AFTER ACTION pub type User { User(id: Int, name: String) } pub fn go(user: User) { authenticate(user.id, user.name) } fn authenticate(id: Int, name: String) -> a { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_function_picks_argument_name_based_on_type.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(\"Hello\", 1)\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble("Hello", 1) ↑ } ----- AFTER ACTION pub fn main() { wibble("Hello", 1) } fn wibble(string: String, int: Int) -> a { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_function_takes_labels_into_account.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(2, n: 1)\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble(2, n: 1) ↑ } ----- AFTER ACTION pub fn main() { wibble(2, n: 1) } fn wibble(int: Int, n n: Int) -> a { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_function_wont_generate_two_arguments_with_the_same_name_if_they_have_the_same_type.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(2, 1)\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble(2, 1) ↑ } ----- AFTER ACTION pub fn main() { wibble(2, 1) } fn wibble(int: Int, int_2: Int) -> a { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_function_works_with_constants.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "const wibble: fn(Int) -> String = wobble" --- ----- BEFORE ACTION const wibble: fn(Int) -> String = wobble ↑ ----- AFTER ACTION const wibble: fn(Int) -> String = wobble fn wobble(int: Int) -> String { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_function_works_with_constants_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\ntype Wibble(a) {\n Wibble(fun: fn(Int) -> a)\n}\n\nconst wibble: Wibble(Int) = Wibble(missing)\n" --- ----- BEFORE ACTION type Wibble(a) { Wibble(fun: fn(Int) -> a) } const wibble: Wibble(Int) = Wibble(missing) ↑ ----- AFTER ACTION type Wibble(a) { Wibble(fun: fn(Int) -> a) } const wibble: Wibble(Int) = Wibble(missing) fn missing(int: Int) -> Int { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_function_works_with_invalid_call.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() -> Bool {\n wibble(1, True, 2.3)\n}\n" --- ----- BEFORE ACTION pub fn main() -> Bool { wibble(1, True, 2.3) ↑ } ----- AFTER ACTION pub fn main() -> Bool { wibble(1, True, 2.3) } fn wibble(int: Int, bool: Bool, float: Float) -> Bool { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_function_works_with_pipeline_steps.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n [1, 2, 3]\n |> sum\n |> int_to_string\n}\n\nfn int_to_string(n: Int) -> String {\n todo\n}\n" --- ----- BEFORE ACTION pub fn main() { [1, 2, 3] |> sum ↑ |> int_to_string } fn int_to_string(n: Int) -> String { todo } ----- AFTER ACTION pub fn main() { [1, 2, 3] |> sum |> int_to_string } fn sum(ints: List(Int)) -> Int { todo } fn int_to_string(n: Int) -> String { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_function_works_with_pipeline_steps_1.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n [1, 2, 3]\n |> map(int_to_string)\n |> join\n}\n\nfn map(list: List(a), fun: fn(a) -> b) -> List(b) {\n todo\n}\n\nfn join(n: List(String)) -> String {\n todo\n}\n" --- ----- BEFORE ACTION pub fn main() { [1, 2, 3] |> map(int_to_string) ↑ |> join } fn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo } fn join(n: List(String)) -> String { todo } ----- AFTER ACTION pub fn main() { [1, 2, 3] |> map(int_to_string) |> join } fn int_to_string(int: Int) -> String { todo } fn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo } fn join(n: List(String)) -> String { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_json_encoder.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Person {\n Person(name: String, age: Int, height: Float, is_cool: Bool)\n}\n" --- ----- BEFORE ACTION pub type Person { ↑ Person(name: String, age: Int, height: Float, is_cool: Bool) } ----- AFTER ACTION import gleam/json pub type Person { Person(name: String, age: Int, height: Float, is_cool: Bool) } fn person_to_json(person: Person) -> json.Json { let Person(name:, age:, height:, is_cool:) = person json.object([ #("name", json.string(name)), #("age", json.int(age)), #("height", json.float(height)), #("is_cool", json.bool(is_cool)), ]) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_json_encoder_already_imported_module.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport gleam/json as json_encoding\n\npub type Wibble {\n Wibble(a: Int, b: Float, c: String)\n}\n" --- ----- BEFORE ACTION import gleam/json as json_encoding pub type Wibble { ↑ Wibble(a: Int, b: Float, c: String) } ----- AFTER ACTION import gleam/json as json_encoding pub type Wibble { Wibble(a: Int, b: Float, c: String) } fn wibble_to_json(wibble: Wibble) -> json_encoding.Json { let Wibble(a:, b:, c:) = wibble json_encoding.object([ #("a", json_encoding.int(a)), #("b", json_encoding.float(b)), #("c", json_encoding.string(c)), ]) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_json_encoder_complex_types.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport gleam/option\nimport gleam/dict\n\npub type Something\n\npub type Wibble(value) {\n Wibble(\n maybe: option.Option(Int),\n something: Something,\n map: dict.Dict(String, List(Float)),\n unknown: List(value),\n )\n}\n" --- ----- BEFORE ACTION import gleam/option import gleam/dict pub type Something pub type Wibble(value) { ↑ Wibble( maybe: option.Option(Int), something: Something, map: dict.Dict(String, List(Float)), unknown: List(value), ) } ----- AFTER ACTION import gleam/json import gleam/option import gleam/dict pub type Something pub type Wibble(value) { Wibble( maybe: option.Option(Int), something: Something, map: dict.Dict(String, List(Float)), unknown: List(value), ) } fn wibble_to_json(wibble: Wibble(value)) -> json.Json { let Wibble(maybe:, something:, map:, unknown:) = wibble json.object([ #("maybe", case maybe { option.None -> json.null() option.Some(value) -> json.int(value) }), #("something", todo as "Encoder for Something"), #("map", json.dict(map, fn(string) { string }, json.array(_, json.float))), #("unknown", json.array(unknown, todo as "Encoder for value")), ]) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_json_encoder_for_multi_variant_type.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble {\n Wibble(wibble: Int, next: Wibble)\n Wobble(wobble: Float, text: String, values: List(Bool))\n}\n" --- ----- BEFORE ACTION pub type Wibble { ↑ Wibble(wibble: Int, next: Wibble) Wobble(wobble: Float, text: String, values: List(Bool)) } ----- AFTER ACTION import gleam/json pub type Wibble { Wibble(wibble: Int, next: Wibble) Wobble(wobble: Float, text: String, values: List(Bool)) } fn wibble_to_json(wibble: Wibble) -> json.Json { case wibble { Wibble(wibble:, next:) -> json.object([ #("type", json.string("wibble")), #("wibble", json.int(wibble)), #("next", wibble_to_json(next)), ]) Wobble(wobble:, text:, values:) -> json.object([ #("type", json.string("wobble")), #("wobble", json.float(wobble)), #("text", json.string(text)), #("values", json.array(values, json.bool)), ]) } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_json_encoder_for_multi_variant_type_multi_word_name.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble {\n OneTwoThree(wibble: Int, next: Wibble)\n FourFive(wobble: Float, text: String, values: List(Bool))\n SixSevenEight(one_two: Float)\n}\n" --- ----- BEFORE ACTION pub type Wibble { ↑ OneTwoThree(wibble: Int, next: Wibble) FourFive(wobble: Float, text: String, values: List(Bool)) SixSevenEight(one_two: Float) } ----- AFTER ACTION import gleam/json pub type Wibble { OneTwoThree(wibble: Int, next: Wibble) FourFive(wobble: Float, text: String, values: List(Bool)) SixSevenEight(one_two: Float) } fn wibble_to_json(wibble: Wibble) -> json.Json { case wibble { OneTwoThree(wibble:, next:) -> json.object([ #("type", json.string("one_two_three")), #("wibble", json.int(wibble)), #("next", wibble_to_json(next)), ]) FourFive(wobble:, text:, values:) -> json.object([ #("type", json.string("four_five")), #("wobble", json.float(wobble)), #("text", json.string(text)), #("values", json.array(values, json.bool)), ]) SixSevenEight(one_two:) -> json.object([ #("type", json.string("six_seven_eight")), #("one_two", json.float(one_two)), ]) } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_json_encoder_for_type_with_multiple_variants_with_no_fields.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble {\n Wibble\n Wobble\n Woo\n}\n" --- ----- BEFORE ACTION pub type Wibble { ↑ Wibble Wobble Woo } ----- AFTER ACTION import gleam/json pub type Wibble { Wibble Wobble Woo } fn wibble_to_json(wibble: Wibble) -> json.Json { case wibble { Wibble -> json.string("wibble") Wobble -> json.string("wobble") Woo -> json.string("woo") } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_json_encoder_for_variant_with_no_fields.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble {\n Wibble\n}\n" --- ----- BEFORE ACTION pub type Wibble { ↑ Wibble } ----- AFTER ACTION import gleam/json pub type Wibble { Wibble } fn wibble_to_json(wibble: Wibble) -> json.Json { json.string("wibble") } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_json_encoder_for_variants_with_mixed_fields.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble {\n Wibble\n Wobble(field: String, field1: Int)\n}\n" --- ----- BEFORE ACTION pub type Wibble { ↑ Wibble Wobble(field: String, field1: Int) } ----- AFTER ACTION import gleam/json pub type Wibble { Wibble Wobble(field: String, field1: Int) } fn wibble_to_json(wibble: Wibble) -> json.Json { case wibble { Wibble -> json.object([ #("type", json.string("wibble")), ]) Wobble(field:, field1:) -> json.object([ #("type", json.string("wobble")), #("field", json.string(field)), #("field1", json.int(field1)), ]) } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_json_encoder_list_of_tuples.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble {\n Wibble(values: List(#(Int, String)))\n}\n" --- ----- BEFORE ACTION pub type Wibble { ↑ Wibble(values: List(#(Int, String))) } ----- AFTER ACTION import gleam/json pub type Wibble { Wibble(values: List(#(Int, String))) } fn wibble_to_json(wibble: Wibble) -> json.Json { let Wibble(values:) = wibble json.object([ #("values", json.array(values, fn(value) { json.preprocessed_array([ json.int(value.0), json.string(value.1), ]) })), ]) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_json_encoder_recursive_type.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport gleam/option.{Some}\n\npub type LinkedList {\n LinkedList(value: Int, next: option.Option(LinkedList))\n}\n" --- ----- BEFORE ACTION import gleam/option.{Some} pub type LinkedList { ↑ LinkedList(value: Int, next: option.Option(LinkedList)) } ----- AFTER ACTION import gleam/json import gleam/option.{Some} pub type LinkedList { LinkedList(value: Int, next: option.Option(LinkedList)) } fn linked_list_to_json(linked_list: LinkedList) -> json.Json { let LinkedList(value:, next:) = linked_list json.object([ #("value", json.int(value)), #("next", case next { option.None -> json.null() Some(value) -> linked_list_to_json(value) }), ]) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_json_encoder_tuple.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble {\n Wibble(tuple: #(Int, Float, #(String, Bool)))\n}\n" --- ----- BEFORE ACTION pub type Wibble { ↑ Wibble(tuple: #(Int, Float, #(String, Bool))) } ----- AFTER ACTION import gleam/json pub type Wibble { Wibble(tuple: #(Int, Float, #(String, Bool))) } fn wibble_to_json(wibble: Wibble) -> json.Json { let Wibble(tuple:) = wibble json.object([ #("tuple", json.preprocessed_array([ json.int(tuple.0), json.float(tuple.1), json.preprocessed_array([ json.string(tuple.2.0), json.bool(tuple.2.1), ]), ])), ]) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_qualified_variant_in_other_module.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport other\n\npub fn main() -> other.Wibble {\n let assert other.Wobble = new()\n}\n\npub fn new() -> other.Wibble { todo }\n" --- ----- BEFORE ACTION import other pub fn main() -> other.Wibble { let assert other.Wobble = new() ↑ } pub fn new() -> other.Wibble { todo } ----- AFTER ACTION // --- Edits applied to module 'other' pub type Wibble { Wobble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_to_json_function_ignores_nil_and_nil_tuple_fields_with_underscore.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\nimport gleam/json\nimport gleam/dict.{type Dict}\n\n\npub type Nothing {\n No(val: #(Nil), another: #(Nil, #(Nil, Int)))\n Nope(val: List(#(Nil)))\n}\n" --- ----- BEFORE ACTION import gleam/json import gleam/dict.{type Dict} pub type Nothing { ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ No(val: #(Nil), another: #(Nil, #(Nil, Int))) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Nope(val: List(#(Nil))) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ } ↑ ----- AFTER ACTION import gleam/json import gleam/dict.{type Dict} pub type Nothing { No(val: #(Nil), another: #(Nil, #(Nil, Int))) Nope(val: List(#(Nil))) } fn nothing_to_json(nothing: Nothing) -> json.Json { case nothing { No(val: _, another:) -> json.object([ #("type", json.string("no")), #("val", json.preprocessed_array([ json.null(), ])), #("another", json.preprocessed_array([ json.null(), json.preprocessed_array([ json.null(), json.int(another.1.1), ]), ])), ]) Nope(val:) -> json.object([ #("type", json.string("nope")), #("val", json.array(val, fn(_) { json.preprocessed_array([ json.null(), ]) })), ]) } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_to_json_function_uses_json_null_for_nil.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\nimport gleam/json\nimport gleam/dict.{type Dict}\n\n\npub type Nothing {\n Nope(val: Nil)\n Nothing(val1: List(Nil), val2: Dict(Int, Nil))\n}\n" --- ----- BEFORE ACTION import gleam/json import gleam/dict.{type Dict} pub type Nothing { ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Nope(val: Nil) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Nothing(val1: List(Nil), val2: Dict(Int, Nil)) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ } ↑ ----- AFTER ACTION import gleam/json import gleam/dict.{type Dict} pub type Nothing { Nope(val: Nil) Nothing(val1: List(Nil), val2: Dict(Int, Nil)) } fn nothing_to_json(nothing: Nothing) -> json.Json { case nothing { Nope(val: _) -> json.object([ #("type", json.string("nope")), #("val", json.null()), ]) Nothing(val1:, val2:) -> json.object([ #("type", json.string("nothing")), #("val1", json.array(val1, fn(_) { json.null() })), #("val2", json.dict(val2, todo as "Function to stringify Int", fn(_) { json.null() })), ]) } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_unqualified_variant_in_other_module.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport other\n\npub fn main() -> other.Wibble {\n let assert Wobble = new()\n}\n\npub fn new() -> other.Wibble { todo }\n" --- ----- BEFORE ACTION import other pub fn main() -> other.Wibble { let assert Wobble = new() ↑ } pub fn new() -> other.Wibble { todo } ----- AFTER ACTION // --- Edits applied to module 'other' pub type Wibble { Wobble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_variant_from_pattern_with_fields.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble {\n Wibble\n}\n\npub fn new() { Wibble }\n\npub fn main() -> Wibble {\n let assert Wobble(1) = new()\n}\n\n" --- ----- BEFORE ACTION pub type Wibble { Wibble } pub fn new() { Wibble } pub fn main() -> Wibble { let assert Wobble(1) = new() ↑ } ----- AFTER ACTION pub type Wibble { Wibble Wobble(Int) } pub fn new() { Wibble } pub fn main() -> Wibble { let assert Wobble(1) = new() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_variant_from_pattern_with_labelled_fields.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble {\n Wibble\n}\n\npub fn new() { Wibble }\n\npub fn main() -> Wibble {\n let assert Wobble(\"hello\", label: 1) = new()\n}\n\n" --- ----- BEFORE ACTION pub type Wibble { Wibble } pub fn new() { Wibble } pub fn main() -> Wibble { let assert Wobble("hello", label: 1) = new() ↑ } ----- AFTER ACTION pub type Wibble { Wibble Wobble(String, label: Int) } pub fn new() { Wibble } pub fn main() -> Wibble { let assert Wobble("hello", label: 1) = new() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_variant_from_pattern_with_no_fields.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble {\n Wibble\n}\n\npub fn new() { Wibble }\n\npub fn main() -> Wibble {\n let assert Wobble = new()\n}\n\n" --- ----- BEFORE ACTION pub type Wibble { Wibble } pub fn new() { Wibble } pub fn main() -> Wibble { let assert Wobble = new() ↑ } ----- AFTER ACTION pub type Wibble { Wibble Wobble } pub fn new() { Wibble } pub fn main() -> Wibble { let assert Wobble = new() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_variant_with_fields_in_same_module.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble {\n Wibble\n}\n\npub fn main() -> Wibble {\n Wobble(1)\n}" --- ----- BEFORE ACTION pub type Wibble { Wibble } pub fn main() -> Wibble { Wobble(1) ↑ } ----- AFTER ACTION pub type Wibble { Wibble Wobble(Int) } pub fn main() -> Wibble { Wobble(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_variant_with_labels_in_same_module.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble {\n Wibble\n}\n\npub fn main() -> Wibble {\n Wobble(\"hello\", label: 1)\n}" --- ----- BEFORE ACTION pub type Wibble { Wibble } pub fn main() -> Wibble { Wobble("hello", label: 1) ↑ } ----- AFTER ACTION pub type Wibble { Wibble Wobble(String, label: Int) } pub fn main() -> Wibble { Wobble("hello", label: 1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generate_variant_with_no_fields_in_same_module.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble {\n Wibble\n}\n\npub fn main() -> Wibble {\n Wobble\n}" --- ----- BEFORE ACTION pub type Wibble { Wibble } pub fn main() -> Wibble { Wobble ↑ } ----- AFTER ACTION pub type Wibble { Wibble Wobble } pub fn main() -> Wibble { Wobble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generated_function_annotations_are_not_affected_by_other_functions.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nfn wibble(a: a, b: b, c: c) -> d { todo }\n\npub fn main() {\n let x = todo\n let y = todo\n let #(a, b) = something(x, y)\n b\n}\n" --- ----- BEFORE ACTION fn wibble(a: a, b: b, c: c) -> d { todo } pub fn main() { let x = todo let y = todo let #(a, b) = something(x, y) ↑ b } ----- AFTER ACTION fn wibble(a: a, b: b, c: c) -> d { todo } pub fn main() { let x = todo let y = todo let #(a, b) = something(x, y) b } fn something(x: a, y: b) -> #(c, d) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generating_function_in_other_module_uses_labels.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport wibble\n\npub fn main() {\n wibble.wibble(\"Unlabelled\", int: 1, bool: True)\n}\n" --- ----- BEFORE ACTION import wibble pub fn main() { wibble.wibble("Unlabelled", int: 1, bool: True) ↑ } ----- AFTER ACTION // --- Edits applied to module 'wibble' pub fn wibble(string: String, int int: Int, bool bool: Bool) -> a { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__generating_function_in_other_module_uses_local_names.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport wibble\n\npub fn main() -> List(Nil) {\n wibble.wibble(1, #(True, \"Hello\"))\n}\n" --- ----- BEFORE ACTION import wibble pub fn main() -> List(Nil) { wibble.wibble(1, #(True, "Hello")) ↑ } ----- AFTER ACTION // --- Edits applied to module 'wibble' import gleam.{type Int as Number, type Bool as Boolean, type String as Text, type Nil as Nothing} pub fn wibble(int: Number, value: #(Boolean, Text)) -> List(Nothing) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__import_internal_module_from_same_package.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n internal.some_internal_function()\n}\n" --- ----- BEFORE ACTION pub fn main() { internal.some_internal_function() ↑ } ----- AFTER ACTION import app/internal pub fn main() { internal.some_internal_function() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__import_module_from_constructor.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let value = values.Value(10)\n}\n" --- ----- BEFORE ACTION pub fn main() { let value = values.Value(10) ▔▔▔▔▔▔↑ } ----- AFTER ACTION import values pub fn main() { let value = values.Value(10) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__import_module_from_function.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n result.is_ok()\n}\n" --- ----- BEFORE ACTION pub fn main() { result.is_ok() ▔▔▔▔▔▔↑ } ----- AFTER ACTION import result pub fn main() { result.is_ok() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__import_module_from_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main(res) {\n case res {\n result.Ok(_) -> Nil\n result.Error(_) -> Nil\n }\n}\n" --- ----- BEFORE ACTION pub fn main(res) { case res { result.Ok(_) -> Nil ▔▔▔▔▔▔↑ result.Error(_) -> Nil } } ----- AFTER ACTION import result pub fn main(res) { case res { result.Ok(_) -> Nil result.Error(_) -> Nil } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__import_module_from_type.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: type Wobble = wibble.Wubble --- ----- BEFORE ACTION type Wobble = wibble.Wubble ▔▔▔▔▔▔↑ ----- AFTER ACTION import mod/wibble type Wobble = wibble.Wubble ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__import_path_module_from_function.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n io.println(\"Hello, world!\")\n}\n" --- ----- BEFORE ACTION pub fn main() { io.println("Hello, world!") ▔▔↑ } ----- AFTER ACTION import gleam/io pub fn main() { io.println("Hello, world!") } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__import_similar_module.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n reult.is_ok()\n}\n" --- ----- BEFORE ACTION pub fn main() { reult.is_ok() ▔▔▔▔▔↑ } ----- AFTER ACTION import result pub fn main() { result.is_ok() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__inexhaustive_let_alias_to_case.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let 10 as ten = 10\n}" --- ----- BEFORE ACTION pub fn main() { let 10 as ten = 10 ▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let ten = case 10 { 10 as ten -> ten _ -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__inexhaustive_let_bit_array_to_case.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let <> = <<73, 98>>\n}" --- ----- BEFORE ACTION pub fn main() { let <> = <<73, 98>> ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let #(bits1, bits2) = case <<73, 98>> { <> -> #(bits1, bits2) _ -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__inexhaustive_let_result_to_case.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(result) {\n let Ok(value) = result\n}" --- ----- BEFORE ACTION pub fn main(result) { let Ok(value) = result ▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main(result) { let value = case result { Ok(value) -> value Error(_) -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__inexhaustive_let_string_prefix_pattern_alias_to_case.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let \"123\" as one_two_three <> rest = \"123456\"\n}" --- ----- BEFORE ACTION pub fn main() { let "123" as one_two_three <> rest = "123456" ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let #(one_two_three, rest) = case "123456" { "123" as one_two_three <> rest -> #(one_two_three, rest) _ -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__inexhaustive_let_string_prefix_to_case.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let \"_\" <> thing = \"_Hello\"\n}" --- ----- BEFORE ACTION pub fn main() { let "_" <> thing = "_Hello" ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let thing = case "_Hello" { "_" <> thing -> thing _ -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__inexhaustive_let_to_case_discard.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let [_elem] = [6]\n}" --- ----- BEFORE ACTION pub fn main() { let [_elem] = [6] ▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let _ = case [6] { [_elem] -> Nil [] -> todo [_, _, ..] -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__inexhaustive_let_to_case_indented.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(result) {\n {\n let Ok(value) = result\n }\n}" --- ----- BEFORE ACTION pub fn main(result) { { let Ok(value) = result ▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } } ----- AFTER ACTION pub fn main(result) { { let value = case result { Ok(value) -> value Error(_) -> todo } } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__inexhaustive_let_to_case_multi_variables.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let [var1, var2, _var3, var4] = [1, 2, 3, 4]\n}" --- ----- BEFORE ACTION pub fn main() { let [var1, var2, _var3, var4] = [1, 2, 3, 4] ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let #(var1, var2, var4) = case [1, 2, 3, 4] { [var1, var2, _var3, var4] -> #(var1, var2, var4) [] -> todo [_] -> todo [_, _] -> todo [_, _, _] -> todo [_, _, _, _, _, ..] -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__inexhaustive_let_to_case_no_variables.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let [] = []\n}" --- ----- BEFORE ACTION pub fn main() { let [] = [] ▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let _ = case [] { [] -> Nil [_, ..] -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__inexhaustive_let_tuple_to_case.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let #(first, 10, third) = #(5, 10, 15)\n}\n" --- ----- BEFORE ACTION pub fn main() { let #(first, 10, third) = #(5, 10, 15) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let #(first, third) = case #(5, 10, 15) { #(first, 10, third) -> #(first, third) #(_, _, _) -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__inline_variable.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport gleam/io\n\npub fn main() {\n let message = \"Hello!\"\n io.println(message)\n}\n" --- ----- BEFORE ACTION import gleam/io pub fn main() { let message = "Hello!" io.println(message) ↑ } ----- AFTER ACTION import gleam/io pub fn main() { io.println("Hello!") } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__inline_variable_from_definition.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport gleam/io\n\npub fn main() {\n let message = \"Hello!\"\n io.println(message)\n}\n" --- ----- BEFORE ACTION import gleam/io pub fn main() { let message = "Hello!" ↑ io.println(message) } ----- AFTER ACTION import gleam/io pub fn main() { io.println("Hello!") } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__inline_variable_in_case_scope.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport gleam/io\n\npub fn main(x) {\n case x {\n True -> {\n let message = \"Hello!\"\n io.println(message)\n }\n False -> Nil\n }\n}\n" --- ----- BEFORE ACTION import gleam/io pub fn main(x) { case x { True -> { let message = "Hello!" ↑ io.println(message) } False -> Nil } } ----- AFTER ACTION import gleam/io pub fn main(x) { case x { True -> { io.println("Hello!") } False -> Nil } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__inline_variable_in_nested_scope.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport gleam/io\n\npub fn main() {\n let _ = {\n let message = \"Hello!\"\n io.println(message)\n }\n}\n" --- ----- BEFORE ACTION import gleam/io pub fn main() { let _ = { let message = "Hello!" ↑ io.println(message) } } ----- AFTER ACTION import gleam/io pub fn main() { let _ = { io.println("Hello!") } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__inline_variable_in_record_update.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\ntype Couple {\n Couple(l: Int, r: Int)\n}\n\npub fn main() {\n let c1 = Couple(l: 1, r: 1)\n let c2 = Couple(..c1, r: 1)\n}\n" --- ----- BEFORE ACTION type Couple { Couple(l: Int, r: Int) } pub fn main() { let c1 = Couple(l: 1, r: 1) let c2 = Couple(..c1, r: 1) ↑ } ----- AFTER ACTION type Couple { Couple(l: Int, r: Int) } pub fn main() { let c2 = Couple(..Couple(l: 1, r: 1), r: 1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__inline_variable_label_shorthand.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Example {\n Example(sum: Int, nil: Nil)\n}\n\npub fn main() {\n let sum = 1 + 1\n\n Example(Nil, sum:)\n}\n" --- ----- BEFORE ACTION pub type Example { Example(sum: Int, nil: Nil) } pub fn main() { let sum = 1 + 1 ↑ Example(Nil, sum:) } ----- AFTER ACTION pub type Example { Example(sum: Int, nil: Nil) } pub fn main() { Example(Nil, sum: 1 + 1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__inline_variable_when_over_let_keyword.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let x = 123\n x + 1\n}\n" --- ----- BEFORE ACTION pub fn main() { let x = 123 ↑ x + 1 } ----- AFTER ACTION pub fn main() { 123 + 1 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__inline_variable_with_record_field.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\ntype Couple {\n Couple(l: Int, r: Int)\n}\n\npub fn main() {\n let c1 = Couple(l: 1, r: 1)\n let c2 = c1.l\n echo c2\n}\n" --- ----- BEFORE ACTION type Couple { Couple(l: Int, r: Int) } pub fn main() { let c1 = Couple(l: 1, r: 1) let c2 = c1.l echo c2 ↑ } ----- AFTER ACTION type Couple { Couple(l: Int, r: Int) } pub fn main() { let c1 = Couple(l: 1, r: 1) echo c1.l } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__inner_inexhaustive_let_to_case.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(result) {\n let [wibble] = {\n let Ok(wobble) = {\n result\n }\n [wobble]\n }\n}" --- ----- BEFORE ACTION pub fn main(result) { let [wibble] = { let Ok(wobble) = { ▔▔▔▔▔▔▔▔▔▔▔▔▔↑ result } [wobble] } } ----- AFTER ACTION pub fn main(result) { let [wibble] = { let wobble = case { result } { Ok(wobble) -> wobble Error(_) -> todo } [wobble] } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__interpolate_string_allows_extracting_record_access_syntax.snap ================================================ --- source: language-server/src/tests/action.rs expression: "pub fn main() {\n \"wibble wobble.some_field woo\"\n}" --- ----- BEFORE ACTION pub fn main() { "wibble wobble.some_field woo" ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { "wibble " <> wobble.some_field <> " woo" } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__interpolate_string_does_not_add_empty_string_right_at_the_end.snap ================================================ --- source: language-server/src/tests/action.rs expression: "pub fn main() {\n \"wibble wobble woo\"\n}" --- ----- BEFORE ACTION pub fn main() { "wibble wobble woo" ▔▔▔↑ } ----- AFTER ACTION pub fn main() { "wibble wobble " <> woo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__interpolate_string_does_not_add_empty_string_right_at_the_start.snap ================================================ --- source: language-server/src/tests/action.rs expression: "pub fn main() {\n \"wibble wobble woo\"\n}" --- ----- BEFORE ACTION pub fn main() { "wibble wobble woo" ▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { wibble <> " wobble woo" } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__interpolate_string_inside_string.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n \"wibble wobble woo\"\n}" --- ----- BEFORE ACTION pub fn main() { "wibble wobble woo" ▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { "wibble " <> wobble <> " woo" } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__interpolating_string_as_first_pipeline_step_inserts_brackets.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n \"wibble wobble woo\" |> io.println\n}" --- ----- BEFORE ACTION pub fn main() { "wibble wobble woo" |> io.println ▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { { "wibble " <> wobble <> " woo" } |> io.println } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__label_shorthand_action_only_applies_to_selected_args.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let arg1 = 1\n let arg2 = 2\n Wibble(arg2: arg2, arg1: arg1)\n}\n\npub type Wibble { Wibble(arg1: Int, arg2: Int) }\n" --- ----- BEFORE ACTION pub fn main() { let arg1 = 1 let arg2 = 2 Wibble(arg2: arg2, arg1: arg1) ▔▔▔▔▔▔▔▔▔▔▔↑ } pub type Wibble { Wibble(arg1: Int, arg2: Int) } ----- AFTER ACTION pub fn main() { let arg1 = 1 let arg2 = 2 Wibble(arg2: , arg1: arg1) } pub type Wibble { Wibble(arg1: Int, arg2: Int) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__label_shorthand_action_works_on_labelled_call_args.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let arg1 = 1\n let arg2 = 2\n wibble(arg2: arg2, arg1: arg1)\n}\n\npub fn wibble(arg1 arg1, arg2 arg2) { Nil }\n" --- ----- BEFORE ACTION pub fn main() { let arg1 = 1 let arg2 = 2 wibble(arg2: arg2, arg1: arg1) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } pub fn wibble(arg1 arg1, arg2 arg2) { Nil } ----- AFTER ACTION pub fn main() { let arg1 = 1 let arg2 = 2 wibble(arg2: , arg1: ) } pub fn wibble(arg1 arg1, arg2 arg2) { Nil } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__label_shorthand_action_works_on_labelled_constructor_call_args.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let arg1 = 1\n let arg2 = 2\n Wibble(arg2: arg2, arg1: arg1)\n}\n\npub type Wibble { Wibble(arg1: Int, arg2: Int) }\n" --- ----- BEFORE ACTION pub fn main() { let arg1 = 1 let arg2 = 2 Wibble(arg2: arg2, arg1: arg1) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } pub type Wibble { Wibble(arg1: Int, arg2: Int) } ----- AFTER ACTION pub fn main() { let arg1 = 1 let arg2 = 2 Wibble(arg2: , arg1: ) } pub type Wibble { Wibble(arg1: Int, arg2: Int) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__label_shorthand_action_works_on_labelled_pattern_call_args.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let Wibble(arg1: arg1, arg2: arg2) = todo\n arg1 + arg2\n}\n\npub type Wibble { Wibble(arg1: Int, arg2: Int) }\n" --- ----- BEFORE ACTION pub fn main() { let Wibble(arg1: arg1, arg2: arg2) = todo ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ arg1 + arg2 } pub type Wibble { Wibble(arg1: Int, arg2: Int) } ----- AFTER ACTION pub fn main() { let Wibble(arg1: , arg2: ) = todo arg1 + arg2 } pub type Wibble { Wibble(arg1: Int, arg2: Int) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__label_shorthand_action_works_on_labelled_update_call_args.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let arg1 = 1\n Wibble(..todo, arg1: arg1)\n}\n\npub type Wibble { Wibble(arg1: Int, arg2: Int) }\n" --- ----- BEFORE ACTION pub fn main() { let arg1 = 1 Wibble(..todo, arg1: arg1) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } pub type Wibble { Wibble(arg1: Int, arg2: Int) } ----- AFTER ACTION pub fn main() { let arg1 = 1 Wibble(..todo, arg1: ) } pub type Wibble { Wibble(arg1: Int, arg2: Int) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__merge_case_branch.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn go(n: Int) {\n case n {\n 1 -> todo\n 2 -> todo\n _ -> todo\n }\n }" --- ----- BEFORE ACTION pub fn go(n: Int) { case n { 1 -> todo ▔▔▔▔▔▔▔▔▔ 2 -> todo ▔▔▔▔↑ _ -> todo } } ----- AFTER ACTION pub fn go(n: Int) { case n { 1 | 2 -> todo _ -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__merge_case_branch_can_merge_branches_defining_the_same_variables.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn go(result) {\n case result {\n [Ok(value), ..] -> todo\n [_, Error(value)] -> todo\n _ -> todo\n }\n}" --- ----- BEFORE ACTION pub fn go(result) { case result { [Ok(value), ..] -> todo ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ [_, Error(value)] -> todo ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ _ -> todo } } ----- AFTER ACTION pub fn go(result) { case result { [Ok(value), ..] | [_, Error(value)] -> todo _ -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__merge_case_branch_can_merge_multiple_branches.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn go(result) {\n case result {\n [_] -> 1\n [Ok(value), ..] -> todo\n [_, Error(value)] -> todo\n [_, _, Error(value)] -> todo\n [_, _] -> 1\n _ -> 2\n }\n}" --- ----- BEFORE ACTION pub fn go(result) { case result { [_] -> 1 [Ok(value), ..] -> todo ▔▔▔▔ [_, Error(value)] -> todo ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ [_, _, Error(value)] -> todo ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ [_, _] -> 1 _ -> 2 } } ----- AFTER ACTION pub fn go(result) { case result { [_] -> 1 [Ok(value), ..] | [_, Error(value)] | [_, _, Error(value)] -> todo [_, _] -> 1 _ -> 2 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__merge_case_branch_with_complex_bodies_1.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn go(n: Int) {\n case n {\n 1 -> Ok(\"one or two\")\n 2 -> Ok(\"one or two\")\n _ -> Error(\"neither one or two\")\n }\n }" --- ----- BEFORE ACTION pub fn go(n: Int) { case n { 1 -> Ok("one or two") ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ 2 -> Ok("one or two") ▔▔▔▔↑ _ -> Error("neither one or two") } } ----- AFTER ACTION pub fn go(n: Int) { case n { 1 | 2 -> Ok("one or two") _ -> Error("neither one or two") } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__merge_case_branch_with_complex_bodies_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn go(n: Int) {\n case n {\n 1 -> n\n 2 -> n\n _ -> panic as \"neither one nor two\"\n }\n }" --- ----- BEFORE ACTION pub fn go(n: Int) { case n { 1 -> n ▔▔▔▔▔▔ 2 -> n ▔▔▔▔↑ _ -> panic as "neither one nor two" } } ----- AFTER ACTION pub fn go(n: Int) { case n { 1 | 2 -> n _ -> panic as "neither one nor two" } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__merge_case_branch_with_complex_bodies_3.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn go(n: Int) {\n case n {\n 1 -> go(n - 1)\n 2 -> go(n - 1)\n _ -> 10\n }\n}" --- ----- BEFORE ACTION pub fn go(n: Int) { case n { 1 -> go(n - 1) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔ 2 -> go(n - 1) ▔▔▔▔↑ _ -> 10 } } ----- AFTER ACTION pub fn go(n: Int) { case n { 1 | 2 -> go(n - 1) _ -> 10 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__merge_case_branch_with_complex_bodies_4.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn go(n: Int) {\n case n {\n 1 -> {\n let a = go(n - 1)\n a * 10\n }\n 2 -> {\n let a = go(n - 1)\n a * 10\n }\n _ -> 10\n }\n}" --- ----- BEFORE ACTION pub fn go(n: Int) { case n { 1 -> { ▔▔▔▔▔▔ let a = go(n - 1) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ a * 10 ▔▔▔▔▔▔▔▔▔▔▔▔ } ▔▔▔▔▔ 2 -> { ▔▔▔▔↑ let a = go(n - 1) a * 10 } _ -> 10 } } ----- AFTER ACTION pub fn go(n: Int) { case n { 1 | 2 -> { let a = go(n - 1) a * 10 } _ -> 10 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__merge_case_branch_with_todo_keeps_the_non_todo_body.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn go(n: Int) {\n case n {\n 1 -> todo\n 2 -> n * 2\n 3 -> todo\n _ -> todo\n }\n }" --- ----- BEFORE ACTION pub fn go(n: Int) { case n { 1 -> todo ▔▔▔▔▔▔▔▔▔ 2 -> n * 2 ▔▔▔▔▔▔▔▔▔▔▔▔▔▔ 3 -> todo ▔▔▔▔↑ _ -> todo } } ----- AFTER ACTION pub fn go(n: Int) { case n { 1 | 2 | 3 -> n * 2 _ -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__merge_case_branch_with_todo_keeps_the_non_todo_body_1.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn go(n: Int) {\n case n {\n 1 -> todo\n 2 -> todo\n 3 -> n * 2\n _ -> todo\n }\n }" --- ----- BEFORE ACTION pub fn go(n: Int) { case n { 1 -> todo ▔▔▔▔▔▔▔▔▔ 2 -> todo ▔▔▔▔▔▔▔▔▔▔▔▔▔ 3 -> n * 2 ▔▔▔▔↑ _ -> todo } } ----- AFTER ACTION pub fn go(n: Int) { case n { 1 | 2 | 3 -> n * 2 _ -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__merge_case_branch_with_todo_keeps_the_non_todo_body_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn go(n: Int) {\n case n {\n 1 -> n * 2\n 2 -> todo\n 3 -> todo\n _ -> todo\n }\n }" --- ----- BEFORE ACTION pub fn go(n: Int) { case n { 1 -> n * 2 ▔▔▔▔▔▔▔▔▔▔ 2 -> todo ▔▔▔▔▔▔▔▔▔▔▔▔▔ 3 -> todo ▔▔▔▔↑ _ -> todo } } ----- AFTER ACTION pub fn go(n: Int) { case n { 1 | 2 | 3 -> n * 2 _ -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__merge_case_branch_works_with_existing_alternative_patterns.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn go(result) {\n case result {\n [] | [_, _, ..]-> todo\n [_] -> todo\n _ -> 2\n }\n}" --- ----- BEFORE ACTION pub fn go(result) { case result { [] | [_, _, ..]-> todo ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ [_] -> todo ▔▔▔▔↑ _ -> 2 } } ----- AFTER ACTION pub fn go(result) { case result { [] | [_, _, ..] | [_] -> todo _ -> 2 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__outer_inexhaustive_let_to_case.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(result) {\n let [wibble] = {\n let Ok(wobble) = {\n result\n }\n [wobble]\n }\n}" --- ----- BEFORE ACTION pub fn main(result) { let [wibble] = { ▔▔▔▔▔▔▔▔▔▔▔↑ let Ok(wobble) = { result } [wobble] } } ----- AFTER ACTION pub fn main(result) { let wibble = case { let Ok(wobble) = { result } [wobble] } { [wibble] -> wibble [] -> todo [_, _, ..] -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_argument_adds_patterns_for_internal_type_inside_module_where_it_is_defined.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\n@internal\npub type Wibble {\n Wibble(Int)\n Wobble(String)\n}\n\npub fn main(thing: Wibble) {}\n" --- ----- BEFORE ACTION @internal pub type Wibble { Wibble(Int) Wobble(String) } pub fn main(thing: Wibble) {} ↑ ----- AFTER ACTION @internal pub type Wibble { Wibble(Int) Wobble(String) } pub fn main(thing: Wibble) { case thing { Wibble(int) -> todo Wobble(string) -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_argument_available_for_internal_type_defined_in_current_module.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\n@internal\npub type Wibble {\n Wobble(label: Int)\n}\n\npub fn main(arg: Wibble) {\n todo\n}\n" --- ----- BEFORE ACTION @internal pub type Wibble { Wobble(label: Int) } pub fn main(arg: Wibble) { ▔▔▔▔▔↑ todo } ----- AFTER ACTION @internal pub type Wibble { Wobble(label: Int) } pub fn main(arg: Wibble) { let Wobble(label:) = arg todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_argument_generates_unique_names_even_with_labels.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble {\n Wibble(String, string: String)\n}\n\npub fn main(wibble: Wibble) {\n todo\n}\n" --- ----- BEFORE ACTION pub type Wibble { Wibble(String, string: String) } pub fn main(wibble: Wibble) { ↑ todo } ----- AFTER ACTION pub type Wibble { Wibble(String, string: String) } pub fn main(wibble: Wibble) { let Wibble(string_2, string:) = wibble todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_argument_multi_item_tuple.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main(tuple: #(Int, String, Bool)) {\n todo\n}\n" --- ----- BEFORE ACTION pub fn main(tuple: #(Int, String, Bool)) { ▔▔▔▔▔▔▔▔▔↑ todo } ----- AFTER ACTION pub fn main(tuple: #(Int, String, Bool)) { let #(int, string, bool) = tuple todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_argument_nicely_formats_code_when_used_on_function_with_empty_body.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(arg: #(Int, String)) {}" --- ----- BEFORE ACTION pub fn main(arg: #(Int, String)) {} ↑ ----- AFTER ACTION pub fn main(arg: #(Int, String)) { let #(int, string) = arg } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_argument_preserves_indentation_of_statement_following_inserted_let.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(arg: #(Int, String)) {\n todo\n//^^^^ This should still have two spaces of indentation!\n}" --- ----- BEFORE ACTION pub fn main(arg: #(Int, String)) { ↑ todo //^^^^ This should still have two spaces of indentation! } ----- AFTER ACTION pub fn main(arg: #(Int, String)) { let #(int, string) = arg todo //^^^^ This should still have two spaces of indentation! } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_argument_single_item_tuple.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main(tuple: #(Int)) {\n todo\n}\n" --- ----- BEFORE ACTION pub fn main(tuple: #(Int)) { ↑ todo } ----- AFTER ACTION pub fn main(tuple: #(Int)) { let #(int) = tuple todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_argument_single_unlabelled_field_is_not_numbered.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble {\n Wibble(Int)\n}\n\npub fn main(arg: Wibble) {}\n" --- ----- BEFORE ACTION pub type Wibble { Wibble(Int) } pub fn main(arg: Wibble) {} ↑ ----- AFTER ACTION pub type Wibble { Wibble(Int) } pub fn main(arg: Wibble) { let Wibble(int) = arg } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_argument_uses_case_with_multiple_constructors.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type CannotBeDestructured {\n One(one: String)\n Two(two: Int)\n}\n\npub fn main(arg: CannotBeDestructured) {\n todo\n}\n" --- ----- BEFORE ACTION pub type CannotBeDestructured { One(one: String) Two(two: Int) } pub fn main(arg: CannotBeDestructured) { ↑ todo } ----- AFTER ACTION pub type CannotBeDestructured { One(one: String) Two(two: Int) } pub fn main(arg: CannotBeDestructured) { case arg { One(one:) -> todo Two(two:) -> todo } todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_argument_uses_label_shorthand_syntax_for_labelled_arguments.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble {\n Wobble(Int, String, i_want_to_see_this: String, and_this: Bool)\n}\n\npub fn main(arg: Wibble) {\n todo\n}\n" --- ----- BEFORE ACTION pub type Wibble { Wobble(Int, String, i_want_to_see_this: String, and_this: Bool) } pub fn main(arg: Wibble) { ▔▔▔▔▔↑ todo } ----- AFTER ACTION pub type Wibble { Wobble(Int, String, i_want_to_see_this: String, and_this: Bool) } pub fn main(arg: Wibble) { let Wobble(int, string, i_want_to_see_this:, and_this:) = arg todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_argument_will_use_aliased_constructor_name.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport wibble.{Wobble as IWantToSeeThisName}\n\npub fn main(arg: wibble.Wibble) {\n todo\n}\n" --- ----- BEFORE ACTION import wibble.{Wobble as IWantToSeeThisName} pub fn main(arg: wibble.Wibble) { ↑ todo } ----- AFTER ACTION import wibble.{Wobble as IWantToSeeThisName} pub fn main(arg: wibble.Wibble) { let IWantToSeeThisName(label:) = arg todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_argument_will_use_aliased_module_name.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport wibble as i_want_to_see_this_name\n\npub fn main(arg: i_want_to_see_this_name.Wibble) {\n todo\n}\n" --- ----- BEFORE ACTION import wibble as i_want_to_see_this_name pub fn main(arg: i_want_to_see_this_name.Wibble) { ↑ todo } ----- AFTER ACTION import wibble as i_want_to_see_this_name pub fn main(arg: i_want_to_see_this_name.Wibble) { let i_want_to_see_this_name.Wobble(label:) = arg todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_argument_will_use_qualified_name.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport wibble\n\npub fn main(arg: wibble.Wibble) {\n todo\n}\n" --- ----- BEFORE ACTION import wibble pub fn main(arg: wibble.Wibble) { ↑ todo } ----- AFTER ACTION import wibble pub fn main(arg: wibble.Wibble) { let wibble.ThisShouldBeQualified(label:) = arg todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_argument_will_use_unqualified_name.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport wibble.{ThisShouldBeUnqualified}\n\npub fn main(arg: wibble.Wibble) {\n todo\n}\n" --- ----- BEFORE ACTION import wibble.{ThisShouldBeUnqualified} pub fn main(arg: wibble.Wibble) { ↑ todo } ----- AFTER ACTION import wibble.{ThisShouldBeUnqualified} pub fn main(arg: wibble.Wibble) { let ThisShouldBeUnqualified(label:) = arg todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_argument_with_multiple_constructors_is_nicely_formatted_in_function_with_empty_body.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type CannotBeDestructured {\n One(one: String)\n Two(two: Int)\n}\n\npub fn main(arg: CannotBeDestructured) {}\n" --- ----- BEFORE ACTION pub type CannotBeDestructured { One(one: String) Two(two: Int) } pub fn main(arg: CannotBeDestructured) {} ↑ ----- AFTER ACTION pub type CannotBeDestructured { One(one: String) Two(two: Int) } pub fn main(arg: CannotBeDestructured) { case arg { One(one:) -> todo Two(two:) -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_argument_with_private_type_from_same_module.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\ntype Wibble {\n Wobble(Int, String)\n}\n\npub fn main(arg: Wibble) {\n todo\n}\n" --- ----- BEFORE ACTION type Wibble { Wobble(Int, String) } pub fn main(arg: Wibble) { ▔▔▔▔▔↑ todo } ----- AFTER ACTION type Wibble { Wobble(Int, String) } pub fn main(arg: Wibble) { let Wobble(int, string) = arg todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_argument_works_on_fn_arguments.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n [#(1, 2)]\n |> map(fn(tuple) {})\n}\n\nfn map(list: List(a), fun: fn(a) -> b) { todo }\n" --- ----- BEFORE ACTION pub fn main() { [#(1, 2)] |> map(fn(tuple) {}) ↑ } fn map(list: List(a), fun: fn(a) -> b) { todo } ----- AFTER ACTION pub fn main() { [#(1, 2)] |> map(fn(tuple) { let #(int, int_2) = tuple }) } fn map(list: List(a), fun: fn(a) -> b) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_argument_works_on_nested_fn_arguments.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n map([[#(1, 2)]], fn(list) {\n map(list, fn(tuple) {\n todo\n })\n })\n}\n\nfn map(list: List(a), fun: fn(a) -> b) { todo }\n" --- ----- BEFORE ACTION pub fn main() { map([[#(1, 2)]], fn(list) { map(list, fn(tuple) { ↑ todo }) }) } fn map(list: List(a), fun: fn(a) -> b) { todo } ----- AFTER ACTION pub fn main() { map([[#(1, 2)]], fn(list) { map(list, fn(tuple) { let #(int, int_2) = tuple todo }) }) } fn map(list: List(a), fun: fn(a) -> b) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_clause_variable.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n case maybe_wibble() {\n Ok(something) -> 1\n Error(_) -> 2\n }\n}\n\ntype Wibble {\n Wobble\n Woo\n}\n\nfn maybe_wibble() { Ok(Wobble) }\n\n" --- ----- BEFORE ACTION pub fn main() { case maybe_wibble() { Ok(something) -> 1 ↑ Error(_) -> 2 } } type Wibble { Wobble Woo } fn maybe_wibble() { Ok(Wobble) } ----- AFTER ACTION pub fn main() { case maybe_wibble() { Ok(Wobble) -> 1 Ok(Woo) -> 1 Error(_) -> 2 } } type Wibble { Wobble Woo } fn maybe_wibble() { Ok(Wobble) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_clause_variable_nested_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n case maybe_wibble() {\n Ok(Wobble(something)) -> 1\n Error(_) -> 2\n }\n}\n\ntype Wibble {\n Wobble(Wibble)\n Woo\n}\n\nfn maybe_wibble() { Ok(Woo) }\n\n" --- ----- BEFORE ACTION pub fn main() { case maybe_wibble() { Ok(Wobble(something)) -> 1 ↑ Error(_) -> 2 } } type Wibble { Wobble(Wibble) Woo } fn maybe_wibble() { Ok(Woo) } ----- AFTER ACTION pub fn main() { case maybe_wibble() { Ok(Wobble(Wobble(wibble))) -> 1 Ok(Wobble(Woo)) -> 1 Error(_) -> 2 } } type Wibble { Wobble(Wibble) Woo } fn maybe_wibble() { Ok(Woo) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_clause_variable_with_block_body.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n case maybe_wibble() {\n Ok(something) -> {\n 1\n 2\n }\n Error(_) -> 2\n }\n}\n\ntype Wibble {\n Wobble\n Woo\n}\n\nfn maybe_wibble() { Ok(Wobble) }\n\n" --- ----- BEFORE ACTION pub fn main() { case maybe_wibble() { Ok(something) -> { ↑ 1 2 } Error(_) -> 2 } } type Wibble { Wobble Woo } fn maybe_wibble() { Ok(Wobble) } ----- AFTER ACTION pub fn main() { case maybe_wibble() { Ok(Wobble) -> { 1 2 } Ok(Woo) -> { 1 2 } Error(_) -> 2 } } type Wibble { Wobble Woo } fn maybe_wibble() { Ok(Wobble) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_clause_variable_with_label.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n case wibble() {\n Wobble(wibble: something) -> 1\n _ -> 2\n }\n}\n\ntype Wibble {\n Wobble(wibble: Wibble)\n Woo\n}\n\nfn new() { Wobble }\n\n" --- ----- BEFORE ACTION pub fn main() { case wibble() { Wobble(wibble: something) -> 1 ↑ _ -> 2 } } type Wibble { Wobble(wibble: Wibble) Woo } fn new() { Wobble } ----- AFTER ACTION pub fn main() { case wibble() { Wobble(wibble: Wobble(wibble:)) -> 1 Wobble(wibble: Woo) -> 1 _ -> 2 } } type Wibble { Wobble(wibble: Wibble) Woo } fn new() { Wobble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_clause_variable_with_label_shorthand.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n case new() {\n Wobble(wibble:) -> 1\n _ -> 2\n }\n}\n\ntype Wibble {\n Wobble(wibble: Wibble)\n Woo\n}\n\nfn new() { Wobble }\n\n" --- ----- BEFORE ACTION pub fn main() { case new() { Wobble(wibble:) -> 1 ↑ _ -> 2 } } type Wibble { Wobble(wibble: Wibble) Woo } fn new() { Wobble } ----- AFTER ACTION pub fn main() { case new() { Wobble(wibble: Wobble(wibble:)) -> 1 Wobble(wibble: Woo) -> 1 _ -> 2 } } type Wibble { Wobble(wibble: Wibble) Woo } fn new() { Wobble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_let_assignment.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let var = #(1, 2)\n}\n" --- ----- BEFORE ACTION pub fn main() { let var = #(1, 2) ↑ } ----- AFTER ACTION pub fn main() { let var = #(1, 2) let #(int, int_2) = var } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_let_assignment_with_multiple_constructors.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble {\n Wobble\n Woo\n}\n\npub fn main() {\n let var = Woo\n todo\n}\n" --- ----- BEFORE ACTION pub type Wibble { Wobble Woo } pub fn main() { let var = Woo ↑ todo } ----- AFTER ACTION pub type Wibble { Wobble Woo } pub fn main() { let var = Woo case var { Wobble -> todo Woo -> todo } todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_list_tail.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(a_list: List(a)) {\n case a_list {\n [] -> todo\n [first, ..rest] -> todo\n }\n}" --- ----- BEFORE ACTION pub fn main(a_list: List(a)) { case a_list { [] -> todo [first, ..rest] -> todo ↑ } } ----- AFTER ACTION pub fn main(a_list: List(a)) { case a_list { [] -> todo [first, ] -> todo [first, first_2, ..rest_2] -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_list_tail_used_in_a_branch.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(a_list: List(a)) {\n case a_list {\n [] -> todo\n [first, ..rest] -> rest\n }\n}" --- ----- BEFORE ACTION pub fn main(a_list: List(a)) { case a_list { [] -> todo [first, ..rest] -> rest ↑ } } ----- AFTER ACTION pub fn main(a_list: List(a)) { case a_list { [] -> todo [first, ] -> rest [first, first_2, ..rest_2] -> rest } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_list_tail_with_shadowed_name.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(a_list: List(a)) {\n case a_list {\n [] -> todo\n [rest, ..else_] -> todo\n }\n}" --- ----- BEFORE ACTION pub fn main(a_list: List(a)) { case a_list { [] -> todo [rest, ..else_] -> todo ↑ } } ----- AFTER ACTION pub fn main(a_list: List(a)) { case a_list { [] -> todo [rest, ] -> todo [rest, first, ..rest_2] -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_list_tail_with_strange_whitespace.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(a_list: List(a)) {\n case a_list {\n [] -> todo\n [first, .. rest] -> todo\n }\n}" --- ----- BEFORE ACTION pub fn main(a_list: List(a)) { case a_list { [] -> todo [first, .. rest] -> todo ↑ } } ----- AFTER ACTION pub fn main(a_list: List(a)) { case a_list { [] -> todo [first, ] -> todo [first, first_2, ..rest_2] -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_list_variable.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(a_list: List(a)) {\n todo\n}" --- ----- BEFORE ACTION pub fn main(a_list: List(a)) { ↑ todo } ----- AFTER ACTION pub fn main(a_list: List(a)) { case a_list { [] -> todo [first, ..rest] -> todo } todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_use_assignment.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n use var <- f\n}\n\nfn f(g) { g(#(1, 2)) }\n" --- ----- BEFORE ACTION pub fn main() { use var <- f ↑ } fn f(g) { g(#(1, 2)) } ----- AFTER ACTION pub fn main() { use var <- f let #(int, int_2) = var } fn f(g) { g(#(1, 2)) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_use_assignment_with_multiple_constructors.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble {\n Wobble\n Woo\n}\n\npub fn main() {\n use var <- f\n}\n\nfn f(g) { g(Wobble) }\n" --- ----- BEFORE ACTION pub type Wibble { Wobble Woo } pub fn main() { use var <- f ↑ } fn f(g) { g(Wobble) } ----- AFTER ACTION pub type Wibble { Wobble Woo } pub fn main() { use var <- f case var { Wobble -> todo Woo -> todo } } fn f(g) { g(Wobble) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_value_with_private_type_from_same_module.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\ntype Wibble {\n Wobble(Int, String)\n}\n\npub fn main() {\n let wibble = Wobble(1, \"Hello\")\n todo\n}\n" --- ----- BEFORE ACTION type Wibble { Wobble(Int, String) } pub fn main() { let wibble = Wobble(1, "Hello") ↑ todo } ----- AFTER ACTION type Wibble { Wobble(Int, String) } pub fn main() { let wibble = Wobble(1, "Hello") let Wobble(int, string) = wibble todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__pattern_match_on_variable_crashes.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble {\n Wibble(Wobble)\n}\n\npub type Wobble {\n Wobble\n Wubble\n}\n\npub fn main() {\n let Wibble(wobble) = todo\n\n case todo {\n _ -> todo\n }\n}\n" --- ----- BEFORE ACTION pub type Wibble { Wibble(Wobble) } pub type Wobble { Wobble Wubble } pub fn main() { let Wibble(wobble) = todo ↑ case todo { _ -> todo } } ----- AFTER ACTION pub type Wibble { Wibble(Wobble) } pub type Wobble { Wobble Wubble } pub fn main() { let Wibble(wobble) = todo case wobble { Wobble -> todo Wubble -> todo } case todo { _ -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_aliased_to_unqualified_aliased_type.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport wobble as wob\n\npub fn main(x) -> wob.Wibble(a) {\n todo\n}\n" --- ----- BEFORE ACTION import wobble as wob pub fn main(x) -> wob.Wibble(a) { ▔▔▔▔↑ todo } ----- AFTER ACTION import wobble.{type Wibble} as wob pub fn main(x) -> Wibble(a) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_aliased_type.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport wobble\n\npub fn main(x) -> wobble.Wibble(a) {\n todo\n}\n" --- ----- BEFORE ACTION import wobble pub fn main(x) -> wobble.Wibble(a) { ▔▔▔▔▔▔▔↑ todo } ----- AFTER ACTION import wobble.{type Wibble} pub fn main(x) -> Wibble(a) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_aliased_type_with_multiple_imports.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport other/wobble as other\nimport wibble/wobble\n\npub fn main(x) -> wobble.Wibble(a) {\n todo\n}\n" --- ----- BEFORE ACTION import other/wobble as other import wibble/wobble pub fn main(x) -> wobble.Wibble(a) { ▔▔▔▔▔▔▔↑ todo } ----- AFTER ACTION import other/wobble as other import wibble/wobble.{type Wibble} pub fn main(x) -> Wibble(a) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_basic_multiple.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option\n\npub fn main() {\n option.Some(1)\n option.Some(1)\n todo\n}\n" --- ----- BEFORE ACTION import option pub fn main() { option.Some(1) ▔▔▔▔▔↑ option.Some(1) todo } ----- AFTER ACTION import option.{Some} pub fn main() { Some(1) Some(1) todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_basic_record_without_argument.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport wobble\n\npub fn main() {\n wobble.Wibble\n}\n" --- ----- BEFORE ACTION import wobble pub fn main() { wobble.Wibble ▔▔↑ } ----- AFTER ACTION import wobble.{Wibble} pub fn main() { Wibble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_basic_type_without_argument.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport wobble\n\npub fn identity(x: wobble.Wobble) -> wobble.Wobble {\n x\n}\n" --- ----- BEFORE ACTION import wobble pub fn identity(x: wobble.Wobble) -> wobble.Wobble { ▔↑ x } ----- AFTER ACTION import wobble.{type Wobble} pub fn identity(x: Wobble) -> Wobble { x } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_basic_with_argument.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option\n\npub fn main() {\n option.Some(1)\n}\n" --- ----- BEFORE ACTION import option pub fn main() { option.Some(1) ▔▔▔▔▔↑ } ----- AFTER ACTION import option.{Some} pub fn main() { Some(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_below_constructor.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\n\npub fn main() {\n option.Some(1)\n}\n\nimport option\n" --- ----- BEFORE ACTION pub fn main() { option.Some(1) ▔▔▔▔▔↑ } import option ----- AFTER ACTION pub fn main() { Some(1) } import option.{Some} ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_between_constructors.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\n\npub fn main() {\n option.Some(1)\n}\n\nimport option\n\npub fn identity(x: option.Option(Int)) -> option.Option(Int) {\n option.Some(1)\n x\n}\n" --- ----- BEFORE ACTION pub fn main() { option.Some(1) ▔▔▔▔▔↑ } import option pub fn identity(x: option.Option(Int)) -> option.Option(Int) { option.Some(1) x } ----- AFTER ACTION pub fn main() { Some(1) } import option.{Some} pub fn identity(x: option.Option(Int)) -> option.Option(Int) { Some(1) x } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_constant_multiple_occurrences.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option\n\nconst a = option.None\nconst b = option.Some(option.None)\n" --- ----- BEFORE ACTION import option const a = option.None ↑ const b = option.Some(option.None) ----- AFTER ACTION import option.{None} const a = None const b = option.Some(None) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_constructor_as_argument.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option\n\npub fn main() {\n option.map(option.Some(1), fn(x) { x + 1 })\n}\n" --- ----- BEFORE ACTION import option pub fn main() { option.map(option.Some(1), fn(x) { x + 1 }) ▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION import option.{Some} pub fn main() { option.map(Some(1), fn(x) { x + 1 }) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_constructor_complex_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option\n\npub fn main() {\n case [option.Some(1), option.None] {\n [option.None, ..] -> todo\n [option.Some(_), ..] -> todo\n _ -> todo\n }\n case option.Some(1), option.Some(2) {\n option.None, option.Some(_) -> todo\n option.Some(_), option.Some(val) -> todo\n _ -> todo\n }\n}\n" --- ----- BEFORE ACTION import option pub fn main() { case [option.Some(1), option.None] { ▔▔▔▔▔▔▔↑ [option.None, ..] -> todo [option.Some(_), ..] -> todo _ -> todo } case option.Some(1), option.Some(2) { option.None, option.Some(_) -> todo option.Some(_), option.Some(val) -> todo _ -> todo } } ----- AFTER ACTION import option.{Some} pub fn main() { case [Some(1), option.None] { [option.None, ..] -> todo [Some(_), ..] -> todo _ -> todo } case Some(1), Some(2) { option.None, Some(_) -> todo Some(_), Some(val) -> todo _ -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_constructor_different_module_same_name_inner.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option\nimport opt\n\npub fn main() {\n option.Some(opt.Some(1))\n todo\n}\n" --- ----- BEFORE ACTION import option import opt pub fn main() { option.Some(opt.Some(1)) ▔▔▔▔▔▔▔▔↑ todo } ----- AFTER ACTION import option import opt.{Some} pub fn main() { option.Some(Some(1)) todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_constructor_different_module_same_name_outer.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option\nimport opt\n\npub fn main() {\n option.Some(opt.Some(1))\n}\n" --- ----- BEFORE ACTION import option import opt pub fn main() { option.Some(opt.Some(1)) ▔▔▔▔▔▔▔↑ } ----- AFTER ACTION import option.{Some} import opt pub fn main() { Some(opt.Some(1)) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_constructor_different_module_same_type_inner.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option\nimport opt\n\npub fn main() -> option.Option(opt.Option(Int)) {\n todo\n}\n" --- ----- BEFORE ACTION import option import opt pub fn main() -> option.Option(opt.Option(Int)) { ▔▔▔▔▔▔▔▔▔▔↑ todo } ----- AFTER ACTION import option import opt.{type Option} pub fn main() -> option.Option(Option(Int)) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_constructor_different_module_same_type_outer.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option\nimport opt\n\npub fn main() -> option.Option(opt.Option(Int)) {\n todo\n}\n" --- ----- BEFORE ACTION import option import opt pub fn main() -> option.Option(opt.Option(Int)) { ▔▔▔▔▔▔▔↑ todo } ----- AFTER ACTION import option.{type Option} import opt pub fn main() -> Option(opt.Option(Int)) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_custom_type_record_declaration.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport wobble\n\npub type Wibble {\n Wibble(wibble: wobble.Wobble)\n}\n" --- ----- BEFORE ACTION import wobble pub type Wibble { Wibble(wibble: wobble.Wobble) ▔↑ } ----- AFTER ACTION import wobble.{type Wobble} pub type Wibble { Wibble(wibble: Wobble) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_different_constructors.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option\n\npub fn main() {\n option.Some(1)\n option.None\n}" --- ----- BEFORE ACTION import option pub fn main() { option.Some(1) ▔▔▔▔▔↑ option.None } ----- AFTER ACTION import option.{Some} pub fn main() { Some(1) option.None } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_from_constant_also_updates_functions.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option\n\nconst default = option.None\n\npub fn get_or_default(value: a) {\n case value {\n option.Some(x) -> x\n option.None -> value\n }\n}\n" --- ----- BEFORE ACTION import option const default = option.None ↑ pub fn get_or_default(value: a) { case value { option.Some(x) -> x option.None -> value } } ----- AFTER ACTION import option.{None} const default = None pub fn get_or_default(value: a) { case value { option.Some(x) -> x None -> value } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_from_function_also_updates_constants.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option\n\nconst default = option.None\n\npub fn get_or_default(value: a) {\n case value {\n option.Some(x) -> x\n option.None -> value\n }\n}\n" --- ----- BEFORE ACTION import option const default = option.None pub fn get_or_default(value: a) { case value { option.Some(x) -> x option.None -> value ↑ } } ----- AFTER ACTION import option.{None} const default = None pub fn get_or_default(value: a) { case value { option.Some(x) -> x None -> value } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_in_case_with_argument.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option\n\npub fn main(x) {\n case option.Some(1) {\n option.Some(value) -> value\n option.None -> 0\n }\n}\n" --- ----- BEFORE ACTION import option pub fn main(x) { case option.Some(1) { ▔▔▔▔▔↑ option.Some(value) -> value option.None -> 0 } } ----- AFTER ACTION import option.{Some} pub fn main(x) { case Some(1) { Some(value) -> value option.None -> 0 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_in_case_without_argument.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport wobble\n\npub fn main() {\n case wobble.Wibble {\n wobble.Wibble -> 1\n wobble.Wubble(1) -> 2\n }\n}\n" --- ----- BEFORE ACTION import wobble pub fn main() { case wobble.Wibble { ▔▔↑ wobble.Wibble -> 1 wobble.Wubble(1) -> 2 } } ----- AFTER ACTION import wobble.{Wibble} pub fn main() { case Wibble { Wibble -> 1 wobble.Wubble(1) -> 2 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_in_constant_record.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option\n\nconst some = option.Some(1)\n" --- ----- BEFORE ACTION import option const some = option.Some(1) ↑ ----- AFTER ACTION import option.{Some} const some = Some(1) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_in_constant_var.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option\n\nconst none = option.None\n" --- ----- BEFORE ACTION import option const none = option.None ↑ ----- AFTER ACTION import option.{None} const none = None ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_in_list_and_tuple.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option\n\npub fn main() {\n let list = [option.Some(1), option.None]\n let tuple = #(option.Some(2), option.None)\n}\n" --- ----- BEFORE ACTION import option pub fn main() { let list = [option.Some(1), option.None] ▔▔▔▔▔▔▔▔▔▔▔↑ let tuple = #(option.Some(2), option.None) } ----- AFTER ACTION import option.{Some} pub fn main() { let list = [Some(1), option.None] let tuple = #(Some(2), option.None) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_in_nested_constant.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option\n\ntype Result(a) {\n Result(expected: a, actual: option.Option(a))\n}\n\nconst zero = Result(0, option.None)\n" --- ----- BEFORE ACTION import option type Result(a) { Result(expected: a, actual: option.Option(a)) } const zero = Result(0, option.None) ↑ ----- AFTER ACTION import option.{None} type Result(a) { Result(expected: a, actual: option.Option(a)) } const zero = Result(0, None) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_in_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option\n\npub fn main() -> Int {\n case option.Some(1) {\n option.Some(value) -> value\n option.None -> 0\n }\n}\n" --- ----- BEFORE ACTION import option pub fn main() -> Int { case option.Some(1) { option.Some(value) -> value ▔▔▔▔▔▔▔▔↑ option.None -> 0 } } ----- AFTER ACTION import option.{Some} pub fn main() -> Int { case Some(1) { Some(value) -> value option.None -> 0 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_in_pattern_without_argument.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport wobble\n\npub fn main() {\n case wobble.Wibble {\n wobble.Wibble -> 1\n wobble.Wubble(1) -> 2\n }\n let wob = wobble.Wibble\n todo\n}\n" --- ----- BEFORE ACTION import wobble pub fn main() { case wobble.Wibble { ▔▔▔▔▔▔▔▔↑ wobble.Wibble -> 1 wobble.Wubble(1) -> 2 } let wob = wobble.Wibble todo } ----- AFTER ACTION import wobble.{Wibble} pub fn main() { case Wibble { Wibble -> 1 wobble.Wubble(1) -> 2 } let wob = Wibble todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_multiple_generic_type.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport result\n\npub fn main() -> result.Result(Int, String) {\n result.Ok(1)\n}\n" --- ----- BEFORE ACTION import result pub fn main() -> result.Result(Int, String) { ▔▔▔▔▔▔▔↑ result.Ok(1) } ----- AFTER ACTION import result.{type Result} pub fn main() -> Result(Int, String) { result.Ok(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_multiple_imports.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option\nimport wobble\n\npub fn main() {\n option.Some(2)\n wobble.Wibble(1)\n}\n" --- ----- BEFORE ACTION import option import wobble pub fn main() { option.Some(2) wobble.Wibble(1) ▔▔▔▔▔▔▔↑ } ----- AFTER ACTION import option import wobble.{Wibble} pub fn main() { option.Some(2) Wibble(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_multiple_line.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option.{\n type Option,\n None,\n}\n\npub fn main() {\n option.Some(1)\n}\n" --- ----- BEFORE ACTION import option.{ type Option, None, } pub fn main() { option.Some(1) ▔▔▔▔▔↑ } ----- AFTER ACTION import option.{ type Option, None, Some } pub fn main() { Some(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_multiple_line_aliased.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option.{\n type Option,\n None} as opt\n\npub fn main() {\n opt.Some(1)\n}\n" --- ----- BEFORE ACTION import option.{ type Option, None} as opt pub fn main() { opt.Some(1) ▔▔▔▔▔↑ } ----- AFTER ACTION import option.{ type Option, None, Some} as opt pub fn main() { Some(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_multiple_line_bad_format_multiple_whitespace.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option.{ }\n\npub fn main() {\n option.Some(1)\n}\n" --- ----- BEFORE ACTION import option.{ } pub fn main() { option.Some(1) ▔▔▔▔▔↑ } ----- AFTER ACTION import option.{Some } pub fn main() { Some(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_multiple_line_bad_format_with_trailing_comma.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option.{type Option,\n None,\n\n}\n\npub fn main() {\n option.Some(1)\n}\n" --- ----- BEFORE ACTION import option.{type Option, None, } pub fn main() { option.Some(1) ▔▔▔▔▔↑ } ----- AFTER ACTION import option.{type Option, None, Some } pub fn main() { Some(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_multiple_line_bad_format_without_trailing_comma.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option.{type Option,\n None\n\n}\n\npub fn main() {\n option.Some(1)\n}\n" --- ----- BEFORE ACTION import option.{type Option, None } pub fn main() { option.Some(1) ▔▔▔▔▔↑ } ----- AFTER ACTION import option.{type Option, None, Some } pub fn main() { Some(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_nested_constructor_inner.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option\nimport wobble\n\npub fn main(x) -> option.Option(wobble.Wibble) {\n option.Some(wobble.Wobble(1))\n}\n" --- ----- BEFORE ACTION import option import wobble pub fn main(x) -> option.Option(wobble.Wibble) { option.Some(wobble.Wobble(1)) ▔▔▔▔▔▔▔↑ } ----- AFTER ACTION import option import wobble.{Wobble} pub fn main(x) -> option.Option(wobble.Wibble) { option.Some(Wobble(1)) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_nested_constructor_outer.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option\nimport wobble\npub fn main(x) -> option.Option(wobble.Wibble) {\n option.Some(wobble.Wobble(1))\n}\n" --- ----- BEFORE ACTION import option import wobble pub fn main(x) -> option.Option(wobble.Wibble) { option.Some(wobble.Wobble(1)) ▔▔↑ } ----- AFTER ACTION import option.{Some} import wobble pub fn main(x) -> option.Option(wobble.Wibble) { Some(wobble.Wobble(1)) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_nested_type_inner.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option\nimport wobble\n\npub fn main(x) -> option.Option(wobble.Wibble) {\n todo\n}\n" --- ----- BEFORE ACTION import option import wobble pub fn main(x) -> option.Option(wobble.Wibble) { ▔▔▔▔▔▔▔↑ todo } ----- AFTER ACTION import option import wobble.{type Wibble} pub fn main(x) -> option.Option(Wibble) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_nested_type_outer.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option\nimport wobble\npub fn main(x) -> option.Option(wobble.Wibble) {\n todo\n}\n" --- ----- BEFORE ACTION import option import wobble pub fn main(x) -> option.Option(wobble.Wibble) { ▔▔↑ todo } ----- AFTER ACTION import option.{type Option} import wobble pub fn main(x) -> Option(wobble.Wibble) { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_type.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option\n\npub fn main(x) -> option.Option(Int) {\n option.Some(1)\n}\n" --- ----- BEFORE ACTION import option pub fn main(x) -> option.Option(Int) { ▔▔▔▔▔▔▔↑ option.Some(1) } ----- AFTER ACTION import option.{type Option} pub fn main(x) -> Option(Int) { option.Some(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_when_unqualified_exists.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option.{Some}\n\npub fn main() {\n option.Some(1)\n}\n" --- ----- BEFORE ACTION import option.{Some} pub fn main() { option.Some(1) ▔▔▔▔▔↑ } ----- AFTER ACTION import option.{Some} pub fn main() { Some(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_with_alias.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option as opt\n\npub fn main() {\n opt.Some(1)\n}\n" --- ----- BEFORE ACTION import option as opt pub fn main() { opt.Some(1) ▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION import option.{Some} as opt pub fn main() { Some(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_with_alias_multiple.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option as opt\n\npub fn main() {\n opt.Some(1)\n opt.Some(1)\n}\n\npub fn identity(x: opt.Option(Int)) -> opt.Option(Int) {\n opt.Some(1)\n x\n}\n" --- ----- BEFORE ACTION import option as opt pub fn main() { opt.Some(1) ▔▔▔▔▔▔▔▔↑ opt.Some(1) } pub fn identity(x: opt.Option(Int)) -> opt.Option(Int) { opt.Some(1) x } ----- AFTER ACTION import option.{Some} as opt pub fn main() { Some(1) Some(1) } pub fn identity(x: opt.Option(Int)) -> opt.Option(Int) { Some(1) x } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_with_comma.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option.{None, }\n\npub fn main() {\n option.Some(1)\n}\n" --- ----- BEFORE ACTION import option.{None, } pub fn main() { option.Some(1) ▔▔▔▔▔↑ } ----- AFTER ACTION import option.{None, Some } pub fn main() { Some(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_import_with_comma_pos_not_end.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option.{None, } as opt\n\npub fn main() {\n opt.Some(1)\n}\n" --- ----- BEFORE ACTION import option.{None, } as opt pub fn main() { opt.Some(1) ▔▔▔▔▔↑ } ----- AFTER ACTION import option.{None, Some } as opt pub fn main() { Some(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__qualified_to_unqualified_record_value_constructor_module_name.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option\n\npub fn main() {\n option.Some(1)\n}\n" --- ----- BEFORE ACTION import option pub fn main() { option.Some(1) ↑ } ----- AFTER ACTION import option.{Some} pub fn main() { Some(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_aliased_unused_value.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\n// test\nimport result.{is_ok as ok}\nimport option\n\npub fn main() {\n result.is_ok\n}\n" --- ----- BEFORE ACTION // test ▔▔▔▔▔▔▔ import result.{is_ok as ok} ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ import option ▔▔▔▔▔▔▔▔▔▔▔▔▔ pub fn main() { ↑ result.is_ok } ----- AFTER ACTION // test import result.{} pub fn main() { result.is_ok } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_block_1.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n { 1 }\n}\n" --- ----- BEFORE ACTION pub fn main() { { 1 } ↑ } ----- AFTER ACTION pub fn main() { 1 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_block_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n { main() <> 2 }\n}\n" --- ----- BEFORE ACTION pub fn main() { { main() <> 2 } ↑ } ----- AFTER ACTION pub fn main() { main() <> 2 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_block_3.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n case 1 {\n _ -> { main() <> 2 }\n }\n}\n" --- ----- BEFORE ACTION pub fn main() { case 1 { _ -> { main() <> 2 } ↑ } } ----- AFTER ACTION pub fn main() { case 1 { _ -> main() <> 2 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_block_triggers_on_the_innermost_selected_block.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(x) {\n {\n main({\n 1\n })\n }\n}\n" --- ----- BEFORE ACTION pub fn main(x) { { main({ 1 ↑ }) } } ----- AFTER ACTION pub fn main(x) { { main( 1 ) } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_block_unwraps_a_single_expression_in_a_binop.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(x) {\n { main(1) } * 3\n}\n" --- ----- BEFORE ACTION pub fn main(x) { { main(1) } * 3 ↑ } ----- AFTER ACTION pub fn main(x) { main(1) * 3 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n echo 1 + 2\n}" --- ----- BEFORE ACTION pub fn main() { echo 1 + 2 ↑ } ----- AFTER ACTION pub fn main() { 1 + 2 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_as_function_arg.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n wibble([], echo 1 + 2)\n}" --- ----- BEFORE ACTION pub fn main() { wibble([], echo 1 + 2) ↑ } ----- AFTER ACTION pub fn main() { wibble([], 1 + 2) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_before_pipeline.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n echo [1, 2, 3] |> wibble\n}" --- ----- BEFORE ACTION pub fn main() { echo [1, 2, 3] |> wibble ↑ } ----- AFTER ACTION pub fn main() { [1, 2, 3] |> wibble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_before_pipeline_selecting_step.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n echo [1, 2, 3] |> wibble\n}" --- ----- BEFORE ACTION pub fn main() { echo [1, 2, 3] |> wibble ↑ } ----- AFTER ACTION pub fn main() { [1, 2, 3] |> wibble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_in_pipeline_step.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n [1, 2, 3]\n |> echo\n |> wibble\n}" --- ----- BEFORE ACTION pub fn main() { [1, 2, 3] |> echo ↑ |> wibble } ----- AFTER ACTION pub fn main() { [1, 2, 3] |> wibble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_in_pipeline_step_with_message.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n [1, 2, 3]\n |> echo as message\n |> wibble\n}" --- ----- BEFORE ACTION pub fn main() { [1, 2, 3] |> echo as message ↑ |> wibble } ----- AFTER ACTION pub fn main() { [1, 2, 3] |> wibble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_in_single_line_pipeline_step.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n [1, 2, 3] |> echo |> wibble\n}" --- ----- BEFORE ACTION pub fn main() { [1, 2, 3] |> echo |> wibble ↑ } ----- AFTER ACTION pub fn main() { [1, 2, 3] |> wibble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_in_single_line_pipeline_step_with_message.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n [1, 2, 3] |> echo as \"message\" |> wibble\n}" --- ----- BEFORE ACTION pub fn main() { [1, 2, 3] |> echo as "message" |> wibble ↑ } ----- AFTER ACTION pub fn main() { [1, 2, 3] |> wibble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_last_in_long_pipeline_step.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n [1, 2, 3]\n |> wibble\n |> echo\n}" --- ----- BEFORE ACTION pub fn main() { [1, 2, 3] |> wibble |> echo ↑ } ----- AFTER ACTION pub fn main() { [1, 2, 3] |> wibble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_last_in_long_pipeline_step_with_message.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n [1, 2, 3]\n |> wibble\n |> echo as \"message\"\n}" --- ----- BEFORE ACTION pub fn main() { [1, 2, 3] |> wibble |> echo as "message" ↑ } ----- AFTER ACTION pub fn main() { [1, 2, 3] |> wibble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_last_in_short_pipeline_step.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n [1, 2, 3]\n |> echo\n}" --- ----- BEFORE ACTION pub fn main() { [1, 2, 3] |> echo ↑ } ----- AFTER ACTION pub fn main() { [1, 2, 3] } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_last_in_short_pipeline_step_with_message.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n [1, 2, 3]\n |> echo as \"message\"\n}" --- ----- BEFORE ACTION pub fn main() { [1, 2, 3] |> echo as "message" ↑ } ----- AFTER ACTION pub fn main() { [1, 2, 3] } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_removes_all_echos.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n echo wibble(echo 1, 2)\n}" --- ----- BEFORE ACTION pub fn main() { echo wibble(echo 1, 2) ↑ } ----- AFTER ACTION pub fn main() { wibble(1, 2) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_removes_all_echos_1.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n echo 1 |> echo |> echo |> wibble |> echo\n echo wibble(echo 1, echo 2)\n echo 1\n}" --- ----- BEFORE ACTION pub fn main() { echo 1 |> echo |> echo |> wibble |> echo ↑ echo wibble(echo 1, echo 2) echo 1 } ----- AFTER ACTION pub fn main() { 1 |> wibble wibble(1, 2) 1 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_removes_does_not_remove_entire_echo_statement_if_its_the_return.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n echo 1\n}" --- ----- BEFORE ACTION pub fn main() { echo 1 ↑ } ----- AFTER ACTION pub fn main() { 1 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_removes_does_not_remove_entire_echo_statement_if_its_the_return_of_a_fn.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n fn() {\n echo 1\n }\n}" --- ----- BEFORE ACTION pub fn main() { fn() { echo 1 ↑ } } ----- AFTER ACTION pub fn main() { fn() { 1 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_removes_entire_echo_statement_used_with_a_var.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let a = 1\n echo a\n Nil\n}" --- ----- BEFORE ACTION pub fn main() { let a = 1 echo a ↑ Nil } ----- AFTER ACTION pub fn main() { let a = 1 Nil } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_removes_entire_echo_statement_used_with_literals.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n echo 1\n Nil\n}" --- ----- BEFORE ACTION pub fn main() { echo 1 ↑ Nil } ----- AFTER ACTION pub fn main() { Nil } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_removes_entire_echo_statement_used_with_literals_and_message.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n echo 1 as \"message\"\n Nil\n}" --- ----- BEFORE ACTION pub fn main() { echo 1 as "message" ↑ Nil } ----- AFTER ACTION pub fn main() { Nil } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_removes_entire_echo_statement_used_with_literals_in_a_fn.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n fn() {\n echo 1\n Nil\n }\n}" --- ----- BEFORE ACTION pub fn main() { fn() { echo 1 ↑ Nil } } ----- AFTER ACTION pub fn main() { fn() { Nil } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_removes_multiple_entire_echo_statement_used_with_literals.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n echo 1\n echo \"wibble\"\n Nil\n}" --- ----- BEFORE ACTION pub fn main() { echo 1 ↑ echo "wibble" Nil } ----- AFTER ACTION pub fn main() { Nil } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_removes_multiple_entire_echo_statement_used_with_literals_but_stops_at_comments.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n echo 1\n\n // Oh no I hope I'm not deleted by the code action!!\n Nil\n}" --- ----- BEFORE ACTION pub fn main() { echo 1 ↑ // Oh no I hope I'm not deleted by the code action!! Nil } ----- AFTER ACTION pub fn main() { // Oh no I hope I'm not deleted by the code action!! Nil } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_removes_multiple_entire_echo_statement_used_with_literals_in_a_fn.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n fn() {\n echo 1\n echo \"wibble\"\n Nil\n }\n}" --- ----- BEFORE ACTION pub fn main() { fn() { echo 1 ↑ echo "wibble" Nil } } ----- AFTER ACTION pub fn main() { fn() { Nil } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_selecting_expression.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n echo 1 + 2\n}" --- ----- BEFORE ACTION pub fn main() { echo 1 + 2 ▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { 1 + 2 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_selecting_message.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n echo 1 + 2 as \"message\"\n}" --- ----- BEFORE ACTION pub fn main() { echo 1 + 2 as "message" ↑ } ----- AFTER ACTION pub fn main() { 1 + 2 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_with_message.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n echo 1 + 2 as \"message\"\n}" --- ----- BEFORE ACTION pub fn main() { echo 1 + 2 as "message" ↑ } ----- AFTER ACTION pub fn main() { 1 + 2 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_with_message_and_comment.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n echo 1 + 2\n // Hello!\n as \"message\"\n}" --- ----- BEFORE ACTION pub fn main() { echo 1 + 2 ↑ // Hello! as "message" } ----- AFTER ACTION pub fn main() { 1 + 2 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_with_message_and_comment_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n echo 1 + 2 as\n // Hello!\n \"message\"\n}" --- ----- BEFORE ACTION pub fn main() { echo 1 + 2 as ↑ // Hello! "message" } ----- AFTER ACTION pub fn main() { 1 + 2 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_with_message_and_comment_3.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n echo 1 + 2 as\n // Hello!\n \"message\"\n\n Nil\n}" --- ----- BEFORE ACTION pub fn main() { echo 1 + 2 as ↑ // Hello! "message" Nil } ----- AFTER ACTION pub fn main() { 1 + 2 Nil } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_echo_with_message_removes_does_not_remove_entire_echo_statement_if_its_the_return.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n echo 1 as \"message\"\n}" --- ----- BEFORE ACTION pub fn main() { echo 1 as "message" ↑ } ----- AFTER ACTION pub fn main() { 1 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_entire_unused_import.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\n// test\nimport result.{unused, unused_again}\n\npub fn main() {\n todo\n}\n" --- ----- BEFORE ACTION // test ▔▔▔▔▔▔▔ import result.{unused, unused_again} ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ pub fn main() { ↑ todo } ----- AFTER ACTION // test pub fn main() { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_multiple_redundant_tuple_with_catch_all_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n case #(1, 2), #(3, 4) {\n #(2, 2), #(2, 2) -> 0\n #(1, 2), _ -> 0\n _, #(1, 2) -> 0\n _, _ -> 1\n }\n}" --- ----- BEFORE ACTION pub fn main() { case #(1, 2), #(3, 4) { ▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ #(2, 2), #(2, 2) -> 0 #(1, 2), _ -> 0 _, #(1, 2) -> 0 _, _ -> 1 } } ----- AFTER ACTION pub fn main() { case 1, 2, 3, 4 { 2, 2, 2, 2 -> 0 1, 2, _, _ -> 0 _, _, 1, 2 -> 0 _, _, _, _ -> 1 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_multiple_unused_values.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\n// test\nimport result.{type Unused, used, unused, unused_again, type Used, used_again}\n\npub fn main(x: Used) {\n #(used, used_again)\n}\n" --- ----- BEFORE ACTION // test ▔▔▔▔▔▔▔ import result.{type Unused, used, unused, unused_again, type Used, used_again} ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ pub fn main(x: Used) { ↑ #(used, used_again) } ----- AFTER ACTION // test import result.{used, type Used, used_again} pub fn main(x: Used) { #(used, used_again) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_multiple_unused_values_2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\n// test\nimport result.{type Unused, used, unused, type Used, unused_again}\n\npub fn main(x: Used) {\n used\n}\n" --- ----- BEFORE ACTION // test ▔▔▔▔▔▔▔ import result.{type Unused, used, unused, type Used, unused_again} ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ pub fn main(x: Used) { ↑ used } ----- AFTER ACTION // test import result.{used, type Used} pub fn main(x: Used) { used } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_opaque_from_private_type.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "opaque type Wibble {\n Wobble\n}\n" --- ----- BEFORE ACTION opaque type Wibble { ↑ Wobble } ----- AFTER ACTION type Wibble { Wobble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_redundant_tuple_in_case_retain_extras.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n case\n #(\n // first comment\n 1,\n // second comment\n 2,\n 3 // third comment before comma\n\n ,\n\n // fourth comment after comma\n\n )\n {\n #(\n // first comment\n a,\n // second comment\n b,\n c // third comment before comma\n\n ,\n\n // fourth comment after comma\n\n ) -> 0\n }\n}\n" --- ----- BEFORE ACTION pub fn main() { case #( ▔▔ // first comment ▔▔▔▔▔▔↑ 1, // second comment 2, 3 // third comment before comma , // fourth comment after comma ) { #( // first comment a, // second comment b, c // third comment before comma , // fourth comment after comma ) -> 0 } } ----- AFTER ACTION pub fn main() { case // first comment 1, // second comment 2, 3 // third comment before comma // fourth comment after comma { // first comment a, // second comment b, c // third comment before comma // fourth comment after comma -> 0 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_redundant_tuple_in_case_subject_nested.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n case #(case #(0) { #(a) -> 0 }) { #(b) -> 0 }\n}" --- ----- BEFORE ACTION pub fn main() { case #(case #(0) { #(a) -> 0 }) { #(b) -> 0 } ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { case case 0 { a -> 0 } { b -> 0 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_redundant_tuple_in_case_subject_only_safe_remove.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n case #(0), #(1) {\n #(1), #(b) -> 0\n a, #(0) -> 1 // The first of this clause is not a tuple\n #(a), #(b) -> 2\n }\n}\n" --- ----- BEFORE ACTION pub fn main() { case #(0), #(1) { ▔▔▔▔▔▔↑ #(1), #(b) -> 0 a, #(0) -> 1 // The first of this clause is not a tuple #(a), #(b) -> 2 } } ----- AFTER ACTION pub fn main() { case #(0), 1 { #(1), b -> 0 a, 0 -> 1 // The first of this clause is not a tuple #(a), b -> 2 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_redundant_tuple_in_case_subject_simple.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n case #(1) { #(a) -> 0 }\n case #(1, 2) { #(a, b) -> 0 }\n}" --- ----- BEFORE ACTION pub fn main() { case #(1) { #(a) -> 0 } ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ case #(1, 2) { #(a, b) -> 0 } ▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { case 1 { a -> 0 } case 1, 2 { a, b -> 0 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_redundant_tuple_with_catch_all_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n case #(1, 2) {\n #(1, 2) -> 0\n _ -> 1\n }\n}" --- ----- BEFORE ACTION pub fn main() { case #(1, 2) { ▔▔▔▔▔▔▔▔▔▔▔↑ #(1, 2) -> 0 _ -> 1 } } ----- AFTER ACTION pub fn main() { case 1, 2 { 1, 2 -> 0 _, _ -> 1 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_unreachable_clauses.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main(x) {\n case x {\n Ok(n) -> 1\n Ok(_) -> 2\n Error(_) -> todo\n Ok(1) -> 3\n }\n}\n" --- ----- BEFORE ACTION pub fn main(x) { case x { Ok(n) -> 1 Ok(_) -> 2 Error(_) -> todo Ok(1) -> 3 ↑ } } ----- AFTER ACTION pub fn main(x) { case x { Ok(n) -> 1 Error(_) -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_unused_alias.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\n// test\nimport result.{is_ok} as res\nimport option\n\npub fn main() {\n is_ok\n}\n" --- ----- BEFORE ACTION // test ▔▔▔▔▔▔▔ import result.{is_ok} as res ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ import option ▔▔▔▔▔▔▔▔▔▔▔▔▔ pub fn main() { ↑ is_ok } ----- AFTER ACTION // test import result.{is_ok} pub fn main() { is_ok } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_unused_simple.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\n// test\nimport // comment\nlist as lispy\nimport result\nimport option\n\npub fn main() {\n result.is_ok\n}\n" --- ----- BEFORE ACTION // test ▔▔▔▔▔▔▔ import // comment ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ list as lispy ▔▔▔▔▔▔▔▔▔▔▔▔▔ import result ▔▔▔▔▔▔▔▔▔▔▔▔▔ import option ▔▔▔▔▔▔▔↑ pub fn main() { result.is_ok } ----- AFTER ACTION // test pub fn main() { result.is_ok } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_unused_start_of_file.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "import option\nimport result\n\npub fn main() {\n result.is_ok\n}\n" --- ----- BEFORE ACTION import option ▔▔▔▔▔▔▔▔▔▔▔▔▔ import result ▔▔▔▔▔▔▔▔▔▔▔▔▔ pub fn main() { ↑ result.is_ok } ----- AFTER ACTION pub fn main() { result.is_ok } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__remove_unused_value.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\n// test\nimport result.{is_ok}\nimport option\n\npub fn main() {\n result.is_ok\n}\n" --- ----- BEFORE ACTION // test ▔▔▔▔▔▔▔ import result.{is_ok} ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ import option ▔▔▔▔▔▔▔▔▔▔▔▔▔ pub fn main() { ↑ result.is_ok } ----- AFTER ACTION // test import result.{} pub fn main() { result.is_ok } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_bit_array_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let assert <> = <<73>>\n}" --- ----- BEFORE ACTION pub fn main() { let assert <> = <<73>> ▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let assert <> = <<73>> } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_bit_array_pattern_discard.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let assert <<_iDontCare>> = <<97>>\n}" --- ----- BEFORE ACTION pub fn main() { let assert <<_iDontCare>> = <<97>> ▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let assert <<_i_dont_care>> = <<97>> } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_case_variable.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n case 21 { twentyOne -> {Nil} }\n}" --- ----- BEFORE ACTION pub fn main() { case 21 { twentyOne -> {Nil} } ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { case 21 { twenty_one -> {Nil} } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_case_variable_discard.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n case 21 { _twentyOne -> {Nil} }\n}" --- ----- BEFORE ACTION pub fn main() { case 21 { _twentyOne -> {Nil} } ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { case 21 { _twenty_one -> {Nil} } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_const.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: const myInvalid_Constant = 42 --- ----- BEFORE ACTION const myInvalid_Constant = 42 ↑ ----- AFTER ACTION const my_invalid_constant = 42 ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_constructor.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "type MyType { The_Constructor(Int) }" --- ----- BEFORE ACTION type MyType { The_Constructor(Int) } ↑ ----- AFTER ACTION type MyType { TheConstructor(Int) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_constructor_arg.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "type IntWrapper { IntWrapper(innerInt: Int) }" --- ----- BEFORE ACTION type IntWrapper { IntWrapper(innerInt: Int) } ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ ----- AFTER ACTION type IntWrapper { IntWrapper(inner_int: Int) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_constructor_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub type Box { Box(Int) }\npub fn main() {\n let Box(innerValue) = Box(203)\n}" --- ----- BEFORE ACTION pub type Box { Box(Int) } pub fn main() { let Box(innerValue) = Box(203) ↑ } ----- AFTER ACTION pub type Box { Box(Int) } pub fn main() { let Box(inner_value) = Box(203) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_constructor_pattern_discard.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub type Box { Box(Int) }\npub fn main() {\n let Box(_ignoredInner) = Box(203)\n}" --- ----- BEFORE ACTION pub type Box { Box(Int) } pub fn main() { let Box(_ignoredInner) = Box(203) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub type Box { Box(Int) } pub fn main() { let Box(_ignored_inner) = Box(203) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_custom_type.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "type Boxed_value { Box(Int) }" --- ----- BEFORE ACTION type Boxed_value { Box(Int) } ▔▔▔▔▔↑ ----- AFTER ACTION type BoxedValue { Box(Int) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_function.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "fn doStuff() {}" --- ----- BEFORE ACTION fn doStuff() {} ▔▔▔▔▔▔▔▔▔▔▔▔▔↑ ----- AFTER ACTION fn do_stuff() {} ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_function_type_parameter_name.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "fn identity(value: someType) { value }" --- ----- BEFORE ACTION fn identity(value: someType) { value } ▔▔▔▔▔▔▔▔↑ ----- AFTER ACTION fn identity(value: some_type) { value } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_list_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let assert [theElement] = [9.4]\n}" --- ----- BEFORE ACTION pub fn main() { let assert [theElement] = [9.4] ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let assert [the_element] = [9.4] } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_list_pattern_discard.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let assert [_elemOne] = [False]\n}" --- ----- BEFORE ACTION pub fn main() { let assert [_elemOne] = [False] ↑ } ----- AFTER ACTION pub fn main() { let assert [_elem_one] = [False] } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_parameter.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "fn add(numA: Int, num_b: Int) { numA + num_b }" --- ----- BEFORE ACTION fn add(numA: Int, num_b: Int) { numA + num_b } ↑ ----- AFTER ACTION fn add(num_a: Int, num_b: Int) { numA + num_b } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_parameter_discard.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "fn ignore(_ignoreMe: Bool) { 98 }" --- ----- BEFORE ACTION fn ignore(_ignoreMe: Bool) { 98 } ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ ----- AFTER ACTION fn ignore(_ignore_me: Bool) { 98 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_parameter_discard_name2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "fn ignore(labelled_discard _ignoreMe: Bool) { 98 }" --- ----- BEFORE ACTION fn ignore(labelled_discard _ignoreMe: Bool) { 98 } ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ ----- AFTER ACTION fn ignore(labelled_discard _ignore_me: Bool) { 98 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_parameter_discard_name3.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let ignore = fn(_ignoreMe: Bool) { 98 }\n}" --- ----- BEFORE ACTION pub fn main() { let ignore = fn(_ignoreMe: Bool) { 98 } ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let ignore = fn(_ignore_me: Bool) { 98 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_parameter_label.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "fn func(thisIsALabel param: Int) { param }" --- ----- BEFORE ACTION fn func(thisIsALabel param: Int) { param } ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ ----- AFTER ACTION fn func(this_is_a_label param: Int) { param } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_parameter_label2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "fn ignore(thisIsALabel _ignore: Int) { 25 }" --- ----- BEFORE ACTION fn ignore(thisIsALabel _ignore: Int) { 25 } ↑ ----- AFTER ACTION fn ignore(this_is_a_label _ignore: Int) { 25 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_parameter_name2.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "fn pass(label paramName: Bool) { paramName }" --- ----- BEFORE ACTION fn pass(label paramName: Bool) { paramName } ↑ ----- AFTER ACTION fn pass(label param_name: Bool) { paramName } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_parameter_name3.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let add = fn(numA: Int, num_b: Int) { numA + num_b }\n}" --- ----- BEFORE ACTION pub fn main() { let add = fn(numA: Int, num_b: Int) { numA + num_b } ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let add = fn(num_a: Int, num_b: Int) { numA + num_b } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_pattern_assignment.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let assert 42 as theAnswer = 42\n}" --- ----- BEFORE ACTION pub fn main() { let assert 42 as theAnswer = 42 ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let assert 42 as the_answer = 42 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_string_prefix_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let assert \"prefix\" <> coolSuffix = \"prefix-suffix\"\n}" --- ----- BEFORE ACTION pub fn main() { let assert "prefix" <> coolSuffix = "prefix-suffix" ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let assert "prefix" <> cool_suffix = "prefix-suffix" } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_string_prefix_pattern_alias.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let assert \"prefix\" as thePrefix <> _suffix = \"prefix-suffix\"\n}" --- ----- BEFORE ACTION pub fn main() { let assert "prefix" as thePrefix <> _suffix = "prefix-suffix" ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let assert "prefix" as the_prefix <> _suffix = "prefix-suffix" } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_string_prefix_pattern_discard.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let assert \"prefix\" <> _boringSuffix = \"prefix-suffix\"\n}" --- ----- BEFORE ACTION pub fn main() { let assert "prefix" <> _boringSuffix = "prefix-suffix" ▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let assert "prefix" <> _boring_suffix = "prefix-suffix" } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_tuple_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let #(a, secondValue) = #(1, 2)\n}" --- ----- BEFORE ACTION pub fn main() { let #(a, secondValue) = #(1, 2) ▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let #(a, second_value) = #(1, 2) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_tuple_pattern_discard.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let #(a, _secondValue) = #(1, 2)\n}" --- ----- BEFORE ACTION pub fn main() { let #(a, _secondValue) = #(1, 2) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let #(a, _second_value) = #(1, 2) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_type_alias.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: type Fancy_Bool = Bool --- ----- BEFORE ACTION type Fancy_Bool = Bool ▔▔▔▔▔▔▔▔▔▔↑ ----- AFTER ACTION type FancyBool = Bool ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_type_alias_parameter_name.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: type Phantom(phantomType) = Int --- ----- BEFORE ACTION type Phantom(phantomType) = Int ▔▔▔▔▔▔▔▔▔▔▔↑ ----- AFTER ACTION type Phantom(phantom_type) = Int ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_type_parameter_name.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "type Wrapper(innerType) {}" --- ----- BEFORE ACTION type Wrapper(innerType) {} ▔▔▔▔▔▔▔▔▔↑ ----- AFTER ACTION type Wrapper(inner_type) {} ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_use.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "fn use_test(f) { f(Nil) }\npub fn main() {use useVar <- use_test()}" --- ----- BEFORE ACTION fn use_test(f) { f(Nil) } pub fn main() {use useVar <- use_test()} ▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ ----- AFTER ACTION fn use_test(f) { f(Nil) } pub fn main() {use use_var <- use_test()} ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_use_discard.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "fn use_test(f) { f(Nil) }\npub fn main() {use _discardVar <- use_test()}" --- ----- BEFORE ACTION fn use_test(f) { f(Nil) } pub fn main() {use _discardVar <- use_test()} ↑ ----- AFTER ACTION fn use_test(f) { f(Nil) } pub fn main() {use _discard_var <- use_test()} ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_variable.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let theAnswer = 42\n}" --- ----- BEFORE ACTION pub fn main() { let theAnswer = 42 ▔▔▔↑ } ----- AFTER ACTION pub fn main() { let the_answer = 42 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_invalid_variable_discard.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let _boringNumber = 72\n}" --- ----- BEFORE ACTION pub fn main() { let _boringNumber = 72 ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let _boring_number = 72 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__rename_module_for_imported.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport gleam/io\n\npub fn main() {\n i.println(\"Hello, world!\")\n}\n" --- ----- BEFORE ACTION import gleam/io pub fn main() { i.println("Hello, world!") ▔▔↑ } ----- AFTER ACTION import gleam/io pub fn main() { io.println("Hello, world!") } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__replace_nested_underscore_in_let_annotation.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub fn wibble() {\n let a: Result(Result(_, Nil), Nil) = Ok(Ok(\"hello\"))\n}\n " --- ----- BEFORE ACTION pub fn wibble() { let a: Result(Result(_, Nil), Nil) = Ok(Ok("hello")) ↑ } ----- AFTER ACTION pub fn wibble() { let a: Result(Result(String, Nil), Nil) = Ok(Ok("hello")) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__replace_nested_underscore_with_function_return_type.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub fn wibble() -> Result(Result(_, Nil), Int) {\n Ok(Ok(\"hello\"))\n}\n " --- ----- BEFORE ACTION pub fn wibble() -> Result(Result(_, Nil), Int) { ↑ Ok(Ok("hello")) } ----- AFTER ACTION pub fn wibble() -> Result(Result(String, Nil), Int) { Ok(Ok("hello")) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__replace_nested_underscore_with_generic_type.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub fn wibble() -> Result(a, _) {\n Ok(todo)\n}\n " --- ----- BEFORE ACTION pub fn wibble() -> Result(a, _) { ↑ Ok(todo) } ----- AFTER ACTION pub fn wibble() -> Result(a, b) { Ok(todo) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__replace_nested_underscore_with_tuple_type.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub fn wibble() {\n let a: #(Int, _, Int) = #(1, \"hello\", 2)\n}\n " --- ----- BEFORE ACTION pub fn wibble() { let a: #(Int, _, Int) = #(1, "hello", 2) ↑ } ----- AFTER ACTION pub fn wibble() { let a: #(Int, String, Int) = #(1, "hello", 2) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__replace_underscore_in_fn_expr_argument.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub fn wibble() {\n fn(a: _) { a + 2 }\n}\n " --- ----- BEFORE ACTION pub fn wibble() { fn(a: _) { a + 2 } ↑ } ----- AFTER ACTION pub fn wibble() { fn(a: Int) { a + 2 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__replace_underscore_in_function_argument.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub fn wibble(a: _) -> Int {\n a + 2\n}\n " --- ----- BEFORE ACTION pub fn wibble(a: _) -> Int { ↑ a + 2 } ----- AFTER ACTION pub fn wibble(a: Int) -> Int { a + 2 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__replace_underscore_in_let_annotation.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub fn wibble() {\n let a: _ = Ok(Ok(\"hello\"))\n}\n " --- ----- BEFORE ACTION pub fn wibble() { let a: _ = Ok(Ok("hello")) ↑ } ----- AFTER ACTION pub fn wibble() { let a: Result(Result(String, a), b) = Ok(Ok("hello")) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__replace_underscore_with_function_return_type.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub fn wibble() -> _ {\n 12\n}\n " --- ----- BEFORE ACTION pub fn wibble() -> _ { ↑ 12 } ----- AFTER ACTION pub fn wibble() -> Int { 12 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__replace_underscore_with_type.snap ================================================ --- source: language-server/src/tests/action.rs expression: "\npub fn wibble() -> Result(Result(_, Nil), Int) {\n Ok(Ok(\"hello\"))\n}\n " --- ----- BEFORE ACTION pub fn wibble() -> Result(Result(_, Nil), Int) { ↑ Ok(Ok("hello")) } ----- AFTER ACTION pub fn wibble() -> Result(Result(String, Nil), Int) { Ok(Ok("hello")) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__selected_statements_do_not_select_outer_block.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n let c = {\n let a = 10\n let b = 20\n a + b\n }\n\n echo c\n}\n" --- ----- BEFORE ACTION pub fn main() { let c = { let a = 10 ▔▔▔▔▔▔▔▔▔▔ let b = 20 ▔▔▔▔▔▔▔▔▔▔▔▔▔▔ a + b ▔▔▔▔▔▔↑ } echo c } ----- AFTER ACTION pub fn main() { let c = { function() } echo c } fn function() -> Int { let a = 10 let b = 20 a + b } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__split_string.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n \"wibble wobble woo\"\n}" --- ----- BEFORE ACTION pub fn main() { "wibble wobble woo" ↑ } ----- AFTER ACTION pub fn main() { "wibble " <> todo <> "wobble woo" } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__splitting_string_as_first_pipeline_step_inserts_brackets.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n \"wibble wobble\" |> io.println\n}" --- ----- BEFORE ACTION pub fn main() { "wibble wobble" |> io.println ↑ } ----- AFTER ACTION pub fn main() { { "wibble " <> todo <> " wobble" } |> io.println } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__turn_call_into_use_starts_from_innermost_function.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(10, 20, fn(a) {\n wibble(30, 40, fn(b) {\n a + b\n })\n })\n}\n\nfn wibble(m, n, f) {\n f(1)\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble(10, 20, fn(a) { wibble(30, 40, fn(b) { ▔▔▔▔↑ a + b }) }) } fn wibble(m, n, f) { f(1) } ----- AFTER ACTION pub fn main() { wibble(10, 20, fn(a) { use b <- wibble(30, 40) a + b }) } fn wibble(m, n, f) { f(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__turn_call_into_use_with_another_use_in_the_way.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(10, 20, fn(a) {\n use b <- wibble(30, 40)\n a + b\n })\n}\n\nfn wibble(m, n, f) {\n f(1)\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble(10, 20, fn(a) { use b <- wibble(30, 40) ↑ a + b }) } fn wibble(m, n, f) { f(1) } ----- AFTER ACTION pub fn main() { use a <- wibble(10, 20) use b <- wibble(30, 40) a + b } fn wibble(m, n, f) { f(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__turn_call_into_use_with_fn_with_no_args.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(fn() { todo })\n}\n\nfn wibble(f) {\n f()\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble(fn() { todo }) ↑ } fn wibble(f) { f() } ----- AFTER ACTION pub fn main() { use <- wibble todo } fn wibble(f) { f() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__turn_call_into_use_with_last_function_in_a_block.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n {\n wibble(10, 20, fn(a) { todo })\n wibble(1, 11, fn(a) { todo })\n }\n Nil\n}\n\nfn wibble(m, n, f) {\n f(1)\n}\n" --- ----- BEFORE ACTION pub fn main() { { wibble(10, 20, fn(a) { todo }) wibble(1, 11, fn(a) { todo }) ▔▔▔▔▔▔▔▔▔▔↑ } Nil } fn wibble(m, n, f) { f(1) } ----- AFTER ACTION pub fn main() { { wibble(10, 20, fn(a) { todo }) use a <- wibble(1, 11) todo } Nil } fn wibble(m, n, f) { f(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__turn_call_into_use_with_module_function.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport other\npub fn main() {\n other.wibble(10, 20, fn(a) {\n todo\n a + b\n })\n}\n" --- ----- BEFORE ACTION import other pub fn main() { other.wibble(10, 20, fn(a) { ↑ todo a + b }) } ----- AFTER ACTION import other pub fn main() { use a <- other.wibble(10, 20) todo a + b } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__turn_call_into_use_with_out_of_order_arguments.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n fold(0, over: [], with: fn (a, b) { todo })\n}\n\nfn fold(over list: List(a), from acc: acc, with fun: fn(acc, a) -> acc) -> acc {\n todo\n}\n" --- ----- BEFORE ACTION pub fn main() { fold(0, over: [], with: fn (a, b) { todo }) ↑ } fn fold(over list: List(a), from acc: acc, with fun: fn(acc, a) -> acc) -> acc { todo } ----- AFTER ACTION pub fn main() { use a, b <- fold(0, over: []) todo } fn fold(over list: List(a), from acc: acc, with fun: fn(acc, a) -> acc) -> acc { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__turn_call_into_use_with_single_line_body.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(fn(a, b) { todo })\n}\n\nfn wibble(f) {\n f(todo, todo)\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble(fn(a, b) { todo }) ↑ } fn wibble(f) { f(todo, todo) } ----- AFTER ACTION pub fn main() { use a, b <- wibble todo } fn wibble(f) { f(todo, todo) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__turn_call_with_fn_with_type_annotations_into_use.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(1, 2, fn(a: Int) {\n todo\n })\n}\n\nfn wibble(m, n, f) {\n f(1)\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble(1, 2, fn(a: Int) { ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ todo }) } fn wibble(m, n, f) { f(1) } ----- AFTER ACTION pub fn main() { use a: Int <- wibble(1, 2) todo } fn wibble(m, n, f) { f(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__turn_call_with_multiline_fn_into_use.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(1, 2, fn(a) {\n todo\n case todo {\n _ -> todo\n }\n })\n}\n\nfn wibble(m, n, f) {\n f(1)\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble(1, 2, fn(a) { ▔▔▔▔▔▔↑ todo case todo { _ -> todo } }) } fn wibble(m, n, f) { f(1) } ----- AFTER ACTION pub fn main() { use a <- wibble(1, 2) todo case todo { _ -> todo } } fn wibble(m, n, f) { f(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__turn_call_with_multiple_arguments_into_use.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn main() {\n wibble(1, 2, fn(a) { todo })\n}\n\nfn wibble(m, n, f) {\n f(1)\n}\n" --- ----- BEFORE ACTION pub fn main() { wibble(1, 2, fn(a) { todo }) ↑ } fn wibble(m, n, f) { f(1) } ----- AFTER ACTION pub fn main() { use a <- wibble(1, 2) todo } fn wibble(m, n, f) { f(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__type_variables_are_not_duplicated_when_adding_annotations.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nfn wibble(a: a, b: b, c: c) -> d { todo }\n\nfn many_args(a, b, c, d: d, e: a, f, g) {\n todo\n}\n" --- ----- BEFORE ACTION fn wibble(a: a, b: b, c: c) -> d { todo } fn many_args(a, b, c, d: d, e: a, f, g) { ↑ todo } ----- AFTER ACTION fn wibble(a: a, b: b, c: c) -> d { todo } fn many_args(a: b, b: c, c: e, d: d, e: a, f: f, g: g) -> h { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__type_variables_from_other_functions_do_not_change_annotations.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nfn wibble(a: a, b: b, c: c) -> d { todo }\n\nfn pair(a, b) {\n #(a, b)\n}\n" --- ----- BEFORE ACTION fn wibble(a: a, b: b, c: c) -> d { todo } fn pair(a, b) { ↑ #(a, b) } ----- AFTER ACTION fn wibble(a: a, b: b, c: c) -> d { todo } fn pair(a: a, b: b) -> #(a, b) { #(a, b) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__type_variables_from_other_functions_do_not_change_annotations_constant.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nfn wibble(a: a, b: b, c: c) -> d { todo }\n\nconst empty = []\n" --- ----- BEFORE ACTION fn wibble(a: a, b: b, c: c) -> d { todo } const empty = [] ↑ ----- AFTER ACTION fn wibble(a: a, b: b, c: c) -> d { todo } const empty: List(a) = [] ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__type_variables_in_let_bindings_are_considered_when_adding_annotations.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nfn wibble(a, b, c) {\n let x: a = todo\n fn(a: b, b: c) -> d {\n todo\n }\n}\n" --- ----- BEFORE ACTION fn wibble(a, b, c) { ↑ let x: a = todo fn(a: b, b: c) -> d { todo } } ----- AFTER ACTION fn wibble(a: e, b: f, c: g) -> fn(b, c) -> d { let x: a = todo fn(a: b, b: c) -> d { todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__unqualified_to_qualified_import_after_constructor.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn create_user(name: String) -> User {\n User(name: name, id: 1)\n}\n\nimport user.{type User, User}\n" --- ----- BEFORE ACTION pub fn create_user(name: String) -> User { User(name: name, id: 1) ▔▔▔▔▔↑ } import user.{type User, User} ----- AFTER ACTION pub fn create_user(name: String) -> User { user.User(name: name, id: 1) } import user.{type User, } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__unqualified_to_qualified_import_bad_formatted_comma.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option.{type Option , Some}\n\npub fn maybe_increment(x: Option(Int)) -> Option(Int) {\n case x {\n Some(value) -> Some(value + 1)\n _ -> x\n }\n}\n" --- ----- BEFORE ACTION import option.{type Option , Some} pub fn maybe_increment(x: Option(Int)) -> Option(Int) { ▔▔▔↑ case x { Some(value) -> Some(value + 1) _ -> x } } ----- AFTER ACTION import option.{Some} pub fn maybe_increment(x: option.Option(Int)) -> option.Option(Int) { case x { Some(value) -> Some(value + 1) _ -> x } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__unqualified_to_qualified_import_bad_formatted_type_constructor.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option.{type Option, Some}\n\npub fn maybe_increment(x: Option(Int)) -> Option(Int) {\n case x {\n Some(value) -> Some(value + 1)\n _ -> x\n }\n}\n" --- ----- BEFORE ACTION import option.{type Option, Some} pub fn maybe_increment(x: Option(Int)) -> Option(Int) { ▔▔▔↑ case x { Some(value) -> Some(value + 1) _ -> x } } ----- AFTER ACTION import option.{Some} pub fn maybe_increment(x: option.Option(Int)) -> option.Option(Int) { case x { Some(value) -> Some(value + 1) _ -> x } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__unqualified_to_qualified_import_bad_formatted_type_constructor_with_alias.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option.{type Option as Maybe, Some}\n\npub fn maybe_increment(x: Maybe(Int)) -> Maybe(Int) {\n case x {\n Some(value) -> Some(value + 1)\n _ -> x\n }\n}\n" --- ----- BEFORE ACTION import option.{type Option as Maybe, Some} pub fn maybe_increment(x: Maybe(Int)) -> Maybe(Int) { ▔▔▔↑ case x { Some(value) -> Some(value + 1) _ -> x } } ----- AFTER ACTION import option.{Some} pub fn maybe_increment(x: option.Option(Int)) -> option.Option(Int) { case x { Some(value) -> Some(value + 1) _ -> x } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__unqualified_to_qualified_import_between_constructors.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn create_user(name: String) -> User {\n User(name: name, id: 1)\n}\n\nimport user.{type User, User}\n\npub fn user_list(users: List(User)) -> List(String) {\n [User(name: \"John\", id: 1),\n User(name: \"Jane\", id: 2)]\n}\n\n" --- ----- BEFORE ACTION pub fn create_user(name: String) -> User { User(name: name, id: 1) ▔▔▔▔▔↑ } import user.{type User, User} pub fn user_list(users: List(User)) -> List(String) { [User(name: "John", id: 1), User(name: "Jane", id: 2)] } ----- AFTER ACTION pub fn create_user(name: String) -> User { user.User(name: name, id: 1) } import user.{type User, } pub fn user_list(users: List(User)) -> List(String) { [user.User(name: "John", id: 1), user.User(name: "Jane", id: 2)] } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__unqualified_to_qualified_import_constant.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport mymath.{pi}\n\npub fn circle_area(radius: Float) -> Float {\n pi *. radius *. radius\n}\n\npub fn circle_circumference(radius: Float) -> Float {\n 2. *. pi *. radius\n}\n" --- ----- BEFORE ACTION import mymath.{pi} pub fn circle_area(radius: Float) -> Float { pi *. radius *. radius ▔▔▔▔▔↑ } pub fn circle_circumference(radius: Float) -> Float { 2. *. pi *. radius } ----- AFTER ACTION import mymath.{} pub fn circle_area(radius: Float) -> Float { mymath.pi *. radius *. radius } pub fn circle_circumference(radius: Float) -> Float { 2. *. mymath.pi *. radius } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__unqualified_to_qualified_import_constructor_complex_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option.{None, Some}\n\npub fn main() {\n case [Some(1), None] {\n [None, ..] -> todo\n [Some(_), ..] -> todo\n _ -> todo\n }\n case Some(1), Some(2) {\n None, Some(_) -> todo\n Some(_), Some(val) -> todo\n _ -> todo\n }\n}\n" --- ----- BEFORE ACTION import option.{None, Some} pub fn main() { case [Some(1), None] { ▔▔▔▔▔↑ [None, ..] -> todo [Some(_), ..] -> todo _ -> todo } case Some(1), Some(2) { None, Some(_) -> todo Some(_), Some(val) -> todo _ -> todo } } ----- AFTER ACTION import option.{None, } pub fn main() { case [option.Some(1), None] { [None, ..] -> todo [option.Some(_), ..] -> todo _ -> todo } case option.Some(1), option.Some(2) { None, option.Some(_) -> todo option.Some(_), option.Some(val) -> todo _ -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__unqualified_to_qualified_import_function.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport list.{map}\n\npub fn main() {\n let identity = map([1, 2, 3], fn(x) { x })\n let double = map([1, 2, 3], fn(x) { x * 2 })\n}\n" --- ----- BEFORE ACTION import list.{map} pub fn main() { let identity = map([1, 2, 3], fn(x) { x }) ▔▔▔▔↑ let double = map([1, 2, 3], fn(x) { x * 2 }) } ----- AFTER ACTION import list.{} pub fn main() { let identity = list.map([1, 2, 3], fn(x) { x }) let double = list.map([1, 2, 3], fn(x) { x * 2 }) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__unqualified_to_qualified_import_in_constant_record.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option.{Some}\n\nconst some = Some(1)\n" --- ----- BEFORE ACTION import option.{Some} const some = Some(1) ↑ ----- AFTER ACTION import option.{} const some = option.Some(1) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__unqualified_to_qualified_import_in_constant_var.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option.{None}\n\nconst none = None\n" --- ----- BEFORE ACTION import option.{None} const none = None ↑ ----- AFTER ACTION import option.{} const none = option.None ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__unqualified_to_qualified_import_in_list_and_tuple.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option.{Some}\n\npub fn main() {\n let list = [Some(1), option.None]\n let tuple = #(Some(2), option.None)\n}\n" --- ----- BEFORE ACTION import option.{Some} pub fn main() { let list = [Some(1), option.None] ▔▔▔▔▔↑ let tuple = #(Some(2), option.None) } ----- AFTER ACTION import option.{} pub fn main() { let list = [option.Some(1), option.None] let tuple = #(option.Some(2), option.None) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__unqualified_to_qualified_import_in_nested_constant.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option.{type Option, None}\n\ntype Result(a) {\n Result(expected: a, actual: Option(a))\n}\n\nconst zero = Result(0, None)\n" --- ----- BEFORE ACTION import option.{type Option, None} type Result(a) { Result(expected: a, actual: Option(a)) } const zero = Result(0, None) ↑ ----- AFTER ACTION import option.{type Option, } type Result(a) { Result(expected: a, actual: Option(a)) } const zero = Result(0, option.None) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__unqualified_to_qualified_import_in_pattern_matching.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport result.{type Result, Ok, Error}\n\npub fn process_result(res: Result(Int, String)) -> Int {\n case res {\n Ok(value) -> value\n Error(_) -> 0\n }\n}\n" --- ----- BEFORE ACTION import result.{type Result, Ok, Error} pub fn process_result(res: Result(Int, String)) -> Int { case res { Ok(value) -> value ▔▔▔↑ Error(_) -> 0 } } ----- AFTER ACTION import result.{type Result, Error} pub fn process_result(res: Result(Int, String)) -> Int { case res { result.Ok(value) -> value Error(_) -> 0 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__unqualified_to_qualified_import_multiple_line_aliased.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option.{\n type Option,\n None,\n Some\n} as opt\n\npub fn main() {\n Some(1)\n}\n" --- ----- BEFORE ACTION import option.{ type Option, None, Some } as opt pub fn main() { Some(1) ▔▔▔▔↑ } ----- AFTER ACTION import option.{ type Option, None, } as opt pub fn main() { opt.Some(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__unqualified_to_qualified_import_multiple_line_bad_format_without_trailing_comma.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option.{type Option,\n Some\n\n}\n\npub fn main() {\n Some(1)\n}\n" --- ----- BEFORE ACTION import option.{type Option, Some } pub fn main() { Some(1) ▔▔▔▔↑ } ----- AFTER ACTION import option.{type Option, } pub fn main() { option.Some(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__unqualified_to_qualified_import_multiple_occurrences.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport list.{map, filter}\n\npub fn process_list(items: List(Int)) -> List(Int) {\n items\n |> map(fn(x) { x + 1 })\n |> map(fn(x) { x * 2 })\n}\n" --- ----- BEFORE ACTION import list.{map, filter} pub fn process_list(items: List(Int)) -> List(Int) { items |> map(fn(x) { x + 1 }) ▔▔▔▔▔▔↑ |> map(fn(x) { x * 2 }) } ----- AFTER ACTION import list.{filter} pub fn process_list(items: List(Int)) -> List(Int) { items |> list.map(fn(x) { x + 1 }) |> list.map(fn(x) { x * 2 }) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__unqualified_to_qualified_import_nested_function_call.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport list.{map, flatten}\nimport operation.{double}\n\npub fn process_names(names: List(List(Int))) -> List(Int) {\n names\n |> flatten\n |> map(double)\n}\n" --- ----- BEFORE ACTION import list.{map, flatten} import operation.{double} pub fn process_names(names: List(List(Int))) -> List(Int) { names |> flatten |> map(double) ▔▔▔▔↑ } ----- AFTER ACTION import list.{map, flatten} import operation.{} pub fn process_names(names: List(List(Int))) -> List(Int) { names |> flatten |> map(operation.double) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__unqualified_to_qualified_import_record_constructor.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport user.{type User, User}\n\npub fn create_user(name: String) -> User {\n User(name: name, id: 1)\n}\n" --- ----- BEFORE ACTION import user.{type User, User} pub fn create_user(name: String) -> User { User(name: name, id: 1) ▔▔▔▔▔↑ } ----- AFTER ACTION import user.{type User, } pub fn create_user(name: String) -> User { user.User(name: name, id: 1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__unqualified_to_qualified_import_type_annotation.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport option.{type Option, Some}\n\npub fn maybe_increment(x: Option(Int)) -> Option(Int) {\n case x {\n Some(value) -> Some(value + 1)\n _ -> x\n }\n}\n" --- ----- BEFORE ACTION import option.{type Option, Some} pub fn maybe_increment(x: Option(Int)) -> Option(Int) { ▔▔▔↑ case x { Some(value) -> Some(value + 1) _ -> x } } ----- AFTER ACTION import option.{Some} pub fn maybe_increment(x: option.Option(Int)) -> option.Option(Int) { case x { Some(value) -> Some(value + 1) _ -> x } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__unqualified_to_qualified_import_variable_shadowing.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\n\nimport wibble.{wobble}\n\npub fn example() {\n echo wobble\n\n let wobble = 1\n\n echo wobble\n\n let _ = fn(wobble) {\n echo wobble\n }\n\n todo\n}\n" --- ----- BEFORE ACTION import wibble.{wobble} pub fn example() { echo wobble ▔▔▔↑ let wobble = 1 echo wobble let _ = fn(wobble) { echo wobble } todo } ----- AFTER ACTION import wibble.{} pub fn example() { echo wibble.wobble let wobble = 1 echo wobble let _ = fn(wobble) { echo wobble } todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__unqualified_to_qualified_import_with_alias.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport list.{map as transform}\n\npub fn double_list(items: List(Int)) -> List(Int) {\n transform(items, fn(x) { x * 2 })\n}\n" --- ----- BEFORE ACTION import list.{map as transform} pub fn double_list(items: List(Int)) -> List(Int) { transform(items, fn(x) { x * 2 }) ▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION import list.{} pub fn double_list(items: List(Int)) -> List(Int) { list.map(items, fn(x) { x * 2 }) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__unqualified_to_qualified_import_with_alias_and_module_alias.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport list.{map as transform} as lst\n\npub fn double_list(items: List(Int)) -> List(Int) {\n transform(items, fn(x) { x * 2 })\n}\n" --- ----- BEFORE ACTION import list.{map as transform} as lst pub fn double_list(items: List(Int)) -> List(Int) { transform(items, fn(x) { x * 2 }) ▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION import list.{} as lst pub fn double_list(items: List(Int)) -> List(Int) { lst.map(items, fn(x) { x * 2 }) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__unqualify_already_imported_type.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\nimport wibble.{type Wibble}\n\npub fn main() -> wibble.Wibble {\n todo\n}\n" --- ----- BEFORE ACTION import wibble.{type Wibble} pub fn main() -> wibble.Wibble { ↑ todo } ----- AFTER ACTION import wibble.{type Wibble} pub fn main() -> Wibble { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__use_label_shorthand_works_for_alternative_patterns.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble { Wibble(arg: Int, arg2: String) }\n\npub fn main() {\n case Wibble(1, \"wibble\") {\n Wibble(arg2: arg2, ..) | Wibble(arg: 1, arg2: arg2) -> todo\n }\n}\n " --- ----- BEFORE ACTION pub type Wibble { Wibble(arg: Int, arg2: String) } pub fn main() { ▔▔▔▔▔▔▔▔ case Wibble(1, "wibble") { ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Wibble(arg2: arg2, ..) | Wibble(arg: 1, arg2: arg2) -> todo ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } } ----- AFTER ACTION pub type Wibble { Wibble(arg: Int, arg2: String) } pub fn main() { case Wibble(1, "wibble") { Wibble(arg2: , ..) | Wibble(arg: 1, arg2: ) -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__use_label_shorthand_works_for_nested_calls.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn wibble(arg arg: Int) -> Int { arg }\n\npub fn main() {\n let arg = 1\n wibble(wibble(arg: arg))\n}\n " --- ----- BEFORE ACTION pub fn wibble(arg arg: Int) -> Int { arg } pub fn main() { ▔▔▔▔▔▔▔▔ let arg = 1 ▔▔▔▔▔▔▔▔▔▔▔▔▔ wibble(wibble(arg: arg)) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ } ↑ ----- AFTER ACTION pub fn wibble(arg arg: Int) -> Int { arg } pub fn main() { let arg = 1 wibble(wibble(arg: )) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__use_label_shorthand_works_for_nested_patterns.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble { Wibble(arg: Int, arg2: Wobble) }\npub type Wobble { Wobble(arg: Int, arg2: String) }\n\npub fn main() {\n let Wibble(arg2: Wobble(arg: arg, arg2: arg2), ..) = todo\n}\n " --- ----- BEFORE ACTION pub type Wibble { Wibble(arg: Int, arg2: Wobble) } pub type Wobble { Wobble(arg: Int, arg2: String) } pub fn main() { ▔▔▔▔▔▔▔▔ let Wibble(arg2: Wobble(arg: arg, arg2: arg2), ..) = todo ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub type Wibble { Wibble(arg: Int, arg2: Wobble) } pub type Wobble { Wobble(arg: Int, arg2: String) } pub fn main() { let Wibble(arg2: Wobble(arg: , arg2: ), ..) = todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__use_label_shorthand_works_for_nested_record_updates.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Wibble { Wibble(arg: Int, arg2: Wobble) }\npub type Wobble { Wobble(arg: Int, arg2: String) }\n\npub fn main() {\n let arg = 1\n let arg2 = \"a\"\n Wibble(..todo, arg2: Wobble(arg: arg, arg2: arg2))\n}\n " --- ----- BEFORE ACTION pub type Wibble { Wibble(arg: Int, arg2: Wobble) } pub type Wobble { Wobble(arg: Int, arg2: String) } pub fn main() { let arg = 1 let arg2 = "a" Wibble(..todo, arg2: Wobble(arg: arg, arg2: arg2)) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑ } ----- AFTER ACTION pub type Wibble { Wibble(arg: Int, arg2: Wobble) } pub type Wobble { Wobble(arg: Int, arg2: String) } pub fn main() { let arg = 1 let arg2 = "a" Wibble(..todo, arg2: Wobble(arg: , arg2: )) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__wrap_assignment_value_in_block.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub fn main() {\n let var = \"value\"\n}" --- ----- BEFORE ACTION pub fn main() { let var = "value" ▔▔▔▔↑ } ----- AFTER ACTION pub fn main() { let var = { "value" } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__wrap_case_assignment_of_record_access_in_block.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub type Record {\n R(left: Int, right: Int)\n}\n\npub fn main() {\n let r = R(1, 2)\n let l = r.left\n l\n}\n" --- ----- BEFORE ACTION pub type Record { R(left: Int, right: Int) } pub fn main() { let r = R(1, 2) let l = r.left ↑ l } ----- AFTER ACTION pub type Record { R(left: Int, right: Int) } pub fn main() { let r = R(1, 2) let l = { r.left } l } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__wrap_case_clause_in_block.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn f(option) {\n case option {\n Some(content) -> content\n None -> panic\n }\n}" --- ----- BEFORE ACTION pub fn f(option) { case option { Some(content) -> content ↑ None -> panic } } ----- AFTER ACTION pub fn f(option) { case option { Some(content) -> { content } None -> panic } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__wrap_case_clause_inside_assignment_in_block.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub type PokemonType {\n Air\n Water\n Fire\n}\n\n pub fn f(pokemon_type: PokemonType) {\n let damage = case pokemon_type {\n Water -> soak()\n Fire -> burn()\n }\n\n \"Pokemon did \" <> damage\n }" --- ----- BEFORE ACTION pub type PokemonType { Air Water Fire } pub fn f(pokemon_type: PokemonType) { let damage = case pokemon_type { Water -> soak() Fire -> burn() ↑ } "Pokemon did " <> damage } ----- AFTER ACTION pub type PokemonType { Air Water Fire } pub fn f(pokemon_type: PokemonType) { let damage = case pokemon_type { Water -> soak() Fire -> { burn() } } "Pokemon did " <> damage } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__wrap_case_clause_with_guard_in_block.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn f(option) {\n case option {\n Some(integer) if integer > 0 -> integer\n Some(integer) -> 0\n None -> panic\n }\n}" --- ----- BEFORE ACTION pub fn f(option) { case option { Some(integer) if integer > 0 -> integer ↑ Some(integer) -> 0 None -> panic } } ----- AFTER ACTION pub fn f(option) { case option { Some(integer) if integer > 0 -> { integer } Some(integer) -> 0 None -> panic } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__wrap_case_clause_with_multiple_patterns_in_block.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "pub type PokemonType {\n Air\n Water\n Fire\n}\n\n pub fn f(pokemon_type: PokemonType) {\n case pokemon_type {\n Water | Air -> soak()\n Fire -> burn()\n }\n }" --- ----- BEFORE ACTION pub type PokemonType { Air Water Fire } pub fn f(pokemon_type: PokemonType) { case pokemon_type { Water | Air -> soak() ↑ Fire -> burn() } } ----- AFTER ACTION pub type PokemonType { Air Water Fire } pub fn f(pokemon_type: PokemonType) { case pokemon_type { Water | Air -> { soak() } Fire -> burn() } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__action__wrap_nested_case_clause_in_block.snap ================================================ --- source: compiler-core/src/language_server/tests/action.rs expression: "\npub fn f(result) {\n case result {\n Ok(reresult) -> {\n case reresult {\n Ok(w) -> w\n Error(_) -> panic\n }\n }\n Error(_) -> panic\n }\n}" --- ----- BEFORE ACTION pub fn f(result) { case result { Ok(reresult) -> { case reresult { Ok(w) -> w ↑ Error(_) -> panic } } Error(_) -> panic } } ----- AFTER ACTION pub fn f(result) { case result { Ok(reresult) -> { case reresult { Ok(w) -> { w } Error(_) -> panic } } Error(_) -> panic } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__argument_shadowing.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main(x: Int) {\n fn(x: Float) {\n\n }\n}\n" --- pub fn main(x: Int) { fn(x: Float) { | } } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True main kind: Function detail: fn(Int) -> fn(Float) -> a sort: 03_main desc: app edits: [3:0-3:0]: "main" x kind: Variable detail: Float sort: 3_x desc: app docs: "A locally defined variable." edits: [3:0-3:0]: "x" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__argument_variable_shadowing.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main(x: Int) {\n let x = [1, 2]\n\n}\n" --- pub fn main(x: Int) { let x = [1, 2] | } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True main kind: Function detail: fn(Int) -> List(Int) sort: 3_main desc: app edits: [3:0-3:0]: "main" x kind: Variable detail: List(Int) sort: 3_x desc: app docs: "A locally defined variable." edits: [3:0-3:0]: "x" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__autocomplete_doesnt_delete_the_piece_of_code_that_comes_after.snap ================================================ --- source: compiler-core/src/language_server/tests/completion.rs expression: "\nimport list\npub fn main(x: Int) {\n list.list.filter([1, 2, 3], todo)\n}\n" --- import list pub fn main(x: Int) { list.|list.filter([1, 2, 3], todo) } ----- After applying completion ----- import list pub fn main(x: Int) { list.maplist.filter([1, 2, 3], todo) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__autocomplete_doesnt_delete_the_piece_of_code_that_comes_after_2.snap ================================================ --- source: compiler-core/src/language_server/tests/completion.rs expression: "\nimport list\npub fn main(x: Int) {\n list.mlist.filter([1, 2, 3], todo)\n}\n" --- import list pub fn main(x: Int) { list.m|list.filter([1, 2, 3], todo) } ----- After applying completion ----- import list pub fn main(x: Int) { list.maplist.filter([1, 2, 3], todo) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__case_subject.snap ================================================ --- source: compiler-core/src/language_server/tests/completion.rs expression: "\npub fn main(something: Bool) {\n case so\n}\n" --- pub fn main(something: Bool) { case so| } ----- After applying completion ----- pub fn main(something: Bool) { case something } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__complete_echo_keyword.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "pub fn main() { e wibble }" --- pub fn main() { e| wibble } ----- After applying completion ----- pub fn main() { echo wibble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__complete_keyword_being_typed.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "pub fn main() { t }" --- pub fn main() { t| } ----- After applying completion ----- pub fn main() { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__complete_panic_keyword.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "pub fn main() { wibble(p) }" --- pub fn main() { wibble(p|) } ----- After applying completion ----- pub fn main() { wibble(panic) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completion_for_partially_correct_existing_module_select.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nimport gleam/list\n\npub fn new() {\n list.fer\n// ^ cursor right after the 'f'\n}\n" --- import gleam/list pub fn new() { list.f|er // ^ cursor right after the 'f' } ----- Completion content ----- list.filter kind: Function detail: fn() -> a sort: 4_list.filter desc: app edits: [4:2-4:10]: "list.filter" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completion_for_type.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "pub fn new() -> wibble.Wibble {}" --- pub fn new() -> wibble.|Wibble {} ----- Completion content ----- wibble.Wibble kind: Class detail: Type sort: 6_wibble.Wibble [0:0-0:0]: "import wibble\n\n" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_a_const_annotation.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\n\nconst wibble: Int = 7\n\npub fn main() {\n let wibble: Int = 7\n}\n" --- const wibble: In|t = 7 pub fn main() { let wibble: Int = 7 } ----- Completion content ----- BitArray kind: Class detail: Type sort: 5_BitArray Bool kind: Class detail: Type sort: 5_Bool Float kind: Class detail: Type sort: 5_Float Int kind: Class detail: Type sort: 5_Int List kind: Class detail: Type sort: 5_List Nil kind: Class detail: Type sort: 5_Nil Result kind: Class detail: Type sort: 5_Result String kind: Class detail: Type sort: 5_String UtfCodepoint kind: Class detail: Type sort: 5_UtfCodepoint ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_a_function_arg_annotation.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn wibble(\n _: String,\n) -> Nil {\n Nil\n}\n" --- pub fn wibble( _: String|, ) -> Nil { Nil } ----- Completion content ----- BitArray kind: Class detail: Type sort: 5_BitArray Bool kind: Class detail: Type sort: 5_Bool Float kind: Class detail: Type sort: 5_Float Int kind: Class detail: Type sort: 5_Int List kind: Class detail: Type sort: 5_List Nil kind: Class detail: Type sort: 5_Nil Result kind: Class detail: Type sort: 5_Result String kind: Class detail: Type sort: 5_String UtfCodepoint kind: Class detail: Type sort: 5_UtfCodepoint ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_a_function_return_annotation.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn wibble(\n _: String,\n) -> Nil {\n Nil\n}\n" --- pub fn wibble( _: String, ) -> Ni|l { Nil } ----- Completion content ----- BitArray kind: Class detail: Type sort: 5_BitArray Bool kind: Class detail: Type sort: 5_Bool Float kind: Class detail: Type sort: 5_Float Int kind: Class detail: Type sort: 5_Int List kind: Class detail: Type sort: 5_List Nil kind: Class detail: Type sort: 5_Nil Result kind: Class detail: Type sort: 5_Result String kind: Class detail: Type sort: 5_String UtfCodepoint kind: Class detail: Type sort: 5_UtfCodepoint ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_a_var_annotation.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main() {\n let wibble: Int = 7\n}\n" --- pub fn main() { let wibble: In|t = 7 } ----- Completion content ----- BitArray kind: Class detail: Type sort: 5_BitArray Bool kind: Class detail: Type sort: 5_Bool Float kind: Class detail: Type sort: 5_Float Int kind: Class detail: Type sort: 5_Int List kind: Class detail: Type sort: 5_List Nil kind: Class detail: Type sort: 5_Nil Result kind: Class detail: Type sort: 5_Result String kind: Class detail: Type sort: 5_String UtfCodepoint kind: Class detail: Type sort: 5_UtfCodepoint ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_an_import.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "import dep\n\npub fn main() {\n 0\n}" --- import dep| pub fn main() { 0 } ----- Completion content ----- gleam kind: Module edits: [0:7-0:10]: "gleam" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_an_import_from_dependency.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "import gleam\n\npub fn main() {\n 0\n}" --- import gle|am pub fn main() { 0 } ----- Completion content ----- example_module kind: Module edits: [0:7-0:12]: "example_module" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_an_import_from_dependency_with_docs.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "//// Main package\n//// documentation!\n\nimport gleam\n\npub fn main() {\n 0\n}" --- //// Main package //// documentation! import gle|am pub fn main() { 0 } ----- Completion content ----- example_module kind: Module edits: [3:7-3:12]: "example_module" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_an_import_no_test.snap ================================================ --- source: compiler-core/src/language_server/tests/completion.rs expression: "import gleam\n\npub fn main() {\n 0\n}" --- import gle|am pub fn main() { 0 } ----- Completion content ----- ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_an_import_not_from_dev_dependency.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "import gleam\n\npub fn main() {\n 0\n}" --- import gle|am pub fn main() { 0 } ----- Completion content ----- example_module kind: Module edits: [0:7-0:12]: "example_module" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_an_import_not_from_dev_dependency_in_dev.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "import gleam\n\npub fn main() {\n 0\n}\n" --- import gle|am pub fn main() { 0 } ----- Completion content ----- app kind: Module edits: [0:7-0:12]: "app" example_module kind: Module edits: [0:7-0:12]: "example_module" indirect_module kind: Module edits: [0:7-0:12]: "indirect_module" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_an_import_not_from_dev_dependency_in_test.snap ================================================ --- source: compiler-core/src/language_server/tests/completion.rs expression: completions --- [ CompletionItem { label: "app", label_details: None, kind: Some( Module, ), detail: None, documentation: None, deprecated: None, preselect: None, sort_text: None, filter_text: None, insert_text: None, insert_text_format: None, insert_text_mode: None, text_edit: Some( Edit( TextEdit { range: Range { start: Position { line: 0, character: 7, }, end: Position { line: 0, character: 12, }, }, new_text: "app", }, ), ), additional_text_edits: None, command: None, commit_characters: None, data: None, tags: None, }, CompletionItem { label: "example_module", label_details: None, kind: Some( Module, ), detail: None, documentation: None, deprecated: None, preselect: None, sort_text: None, filter_text: None, insert_text: None, insert_text_format: None, insert_text_mode: None, text_edit: Some( Edit( TextEdit { range: Range { start: Position { line: 0, character: 7, }, end: Position { line: 0, character: 12, }, }, new_text: "example_module", }, ), ), additional_text_edits: None, command: None, commit_characters: None, data: None, tags: None, }, CompletionItem { label: "indirect_module", label_details: None, kind: Some( Module, ), detail: None, documentation: None, deprecated: None, preselect: None, sort_text: None, filter_text: None, insert_text: None, insert_text_format: None, insert_text_mode: None, text_edit: Some( Edit( TextEdit { range: Range { start: Position { line: 0, character: 7, }, end: Position { line: 0, character: 12, }, }, new_text: "indirect_module", }, ), ), additional_text_edits: None, command: None, commit_characters: None, data: None, tags: None, }, ] ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_an_import_not_from_indirect_dependency.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "import gleam\n\npub fn main() {\n 0\n}" --- import gle|am pub fn main() { 0 } ----- Completion content ----- example_module kind: Module edits: [0:7-0:12]: "example_module" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_an_import_preceeding_whitespace.snap ================================================ --- source: language-server/src/tests/completion.rs expression: " import gleam\n\npub fn main() {\n 0\n}" --- i|mport gleam pub fn main() { 0 } ----- Completion content ----- dep kind: Module edits: [0:7-0:13]: "dep" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_an_import_start.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "import gleam\n\npub fn main() {\n 0\n}" --- |import gleam pub fn main() { 0 } ----- Completion content ----- dep kind: Module edits: [0:7-0:12]: "dep" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_an_import_while_in_dev.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "import gleam\n\npub fn main() {\n 0\n}" --- import gleam| pub fn main() { 0 } ----- Completion content ----- app kind: Module edits: [0:7-0:12]: "app" dev_helper kind: Module edits: [0:7-0:12]: "dev_helper" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_an_import_while_in_test.snap ================================================ --- source: compiler-core/src/language_server/tests/completion.rs expression: completions --- [ CompletionItem { label: "app", label_details: None, kind: Some( Module, ), detail: None, documentation: None, deprecated: None, preselect: None, sort_text: None, filter_text: None, insert_text: None, insert_text_format: None, insert_text_mode: None, text_edit: Some( Edit( TextEdit { range: Range { start: Position { line: 0, character: 7, }, end: Position { line: 0, character: 12, }, }, new_text: "app", }, ), ), additional_text_edits: None, command: None, commit_characters: None, data: None, tags: None, }, CompletionItem { label: "test_helper", label_details: None, kind: Some( Module, ), detail: None, documentation: None, deprecated: None, preselect: None, sort_text: None, filter_text: None, insert_text: None, insert_text_format: None, insert_text_mode: None, text_edit: Some( Edit( TextEdit { range: Range { start: Position { line: 0, character: 7, }, end: Position { line: 0, character: 12, }, }, new_text: "test_helper", }, ), ), additional_text_edits: None, command: None, commit_characters: None, data: None, tags: None, }, ] ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_an_import_with_docs.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "import gleam\n\npub fn main() {\n 0\n}" --- import gle|am pub fn main() { 0 } ----- Completion content ----- dep kind: Module edits: [0:7-0:12]: "dep" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_an_unqualified_import.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nimport dep.{}\n\npub fn main() {\n 0\n}" --- import dep.{|} pub fn main() { 0 } ----- Completion content ----- Wibble kind: Class detail: Type sort: 4_Wibble edits: [1:12-1:12]: "type Wibble" myfun kind: Function detail: fn() -> Int sort: 4_myfun desc: dep edits: [1:12-1:12]: "myfun" wabble kind: Constant detail: String sort: 4_wabble desc: dep edits: [1:12-1:12]: "wabble" wibble kind: Constant detail: String sort: 4_wibble desc: dep edits: [1:12-1:12]: "wibble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_an_unqualified_import_already_imported.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nimport dep.{wibble,wabble,type Wibble}\n\npub fn main() {\n 0\n}" --- import dep.{|wibble,wabble,type Wibble} pub fn main() { 0 } ----- Completion content ----- myfun kind: Function detail: fn() -> Int sort: 4_myfun desc: dep edits: [1:12-1:12]: "myfun" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_an_unqualified_import_on_new_line.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nimport dep.{\n wibble,\n\n}\n\npub fn main() {\n 0\n}" --- import dep.{ wibble, | } pub fn main() { 0 } ----- Completion content ----- Wibble kind: Class detail: Type sort: 4_Wibble edits: [3:0-3:0]: "type Wibble" myfun kind: Function detail: fn() -> Int sort: 4_myfun desc: dep edits: [3:0-3:0]: "myfun" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_function_labels.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nfn wibble(wibble arg1: String, wobble arg2: String) {\n arg1 <> arg2\n}\n\nfn fun() { // completion inside parens below includes labels\n let wibble = wibble()\n}\n" --- fn wibble(wibble arg1: String, wobble arg2: String) { arg1 <> arg2 } fn fun() { // completion inside parens below includes labels let wibble = wibble(|) } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True fun kind: Function detail: fn() -> String sort: 3_fun desc: app edits: [6:22-6:22]: "fun" wibble kind: Function detail: fn(String, String) -> String sort: 3_wibble desc: app edits: [6:22-6:22]: "wibble" wibble: kind: Field detail: String sort: 1_wibble: wobble: kind: Field detail: String sort: 1_wobble: ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_imported_function_labels.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nimport dep\n\nfn fun() { // completion inside parens below includes labels\n let wibble = dep.wibble()\n}\n" --- import dep fn fun() { // completion inside parens below includes labels let wibble = dep.wibble(|) } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True dep.wibble kind: Function detail: fn(String, String) -> String sort: 4_dep.wibble desc: app edits: [4:26-4:26]: "dep.wibble" fun kind: Function detail: fn() -> String sort: 3_fun desc: app edits: [4:26-4:26]: "fun" wibble: kind: Field detail: String sort: 1_wibble: wobble: kind: Field detail: String sort: 1_wobble: ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_imported_record_fields.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nimport dep\n\nfn fun(wibble: dep.Wibble) {\n wibble.\n // ^ We should see wibble and wobble completions here!\n}\n" --- import dep fn fun(wibble: dep.Wibble) { wibble.| // ^ We should see wibble and wobble completions here! } ----- Completion content ----- wibble kind: Field detail: String sort: 2_wibble wobble kind: Field detail: Int sort: 2_wobble ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_imported_record_labels.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nimport dep\n\nfn fun() { // completion inside parens below includes labels\n let wibble = dep.Wibble()\n}\n" --- import dep fn fun() { // completion inside parens below includes labels let wibble = dep.Wibble(|) } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True dep.Wibble kind: Constructor detail: fn(String, Int) -> Wibble sort: 4_dep.Wibble desc: app edits: [4:26-4:26]: "dep.Wibble" fun kind: Function detail: fn() -> Wibble sort: 3_fun desc: app edits: [4:26-4:26]: "fun" wibble: kind: Field detail: String sort: 1_wibble: wobble: kind: Field detail: Int sort: 1_wobble: ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_internal_record_fields_inside_the_same_module.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\n@internal\npub type Wibble {\n Wibble(wibble: String, wobble: Int)\n}\n\nfn fun(wibble: Wibble) {\n wibble.\n // ^ We should see some completions here!\n}\n" --- @internal pub type Wibble { Wibble(wibble: String, wobble: Int) } fn fun(wibble: Wibble) { wibble.| // ^ We should see some completions here! } ----- Completion content ----- wibble kind: Field detail: String sort: 2_wibble wobble kind: Field detail: Int sort: 2_wobble ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_labels_in_record_update.snap.new ================================================ --- source: language-server/src/tests/completion.rs assertion_line: 2430 expression: "\npub type Wibble {\n Wibble(wibble: Int, wobble: Int, woo: Int)\n}\n\npub fn main() {\n Wibble(..todo, w)\n}\n\npub fn wibble() { todo }\n" snapshot_kind: text --- pub type Wibble { Wibble(wibble: Int, wobble: Int, woo: Int) } pub fn main() { Wibble(..todo, w|) } pub fn wibble() { todo } ----- Completion content ----- ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_outside_a_function.snap ================================================ --- source: compiler-core/src/language_server/tests/completion.rs expression: "\n\npub fn main() {\n 0\n}" --- | pub fn main() { 0 } ----- Completion content ----- ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_prelude_values.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main() {\n let my_bool = T\n}\n" --- pub fn main() { let my_bool = T| } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True main kind: Function detail: fn() -> a sort: 3_main desc: app edits: [2:16-2:17]: "main" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_private_record_access.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\ntype Wibble {\n Wibble(wibble: Int, wobble: Int)\n Wobble(wabble: Int, wobble: Int)\n}\n\nfn fun() {\n let wibble = Wibble(1, 2)\n wibble.wobble\n}\n" --- type Wibble { Wibble(wibble: Int, wobble: Int) Wobble(wabble: Int, wobble: Int) } fn fun() { let wibble = Wibble(1, 2) wibble.wobble| } ----- Completion content ----- wibble kind: Field detail: Int sort: 02_wibble wobble kind: Field detail: Int sort: 02_wobble ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_record_access.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub type Wibble {\n Wibble(wibble: Int, wobble: Int)\n Wobble(wabble: Int, wobble: Int)\n}\n\nfn fun() {\n let wibble = Wibble(1, 2)\n wibble.wobble\n}\n" --- pub type Wibble { Wibble(wibble: Int, wobble: Int) Wobble(wabble: Int, wobble: Int) } fn fun() { let wibble = Wibble(1, 2) wibble.wobble| } ----- Completion content ----- wibble kind: Field detail: Int sort: 02_wibble wobble kind: Field detail: Int sort: 02_wobble ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_record_access_known_variant.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\ntype Wibble {\n Wibble(a: Int, b: Int, c: Int, d: Int)\n Wobble(z: Bool)\n}\n\nfn fun(some_wibble: Wibble) {\n case some_wibble {\n Wibble(..) as w -> w.a\n Wobble(..) -> panic\n }\n}\n" --- type Wibble { Wibble(a: Int, b: Int, c: Int, d: Int) Wobble(z: Bool) } fn fun(some_wibble: Wibble) { case some_wibble { Wibble(..) as w -> w.a| Wobble(..) -> panic } } ----- Completion content ----- a kind: Field detail: Int sort: 02_a b kind: Field detail: Int sort: 02_b c kind: Field detail: Int sort: 02_c d kind: Field detail: Int sort: 02_d ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_record_access_unknown_variant.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\ntype Wibble {\n Wibble(a: Int, b: Int, c: Int, d: Int)\n Wobble(a: Int, z: Bool)\n}\n\nfn fun(some_wibble: Wibble) {\n some_wibble.a\n}\n" --- type Wibble { Wibble(a: Int, b: Int, c: Int, d: Int) Wobble(a: Int, z: Bool) } fn fun(some_wibble: Wibble) { some_wibble.a| } ----- Completion content ----- a kind: Field detail: Int sort: 02_a ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_record_labels.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub type Wibble {\n Wibble(wibble: String, wobble: Int)\n}\n\nfn fun() { // completion inside parens below includes labels\n let wibble = Wibble()\n}\n" --- pub type Wibble { Wibble(wibble: String, wobble: Int) } fn fun() { // completion inside parens below includes labels let wibble = Wibble(|) } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True Wibble kind: Constructor detail: fn(String, Int) -> Wibble sort: 3_Wibble desc: app edits: [6:22-6:22]: "Wibble" fun kind: Function detail: fn() -> Wibble sort: 3_fun desc: app edits: [6:22-6:22]: "fun" wibble: kind: Field detail: String sort: 1_wibble: wobble: kind: Field detail: Int sort: 1_wobble: ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__completions_for_type_import_completions_without_brackets.snap ================================================ --- source: language-server/src/tests/completion.rs expression: import dep. --- import dep.| ----- Completion content ----- Wibble kind: Class detail: Type sort: 4_Wibble edits: [0:11-0:11]: "{type Wibble}" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__constant.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nconst hello = 10\nconst world = he\n" --- const hello = 10 const world = he| ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True hello kind: Constant detail: Int sort: 3_hello desc: app edits: [2:14-2:16]: "hello" world kind: Constant detail: a sort: 3_world desc: app edits: [2:14-2:16]: "world" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__constant_with_many_options.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nimport wibble.{Wobble}\n\ntype Wibble {\n Wibble\n}\n\nconst pi = 3.14159\n\nfn some_function() {\n todo\n}\n\nconst my_constant = a\n" --- import wibble.{Wobble} type Wibble { Wibble } const pi = 3.14159 fn some_function() { todo } const my_constant = a| ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True Wibble kind: EnumMember detail: Wibble sort: 3_Wibble desc: app edits: [13:20-13:21]: "Wibble" Wobble kind: EnumMember detail: Wibble sort: 4_Wobble desc: app edits: [13:20-13:21]: "Wobble" my_constant kind: Constant detail: a sort: 3_my_constant desc: app edits: [13:20-13:21]: "my_constant" pi kind: Constant detail: Float sort: 3_pi desc: app edits: [13:20-13:21]: "pi" some_function kind: Function detail: fn() -> a sort: 3_some_function desc: app edits: [13:20-13:21]: "some_function" wibble.Wobble kind: EnumMember detail: Wibble sort: 4_wibble.Wobble desc: app edits: [13:20-13:21]: "wibble.Wobble" wibble.Wubble kind: EnumMember detail: Wibble sort: 4_wibble.Wubble desc: app edits: [13:20-13:21]: "wibble.Wubble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__constant_with_module_select.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nimport wibble\n\ntype Wibble {\n Wibble\n}\n\nconst pi = 3.14159\n\nfn some_function() {\n todo\n}\n\nconst my_constant = wibble.W\n" --- import wibble type Wibble { Wibble } const pi = 3.14159 fn some_function() { todo } const my_constant = wibble.W| ----- Completion content ----- wibble.Wobble kind: EnumMember detail: Wibble sort: 4_wibble.Wobble desc: app edits: [13:20-13:28]: "wibble.Wobble" wibble.Wubble kind: EnumMember detail: Wibble sort: 4_wibble.Wubble desc: app edits: [13:20-13:28]: "wibble.Wubble" wibble.some_constant kind: Constant detail: Int sort: 4_wibble.some_constant desc: app edits: [13:20-13:28]: "wibble.some_constant" wibble.some_function kind: Function detail: fn() -> a sort: 4_wibble.some_function desc: app edits: [13:20-13:28]: "wibble.some_function" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__do_not_show_completions_when_typing_a_number.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main() { 2 }\npub fn window_by_2() {}\npub fn to_base_32() {}\n" --- pub fn main() { 2| } pub fn window_by_2() {} pub fn to_base_32() {} ----- Completion content ----- ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__for_custom_type_definition.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub type Wibble {\n Wobble\n}" --- pub type Wibble { | Wobble } ----- Completion content ----- BitArray kind: Class detail: Type sort: 5_BitArray Bool kind: Class detail: Type sort: 5_Bool Float kind: Class detail: Type sort: 5_Float Int kind: Class detail: Type sort: 5_Int List kind: Class detail: Type sort: 5_List Nil kind: Class detail: Type sort: 5_Nil Result kind: Class detail: Type sort: 5_Result String kind: Class detail: Type sort: 5_String UtfCodepoint kind: Class detail: Type sort: 5_UtfCodepoint Wibble kind: Class detail: Type sort: 3_Wibble edits: [2:0-2:0]: "Wibble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__for_function_arguments.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn wibble(\n _: String,\n) -> Nil {\n Nil\n}\n" --- pub fn wibble( | _: String, ) -> Nil { Nil } ----- Completion content ----- BitArray kind: Class detail: Type sort: 5_BitArray Bool kind: Class detail: Type sort: 5_Bool Float kind: Class detail: Type sort: 5_Float Int kind: Class detail: Type sort: 5_Int List kind: Class detail: Type sort: 5_List Nil kind: Class detail: Type sort: 5_Nil Result kind: Class detail: Type sort: 5_Result String kind: Class detail: Type sort: 5_String UtfCodepoint kind: Class detail: Type sort: 5_UtfCodepoint ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__for_type_alias.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub type Wibble = Result(\n String,\n String\n)\n" --- pub type Wibble = Result( | String, String ) ----- Completion content ----- BitArray kind: Class detail: Type sort: 5_BitArray Bool kind: Class detail: Type sort: 5_Bool Float kind: Class detail: Type sort: 5_Float Int kind: Class detail: Type sort: 5_Int List kind: Class detail: Type sort: 5_List Nil kind: Class detail: Type sort: 5_Nil Result kind: Class detail: Type sort: 5_Result String kind: Class detail: Type sort: 5_String UtfCodepoint kind: Class detail: Type sort: 5_UtfCodepoint Wibble kind: Class detail: Type sort: 3_Wibble edits: [2:0-2:0]: "Wibble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__importable_adds_extra_new_line_if_import_exists_below_other_definitions.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nimport dep2\n" --- | import dep2 ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True dep.wobble kind: Function detail: fn() -> Nil sort: 6_dep.wobble desc: dep edits: [1:0-1:0]: "dep.wobble" [0:0-0:0]: "import dep\n\n" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__importable_adds_extra_new_line_if_no_imports.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "" --- ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True dep.wobble kind: Function detail: fn() -> Nil sort: 6_dep.wobble desc: dep edits: [1:0-1:0]: "dep.wobble" [0:0-0:0]: "import dep\n\n" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__importable_does_not_add_extra_new_line_if_imports_exist.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "" --- ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True dep.wobble kind: Function detail: fn() -> Nil sort: 6_dep.wobble desc: dep edits: [3:0-3:0]: "dep.wobble" [0:0-0:0]: "import dep\n" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__importable_does_not_add_extra_new_line_if_newline_exists.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "" --- ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True dep.wobble kind: Function detail: fn() -> Nil sort: 6_dep.wobble desc: dep edits: [2:0-2:0]: "dep.wobble" [0:0-0:0]: "import dep\n" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__importable_module_function.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\n" --- | ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True dep.wobble kind: Function detail: fn() -> Nil sort: 6_dep.wobble desc: dep edits: [1:0-1:0]: "dep.wobble" [0:0-0:0]: "import dep\n\n" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__importable_module_function_from_deep_module.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\n" --- | ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True dep.wobble kind: Function detail: fn() -> Nil sort: 6_dep.wobble desc: a/b/dep edits: [1:0-1:0]: "dep.wobble" [0:0-0:0]: "import a/b/dep\n\n" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__importable_module_function_with_existing_imports.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\n//// Some module comments\n// Some other whitespace\n\nimport dep2\n" --- | //// Some module comments // Some other whitespace import dep2 ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True dep.wobble kind: Function detail: fn() -> Nil sort: 6_dep.wobble desc: dep edits: [1:0-1:0]: "dep.wobble" [0:0-0:0]: "import dep\n\n" dep2.wobble kind: Function detail: fn() -> Nil sort: 4_dep2.wobble desc: app edits: [1:0-1:0]: "dep2.wobble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__importable_type.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\n\npub fn wibble(\n _: String,\n) -> Nil {\n Nil\n}\n" --- pub fn wibble( | _: String, ) -> Nil { Nil } ----- Completion content ----- BitArray kind: Class detail: Type sort: 5_BitArray Bool kind: Class detail: Type sort: 5_Bool Float kind: Class detail: Type sort: 5_Float Int kind: Class detail: Type sort: 5_Int List kind: Class detail: Type sort: 5_List Nil kind: Class detail: Type sort: 5_Nil Result kind: Class detail: Type sort: 5_Result String kind: Class detail: Type sort: 5_String UtfCodepoint kind: Class detail: Type sort: 5_UtfCodepoint dep.Zoo kind: Class detail: Type sort: 6_dep.Zoo edits: [3:0-3:0]: "dep.Zoo" [0:0-0:0]: "import dep\n" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__importable_type_from_deep_module.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\n\npub fn wibble(\n _: String,\n) -> Nil {\n Nil\n}\n" --- pub fn wibble( | _: String, ) -> Nil { Nil } ----- Completion content ----- BitArray kind: Class detail: Type sort: 5_BitArray Bool kind: Class detail: Type sort: 5_Bool Float kind: Class detail: Type sort: 5_Float Int kind: Class detail: Type sort: 5_Int List kind: Class detail: Type sort: 5_List Nil kind: Class detail: Type sort: 5_Nil Result kind: Class detail: Type sort: 5_Result String kind: Class detail: Type sort: 5_String UtfCodepoint kind: Class detail: Type sort: 5_UtfCodepoint dep.Zoo kind: Class detail: Type sort: 6_dep.Zoo edits: [3:0-3:0]: "dep.Zoo" [0:0-0:0]: "import a/b/dep\n" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__importable_type_with_existing_imports.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\n//// Some module comments\n// Some other whitespace\n\nimport dep2\n\npub fn wibble(\n _: String,\n) -> Nil {\n Nil\n}\n" --- //// Some module comments // Some other whitespace import dep2 pub fn wibble( | _: String, ) -> Nil { Nil } ----- Completion content ----- BitArray kind: Class detail: Type sort: 5_BitArray Bool kind: Class detail: Type sort: 5_Bool Float kind: Class detail: Type sort: 5_Float Int kind: Class detail: Type sort: 5_Int List kind: Class detail: Type sort: 5_List Nil kind: Class detail: Type sort: 5_Nil Result kind: Class detail: Type sort: 5_Result String kind: Class detail: Type sort: 5_String UtfCodepoint kind: Class detail: Type sort: 5_UtfCodepoint dep.Zoo kind: Class detail: Type sort: 6_dep.Zoo edits: [7:0-7:0]: "dep.Zoo" [4:0-4:0]: "import dep\n" dep2.Zoo kind: Class detail: Type sort: 4_dep2.Zoo edits: [7:0-7:0]: "dep2.Zoo" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__importable_type_with_existing_imports_at_top.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "import dep2\n\npub fn wibble(\n _: String,\n) -> Nil {\n Nil\n}\n" --- import dep2 pub fn wibble( | _: String, ) -> Nil { Nil } ----- Completion content ----- BitArray kind: Class detail: Type sort: 5_BitArray Bool kind: Class detail: Type sort: 5_Bool Float kind: Class detail: Type sort: 5_Float Int kind: Class detail: Type sort: 5_Int List kind: Class detail: Type sort: 5_List Nil kind: Class detail: Type sort: 5_Nil Result kind: Class detail: Type sort: 5_Result String kind: Class detail: Type sort: 5_String UtfCodepoint kind: Class detail: Type sort: 5_UtfCodepoint dep.Zoo kind: Class detail: Type sort: 6_dep.Zoo edits: [3:0-3:0]: "dep.Zoo" [0:0-0:0]: "import dep\n" dep2.Zoo kind: Class detail: Type sort: 4_dep2.Zoo edits: [3:0-3:0]: "dep2.Zoo" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__imported_module_function.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nimport dep\n" --- | import dep ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True dep.wobble kind: Function detail: fn() -> Nil sort: 4_dep.wobble desc: app edits: [1:0-1:0]: "dep.wobble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__imported_public_enum.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nimport dep\n" --- | import dep ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True dep.Left kind: EnumMember detail: Direction sort: 4_dep.Left desc: app edits: [1:0-1:0]: "dep.Left" dep.Right kind: EnumMember detail: Direction sort: 4_dep.Right desc: app edits: [1:0-1:0]: "dep.Right" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__imported_public_record.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nimport dep\n" --- | import dep ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True dep.Box kind: Constructor detail: fn(Int) -> Box sort: 4_dep.Box desc: app edits: [1:0-1:0]: "dep.Box" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__imported_type.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "import dep\n\npub fn wibble(\n _: String,\n) -> Nil {\n Nil\n}\n" --- import dep pub fn wibble( | _: String, ) -> Nil { Nil } ----- Completion content ----- BitArray kind: Class detail: Type sort: 5_BitArray Bool kind: Class detail: Type sort: 5_Bool Float kind: Class detail: Type sort: 5_Float Int kind: Class detail: Type sort: 5_Int List kind: Class detail: Type sort: 5_List Nil kind: Class detail: Type sort: 5_Nil Result kind: Class detail: Type sort: 5_Result String kind: Class detail: Type sort: 5_String UtfCodepoint kind: Class detail: Type sort: 5_UtfCodepoint dep.Zoo kind: Class detail: Type sort: 4_dep.Zoo edits: [3:0-3:0]: "dep.Zoo" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__imported_type_cursor_after_dot.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "import dep\n\npub fn wibble(\n _: dep.Zoo,\n) -> Nil {\n Nil\n}\n" --- import dep pub fn wibble( _: dep.Zoo|, ) -> Nil { Nil } ----- Completion content ----- dep.Zoo kind: Class detail: Type sort: 4_dep.Zoo ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__imported_type_cursor_after_dot_other_matching_modules.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "import dep\nimport dep2\n\npub fn wibble(\n _: dep.Zoo,\n) -> Nil {\n Nil\n}\n" --- import dep import dep2 pub fn wibble( _: dep.Zoo|, ) -> Nil { Nil } ----- Completion content ----- dep.Zoo kind: Class detail: Type sort: 4_dep.Zoo ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__imported_type_cursor_after_dot_other_modules.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "import dep\n\npub fn wibble(\n _: dep.Zoo,\n) -> Nil {\n Nil\n}\n" --- import dep pub fn wibble( _: dep.Zoo|, ) -> Nil { Nil } ----- Completion content ----- dep.Zoo kind: Class detail: Type sort: 4_dep.Zoo ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__imported_type_cursor_mid_phrase_other_modules.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "import dep\n\npub fn wibble(\n _: dep.Zoo,\n) -> Nil {\n Nil\n}\n" --- import dep pub fn wibble( _: dep|.Zoo, ) -> Nil { Nil } ----- Completion content ----- dep.Zoo kind: Class detail: Type sort: 4_dep.Zoo ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__imported_unqualified_module_function.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nimport dep.{wobble}\n" --- | import dep.{wobble} ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True dep.wobble kind: Function detail: fn() -> Nil sort: 4_dep.wobble desc: app edits: [1:0-1:0]: "dep.wobble" wobble kind: Function detail: fn() -> Nil sort: 4_wobble desc: app edits: [1:0-1:0]: "wobble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__imported_unqualified_public_enum.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nimport dep.{Left}\n" --- | import dep.{Left} ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Left kind: EnumMember detail: Direction sort: 4_Left desc: app edits: [1:0-1:0]: "Left" Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True dep.Left kind: EnumMember detail: Direction sort: 4_dep.Left desc: app edits: [1:0-1:0]: "dep.Left" dep.Right kind: EnumMember detail: Direction sort: 4_dep.Right desc: app edits: [1:0-1:0]: "dep.Right" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__imported_unqualified_public_record.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nimport dep.{Box}\n" --- | import dep.{Box} ----- Completion content ----- Box kind: Constructor detail: fn(Int) -> Box sort: 4_Box desc: app edits: [1:0-1:0]: "Box" Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True dep.Box kind: Constructor detail: fn(Int) -> Box sort: 4_dep.Box desc: app edits: [1:0-1:0]: "dep.Box" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__in_custom_type_definition.snap ================================================ --- source: language-server/src/tests/completion.rs expression: import dep --- |import dep ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__internal_modules_from_same_package_are_included.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "import gleam\n\npub fn main() {\n 0\n}" --- |import gleam pub fn main() { 0 } ----- Completion content ----- app/internal kind: Module edits: [0:7-0:12]: "app/internal" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__internal_types_from_a_dependency_are_ignored.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "import dep\n\npub fn wibble(\n _: String,\n) -> Nil {\n Nil\n}" --- import dep pub fn wibble( | _: String, ) -> Nil { Nil } ----- Completion content ----- BitArray kind: Class detail: Type sort: 5_BitArray Bool kind: Class detail: Type sort: 5_Bool Float kind: Class detail: Type sort: 5_Float Int kind: Class detail: Type sort: 5_Int List kind: Class detail: Type sort: 5_List Nil kind: Class detail: Type sort: 5_Nil Result kind: Class detail: Type sort: 5_Result String kind: Class detail: Type sort: 5_String UtfCodepoint kind: Class detail: Type sort: 5_UtfCodepoint ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__internal_types_from_root_package_are_in_the_completions.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "import dep\n\npub fn wibble(\n _: String,\n) -> Nil {\n Nil\n}" --- import dep pub fn wibble( | _: String, ) -> Nil { Nil } ----- Completion content ----- BitArray kind: Class detail: Type sort: 5_BitArray Bool kind: Class detail: Type sort: 5_Bool Float kind: Class detail: Type sort: 5_Float Int kind: Class detail: Type sort: 5_Int List kind: Class detail: Type sort: 5_List Nil kind: Class detail: Type sort: 5_Nil Result kind: Class detail: Type sort: 5_Result String kind: Class detail: Type sort: 5_String UtfCodepoint kind: Class detail: Type sort: 5_UtfCodepoint dep.Alias kind: Class detail: Type sort: 4_dep.Alias edits: [3:0-3:0]: "dep.Alias" dep.AnotherType kind: Class detail: Type sort: 4_dep.AnotherType edits: [3:0-3:0]: "dep.AnotherType" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__internal_types_from_the_same_module_are_in_the_completions.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\n@internal pub type Alias = Result(Int, String)\n@internal pub type AnotherType {\n Wibble\n}\n" --- @internal pub type Alias = Result(Int, String) @internal pub type AnotherType { | Wibble } ----- Completion content ----- Alias kind: Class detail: Type sort: 3_Alias edits: [3:0-3:0]: "Alias" AnotherType kind: Class detail: Type sort: 3_AnotherType edits: [3:0-3:0]: "AnotherType" BitArray kind: Class detail: Type sort: 5_BitArray Bool kind: Class detail: Type sort: 5_Bool Float kind: Class detail: Type sort: 5_Float Int kind: Class detail: Type sort: 5_Int List kind: Class detail: Type sort: 5_List Nil kind: Class detail: Type sort: 5_Nil Result kind: Class detail: Type sort: 5_Result String kind: Class detail: Type sort: 5_String UtfCodepoint kind: Class detail: Type sort: 5_UtfCodepoint ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__internal_values_from_a_dependency_are_ignored.snap ================================================ --- source: language-server/src/tests/completion.rs expression: import dep --- |import dep ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__internal_values_from_root_package_are_in_the_completions.snap ================================================ --- source: language-server/src/tests/completion.rs expression: import dep --- |import dep ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True dep.Wobble kind: EnumMember detail: Wibble sort: 4_dep.Wobble desc: app edits: [1:0-1:0]: "dep.Wobble" dep.main kind: Function detail: fn() -> Int sort: 4_dep.main desc: app edits: [1:0-1:0]: "dep.main" dep.random_float kind: Function detail: fn() -> Float sort: 4_dep.random_float desc: app edits: [1:0-1:0]: "dep.random_float" dep.wibble kind: Constant detail: Int sort: 4_dep.wibble desc: app edits: [1:0-1:0]: "dep.wibble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__internal_values_from_the_same_module_are_in_the_completions.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\n@external(erlang, \"rand\", \"uniform\")\n@internal pub fn random_float() -> Float\n@internal pub fn main() { 0 }\n@internal pub type Wibble { Wobble }\n@internal pub const wibble = 1\n" --- | @external(erlang, "rand", "uniform") @internal pub fn random_float() -> Float @internal pub fn main() { 0 } @internal pub type Wibble { Wobble } @internal pub const wibble = 1 ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True Wobble kind: EnumMember detail: Wibble sort: 3_Wobble desc: app edits: [1:0-1:0]: "Wobble" main kind: Function detail: fn() -> Int sort: 3_main desc: app edits: [1:0-1:0]: "main" random_float kind: Function detail: fn() -> Float sort: 3_random_float desc: app edits: [1:0-1:0]: "random_float" wibble kind: Constant detail: Int sort: 3_wibble desc: app edits: [1:0-1:0]: "wibble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__labelled_arguments.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub type Wibble {\n Wibble(wibble: Int, wobble: Float)\n}\n\npub fn main() {\n Wibble(w)\n}\n" --- pub type Wibble { Wibble(wibble: Int, wobble: Float) } pub fn main() { Wibble(w|) } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True Wibble kind: Constructor detail: fn(Int, Float) -> Wibble sort: 3_Wibble desc: app edits: [6:9-6:10]: "Wibble" main kind: Function detail: fn() -> Wibble sort: 3_main desc: app edits: [6:9-6:10]: "main" wibble: kind: Field detail: Int sort: 1_wibble: wobble: kind: Field detail: Float sort: 1_wobble: ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__labelled_arguments_after_label.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub type Wibble {\n Wibble(wibble: Int, wobble: Float)\n}\n\npub fn main() {\n Wibble(wibble: w)\n}\n" --- pub type Wibble { Wibble(wibble: Int, wobble: Float) } pub fn main() { Wibble(wibble: w|) } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True Wibble kind: Constructor detail: fn(Int, Float) -> Wibble sort: 3_Wibble desc: app edits: [6:17-6:18]: "Wibble" main kind: Function detail: fn() -> Wibble sort: 3_main desc: app edits: [6:17-6:18]: "main" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__labelled_arguments_from_different_module.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nimport wibble\n\npub fn main() {\n wibble.divide(10, b)\n}\n" --- import wibble pub fn main() { wibble.divide(10, b|) } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True by: kind: Field detail: Int sort: 1_by: main kind: Function detail: fn() -> Int sort: 3_main desc: app edits: [4:20-4:21]: "main" wibble.divide kind: Function detail: fn(Int, Int) -> Int sort: 4_wibble.divide desc: app edits: [4:20-4:21]: "wibble.divide" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__labelled_arguments_function_call.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn divide(x: Int, by y: Int) { x / y }\n\npub fn main() {\n divide(10, b)\n}\n" --- pub fn divide(x: Int, by y: Int) { x / y } pub fn main() { divide(10, b|) } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True by: kind: Field detail: Int sort: 1_by: divide kind: Function detail: fn(Int, Int) -> Int sort: 3_divide desc: app edits: [4:13-4:14]: "divide" main kind: Function detail: fn() -> Int sort: 3_main desc: app edits: [4:13-4:14]: "main" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__labelled_arguments_with_existing_label.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub type Wibble {\n Wibble(wibble: Int, wobble: Float)\n}\n\npub fn main() {\n Wibble(wibble: 10, w)\n}\n" --- pub type Wibble { Wibble(wibble: Int, wobble: Float) } pub fn main() { Wibble(wibble: 10, w|) } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True Wibble kind: Constructor detail: fn(Int, Float) -> Wibble sort: 3_Wibble desc: app edits: [6:21-6:22]: "Wibble" main kind: Function detail: fn() -> Wibble sort: 3_main desc: app edits: [6:21-6:22]: "main" wobble: kind: Field detail: Float sort: 1_wobble: ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__local_private_type.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\ntype Zoo = Int\n\npub fn wibble(\n x: String,\n) -> String {\n \"ok\"\n}\n" --- type Zoo = Int pub fn wibble( | x: String, ) -> String { "ok" } ----- Completion content ----- BitArray kind: Class detail: Type sort: 5_BitArray Bool kind: Class detail: Type sort: 5_Bool Float kind: Class detail: Type sort: 5_Float Int kind: Class detail: Type sort: 5_Int List kind: Class detail: Type sort: 5_List Nil kind: Class detail: Type sort: 5_Nil Result kind: Class detail: Type sort: 5_Result String kind: Class detail: Type sort: 5_String UtfCodepoint kind: Class detail: Type sort: 5_UtfCodepoint Zoo kind: Class detail: Type sort: 3_Zoo edits: [4:0-4:0]: "Zoo" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__local_public_enum.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub type Direction {\n Left\n Right\n}\n" --- | pub type Direction { Left Right } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Left kind: EnumMember detail: Direction sort: 3_Left desc: app edits: [1:0-1:0]: "Left" Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok Right kind: EnumMember detail: Direction sort: 3_Right desc: app edits: [1:0-1:0]: "Right" True kind: EnumMember detail: gleam sort: 5_True ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__local_public_enum_with_documentation.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub type Direction {\n /// Hello\n Left\n /// Goodbye\n Right\n}\n" --- | pub type Direction { /// Hello Left /// Goodbye Right } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Left kind: EnumMember detail: Direction sort: 3_Left desc: app docs: " Hello\n" edits: [1:0-1:0]: "Left" Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok Right kind: EnumMember detail: Direction sort: 3_Right desc: app docs: " Goodbye\n" edits: [1:0-1:0]: "Right" True kind: EnumMember detail: gleam sort: 5_True ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__local_public_function.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main() {\n 0\n}" --- | pub fn main() { 0 } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True main kind: Function detail: fn() -> Int sort: 3_main desc: app edits: [1:0-1:0]: "main" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__local_public_function_with_documentation.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\n/// Hello\npub fn main() {\n 0\n}" --- | /// Hello pub fn main() { 0 } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True main kind: Function detail: fn() -> Int sort: 3_main desc: app docs: " Hello\n" edits: [1:0-1:0]: "main" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__local_public_record.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub type Box {\n/// Hello\n Box(Int, Int, Float)\n}\n" --- | pub type Box { /// Hello Box(Int, Int, Float) } ----- Completion content ----- Box kind: Constructor detail: fn(Int, Int, Float) -> Box sort: 3_Box desc: app docs: " Hello\n" edits: [1:0-1:0]: "Box" Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__local_public_record_with_documentation.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub type Box {\n Box(Int, Int, Float)\n}\n" --- | pub type Box { Box(Int, Int, Float) } ----- Completion content ----- Box kind: Constructor detail: fn(Int, Int, Float) -> Box sort: 3_Box desc: app edits: [1:0-1:0]: "Box" Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__local_variable.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main(wibble: Int) {\n let wobble = 1\n w\n\n let wabble = 2\n}\n" --- pub fn main(wibble: Int) { let wobble = 1 w| let wabble = 2 } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True main kind: Function detail: fn(Int) -> Int sort: 3_main desc: app edits: [3:2-3:3]: "main" wibble kind: Variable detail: Int sort: 3_wibble desc: app docs: "A locally defined variable." edits: [3:2-3:3]: "wibble" wobble kind: Variable detail: Int sort: 3_wobble desc: app docs: "A locally defined variable." edits: [3:2-3:3]: "wobble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__local_variable_anonymous_function.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main() {\n let add_one = fn(wibble: Int) { wibble + 1 }\n add_one(1)\n}\n" --- pub fn main() { let add_one = fn(wibble: Int) { wibble| + 1 } add_one(1) } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True main kind: Function detail: fn() -> Int sort: 03_main desc: app edits: [2:34-2:40]: "main" wibble kind: Variable detail: Int sort: 03_wibble desc: app docs: "A locally defined variable." edits: [2:34-2:40]: "wibble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__local_variable_as.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nfn wibble() {\n let b as c = 5\n\n}\n" --- fn wibble() { let b as c = 5 | } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True b kind: Variable detail: Int sort: 3_b desc: app docs: "A locally defined variable." edits: [3:0-3:0]: "b" c kind: Variable detail: Int sort: 3_c desc: app docs: "A locally defined variable." edits: [3:0-3:0]: "c" wibble kind: Function detail: fn() -> Int sort: 3_wibble desc: app edits: [3:0-3:0]: "wibble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__local_variable_bit_array.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nfn wibble() {\n let assert <> as i = <<1:1>>\n\n}\n" --- fn wibble() { let assert <> as i = <<1:1>> | } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True h kind: Variable detail: Int sort: 3_h desc: app docs: "A locally defined variable." edits: [3:0-3:0]: "h" i kind: Variable detail: BitArray sort: 3_i desc: app docs: "A locally defined variable." edits: [3:0-3:0]: "i" wibble kind: Function detail: fn() -> BitArray sort: 3_wibble desc: app edits: [3:0-3:0]: "wibble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__local_variable_case_expression.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main() {\n case True {\n True as wibble -> { todo }\n False -> { todo }\n }\n}\n" --- pub fn main() { case True { True as wibble -> { t|odo } False -> { todo } } } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True main kind: Function detail: fn() -> a sort: 3_main desc: app edits: [3:24-3:25]: "main" todo kind: Keyword sort: 00_todo wibble kind: Variable detail: Bool sort: 3_wibble desc: app docs: "A locally defined variable." edits: [3:24-3:28]: "wibble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__local_variable_function_call.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nfn add_one(wibble: Int) -> Int {\n wibble + 1\n}\n\npub fn main() {\n let wobble = 1\n add_one(wobble)\n}\n" --- fn add_one(wibble: Int) -> Int { wibble + 1 } pub fn main() { let wobble = 1 add_one(wobble|) } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True add_one kind: Function detail: fn(Int) -> Int sort: 3_add_one desc: app edits: [7:10-7:16]: "add_one" main kind: Function detail: fn() -> Int sort: 3_main desc: app edits: [7:10-7:16]: "main" wobble kind: Variable detail: Int sort: 3_wobble desc: app docs: "A locally defined variable." edits: [7:10-7:16]: "wobble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__local_variable_ignore_anonymous_function_args.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main() {\n let add_one = fn(wibble: Int) { wibble + 1 }\n let wobble = 1\n w\n}\n" --- pub fn main() { let add_one = fn(wibble: Int) { wibble + 1 } let wobble = 1 w| } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True add_one kind: Variable detail: fn(Int) -> Int sort: 3_add_one desc: app docs: "A locally defined variable." edits: [4:2-4:3]: "add_one" main kind: Function detail: fn() -> a sort: 3_main desc: app edits: [4:2-4:3]: "main" wobble kind: Variable detail: Int sort: 3_wobble desc: app docs: "A locally defined variable." edits: [4:2-4:3]: "wobble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__local_variable_ignore_anonymous_function_args_nested.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main() {\n let add_one = fn(wibble: Int) {\n let wabble = 1\n let add_two = fn(wobble: Int) { wobble + 2 }\n wibble + add_two(1)\n }\n add_one(1)\n}\n" --- pub fn main() { let add_one = fn(wibble: Int) { let wabble = 1 let add_two = fn(wobble: Int) { wobble + 2 } wibble| + add_two(1) } add_one(1) } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True add_two kind: Variable detail: fn(Int) -> Int sort: 03_add_two desc: app docs: "A locally defined variable." edits: [5:4-5:10]: "add_two" main kind: Function detail: fn() -> Int sort: 03_main desc: app edits: [5:4-5:10]: "main" wabble kind: Variable detail: Int sort: 03_wabble desc: app docs: "A locally defined variable." edits: [5:4-5:10]: "wabble" wibble kind: Variable detail: Int sort: 03_wibble desc: app docs: "A locally defined variable." edits: [5:4-5:10]: "wibble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__local_variable_ignore_anonymous_function_returned.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main() {\n fn(wibble: Int) {\n let wabble = 1\n let add_two = fn(wobble: Int) { wobble + 2 }\n wibble + add_two(1)\n }\n}\n" --- pub fn main() { fn(wibble: Int) { let wabble = 1 let add_two = fn(wobble: Int) { wobble + 2 } wibble| + add_two(1) } } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True add_two kind: Variable detail: fn(Int) -> Int sort: 03_add_two desc: app docs: "A locally defined variable." edits: [5:4-5:10]: "add_two" main kind: Function detail: fn() -> fn(Int) -> Int sort: 3_main desc: app edits: [5:4-5:10]: "main" wabble kind: Variable detail: Int sort: 03_wabble desc: app docs: "A locally defined variable." edits: [5:4-5:10]: "wabble" wibble kind: Variable detail: Int sort: 03_wibble desc: app docs: "A locally defined variable." edits: [5:4-5:10]: "wibble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__local_variable_ignore_within_function.snap ================================================ --- source: compiler-core/src/language_server/tests/completion.rs expression: "\nfn main(a, b, z) {\n Nil\n}\n" --- fn main(a, b, |z) { Nil } ----- Completion content ----- ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__local_variable_ignored.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nfn wibble() {\n let a = 1\n let _b = 2\n\n}\n" --- fn wibble() { let a = 1 let _b = 2 | } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True a kind: Variable detail: Int sort: 3_a desc: app docs: "A locally defined variable." edits: [4:0-4:0]: "a" wibble kind: Function detail: fn() -> Int sort: 3_wibble desc: app edits: [4:0-4:0]: "wibble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__local_variable_inside_nested_exprs.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\ntype Wibble { Wobble(List(#(Bool))) }\nfn wibble() {\n Wobble([#(!{\n let wibble = True\n wibble\n })])\n todo\n}\n" --- type Wibble { Wobble(List(#(Bool))) } fn wibble() { Wobble([#(!{ let wibble = True wib|ble })]) todo } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 05_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 05_True Wobble kind: Constructor detail: fn(List(#(Bool))) -> Wibble sort: 3_Wobble desc: app edits: [5:4-5:7]: "Wobble" wibble kind: Variable detail: Bool sort: 03_wibble desc: app docs: "A locally defined variable." edits: [5:4-5:10]: "wibble" wibble kind: Function detail: fn() -> a sort: 3_wibble desc: app ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__local_variable_nested_anonymous_function.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main() {\n let add_one = fn(wibble: Int) {\n let wabble = 1\n let add_two = fn(wobble: Int) { wobble + 2 }\n wibble + add_two(1)\n }\n add_one(1)\n}\n" --- pub fn main() { let add_one = fn(wibble: Int) { let wabble = 1 let add_two = fn(wobble: Int) { wobble| + 2 } wibble + add_two(1) } add_one(1) } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True main kind: Function detail: fn() -> Int sort: 03_main desc: app edits: [4:36-4:42]: "main" wabble kind: Variable detail: Int sort: 03_wabble desc: app docs: "A locally defined variable." edits: [4:36-4:42]: "wabble" wibble kind: Variable detail: Int sort: 03_wibble desc: app docs: "A locally defined variable." edits: [4:36-4:42]: "wibble" wobble kind: Variable detail: Int sort: 03_wobble desc: app docs: "A locally defined variable." edits: [4:36-4:42]: "wobble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__local_variable_pipe.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main() {\n let add_one = fn(wibble: Int) { wibble + 1 }\n let wobble = 1\n wobble |> add_one\n}\n" --- pub fn main() { let add_one = fn(wibble: Int) { wibble + 1 } let wobble = 1 wobble |> add_one| } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True add_one kind: Variable detail: fn(Int) -> Int sort: 03_add_one desc: app docs: "A locally defined variable." edits: [4:12-4:19]: "add_one" main kind: Function detail: fn() -> Int sort: 3_main desc: app edits: [4:12-4:19]: "main" wobble kind: Variable detail: Int sort: 3_wobble desc: app docs: "A locally defined variable." edits: [4:12-4:19]: "wobble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__local_variable_pipe_with_args.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main() {\n let add_one = fn(wibble: Int, wobble: Int) { wibble + wobble }\n let wobble = 1\n let wibble = 2\n wobble |> add_one(1, wibble)\n}\n" --- pub fn main() { let add_one = fn(wibble: Int, wobble: Int) { wibble + wobble } let wobble = 1 let wibble = 2 wobble |> add_one(1, wibble|) } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True add_one kind: Variable detail: fn(Int, Int) -> Int sort: 3_add_one desc: app docs: "A locally defined variable." edits: [5:23-5:29]: "add_one" main kind: Function detail: fn() -> a sort: 3_main desc: app edits: [5:23-5:29]: "main" wibble kind: Variable detail: Int sort: 3_wibble desc: app docs: "A locally defined variable." edits: [5:23-5:29]: "wibble" wobble kind: Variable detail: Int sort: 3_wobble desc: app docs: "A locally defined variable." edits: [5:23-5:29]: "wobble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__local_variable_string.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nfn wibble() {\n let assert \"a\" <> j = \"ab\"\n\n}\n" --- fn wibble() { let assert "a" <> j = "ab" | } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True j kind: Variable detail: String sort: 3_j desc: app docs: "A locally defined variable." edits: [3:0-3:0]: "j" wibble kind: Function detail: fn() -> String sort: 3_wibble desc: app edits: [3:0-3:0]: "wibble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__local_variable_tuple.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nfn wibble() {\n let assert #([d, e] as f, g) = #([0, 1], 2)\n\n}\n" --- fn wibble() { let assert #([d, e] as f, g) = #([0, 1], 2) | } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True d kind: Variable detail: Int sort: 3_d desc: app docs: "A locally defined variable." edits: [3:0-3:0]: "d" e kind: Variable detail: Int sort: 3_e desc: app docs: "A locally defined variable." edits: [3:0-3:0]: "e" f kind: Variable detail: List(Int) sort: 3_f desc: app docs: "A locally defined variable." edits: [3:0-3:0]: "f" g kind: Variable detail: Int sort: 3_g desc: app docs: "A locally defined variable." edits: [3:0-3:0]: "g" wibble kind: Function detail: fn() -> #(List(Int), Int) sort: 3_wibble desc: app edits: [3:0-3:0]: "wibble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__no_completions_for_imported_internal_record_fields.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nimport dep\n\nfn fun(wibble: dep.Wibble) {\n wibble.\n // ^ We should see no completions here!\n}\n" --- import dep fn fun(wibble: dep.Wibble) { wibble.| // ^ We should see no completions here! } ----- Completion content ----- ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__no_label_completions_in_nested_expression.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub type Wibble {\n Wibble(wibble: Int, wobble: Float)\n}\n\npub fn main() {\n Wibble([w])\n}\n" --- pub type Wibble { Wibble(wibble: Int, wobble: Float) } pub fn main() { Wibble([w|]) } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True Wibble kind: Constructor detail: fn(Int, Float) -> Wibble sort: 3_Wibble desc: app edits: [6:10-6:11]: "Wibble" main kind: Function detail: fn() -> Wibble sort: 3_main desc: app edits: [6:10-6:11]: "main" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__no_variable_completions_after_anonymous_function_scope.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main() {\n fn() {\n let something = 10\n }\n s\n}\n" --- pub fn main() { fn() { let something = 10 } s| } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True main kind: Function detail: fn() -> a sort: 3_main desc: app edits: [5:2-5:3]: "main" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__no_variable_completions_after_block_scope.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main() {\n {\n let something = 10\n }\n s\n}\n" --- pub fn main() { { let something = 10 } s| } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True main kind: Function detail: fn() -> a sort: 3_main desc: app edits: [5:2-5:3]: "main" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__no_variable_completions_after_case_clause_scope.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main() {\n case todo {\n something -> Nil\n something_else -> s\n }\n s\n}\n" --- pub fn main() { case todo { something -> Nil something_else -> s| } s } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 05_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True main kind: Function detail: fn() -> a sort: 3_main desc: app edits: [4:22-4:23]: "main" something_else kind: Variable detail: a sort: 03_something_else desc: app docs: "A locally defined variable." edits: [4:22-4:23]: "something_else" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__no_variable_completions_after_case_scope.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main() {\n case todo {\n something -> Nil\n }\n s\n}\n" --- pub fn main() { case todo { something -> Nil } s| } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True main kind: Function detail: fn() -> a sort: 3_main desc: app edits: [5:2-5:3]: "main" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__no_variable_completions_before_case_clause.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main() {\n case todo {\n something -> s\n something_else -> Nil\n }\n s\n}\n" --- pub fn main() { case todo { something -> s| something_else -> Nil } s } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 05_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True main kind: Function detail: fn() -> a sort: 3_main desc: app edits: [3:17-3:18]: "main" something kind: Variable detail: a sort: 03_something desc: app docs: "A locally defined variable." edits: [3:17-3:18]: "something" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__no_variable_completions_before_declaration_in_anonymous_function.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main() {\n fn() {\n s\n let something = 10\n }\n}\n" --- pub fn main() { fn() { s| let something = 10 } } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True main kind: Function detail: fn() -> fn() -> Int sort: 3_main desc: app edits: [3:4-3:5]: "main" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__no_variable_completions_before_declaration_in_block.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main() {\n {\n s\n let something = 10\n }\n}\n" --- pub fn main() { { s| let something = 10 } } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True main kind: Function detail: fn() -> Int sort: 3_main desc: app edits: [3:4-3:5]: "main" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__opaque_type.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub opaque type Wibble {\n Wobble\n}\n" --- | pub opaque type Wibble { Wobble } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True Wobble kind: EnumMember detail: Wibble sort: 3_Wobble desc: app edits: [1:0-1:0]: "Wobble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__prefer_function_which_returns_expected_generic_type.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main() -> Result(Int, Nil) {\n let result = Ok(12)\n let result2 = Error(True)\n r\n}\n" --- pub fn main() -> Result(Int, Nil) { let result = Ok(12) let result2 = Error(True) r| } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 05_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 05_Ok True kind: EnumMember detail: gleam sort: 5_True main kind: Function detail: fn() -> Result(Int, Nil) sort: 03_main desc: app edits: [4:2-4:3]: "main" result kind: Variable detail: Result(Int, a) sort: 03_result desc: app docs: "A locally defined variable." edits: [4:2-4:3]: "result" result2 kind: Variable detail: Result(a, Bool) sort: 3_result2 desc: app docs: "A locally defined variable." edits: [4:2-4:3]: "result2" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__prefer_function_which_returns_expected_type.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main() -> Int {\n a\n}\n\nfn add(a, b) { a + b }\nfn sub(a, b) { a - b }\nfn addf(a, b) { a +. b }\n" --- pub fn main() -> Int { a| } fn add(a, b) { a + b } fn sub(a, b) { a - b } fn addf(a, b) { a +. b } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True add kind: Function detail: fn(Int, Int) -> Int sort: 03_add desc: app edits: [2:2-2:3]: "add" addf kind: Function detail: fn(Float, Float) -> Float sort: 3_addf desc: app edits: [2:2-2:3]: "addf" main kind: Function detail: fn() -> Int sort: 03_main desc: app edits: [2:2-2:3]: "main" sub kind: Function detail: fn(Int, Int) -> Int sort: 03_sub desc: app edits: [2:2-2:3]: "sub" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__prefer_values_matching_expected_type.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main() -> Bool {\n let wibble = 123\n let wubble = True\n let Wobble = 1.5\n w\n}\n" --- pub fn main() -> Bool { let wibble = 123 let wubble = True let Wobble = 1.5 w| } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 05_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 05_True main kind: Function detail: fn() -> Bool sort: 03_main desc: app edits: [5:2-5:3]: "main" wibble kind: Variable detail: Int sort: 3_wibble desc: app docs: "A locally defined variable." edits: [5:2-5:3]: "wibble" wubble kind: Variable detail: Bool sort: 03_wubble desc: app docs: "A locally defined variable." edits: [5:2-5:3]: "wubble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__private_function.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\nfn private() {\n 1\n}\n" --- | fn private() { 1 } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True private kind: Function detail: fn() -> Int sort: 3_private desc: app edits: [1:0-1:0]: "private" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__private_function_in_dep.snap ================================================ --- source: language-server/src/tests/completion.rs expression: import dep --- |import dep ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__private_type.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\ntype Wibble {\n Wobble\n}\n" --- | type Wibble { Wobble } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True Wobble kind: EnumMember detail: Wibble sort: 3_Wobble desc: app edits: [1:0-1:0]: "Wobble" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__private_type_in_dep.snap ================================================ --- source: language-server/src/tests/completion.rs expression: import dep --- |import dep ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__unqualified_imported_type.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "import dep.{type Zoo}\n\npub fn wibble(\n _: String,\n) -> Nil {\n Nil\n}\n" --- import dep.{type Zoo} pub fn wibble( | _: String, ) -> Nil { Nil } ----- Completion content ----- BitArray kind: Class detail: Type sort: 5_BitArray Bool kind: Class detail: Type sort: 5_Bool Float kind: Class detail: Type sort: 5_Float Int kind: Class detail: Type sort: 5_Int List kind: Class detail: Type sort: 5_List Nil kind: Class detail: Type sort: 5_Nil Result kind: Class detail: Type sort: 5_Result String kind: Class detail: Type sort: 5_String UtfCodepoint kind: Class detail: Type sort: 5_UtfCodepoint Zoo kind: Class detail: Type sort: 4_Zoo edits: [3:0-3:0]: "Zoo" dep.Zoo kind: Class detail: Type sort: 4_dep.Zoo edits: [3:0-3:0]: "dep.Zoo" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__completion__variable_shadowing.snap ================================================ --- source: language-server/src/tests/completion.rs expression: "\npub fn main() {\n let x = 1\n let x = [1, 2]\n\n}\n" --- pub fn main() { let x = 1 let x = [1, 2] | } ----- Completion content ----- Error kind: Constructor detail: gleam sort: 5_Error False kind: EnumMember detail: gleam sort: 5_False Nil kind: EnumMember detail: gleam sort: 5_Nil Ok kind: Constructor detail: gleam sort: 5_Ok True kind: EnumMember detail: gleam sort: 5_True main kind: Function detail: fn() -> List(Int) sort: 3_main desc: app edits: [4:0-4:0]: "main" x kind: Variable detail: List(Int) sort: 3_x desc: app docs: "A locally defined variable." edits: [4:0-4:0]: "x" ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_constant.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` const value = 25 const my_constant = value ↑ ----- Jumped to `src/app.gleam` const value = 25 ↑▔▔▔▔▔▔▔▔▔▔ const my_constant = value ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_constant_imported_record.snap ================================================ --- source: language-server/src/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` import wibble const my_constant = wibble.Wibble(10) ↑ ----- Jumped to `build/packages/hex/src/wibble.gleam` pub type Wibble { Wibble(Int) } ↑▔▔▔▔▔▔▔▔▔▔ ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_constant_record.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` type Wibble { Wibble(Int) } const wibble = Wibble(10) ↑ ----- Jumped to `src/app.gleam` type Wibble { Wibble(Int) ↑▔▔▔▔▔▔▔▔▔▔ } const wibble = Wibble(10) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_deep_type_in_module.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` import example_module fn make_var() -> example_module.Wabble(example_module.Wibble(example_module.Wobble)) { ↑ example_module.Wabble(example_module.Wibble(example_module.Wobble(1))) } ----- Jumped to `build/packages/hex/src/example_module.gleam` pub type Wobble { ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Wobble(Int) } pub type Wibble(a) { Wibble(a) } pub type Wabble(a) { Wabble(a) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_external_module_constants.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` import example_module fn main() { example_module.my_num ↑ } ----- Jumped to `build/packages/hex/src/example_module.gleam` pub const my_num = 1 ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_external_module_function_calls.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` import example_module fn main() { example_module.my_fn ↑ } ----- Jumped to `build/packages/hex/src/example_module.gleam` pub fn my_fn() { Nil } ↑▔▔▔▔▔▔▔▔▔▔▔▔▔ ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_external_module_records.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` import example_module fn main() { example_module.Var1(1) ↑ } ----- Jumped to `build/packages/hex/src/example_module.gleam` pub type Rec { Var1(Int) ↑▔▔▔▔▔▔▔▔ Var2(Int, Int) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_from_alternative_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` type Wibble { Wibble Wobble } fn warble(wibble: Wibble) { case wibble { Wibble | Wobble -> 0 ↑ } } ----- Jumped to `src/app.gleam` type Wibble { Wibble Wobble ↑▔▔▔▔▔ } fn warble(wibble: Wibble) { case wibble { Wibble | Wobble -> 0 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_from_anonymous_function.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` pub type Wibble pub fn main() { fn(w: Wibble) { todo } ↑ } ----- Jumped to `src/app.gleam` pub type Wibble ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔ pub fn main() { fn(w: Wibble) { todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_import.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` import example_module ↑ fn main() { example_module.my_num } ----- Jumped to `src/example_module.gleam` pub const my_num = 1 ↑ ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_import_aliased.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` import example_module as example ↑ fn main() { example.my_num } ----- Jumped to `src/example_module.gleam` pub const my_num = 1 ↑ ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_import_unqualified_type.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` import example_module.{type MyType} ↑ fn main() -> MyType { 0 } ----- Jumped to `src/example_module.gleam` pub type MyType = Int ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_import_unqualified_value.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` import example_module.{my_num} ↑ fn main() { my_num } ----- Jumped to `src/example_module.gleam` pub const my_num = 1 ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_imported_constant.snap ================================================ --- source: language-server/src/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` import wibble const my_constant = wibble.value ↑ ----- Jumped to `build/packages/hex/src/wibble.gleam` pub const value = 10 ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_imported_module_constants.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` import example_module fn main() { example_module.my_num ↑ } ----- Jumped to `src/example_module.gleam` pub const my_num = 1 ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_imported_module_records.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` import example_module fn main() { example_module.Var1(1) ↑ } ----- Jumped to `src/example_module.gleam` pub type Rec { Var1(Int) ↑▔▔▔▔▔▔▔▔ Var2(Int, Int) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_local_variable.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` pub fn main() { let x = 1 x ↑ } ----- Jumped to `src/app.gleam` pub fn main() { let x = 1 ↑ x } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_module.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` import wibble pub fn main() { wibble.wibble() ↑ } ----- Jumped to `src/wibble.gleam` pub fn wibble() {} ↑ ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_module_function_calls.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` import example_module fn main() { example_module.my_fn ↑ } ----- Jumped to `src/example_module.gleam` pub fn my_fn() { Nil } ↑▔▔▔▔▔▔▔▔▔▔▔▔▔ ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_of_external_function_in_same_module.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` @external(erlang, "wibble", "wobble") fn external_function() -> Nil fn main() { external_function() ↑ } ----- Jumped to `src/app.gleam` @external(erlang, "wibble", "wobble") fn external_function() -> Nil ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ fn main() { external_function() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_of_local_variable_from_guard.snap ================================================ --- source: language-server/src/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` pub fn main() { let wibble = True let wobble = False case wibble { True if wobble -> !wibble ↑ False if !wobble -> wibble _ -> wobble } } ----- Jumped to `src/app.gleam` pub fn main() { let wibble = True let wobble = False ↑▔▔▔▔▔ case wibble { True if wobble -> !wibble False if !wobble -> wibble _ -> wobble } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_of_module_select_from_guard.snap ================================================ --- source: language-server/src/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` import mod pub fn main() { let wibble = True case wibble { True if mod.wibble < 5 -> !wibble ↑ False if mod.wibble != 10 -> wibble _ -> mod.wibble + 1 } } ----- Jumped to `src/mod.gleam` pub const wibble = 10 ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_of_record_from_guard.snap ================================================ --- source: language-server/src/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` type Wibble { Wibble Wobble } pub fn main() { let wibble = True let wobble = Wibble case wibble { True if wobble == Wibble -> !wibble ↑ False if wobble == Wobble -> wibble _ -> Wibble } } ----- Jumped to `src/app.gleam` type Wibble { Wibble ↑▔▔▔▔▔ Wobble } pub fn main() { let wibble = True let wobble = Wibble case wibble { True if wobble == Wibble -> !wibble False if wobble == Wobble -> wibble _ -> Wibble } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_of_record_module_select_from_guard.snap ================================================ --- source: language-server/src/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` import mod pub fn main() { let wibble = True let wobble = mod.Wibble case wibble { True if wobble == mod.Wobble -> !wibble False if wobble == mod.Wibble -> wibble ↑ _ -> mod.Wibble } } ----- Jumped to `src/mod.gleam` pub type Wobble { Wibble Wobble } ↑▔▔▔▔▔ ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_path_module_function_calls.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` import example_module fn main() { example_module.my_fn ↑ } ----- Jumped to `dep/src/example_module.gleam` pub fn my_fn() { Nil } ↑▔▔▔▔▔▔▔▔▔▔▔▔▔ ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_record_update.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` pub type Wibble { Wibble(one: Int, two: Int) } pub fn main() { Wibble(..todo, one: 1) ↑ } ----- Jumped to `src/app.gleam` pub type Wibble { Wibble(one: Int, two: Int) } ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ pub fn main() { Wibble(..todo, one: 1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_same_module_constants.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` const x = 1 pub fn main() { x ↑ } ----- Jumped to `src/app.gleam` const x = 1 ↑▔▔▔▔▔▔ pub fn main() { x } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_same_module_functions.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` fn add_2(x) { x + 2 } pub fn main() { add_2(1) ↑ } ----- Jumped to `src/app.gleam` fn add_2(x) { ↑▔▔▔▔▔▔▔▔▔▔ x + 2 } pub fn main() { add_2(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_same_module_records.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` pub type Rec { Var1(Int) Var2(Int, Int) } pub fn main() { let a = Var1(1) ↑ let b = Var2(2, 3) } ----- Jumped to `src/app.gleam` pub type Rec { Var1(Int) ↑▔▔▔▔▔▔▔▔ Var2(Int, Int) } pub fn main() { let a = Var1(1) let b = Var2(2, 3) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_type.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` pub type Rec { Var1(Int) Var2(Int, Int) } pub fn make_var() -> Rec { ↑ Var1(1) } ----- Jumped to `src/app.gleam` pub type Rec { ↑▔▔▔▔▔▔▔▔▔▔▔ Var1(Int) Var2(Int, Int) } pub fn make_var() -> Rec { Var1(1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_type_in_module.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` import example_module fn make_var() -> example_module.Rec { ↑ example_module.Var1(1) } ----- Jumped to `build/packages/hex/src/example_module.gleam` pub type Rec { ↑▔▔▔▔▔▔▔▔▔▔▔ Var1(Int) Var2(Int, Int) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_type_in_path_dep.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` import example_module fn make_var() -> example_module.Rec { ↑ example_module.Var1(1) } ----- Jumped to `dep/src/example_module.gleam` pub type Rec { ↑▔▔▔▔▔▔▔▔▔▔▔ Var1(Int) Var2(Int, Int) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_unqualified_function.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` import wibble.{wobble} fn main() { wobble() ↑ } ----- Jumped to `src/wibble.gleam` pub fn wobble() {} ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_unqualified_imported_module_constants.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` import example_module.{my_num} fn main() { my_num ↑ } ----- Jumped to `src/example_module.gleam` pub const my_num = 1 ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_definition_unqualified_imported_module_records.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- ----- Jumping from `src/app.gleam` import example_module.{Var1} fn main() { Var1(1) ↑ } ----- Jumped to `src/example_module.gleam` pub type Rec { Var1(Int) ↑▔▔▔▔▔▔▔▔ Var2(Int, Int) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_type_definition_can_jump_to_all_types_in_a_function_type.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- Jumping to type definition ----- Jumping from `src/app.gleam` import wibble.{type Wibble} import wobble.{type Wobble} import box.{type Box} pub fn main() { let a = fn(wibble: Wibble) { box.Box(wobble.Wobble) } ↑ } ----- Jumped to `dep/src/wibble.gleam` pub type Wibble { Wibble } ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ----- Jumped to `dep/src/box.gleam` pub type Box(a) { Box(a) } ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ----- Jumped to `dep/src/wobble.gleam` pub type Wobble { Wobble } ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_type_definition_can_jump_to_all_types_in_a_tuple.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- Jumping to type definition ----- Jumping from `src/app.gleam` import wibble.{type Wibble} import wobble.{type Wobble} import box.{type Box} pub fn main() { let a: #(Box(Wibble), Wobble) = todo ↑ } ----- Jumped to `dep/src/box.gleam` pub type Box(a) { Box(a) } ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ----- Jumped to `dep/src/wibble.gleam` pub type Wibble { Wibble } ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ----- Jumped to `dep/src/wobble.gleam` pub type Wobble { Wobble } ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_type_definition_can_jump_to_multiple_types.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- Jumping to type definition ----- Jumping from `src/app.gleam` import wibble.{type Wibble, Wibble} import box.{Box} pub fn main() { let a = Box(Wibble) ↑ } ----- Jumped to `dep/src/box.gleam` pub type Box(a) { Box(a) } ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ----- Jumped to `dep/src/wibble.gleam` pub type Wibble { Wibble } ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_type_definition_in_different_file_of_dependency.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- Jumping to type definition ----- Jumping from `src/app.gleam` import wibble.{type Wibble} pub fn main() { use_wibble(todo) ↑ } pub fn use_wibble(wibble: Wibble) { todo } ----- Jumped to `dep/src/wibble.gleam` pub type Wibble ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_type_definition_in_different_file_of_same_project.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- Jumping to type definition ----- Jumping from `src/app.gleam` import wibble.{type Wibble} pub fn main() { use_wibble(todo) ↑ } pub fn use_wibble(wibble: Wibble) { todo } ----- Jumped to `src/wibble.gleam` pub type Wibble ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__definition__goto_type_definition_in_same_file.snap ================================================ --- source: compiler-core/src/language_server/tests/definition.rs expression: output --- Jumping to type definition ----- Jumping from `src/app.gleam` pub type Wibble { Wibble } pub fn main() { let x = Wibble x ↑ } ----- Jumped to `src/app.gleam` pub type Wibble { ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Wibble } pub fn main() { let x = Wibble x } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__document_symbols__doc_symbols_constant.snap ================================================ --- source: compiler-core/src/language_server/tests/document_symbols.rs expression: "doc_symbols(TestProject::for_source(code))" --- [ DocumentSymbol { name: "my_const", detail: Some( "Int", ), kind: Constant, tags: None, deprecated: None, range: Range { start: Position { line: 1, character: 0, }, end: Position { line: 2, character: 22, }, }, selection_range: Range { start: Position { line: 2, character: 10, }, end: Position { line: 2, character: 18, }, }, children: None, }, DocumentSymbol { name: "my_const2", detail: Some( "List(Int)", ), kind: Constant, tags: None, deprecated: None, range: Range { start: Position { line: 4, character: 0, }, end: Position { line: 4, character: 26, }, }, selection_range: Range { start: Position { line: 4, character: 10, }, end: Position { line: 4, character: 19, }, }, children: None, }, ] ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__document_symbols__doc_symbols_function.snap ================================================ --- source: compiler-core/src/language_server/tests/document_symbols.rs expression: "doc_symbols(TestProject::for_source(code))" --- [ DocumentSymbol { name: "super_func", detail: Some( "fn(Int) -> List(Int)", ), kind: Function, tags: None, deprecated: None, range: Range { start: Position { line: 1, character: 0, }, end: Position { line: 4, character: 1, }, }, selection_range: Range { start: Position { line: 2, character: 7, }, end: Position { line: 2, character: 17, }, }, children: None, }, DocumentSymbol { name: "super_func2", detail: Some( "fn(Int) -> List(Int)", ), kind: Function, tags: None, deprecated: None, range: Range { start: Position { line: 6, character: 0, }, end: Position { line: 8, character: 1, }, }, selection_range: Range { start: Position { line: 6, character: 7, }, end: Position { line: 6, character: 18, }, }, children: None, }, ] ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__document_symbols__doc_symbols_type_alias.snap ================================================ --- source: compiler-core/src/language_server/tests/document_symbols.rs expression: "doc_symbols(TestProject::for_source(code))" --- [ DocumentSymbol { name: "FFF", detail: Some( "gleam.Int", ), kind: Class, tags: None, deprecated: None, range: Range { start: Position { line: 1, character: 0, }, end: Position { line: 2, character: 18, }, }, selection_range: Range { start: Position { line: 2, character: 9, }, end: Position { line: 2, character: 12, }, }, children: None, }, DocumentSymbol { name: "FFFF", detail: Some( "gleam.List(gleam.Int)", ), kind: Class, tags: None, deprecated: None, range: Range { start: Position { line: 4, character: 0, }, end: Position { line: 4, character: 25, }, }, selection_range: Range { start: Position { line: 4, character: 9, }, end: Position { line: 4, character: 13, }, }, children: None, }, ] ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__document_symbols__doc_symbols_type_constructor_labeled_args.snap ================================================ --- source: compiler-core/src/language_server/tests/document_symbols.rs expression: "doc_symbols(TestProject::for_source(code))" --- [ DocumentSymbol { name: "B", detail: None, kind: Class, tags: None, deprecated: None, range: Range { start: Position { line: 1, character: 0, }, end: Position { line: 12, character: 1, }, }, selection_range: Range { start: Position { line: 1, character: 9, }, end: Position { line: 1, character: 10, }, }, children: Some( [ DocumentSymbol { name: "C", detail: None, kind: Constructor, tags: None, deprecated: None, range: Range { start: Position { line: 2, character: 4, }, end: Position { line: 2, character: 16, }, }, selection_range: Range { start: Position { line: 2, character: 4, }, end: Position { line: 2, character: 5, }, }, children: Some( [ DocumentSymbol { name: "argc", detail: Some( "Int", ), kind: Field, tags: None, deprecated: None, range: Range { start: Position { line: 2, character: 6, }, end: Position { line: 2, character: 15, }, }, selection_range: Range { start: Position { line: 2, character: 6, }, end: Position { line: 2, character: 10, }, }, children: None, }, ], ), }, DocumentSymbol { name: "D", detail: None, kind: Constructor, tags: None, deprecated: None, range: Range { start: Position { line: 4, character: 4, }, end: Position { line: 5, character: 22, }, }, selection_range: Range { start: Position { line: 5, character: 4, }, end: Position { line: 5, character: 5, }, }, children: Some( [ DocumentSymbol { name: "argd", detail: Some( "List(Int)", ), kind: Field, tags: None, deprecated: None, range: Range { start: Position { line: 5, character: 6, }, end: Position { line: 5, character: 21, }, }, selection_range: Range { start: Position { line: 5, character: 6, }, end: Position { line: 5, character: 10, }, }, children: None, }, ], ), }, DocumentSymbol { name: "E", detail: None, kind: Constructor, tags: None, deprecated: None, range: Range { start: Position { line: 7, character: 4, }, end: Position { line: 11, character: 5, }, }, selection_range: Range { start: Position { line: 8, character: 4, }, end: Position { line: 8, character: 5, }, }, children: Some( [ DocumentSymbol { name: "arge", detail: Some( "Result(Int, Bool)", ), kind: Field, tags: None, deprecated: None, range: Range { start: Position { line: 9, character: 8, }, end: Position { line: 10, character: 31, }, }, selection_range: Range { start: Position { line: 10, character: 8, }, end: Position { line: 10, character: 12, }, }, children: None, }, ], ), }, ], ), }, ] ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__document_symbols__doc_symbols_type_constructor_no_args.snap ================================================ --- source: compiler-core/src/language_server/tests/document_symbols.rs expression: "doc_symbols(TestProject::for_source(code))" --- [ DocumentSymbol { name: "B", detail: None, kind: Class, tags: None, deprecated: None, range: Range { start: Position { line: 1, character: 0, }, end: Position { line: 7, character: 1, }, }, selection_range: Range { start: Position { line: 1, character: 9, }, end: Position { line: 1, character: 10, }, }, children: Some( [ DocumentSymbol { name: "C", detail: None, kind: EnumMember, tags: None, deprecated: None, range: Range { start: Position { line: 2, character: 4, }, end: Position { line: 2, character: 5, }, }, selection_range: Range { start: Position { line: 2, character: 4, }, end: Position { line: 2, character: 5, }, }, children: None, }, DocumentSymbol { name: "D", detail: None, kind: EnumMember, tags: None, deprecated: None, range: Range { start: Position { line: 3, character: 4, }, end: Position { line: 3, character: 5, }, }, selection_range: Range { start: Position { line: 3, character: 4, }, end: Position { line: 3, character: 5, }, }, children: None, }, DocumentSymbol { name: "E", detail: None, kind: EnumMember, tags: None, deprecated: None, range: Range { start: Position { line: 5, character: 4, }, end: Position { line: 6, character: 5, }, }, selection_range: Range { start: Position { line: 6, character: 4, }, end: Position { line: 6, character: 5, }, }, children: None, }, ], ), }, ] ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__document_symbols__doc_symbols_type_constructor_pos_and_labeled_args.snap ================================================ --- source: compiler-core/src/language_server/tests/document_symbols.rs expression: "doc_symbols(TestProject::for_source(code))" --- [ DocumentSymbol { name: "B", detail: None, kind: Class, tags: None, deprecated: None, range: Range { start: Position { line: 1, character: 0, }, end: Position { line: 14, character: 1, }, }, selection_range: Range { start: Position { line: 1, character: 9, }, end: Position { line: 1, character: 10, }, }, children: Some( [ DocumentSymbol { name: "C", detail: None, kind: Constructor, tags: None, deprecated: None, range: Range { start: Position { line: 2, character: 4, }, end: Position { line: 2, character: 21, }, }, selection_range: Range { start: Position { line: 2, character: 4, }, end: Position { line: 2, character: 5, }, }, children: Some( [ DocumentSymbol { name: "argc", detail: Some( "Int", ), kind: Field, tags: None, deprecated: None, range: Range { start: Position { line: 2, character: 11, }, end: Position { line: 2, character: 20, }, }, selection_range: Range { start: Position { line: 2, character: 11, }, end: Position { line: 2, character: 15, }, }, children: None, }, ], ), }, DocumentSymbol { name: "D", detail: None, kind: Constructor, tags: None, deprecated: None, range: Range { start: Position { line: 4, character: 4, }, end: Position { line: 5, character: 27, }, }, selection_range: Range { start: Position { line: 5, character: 4, }, end: Position { line: 5, character: 5, }, }, children: Some( [ DocumentSymbol { name: "argd", detail: Some( "List(Int)", ), kind: Field, tags: None, deprecated: None, range: Range { start: Position { line: 5, character: 11, }, end: Position { line: 5, character: 26, }, }, selection_range: Range { start: Position { line: 5, character: 11, }, end: Position { line: 5, character: 15, }, }, children: None, }, ], ), }, DocumentSymbol { name: "E", detail: None, kind: Constructor, tags: None, deprecated: None, range: Range { start: Position { line: 7, character: 4, }, end: Position { line: 13, character: 5, }, }, selection_range: Range { start: Position { line: 8, character: 4, }, end: Position { line: 8, character: 5, }, }, children: Some( [ DocumentSymbol { name: "arge", detail: Some( "Result(Int, Bool)", ), kind: Field, tags: None, deprecated: None, range: Range { start: Position { line: 11, character: 8, }, end: Position { line: 12, character: 31, }, }, selection_range: Range { start: Position { line: 12, character: 8, }, end: Position { line: 12, character: 12, }, }, children: None, }, ], ), }, ], ), }, ] ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__document_symbols__doc_symbols_type_constructor_pos_args.snap ================================================ --- source: compiler-core/src/language_server/tests/document_symbols.rs expression: "doc_symbols(TestProject::for_source(code))" --- [ DocumentSymbol { name: "B", detail: None, kind: Class, tags: None, deprecated: None, range: Range { start: Position { line: 1, character: 0, }, end: Position { line: 11, character: 1, }, }, selection_range: Range { start: Position { line: 1, character: 9, }, end: Position { line: 1, character: 10, }, }, children: Some( [ DocumentSymbol { name: "C", detail: None, kind: Constructor, tags: None, deprecated: None, range: Range { start: Position { line: 2, character: 4, }, end: Position { line: 2, character: 10, }, }, selection_range: Range { start: Position { line: 2, character: 4, }, end: Position { line: 2, character: 5, }, }, children: None, }, DocumentSymbol { name: "D", detail: None, kind: Constructor, tags: None, deprecated: None, range: Range { start: Position { line: 4, character: 4, }, end: Position { line: 5, character: 16, }, }, selection_range: Range { start: Position { line: 5, character: 4, }, end: Position { line: 5, character: 5, }, }, children: None, }, DocumentSymbol { name: "E", detail: None, kind: Constructor, tags: None, deprecated: None, range: Range { start: Position { line: 7, character: 4, }, end: Position { line: 10, character: 5, }, }, selection_range: Range { start: Position { line: 8, character: 4, }, end: Position { line: 8, character: 5, }, }, children: None, }, ], ), }, ] ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__document_symbols__doc_symbols_type_no_constructors.snap ================================================ --- source: compiler-core/src/language_server/tests/document_symbols.rs expression: "doc_symbols(TestProject::for_source(code))" --- [ DocumentSymbol { name: "A", detail: None, kind: Class, tags: None, deprecated: None, range: Range { start: Position { line: 1, character: 0, }, end: Position { line: 1, character: 10, }, }, selection_range: Range { start: Position { line: 1, character: 9, }, end: Position { line: 1, character: 10, }, }, children: None, }, ] ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__document_symbols__doc_symbols_type_no_constructors_starting_at_documentation.snap ================================================ --- source: compiler-core/src/language_server/tests/document_symbols.rs expression: "doc_symbols(TestProject::for_source(code))" --- [ DocumentSymbol { name: "A", detail: None, kind: Class, tags: None, deprecated: None, range: Range { start: Position { line: 1, character: 0, }, end: Position { line: 2, character: 10, }, }, selection_range: Range { start: Position { line: 2, character: 9, }, end: Position { line: 2, character: 10, }, }, children: None, }, ] ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__document_symbols__doc_symbols_type_no_constructors_starting_at_empty_doc.snap ================================================ --- source: compiler-core/src/language_server/tests/document_symbols.rs expression: "doc_symbols(TestProject::for_source(code))" --- [ DocumentSymbol { name: "A", detail: None, kind: Class, tags: None, deprecated: None, range: Range { start: Position { line: 3, character: 0, }, end: Position { line: 4, character: 10, }, }, selection_range: Range { start: Position { line: 4, character: 9, }, end: Position { line: 4, character: 10, }, }, children: None, }, ] ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__folding_range__folds_import_block.snap ================================================ --- source: language-server/src/tests/folding_range.rs expression: "import one\nimport two\nimport three\n\npub fn main() { 1 }" snapshot_kind: text --- ----- Code ----- import one import two import three pub fn main() { 1 } ----- Ranges ----- 1. lines 0..2 kind: imports ----- Fold 1 (lines 0..2) ----- import one ... pub fn main() { 1 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__folding_range__folds_mixed_definitions_in_source_order.snap ================================================ --- source: language-server/src/tests/folding_range.rs expression: "import one\nimport two\n\npub type W {\n W(Int)\n}\n\npub const xs = [\n 1,\n 2,\n]\n\npub fn main() {\n 1\n}" snapshot_kind: text --- ----- Code ----- import one import two pub type W { W(Int) } pub const xs = [ 1, 2, ] pub fn main() { 1 } ----- Ranges ----- 1. lines 0..1 kind: imports 2. lines 3..5 kind: none 3. lines 7..10 kind: none 4. lines 12..14 kind: none ----- Fold 1 (lines 0..1) ----- import one ... pub type W { W(Int) } pub const xs = [ 1, 2, ] pub fn main() { 1 } ----- Fold 2 (lines 3..5) ----- import one import two pub type W { ... pub const xs = [ 1, 2, ] pub fn main() { 1 } ----- Fold 3 (lines 7..10) ----- import one import two pub type W { W(Int) } pub const xs = [ ... pub fn main() { 1 } ----- Fold 4 (lines 12..14) ----- import one import two pub type W { W(Int) } pub const xs = [ 1, 2, ] pub fn main() { ... ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__folding_range__folds_multiline_constant.snap ================================================ --- source: language-server/src/tests/folding_range.rs expression: "pub const xs = [\n 1,\n 2,\n]" snapshot_kind: text --- ----- Code ----- pub const xs = [ 1, 2, ] ----- Ranges ----- 1. lines 0..3 kind: none ----- Fold 1 (lines 0..3) ----- pub const xs = [ ... ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__folding_range__folds_multiline_custom_type.snap ================================================ --- source: language-server/src/tests/folding_range.rs expression: "pub type W {\n W(Int)\n}" snapshot_kind: text --- ----- Code ----- pub type W { W(Int) } ----- Ranges ----- 1. lines 0..2 kind: none ----- Fold 1 (lines 0..2) ----- pub type W { ... ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__folding_range__folds_multiline_function_body.snap ================================================ --- source: language-server/src/tests/folding_range.rs expression: "pub fn main() {\n let x = 1\n x\n}" snapshot_kind: text --- ----- Code ----- pub fn main() { let x = 1 x } ----- Ranges ----- 1. lines 0..3 kind: none ----- Fold 1 (lines 0..3) ----- pub fn main() { ... ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__folding_range__folds_multiline_type_alias.snap ================================================ --- source: language-server/src/tests/folding_range.rs expression: "pub type Pair =\n #(Int, Int)" snapshot_kind: text --- ----- Code ----- pub type Pair = #(Int, Int) ----- Ranges ----- 1. lines 0..1 kind: none ----- Fold 1 (lines 0..1) ----- pub type Pair = ... ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__folding_range__folds_only_multiline_functions_in_source_order.snap ================================================ --- source: language-server/src/tests/folding_range.rs expression: "pub fn one() {\n 1\n}\n\npub fn two() { 2 }\n\npub fn three() {\n 3\n}" snapshot_kind: text --- ----- Code ----- pub fn one() { 1 } pub fn two() { 2 } pub fn three() { 3 } ----- Ranges ----- 1. lines 0..2 kind: none 2. lines 6..8 kind: none ----- Fold 1 (lines 0..2) ----- pub fn one() { ... pub fn two() { 2 } pub fn three() { 3 } ----- Fold 2 (lines 6..8) ----- pub fn one() { 1 } pub fn two() { 2 } pub fn three() { ... ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__folding_range__folds_separated_import_blocks.snap ================================================ --- source: language-server/src/tests/folding_range.rs expression: "import one\nimport two\n\nimport three\nimport four\n\npub fn main() { 1 }" snapshot_kind: text --- ----- Code ----- import one import two import three import four pub fn main() { 1 } ----- Ranges ----- 1. lines 0..1 kind: imports 2. lines 3..4 kind: imports ----- Fold 1 (lines 0..1) ----- import one ... import three import four pub fn main() { 1 } ----- Fold 2 (lines 3..4) ----- import one import two import three ... pub fn main() { 1 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__documentation_for_shared_record_field_when_variant_is_known.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\npub type Wibble {\n Wibble(\n /// This is some documentation about the wibble field.\n wibble: Int\n )\n Wobble(\n /// This won't show up because it's a Wibble variant\n wibble: Int\n )\n}\n\npub fn wibble(w: Wibble) {\n let assert Wibble(..) = w\n w.wibble\n}\n" --- pub type Wibble { Wibble( /// This is some documentation about the wibble field. wibble: Int ) Wobble( /// This won't show up because it's a Wibble variant wibble: Int ) } pub fn wibble(w: Wibble) { let assert Wibble(..) = w w.wibble ▔▔▔▔▔▔↑▔ } ----- Hover content (markdown) ----- ```gleam Int ``` This is some documentation about the wibble field. ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_assignment_annotation.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nfn wibble() {\n let wobble: Int = 7\n wobble\n}\n" --- fn wibble() { let wobble: Int = 7 ▔▔↑ wobble } ----- Hover content (markdown) ----- ```gleam gleam.Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_contextual_type.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport wibble/wobble\nconst value = wobble.Wobble\n" --- import wibble/wobble const value = wobble.Wobble ▔▔▔▔▔▔↑▔▔▔▔ ----- Hover content (markdown) ----- ```gleam wobble.Wibble ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_contextual_type_aliased.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport wibble/wobble\ntype Local = wobble.Wibble\nconst value = wobble.Wobble\n" --- import wibble/wobble type Local = wobble.Wibble const value = wobble.Wobble ▔▔▔▔▔▔↑▔▔▔▔ ----- Hover content (markdown) ----- ```gleam Local ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_contextual_type_aliased_module.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport wibble/wobble as wubble\nconst value = wubble.Wobble\n" --- import wibble/wobble as wubble const value = wubble.Wobble ▔▔▔▔▔▔↑▔▔▔▔ ----- Hover content (markdown) ----- ```gleam wubble.Wibble ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_contextual_type_annotation.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport wibble/wobble\n\nfn make_wibble() -> wobble.Wibble { wobble.Wibble }\n" --- import wibble/wobble fn make_wibble() -> wobble.Wibble { wobble.Wibble } ▔▔▔▔▔▔▔▔↑▔▔▔▔ ----- Hover content (markdown) ----- ```gleam wobble.Wibble ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_contextual_type_annotation_aliased.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport wibble/wobble\n\ntype Wubble = wobble.Wibble\n\nfn main(wibble: Wubble) {\n wibble\n}\n" --- import wibble/wobble type Wubble = wobble.Wibble fn main(wibble: Wubble) { ▔▔▔▔▔↑ wibble } ----- Hover content (markdown) ----- ```gleam wobble.Wibble ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_contextual_type_annotation_aliased_module.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport wibble/wobble as wubble\n\nfn main(wibble: wubble.Wibble) {\n wibble\n}\n" --- import wibble/wobble as wubble fn main(wibble: wubble.Wibble) { ▔▔▔▔▔▔▔↑▔▔▔▔▔ wibble } ----- Hover content (markdown) ----- ```gleam wubble.Wibble ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_contextual_type_annotation_prelude.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nfn add_one(a: Int) -> Int {\n a + 1\n}\n" --- fn add_one(a: Int) -> Int { ↑▔▔ a + 1 } ----- Hover content (markdown) ----- ```gleam gleam.Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_contextual_type_annotation_unqualified.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport wibble/wobble.{type Wibble}\n\nfn main(wibble: Wibble) {\n wibble\n}\n" --- import wibble/wobble.{type Wibble} fn main(wibble: Wibble) { ↑▔▔▔▔▔ wibble } ----- Hover content (markdown) ----- ```gleam wobble.Wibble ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_contextual_type_annotation_unqualified_aliased.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport wibble/wobble.{type Wibble as Wubble}\n\nfn main(wibble: Wubble) {\n wibble\n}\n" --- import wibble/wobble.{type Wibble as Wubble} fn main(wibble: Wubble) { ↑▔▔▔▔▔ wibble } ----- Hover content (markdown) ----- ```gleam wobble.Wibble ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_contextual_type_arg.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport wibble/wobble\n\nfn do_things(wibble: wobble.Wibble) { wibble }\n" --- import wibble/wobble fn do_things(wibble: wobble.Wibble) { wibble } ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ----- Hover content (markdown) ----- ```gleam wobble.Wibble ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_contextual_type_expression.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport wibble/wobble\n\npub fn main() {\n let wibble = wobble.Wibble\n}\n" --- import wibble/wobble pub fn main() { let wibble = wobble.Wibble ▔▔▔▔▔▔▔▔▔▔▔↑▔ } ----- Hover content (markdown) ----- ```gleam wobble.Wibble ``` View on [HexDocs](https://hexdocs.pm/hex/wibble/wobble.html#Wibble) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_contextual_type_function.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport wibble/wobble\ntype MyInt = Int\nfn func(value: wobble.Wibble) -> MyInt { 1 }\n" --- import wibble/wobble type MyInt = Int fn func(value: wobble.Wibble) -> MyInt { 1 } ▔▔▔↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ----- Hover content (markdown) ----- ```gleam fn(wobble.Wibble) -> MyInt ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_contextual_type_pattern.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport wibble/wobble.{Wibble, Wobble, Wubble}\n\npub fn cycle(wibble: wobble.Wibble) {\n case wibble {\n Wibble -> Wobble\n Wobble -> Wubble\n Wubble -> Wibble\n }\n}\n" --- import wibble/wobble.{Wibble, Wobble, Wubble} pub fn cycle(wibble: wobble.Wibble) { case wibble { Wibble -> Wobble Wobble -> Wubble Wubble -> Wibble ▔↑▔▔▔▔ } } ----- Hover content (markdown) ----- ```gleam wobble.Wibble ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_contextual_type_pattern_spread.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport wibble/wobble.{type Wibble as Wobble}\n\ntype Thing {\n Thing(id: Int, value: Wobble)\n}\n\npub fn main(thing: Thing) {\n case thing {\n Thing(id: 0, ..) -> 12\n _ -> 14\n }\n}\n" --- import wibble/wobble.{type Wibble as Wobble} type Thing { Thing(id: Int, value: Wobble) } pub fn main(thing: Thing) { case thing { Thing(id: 0, ..) -> 12 ↑▔ _ -> 14 } } ----- Hover content (markdown) ----- Unused labelled fields: - `value: Wobble` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_contextual_type_unqualified.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport wibble/wobble.{type Wibble}\nconst value = wobble.Wobble\n" --- import wibble/wobble.{type Wibble} const value = wobble.Wobble ▔▔▔▔▔▔↑▔▔▔▔ ----- Hover content (markdown) ----- ```gleam Wibble ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_contextual_type_unqualified_aliased.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport wibble/wobble.{type Wibble as Wobble}\nconst value = wobble.Wobble\n" --- import wibble/wobble.{type Wibble as Wobble} const value = wobble.Wobble ▔▔▔▔▔▔↑▔▔▔▔ ----- Hover content (markdown) ----- ```gleam Wobble ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_contextual_type_unqualified_import.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport wibble/wobble.{type Wibble as Wobble, Wobble}\n" --- import wibble/wobble.{type Wibble as Wobble, Wobble} ↑▔▔▔▔▔ ----- Hover content (markdown) ----- ```gleam Wobble ``` View on [HexDocs](https://hexdocs.pm/hex/wibble/wobble.html#Wobble) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_expressions_in_function_body.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nfn append(x, y) {\n x <> y\n}\n" --- fn append(x, y) { x <> y ↑ } ----- Hover content (markdown) ----- ```gleam String ``` A locally defined variable. ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_external_function_with_another_value_same_name.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport a/example_module.{my_const as discarded}\nimport b/example_module.{my_const} as _\nfn main() {\n my_const\n}\n" --- import a/example_module.{my_const as discarded} import b/example_module.{my_const} as _ fn main() { my_const ▔▔▔▔↑▔▔▔ } ----- Hover content (markdown) ----- ```gleam Int ``` View on [HexDocs](https://hexdocs.pm/hex/b/example_module.html#my_const) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_external_imported_constants.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport example_module\nfn main() {\n example_module.my_const\n}\n" --- import example_module fn main() { example_module.my_const ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑▔▔▔▔▔ } ----- Hover content (markdown) ----- ```gleam Int ``` View on [HexDocs](https://hexdocs.pm/hex/example_module.html#my_const) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_external_imported_ffi_renamed_function.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport example_module\nfn main() {\n example_module.my_fn\n}\n" --- import example_module fn main() { example_module.my_fn ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑▔ } ----- Hover content (markdown) ----- ```gleam fn() -> Nil ``` View on [HexDocs](https://hexdocs.pm/hex/example_module.html#my_fn) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_external_imported_function.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport example_module\nfn main() {\n example_module.my_fn\n}\n" --- import example_module fn main() { example_module.my_fn ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑▔▔ } ----- Hover content (markdown) ----- ```gleam fn() -> Nil ``` View on [HexDocs](https://hexdocs.pm/hex/example_module.html#my_fn) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_external_imported_function_nested_module.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport my/nested/example_module\nfn main() {\n example_module.my_fn\n}\n" --- import my/nested/example_module fn main() { example_module.my_fn ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑▔ } ----- Hover content (markdown) ----- ```gleam fn() -> Nil ``` View on [HexDocs](https://hexdocs.pm/hex/my/nested/example_module.html#my_fn) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_external_imported_function_renamed_module.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport example_module as renamed_module\nfn main() {\n renamed_module.my_fn\n}\n" --- import example_module as renamed_module fn main() { renamed_module.my_fn ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑▔ } ----- Hover content (markdown) ----- ```gleam fn() -> Nil ``` View on [HexDocs](https://hexdocs.pm/hex/example_module.html#my_fn) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_external_imported_unqualified_constants.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport example_module.{my_const}\nfn main() {\n my_const\n}\n" --- import example_module.{my_const} fn main() { my_const ▔▔▔↑▔▔▔▔ } ----- Hover content (markdown) ----- ```gleam Int ``` View on [HexDocs](https://hexdocs.pm/hex/example_module.html#my_const) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_external_imported_unqualified_function.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport example_module.{my_fn}\nfn main() {\n my_fn\n}\n" --- import example_module.{my_fn} fn main() { my_fn ▔▔▔↑▔ } ----- Hover content (markdown) ----- ```gleam fn() -> Nil ``` View on [HexDocs](https://hexdocs.pm/hex/example_module.html#my_fn) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_external_unqualified_imported_function_renamed_module.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport example_module.{my_fn} as renamed_module\nfn main() {\n my_fn\n}\n" --- import example_module.{my_fn} as renamed_module fn main() { my_fn ▔▔↑▔▔ } ----- Hover content (markdown) ----- ```gleam fn() -> Nil ``` View on [HexDocs](https://hexdocs.pm/hex/example_module.html#my_fn) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_external_value_with_two_modules_same_name.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport a/example_module as _\nimport b/example_module\nfn main() {\n example_module.my_const\n}\n" --- import a/example_module as _ import b/example_module fn main() { example_module.my_const ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑▔▔▔▔ } ----- Hover content (markdown) ----- ```gleam Int ``` View on [HexDocs](https://hexdocs.pm/hex/b/example_module.html#my_const) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_annotation_in_use.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\npub fn main() {\n use something: Int <- todo\n todo\n}\n" --- pub fn main() { use something: Int <- todo ▔↑▔ todo } ----- Hover content (markdown) ----- ```gleam gleam.Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_anonymous_function_annotation.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\n/// An example type.\npub type Wibble\n\npub fn main() {\n fn(w: Wibble) { todo }\n}\n" --- /// An example type. pub type Wibble pub fn main() { fn(w: Wibble) { todo } ▔▔↑▔▔▔ } ----- Hover content (markdown) ----- ```gleam app.Wibble ``` An example type. ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_constant_bit_array.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nconst bits = <<1:2, 3:4>>\n" --- const bits = <<1:2, 3:4>> ▔↑ ----- Hover content (markdown) ----- ```gleam Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_constant_bit_array_segment.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nconst bits = <<1:2, 3:4>>\n" --- const bits = <<1:2, 3:4>> ↑ ----- Hover content (markdown) ----- ```gleam Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_constant_bit_array_segment_option.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nconst bits = <<1:size(2), 3:4>>\n" --- const bits = <<1:size(2), 3:4>> ↑ ----- Hover content (markdown) ----- ```gleam Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_constant_float.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nconst pi = 3.14\n" --- const pi = 3.14 ↑▔▔▔ ----- Hover content (markdown) ----- ```gleam Float ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_constant_int.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nconst ten = 10\n" --- const ten = 10 ↑▔ ----- Hover content (markdown) ----- ```gleam Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_constant_list.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nconst numbers = [2, 4, 6, 8]\n" --- const numbers = [2, 4, 6, 8] ↑▔▔▔▔▔▔▔▔▔▔▔ ----- Hover content (markdown) ----- ```gleam List(Int) ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_constant_list_element.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nconst numbers = [2, 4, 6, 8]\n" --- const numbers = [2, 4, 6, 8] ↑ ----- Hover content (markdown) ----- ```gleam Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_constant_other_constant.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nconst constant1 = 10\nconst constant2 = constant1\n" --- const constant1 = 10 const constant2 = constant1 ▔▔▔↑▔▔▔▔▔ ----- Hover content (markdown) ----- ```gleam Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_constant_record.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\ntype Wibble {\n Wibble(Int)\n}\n\nconst w = Wibble(10)\n" --- type Wibble { Wibble(Int) } const w = Wibble(10) ▔↑▔▔▔▔▔▔▔▔ ----- Hover content (markdown) ----- ```gleam Wibble ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_constant_string.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nconst message = \"Hello!\"\n" --- const message = "Hello!" ▔▔▔▔▔▔↑▔ ----- Hover content (markdown) ----- ```gleam String ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_constant_string_concatenation.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nconst name = \"Bob\"\nconst message = \"Hello \" <> name\n" --- const name = "Bob" const message = "Hello " <> name ▔▔▔▔▔▔▔▔▔↑▔▔▔▔▔▔ ----- Hover content (markdown) ----- ```gleam String ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_constant_string_concatenation_side.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nconst name = \"Bob\"\nconst message = \"Hello \" <> name\n" --- const name = "Bob" const message = "Hello " <> name ↑▔▔▔ ----- Hover content (markdown) ----- ```gleam String ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_constant_tuple.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nconst tuple = #(1, 3.5, False)\n" --- const tuple = #(1, 3.5, False) ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ ----- Hover content (markdown) ----- ```gleam #(Int, Float, Bool) ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_constant_tuple_element.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nconst tuple = #(1, 3.5, False)\n" --- const tuple = #(1, 3.5, False) ↑▔▔▔▔ ----- Hover content (markdown) ----- ```gleam Bool ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_custom_type.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "/// Exciting documentation\n/// Maybe even multiple lines\ntype Wibble {\n /// Some more exciting documentation\n Wibble(String)\n /// The most exciting documentation\n Wobble(arg: Int)\n}" --- /// Exciting documentation /// Maybe even multiple lines type Wibble { ▔▔▔▔▔↑▔▔▔▔▔▔▔ /// Some more exciting documentation ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Wibble(String) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ /// The most exciting documentation ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Wobble(arg: Int) ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ } ▔ ----- Hover content (markdown) ----- ```gleam Wibble ``` Exciting documentation Maybe even multiple lines ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_invalid_record_update_1.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\ntype Wibble {\n Wibble(a: Int, b: Bool)\n}\n\npub fn go(wibble: Wibble) {\n Wibble(..wibble, a: True, b: 1)\n}\n" --- type Wibble { Wibble(a: Int, b: Bool) } pub fn go(wibble: Wibble) { Wibble(..wibble, a: True, b: 1) ↑▔▔▔ } ----- Hover content (markdown) ----- ```gleam Bool ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_invalid_record_update_2.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\ntype Wibble {\n Wibble(a: Int, b: Bool)\n}\n\npub fn go(wibble: Wibble) {\n Wibble(..wibble, a: True, b: 1)\n}\n" --- type Wibble { Wibble(a: Int, b: Bool) } pub fn go(wibble: Wibble) { Wibble(..wibble, a: True, b: 1) ↑ } ----- Hover content (markdown) ----- ```gleam Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_invalid_record_update_3.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\ntype Wibble {\n Wibble(a: Int, b: Bool)\n}\n\npub fn go(wibble: Wibble) {\n Wibble(..wibble, a: True, b: 1)\n}\n" --- type Wibble { Wibble(a: Int, b: Bool) } pub fn go(wibble: Wibble) { Wibble(..wibble, a: True, b: 1) ↑▔▔▔▔▔ } ----- Hover content (markdown) ----- ```gleam fn(Int, Bool) -> Wibble ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_invalid_record_update_4.snap.new ================================================ --- source: language-server/src/tests/hover.rs assertion_line: 2134 expression: "\ntype Wibble {\n Wibble(a: Int, b: Bool)\n}\n\npub fn go(wibble: Wibble) {\n Wibble(..wibble, a: True, b: 1)\n}\n" snapshot_kind: text --- type Wibble { Wibble(a: Int, b: Bool) } pub fn go(wibble: Wibble) { Wibble(..wibble, a: True, b: 1) ↑▔▔▔▔▔▔ } ----- Hover content (markdown) ----- ```gleam Bool ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_label_in_expression.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nfn add(wibble a, wobble b) {\n a + b\n}\n\npub fn main() {\n add(wibble: 1, wobble: 2)\n}\n" --- fn add(wibble a, wobble b) { a + b } pub fn main() { add(wibble: 1, wobble: 2) ▔↑▔▔▔▔▔▔▔ } ----- Hover content (markdown) ----- ```gleam Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_label_in_pattern.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\ntype Wibble {\n Wibble(wibble: Int, wobble: Int)\n}\n\npub fn main() {\n let Wibble(wibble: _, wobble: _) = todo\n todo\n}\n" --- type Wibble { Wibble(wibble: Int, wobble: Int) } pub fn main() { let Wibble(wibble: _, wobble: _) = todo ▔▔▔▔↑▔▔▔▔ todo } ----- Hover content (markdown) ----- ```gleam Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_local_variable_from_guard.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\npub fn main() {\n let wibble = True\n let wobble = False\n case wibble {\n True if wobble -> !wibble\n False if !wobble -> wibble\n _ -> wobble\n }\n}\n" --- pub fn main() { let wibble = True let wobble = False case wibble { True if wobble -> !wibble ▔↑▔▔▔▔ False if !wobble -> wibble _ -> wobble } } ----- Hover content (markdown) ----- ```gleam Bool ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_module_select_from_guard.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport mod\n\npub fn main() {\n let wibble = True\n case wibble {\n True if mod.wibble < 5 -> !wibble\n False if mod.wibble != 10 -> wibble\n _ -> mod.wibble + 1\n }\n}\n" --- import mod pub fn main() { let wibble = True case wibble { True if mod.wibble < 5 -> !wibble ▔▔▔▔↑▔▔▔▔▔ False if mod.wibble != 10 -> wibble _ -> mod.wibble + 1 } } ----- Hover content (markdown) ----- ```gleam Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_module_select_pattern.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport mod\npub fn go(x: mod.Wibble) {\n case x {\n mod.Wibble -> 1\n }\n}\n" --- import mod pub fn go(x: mod.Wibble) { case x { mod.Wibble -> 1 ▔▔▔▔↑▔▔▔▔▔ } } ----- Hover content (markdown) ----- ```gleam mod.Wibble ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_nested_constant.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\ntype Wibble {\n Wibble\n Wobble(BitArray)\n}\n\nconst value = #(1, 2, [Wibble, Wobble(<<1, 2, 3>>), Wibble])\n" --- type Wibble { Wibble Wobble(BitArray) } const value = #(1, 2, [Wibble, Wobble(<<1, 2, 3>>), Wibble]) ↑ ----- Hover content (markdown) ----- ```gleam Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_pattern_in_use.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\ntype Wibble {\n Wibble(Int, Float)\n}\n\npub fn main() {\n use Wibble(int, float) <- todo\n todo\n}\n" --- type Wibble { Wibble(Int, Float) } pub fn main() { use Wibble(int, float) <- todo ↑▔▔ todo } ----- Hover content (markdown) ----- ```gleam Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_pattern_spread_ignoring_all_fields.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\npub type Model {\n Model(\n Int,\n Float,\n label1: Int,\n label2: String,\n )\n}\n\npub fn main() {\n case todo {\n Model(..) -> todo\n }\n}\n" --- pub type Model { Model( Int, Float, label1: Int, label2: String, ) } pub fn main() { case todo { Model(..) -> todo ↑▔ } } ----- Hover content (markdown) ----- Unused positional fields: - `Int` - `Float` Unused labelled fields: - `label1: Int` - `label2: String` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_pattern_spread_ignoring_all_positional_fields.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\npub type Model {\n Model(\n Int,\n Float,\n label1: Int,\n label2: String,\n )\n}\n\npub fn main() {\n case todo {\n Model(_, _, _, ..) -> todo\n }\n}\n" --- pub type Model { Model( Int, Float, label1: Int, label2: String, ) } pub fn main() { case todo { Model(_, _, _, ..) -> todo ↑▔ } } ----- Hover content (markdown) ----- Unused labelled fields: - `label2: String` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_pattern_spread_ignoring_some_fields.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\npub type Model {\n Model(\n Int,\n Float,\n label1: Int,\n label2: String,\n )\n}\n\npub fn main() {\n case todo {\n Model(_, label1: _, ..) -> todo\n }\n}\n" --- pub type Model { Model( Int, Float, label1: Int, label2: String, ) } pub fn main() { case todo { Model(_, label1: _, ..) -> todo ▔↑ } } ----- Hover content (markdown) ----- Unused positional fields: - `Float` Unused labelled fields: - `label2: String` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_record_from_guard.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\ntype Wibble {\n Wibble\n Wobble\n}\n\npub fn main() {\n let wibble = True\n let wobble = Wibble\n case wibble {\n True if wobble == Wibble -> !wibble\n False if wobble == Wobble -> wibble\n _ -> Wibble\n }\n}\n" --- type Wibble { Wibble Wobble } pub fn main() { let wibble = True let wobble = Wibble case wibble { True if wobble == Wibble -> !wibble ▔▔▔▔↑▔ False if wobble == Wobble -> wibble _ -> Wibble } } ----- Hover content (markdown) ----- ```gleam Wibble ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_record_module_select_from_guard.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport mod\n\npub fn main() {\n let wibble = True\n let wobble = mod.Wibble\n case wibble {\n True if wobble == mod.Wobble -> !wibble\n False if wobble == mod.Wibble -> wibble\n _ -> mod.Wibble\n }\n}\n" --- import mod pub fn main() { let wibble = True let wobble = mod.Wibble case wibble { True if wobble == mod.Wobble -> !wibble False if wobble == mod.Wibble -> wibble ▔▔▔▔▔↑▔▔▔▔ _ -> mod.Wibble } } ----- Hover content (markdown) ----- ```gleam mod.Wobble ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_string_prefix_pattern.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\npub fn main() {\n case \"wibble\" {\n \"wib\" as alias <> suffix -> alias <> suffix\n other -> other\n }\n}\n" --- pub fn main() { case "wibble" { "wib" as alias <> suffix -> alias <> suffix ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑▔▔▔▔▔▔▔▔ other -> other } } ----- Hover content (markdown) ----- ```gleam String ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_string_prefix_pattern_prefix_alias.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\npub fn main() {\n case \"wibble\" {\n \"wib\" as alias <> suffix -> alias <> suffix\n other -> other\n }\n}\n" --- pub fn main() { case "wibble" { "wib" as alias <> suffix -> alias <> suffix ↑▔▔▔▔ other -> other } } ----- Hover content (markdown) ----- ```gleam String ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_string_prefix_pattern_prefix_alias_alternative_definition.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\npub fn main() {\n case \"wibble\" {\n \"wib\" as prefix <> rest | \"wob\" as prefix <> rest -> prefix <> rest\n other -> other\n }\n}\n" --- pub fn main() { case "wibble" { "wib" as prefix <> rest | "wob" as prefix <> rest -> prefix <> rest ↑▔▔▔▔▔ other -> other } } ----- Hover content (markdown) ----- ```gleam String ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_string_prefix_pattern_suffix_variable.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\npub fn main() {\n case \"wibble\" {\n \"wib\" as alias <> suffix -> alias <> suffix\n other -> other\n }\n}\n" --- pub fn main() { case "wibble" { "wib" as alias <> suffix -> alias <> suffix ↑▔▔▔▔▔ other -> other } } ----- Hover content (markdown) ----- ```gleam String ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_string_prefix_pattern_suffix_variable_alternative_definition.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\npub fn main() {\n case \"wibble\" {\n \"wib\" <> rest | \"wob\" <> rest -> rest\n other -> other\n }\n}\n" --- pub fn main() { case "wibble" { "wib" <> rest | "wob" <> rest -> rest ↑▔▔▔ other -> other } } ----- Hover content (markdown) ----- ```gleam String ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_for_string_prefix_pattern_suffix_variable_discard.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\npub fn main() {\n case \"wibble\" {\n \"wib\" <> _discard -> Nil\n other -> Nil\n }\n}\n" --- pub fn main() { case "wibble" { "wib" <> _discard -> Nil ▔▔▔▔▔▔▔▔▔↑▔▔▔▔▔▔▔ other -> Nil } } ----- Hover content (markdown) ----- ```gleam String ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_function_arg_annotation_2.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\n/// Exciting documentation\n/// Maybe even multiple lines\nfn append(x: String, y: String) -> String {\n x <> y\n}\n" --- /// Exciting documentation /// Maybe even multiple lines fn append(x: String, y: String) -> String { ▔▔▔▔↑▔ x <> y } ----- Hover content (markdown) ----- ```gleam gleam.String ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_function_arg_annotation_with_documentation.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\n/// Exciting documentation\n/// Maybe even multiple lines\ntype Wibble {\n Wibble(arg: String)\n}\n\nfn identity(x: Wibble) -> Wibble {\n x\n}\n" --- /// Exciting documentation /// Maybe even multiple lines type Wibble { Wibble(arg: String) } fn identity(x: Wibble) -> Wibble { ▔▔▔▔▔↑ x } ----- Hover content (markdown) ----- ```gleam app.Wibble ``` Exciting documentation Maybe even multiple lines ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_function_argument.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\n/// Exciting documentation\n/// Maybe even multiple lines\nfn append(x, y) {\n x <> y\n}\n" --- /// Exciting documentation /// Maybe even multiple lines fn append(x, y) { ↑ x <> y } ----- Hover content (markdown) ----- ```gleam String ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_function_definition.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nfn add_2(x) {\n x + 2\n}\n" --- fn add_2(x) { ▔▔▔↑▔▔▔▔▔▔▔ x + 2 } ----- Hover content (markdown) ----- ```gleam fn(Int) -> Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_function_definition_with_docs.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\n/// Exciting documentation\n/// Maybe even multiple lines\nfn append(x, y) {\n x <> y\n}\n" --- /// Exciting documentation /// Maybe even multiple lines fn append(x, y) { ▔▔▔↑▔▔▔▔▔▔▔▔▔▔▔ x <> y } ----- Hover content (markdown) ----- ```gleam fn(String, String) -> String ``` Exciting documentation Maybe even multiple lines ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_function_return_annotation.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\n/// Exciting documentation\n/// Maybe even multiple lines\nfn append(x: String, y: String) -> String {\n x <> y\n}\n" --- /// Exciting documentation /// Maybe even multiple lines fn append(x: String, y: String) -> String { ▔▔▔▔↑▔ x <> y } ----- Hover content (markdown) ----- ```gleam gleam.String ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_function_return_annotation_with_tuple.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\n/// Exciting documentation\n/// Maybe even multiple lines\nfn append(x: String, y: String) -> #(String, String) {\n #(x, y)\n}\n" --- /// Exciting documentation /// Maybe even multiple lines fn append(x: String, y: String) -> #(String, String) { ▔▔↑▔▔▔ #(x, y) } ----- Hover content (markdown) ----- ```gleam gleam.String ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_import_unqualified_type.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport example_module.{type MyType, MyType}\nfn main() -> MyType {\n MyType\n}\n" --- import example_module.{type MyType, MyType} ▔▔▔▔▔▔▔▔▔▔↑ fn main() -> MyType { MyType } ----- Hover content (markdown) ----- ```gleam example_module.MyType ``` Exciting documentation Maybe even multiple lines ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_import_unqualified_value.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport example_module.{my_num}\nfn main() {\n my_num\n}\n" --- import example_module.{my_num} ▔▔▔↑▔▔ fn main() { my_num } ----- Hover content (markdown) ----- ```gleam Int ``` Exciting documentation Maybe even multiple lines ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_import_unqualified_value_from_hex.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport example_module.{my_num}\nfn main() {\n my_num\n}\n" --- import example_module.{my_num} ▔▔▔↑▔▔ fn main() { my_num } ----- Hover content (markdown) ----- ```gleam Int ``` Exciting documentation Maybe even multiple lines View on [HexDocs](https://hexdocs.pm/hex/example_module.html#my_num) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_imported_function.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport example_module\nfn main() {\n example_module.my_fn\n}\n" --- import example_module fn main() { example_module.my_fn ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔↑▔▔ } ----- Hover content (markdown) ----- ```gleam fn() -> Nil ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_label_shorthand_in_call_arg.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nfn wibble(arg1 arg1: Int, arg2 arg2: Bool) { Nil }\n\nfn main() {\n let arg1 = 1\n let arg2 = True\n wibble(arg2:, arg1:)\n}\n" --- fn wibble(arg1 arg1: Int, arg2 arg2: Bool) { Nil } fn main() { let arg1 = 1 let arg2 = True wibble(arg2:, arg1:) ↑▔▔▔▔ } ----- Hover content (markdown) ----- ```gleam Bool ``` A locally defined variable. ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_label_shorthand_in_pattern_call_arg.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\npub type Wibble { Wibble(arg1: Int, arg2: Bool) }\n\npub fn main() {\n case todo {\n Wibble(arg2:, ..) -> todo\n }\n}\n" --- pub type Wibble { Wibble(arg1: Int, arg2: Bool) } pub fn main() { case todo { Wibble(arg2:, ..) -> todo ▔▔▔▔↑ } } ----- Hover content (markdown) ----- ```gleam Bool ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_label_shorthand_in_pattern_call_arg_2.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\npub type Wibble { Wibble(arg1: Int, arg2: Bool) }\n\npub fn main() {\n let Wibble(arg2:, ..) = todo\n}\n" --- pub type Wibble { Wibble(arg1: Int, arg2: Bool) } pub fn main() { let Wibble(arg2:, ..) = todo ▔↑▔▔▔ } ----- Hover content (markdown) ----- ```gleam Bool ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_local_function.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nfn my_fn() {\n Nil\n}\n\nfn main() {\n my_fn\n}\n" --- fn my_fn() { Nil } fn main() { my_fn ▔↑▔▔▔ } ----- Hover content (markdown) ----- ```gleam fn() -> Nil ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_local_function_in_pipe.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nfn add1(num: Int) -> Int {\n num + 1\n}\n\npub fn main() {\n add1(1)\n\n 1\n |> add1\n |> add1\n |> add1\n}\n" --- fn add1(num: Int) -> Int { num + 1 } pub fn main() { add1(1) ▔↑▔▔ 1 |> add1 |> add1 |> add1 } ----- Hover content (markdown) ----- ```gleam fn(Int) -> Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_local_function_in_pipe_1.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nfn add1(num: Int) -> Int {\n num + 1\n}\n\npub fn main() {\n add1(1)\n\n 1\n |> add1\n |> add1\n |> add1\n}\n" --- fn add1(num: Int) -> Int { num + 1 } pub fn main() { add1(1) 1 |> add1 ▔▔↑▔ |> add1 |> add1 } ----- Hover content (markdown) ----- ```gleam fn(Int) -> Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_local_function_in_pipe_2.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nfn add1(num: Int) -> Int {\n num + 1\n}\n\npub fn main() {\n add1(1)\n\n 1\n |> add1\n |> add1\n |> add1\n}\n" --- fn add1(num: Int) -> Int { num + 1 } pub fn main() { add1(1) 1 |> add1 |> add1 ▔▔↑▔ |> add1 } ----- Hover content (markdown) ----- ```gleam fn(Int) -> Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_local_function_in_pipe_3.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nfn add1(num: Int) -> Int {\n num + 1\n}\n\npub fn main() {\n add1(1)\n\n 1\n |> add1\n |> add1\n |> add1\n}\n" --- fn add1(num: Int) -> Int { num + 1 } pub fn main() { add1(1) 1 |> add1 |> add1 |> add1 ▔▔↑▔ } ----- Hover content (markdown) ----- ```gleam fn(Int) -> Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_module_constant.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\n/// Exciting documentation\n/// Maybe even multiple lines\nconst one = 1\n" --- /// Exciting documentation /// Maybe even multiple lines const one = 1 ▔▔▔▔▔▔↑▔▔ ----- Hover content (markdown) ----- ```gleam Int ``` Exciting documentation Maybe even multiple lines ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_module_constant_annotation.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\n/// Exciting documentation\n/// Maybe even multiple lines\nconst one: Int = 1\n" --- /// Exciting documentation /// Maybe even multiple lines const one: Int = 1 ▔▔↑ ----- Hover content (markdown) ----- ```gleam gleam.Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_on_pipe_with_invalid_step.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\npub fn main() {\n [1, 2, 3]\n |> map(wibble)\n |> filter(fn(value) { value })\n}\n\nfn map(list: List(a), fun: fn(a) -> b) -> List(b) {}\nfn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) {}\n" --- pub fn main() { [1, 2, 3] ↑▔▔▔▔▔▔▔▔ |> map(wibble) |> filter(fn(value) { value }) } fn map(list: List(a), fun: fn(a) -> b) -> List(b) {} fn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) {} ----- Hover content (markdown) ----- ```gleam List(Int) ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_on_pipe_with_invalid_step_1.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\npub fn main() {\n [1, 2, 3]\n |> map(wibble)\n |> filter(fn(value) { value })\n}\n\nfn map(list: List(a), fun: fn(a) -> b) -> List(b) {}\nfn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) {}\n" --- pub fn main() { [1, 2, 3] ↑ |> map(wibble) |> filter(fn(value) { value }) } fn map(list: List(a), fun: fn(a) -> b) -> List(b) {} fn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) {} ----- Hover content (markdown) ----- ```gleam Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_on_pipe_with_invalid_step_2.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\npub fn main() {\n [1, 2, 3]\n |> map(wibble)\n |> filter(fn(value) { value })\n}\n\nfn map(list: List(a), fun: fn(a) -> b) -> List(b) {}\nfn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) {}\n" --- pub fn main() { [1, 2, 3] |> map(wibble) ↑▔▔ |> filter(fn(value) { value }) } fn map(list: List(a), fun: fn(a) -> b) -> List(b) {} fn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) {} ----- Hover content (markdown) ----- ```gleam fn(List(Int), fn(Int) -> Bool) -> List(Bool) ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_on_pipe_with_invalid_step_3.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\npub fn main() {\n [1, 2, 3]\n |> map(wibble)\n |> filter(fn(value) { value })\n}\n\nfn map(list: List(a), fun: fn(a) -> b) -> List(b) {}\nfn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) {}\n" --- pub fn main() { [1, 2, 3] |> map(wibble) ↑▔▔▔▔▔ |> filter(fn(value) { value }) } fn map(list: List(a), fun: fn(a) -> b) -> List(b) {} fn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) {} ----- Hover content (markdown) ----- ```gleam fn(Int) -> Bool ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_on_pipe_with_invalid_step_4.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\npub fn main() {\n [1, 2, 3]\n |> map(wibble)\n |> filter(fn(value) { value })\n}\n\nfn map(list: List(a), fun: fn(a) -> b) -> List(b) {}\nfn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) {}\n" --- pub fn main() { [1, 2, 3] |> map(wibble) |> filter(fn(value) { value }) ↑▔▔▔▔▔ } fn map(list: List(a), fun: fn(a) -> b) -> List(b) {} fn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) {} ----- Hover content (markdown) ----- ```gleam fn(List(Bool), fn(Bool) -> Bool) -> List(Bool) ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_on_pipe_with_invalid_step_5.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\npub fn main() {\n [1, 2, 3]\n |> map(wibble)\n |> filter(fn(value) { value })\n}\n\nfn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo }\nfn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) { todo }\n" --- pub fn main() { [1, 2, 3] |> map(wibble) |> filter(fn(value) { value }) ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ } fn map(list: List(a), fun: fn(a) -> b) -> List(b) { todo } fn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) { todo } ----- Hover content (markdown) ----- ```gleam fn(Bool) -> Bool ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_on_pipe_with_invalid_step_6.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\npub fn main() {\n [1, 2, 3]\n |> wibble\n |> filter(fn(value) { value })\n}\n\nfn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) { todo }\n" --- pub fn main() { [1, 2, 3] |> wibble ↑▔▔▔▔▔ |> filter(fn(value) { value }) } fn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) { todo } ----- Hover content (markdown) ----- ```gleam fn(List(Int)) -> List(Bool) ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_on_pipe_with_invalid_step_8.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\npub fn main() {\n [1, 2, 3]\n |> wibble\n |> filter(fn(value) { value })\n}\n\nfn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) { todo }\n" --- pub fn main() { [1, 2, 3] |> wibble |> filter(fn(value) { value }) ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ } fn filter(list: List(a), fun: fn(a) -> Bool) -> List(a) { todo } ----- Hover content (markdown) ----- ```gleam fn(Bool) -> Bool ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_over_block_in_list_spread.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\npub fn main() {\n [1, 2, ..{\n let x = 1\n [x]\n }]\n}\n" --- pub fn main() { [1, 2, ..{ let x = 1 ↑ [x] }] } ----- Hover content (markdown) ----- ```gleam Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_over_imported_module.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport wibble\n" --- import wibble ▔▔▔▔▔▔▔↑▔▔▔▔▔ ----- Hover content (markdown) ----- ```gleam wibble ``` This is the wibble module. Here is some documentation about it. This module does stuff View on [HexDocs](https://hexdocs.pm/hex/wibble.html) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_over_module_name.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport wibble\n\npub fn main() {\n wibble.wibble()\n}\n" --- import wibble pub fn main() { wibble.wibble() ↑▔▔▔▔▔ } ----- Hover content (markdown) ----- ```gleam wibble ``` This is the wibble module. Here is some documentation about it. This module does stuff View on [HexDocs](https://hexdocs.pm/hex/wibble.html) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_over_module_name_in_annotation.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport wibble\n\npub fn main(w: wibble.Wibble) {\n todo\n}\n" --- import wibble pub fn main(w: wibble.Wibble) { ↑▔▔▔▔▔ todo } ----- Hover content (markdown) ----- ```gleam wibble ``` This is the wibble module. Here is some documentation about it. This module does stuff View on [HexDocs](https://hexdocs.pm/hex/wibble.html) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_over_module_with_path.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport wibble/wobble\n\npub fn main() {\n wobble.wibble()\n}\n" --- import wibble/wobble pub fn main() { wobble.wibble() ↑▔▔▔▔▔ } ----- Hover content (markdown) ----- ```gleam wibble/wobble ``` The module documentation View on [HexDocs](https://hexdocs.pm/hex/wibble/wobble.html) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_prelude_type.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nconst number = 100\n" --- const number = 100 ▔▔▔▔▔▔▔▔▔↑▔▔ ----- Hover content (markdown) ----- ```gleam Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_print_alias_when_parameters_match.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\ntype MyResult(a, b) = Result(a, b)\n\nfn do_thing() -> MyResult(Int, Int) {\n Error(1)\n}\n" --- type MyResult(a, b) = Result(a, b) fn do_thing() -> MyResult(Int, Int) { ▔▔▔↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Error(1) } ----- Hover content (markdown) ----- ```gleam fn() -> MyResult(Int, Int) ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_print_aliased_imported_generic_type.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport gleam/option.{type Option as Maybe}\n\nconst none: Maybe(Int) = option.None\n" --- import gleam/option.{type Option as Maybe} const none: Maybe(Int) = option.None ▔▔▔▔▔▔▔▔▔↑▔▔▔▔▔▔▔▔▔▔▔▔ ----- Hover content (markdown) ----- ```gleam Maybe(Int) ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_print_imported_alias.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport aliases.{type Aliased}\nconst thing: Aliased = 10\n" --- import aliases.{type Aliased} const thing: Aliased = 10 ▔▔▔▔▔▔▔▔▔▔↑▔▔▔▔▔▔▔▔▔ ----- Hover content (markdown) ----- ```gleam Aliased ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_print_qualified_prelude_type_when_shadowed_by_alias.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\ntype Result = #(Bool, String)\nconst ok = Ok(10)\n" --- type Result = #(Bool, String) const ok = Ok(10) ▔▔▔▔▔▔▔↑ ----- Hover content (markdown) ----- ```gleam gleam.Result(Int, a) ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_print_qualified_prelude_type_when_shadowed_by_imported_alias.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport alias.{type Bool}\nconst value = True\n" --- import alias.{type Bool} const value = True ▔▔▔▔▔▔↑▔▔▔▔ ----- Hover content (markdown) ----- ```gleam gleam.Bool ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_print_type_variable_names.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nfn main(value: Result(ok, error)) {\n let v = value\n v\n}\n" --- fn main(value: Result(ok, error)) { let v = value ↑ v } ----- Hover content (markdown) ----- ```gleam Result(ok, error) ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_print_unbound_type_variable_name_without_conflicts.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nfn make_ok(value: a) {\n let result = Ok(value)\n result\n}\n" --- fn make_ok(value: a) { let result = Ok(value) ▔▔↑▔▔▔ result } ----- Hover content (markdown) ----- ```gleam Result(a, b) ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_print_unbound_type_variable_names.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nfn make_ok(value: some_type) {\n let result = Ok(value)\n result\n}\n" --- fn make_ok(value: some_type) { let result = Ok(value) ▔▔↑▔▔▔ result } ----- Hover content (markdown) ----- ```gleam Result(some_type, a) ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_print_underlying_for_alias_with_parameters.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\ntype LocalResult = Result(String, Int)\n\nfn do_thing() -> LocalResult {\n Error(1)\n}\n" --- type LocalResult = Result(String, Int) fn do_thing() -> LocalResult { ▔▔▔↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Error(1) } ----- Hover content (markdown) ----- ```gleam fn() -> Result(String, Int) ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_print_underlying_for_imported_alias.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport alias.{type A}\n\nfn wibble() -> Result(Int, String) {\n todo\n}\n" --- import alias.{type A} fn wibble() -> Result(Int, String) { ▔▔▔▔▔▔▔↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ todo } ----- Hover content (markdown) ----- ```gleam fn() -> Result(Int, String) ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_shadowed_prelude_type.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\ntype Int { Int }\nconst number = 100\n" --- type Int { Int } const number = 100 ▔▔▔▔▔▔▔▔▔↑▔▔ ----- Hover content (markdown) ----- ```gleam gleam.Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_shadowed_prelude_type_imported.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport numbers.{type Int}\nconst number = 100\n" --- import numbers.{type Int} const number = 100 ▔▔▔▔▔▔▔▔▔↑▔▔ ----- Hover content (markdown) ----- ```gleam gleam.Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_type_alias_annotation.snap ================================================ --- source: language-server/src/tests/hover.rs expression: type Wibble = Int --- type Wibble = Int ▔↑▔ ----- Hover content (markdown) ----- ```gleam gleam.Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_type_constructor_annotation.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\ntype Wibble {\n Wibble(arg: String)\n}\n" --- type Wibble { Wibble(arg: String) ▔▔▔▔↑▔ } ----- Hover content (markdown) ----- ```gleam gleam.String ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_type_constructor_with_label.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\n/// Exciting documentation\n/// Maybe even multiple lines\ntype Wibble {\n /// Some more exciting documentation\n Wibble(arg: String)\n /// The most exciting documentation\n Wobble(Int)\n}\n" --- /// Exciting documentation /// Maybe even multiple lines type Wibble { /// Some more exciting documentation Wibble(arg: String) ↑▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ /// The most exciting documentation Wobble(Int) } ----- Hover content (markdown) ----- ```gleam Wibble(arg: String) ``` Some more exciting documentation ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_type_constructor_with_no_fields.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\n/// Exciting documentation\n/// Maybe even multiple lines\ntype Wibble {\n /// Some more exciting documentation\n Wibble\n}\n" --- /// Exciting documentation /// Maybe even multiple lines type Wibble { /// Some more exciting documentation Wibble ↑▔▔▔▔▔ } ----- Hover content (markdown) ----- ```gleam Wibble ``` Some more exciting documentation ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_type_constructor_with_no_label.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\n/// Exciting documentation\n/// Maybe even multiple lines\ntype Wibble {\n /// Some more exciting documentation\n Wibble(arg: String)\n /// The most exciting documentation\n Wobble(Int)\n}\n" --- /// Exciting documentation /// Maybe even multiple lines type Wibble { /// Some more exciting documentation Wibble(arg: String) /// The most exciting documentation Wobble(Int) ↑▔▔▔▔▔▔▔▔▔▔ } ----- Hover content (markdown) ----- ```gleam Wobble(Int) ``` The most exciting documentation ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_variable_in_use_expression.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nfn b(fun: fn(Int) -> String) {\n fun(42)\n}\n\nfn do_stuff() {\n let c = \"done\"\n\n use a <- b\n c\n}\n" --- fn b(fun: fn(Int) -> String) { fun(42) } fn do_stuff() { let c = "done" use a <- b ↑ c } ----- Hover content (markdown) ----- ```gleam Int ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_variable_in_use_expression_1.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nfn b(fun: fn(Int) -> String) {\n fun(42)\n}\n\nfn do_stuff() {\n let c = \"done\"\n\n use a <- b\n c\n}\n" --- fn b(fun: fn(Int) -> String) { fun(42) } fn do_stuff() { let c = "done" use a <- b ↑ c } ----- Hover content (markdown) ----- ```gleam fn(fn(Int) -> String) -> String ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_variable_in_use_expression_2.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nfn b(fun: fn(Int) -> String) {\n fun(42)\n}\n\nfn do_stuff() {\n let c = \"done\"\n\n use a <- b\n c\n}\n" --- fn b(fun: fn(Int) -> String) { fun(42) } fn do_stuff() { let c = "done" use a <- b c ↑ } ----- Hover content (markdown) ----- ```gleam String ``` A locally defined variable. ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__hover_works_even_for_invalid_code.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nfn invalid() { 1 + Nil }\nfn valid() { Nil }\n" --- fn invalid() { 1 + Nil } fn valid() { Nil } ▔▔▔↑▔▔▔▔▔▔ ----- Hover content (markdown) ----- ```gleam fn() -> Nil ``` ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__no_documentation_for_shared_record_field.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\npub type Wibble {\n Wibble(\n /// This is some documentation about the wibble field.\n wibble: Int\n )\n Wobble(\n /// Here's some documentation explaining a field of Wobble\n wibble: Int\n )\n}\n\npub fn wibble(w: Wibble) {\n w.wibble\n}\n" --- pub type Wibble { Wibble( /// This is some documentation about the wibble field. wibble: Int ) Wobble( /// Here's some documentation explaining a field of Wobble wibble: Int ) } pub fn wibble(w: Wibble) { w.wibble ▔▔▔▔▔▔↑▔ } ----- Hover content (markdown) ----- ```gleam Int ``` ## Wibble This is some documentation about the wibble field. ## Wobble Here's some documentation explaining a field of Wobble ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__no_hexdocs_link_when_hovering_over_local_module.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\nimport wibble\n" --- import wibble ▔▔▔▔▔▔▔↑▔▔▔▔▔ ----- Hover content (markdown) ----- ```gleam wibble ``` This is the wibble module. Here is some documentation about it. This module does stuff ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__hover__record_field_documentation.snap ================================================ --- source: language-server/src/tests/hover.rs expression: "\npub type Wibble {\n Wibble(\n /// This is some documentation about the wibble field.\n wibble: Int\n )\n}\n\npub fn wibble(w: Wibble) {\n w.wibble\n}\n" --- pub type Wibble { Wibble( /// This is some documentation about the wibble field. wibble: Int ) } pub fn wibble(w: Wibble) { w.wibble ▔▔▔▔▔▔↑▔ } ----- Hover content (markdown) ----- ```gleam Int ``` This is some documentation about the wibble field. ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_aliased_const.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\npub const wibble = 123\n\npub fn main() {\n wibble\n}\n" --- -- mod.gleam import app.{wibble as other} ▔▔▔▔▔▔ fn wobble() { other ▔▔▔▔▔ } -- app.gleam pub const wibble = 123 ▔▔▔▔▔▔ pub fn main() { wibble ↑▔▔▔▔▔ } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_aliased_function.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\npub fn wibble() {\n 123\n}\n\npub fn main() {\n wibble()\n}\n" --- -- mod.gleam import app.{wibble as other} ▔▔▔▔▔▔ fn wobble() { other() ▔▔▔▔▔ } -- app.gleam pub fn wibble() { ▔▔▔▔▔▔ 123 } pub fn main() { wibble() ↑▔▔▔▔▔ } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_aliased_type.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\npub type Wibble { Wibble }\n\npub fn main() -> Wibble {\n todo\n}\n" --- -- mod.gleam import app.{type Wibble as Wobble} ▔▔▔▔▔▔ fn wobble() -> Wobble { ▔▔▔▔▔▔ todo } fn other(w: app.Wibble) { ▔▔▔▔▔▔ todo } -- app.gleam pub type Wibble { Wibble } ▔▔▔▔▔▔ pub fn main() -> Wibble { ↑▔▔▔▔▔ todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_aliased_value.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\npub type Wibble { Wibble }\n\npub fn main() {\n Wibble\n}\n" --- -- mod.gleam import app.{Wibble as Wobble} ▔▔▔▔▔▔ fn wobble() { Wobble ▔▔▔▔▔▔ } -- app.gleam pub type Wibble { Wibble } ↑▔▔▔▔▔ pub fn main() { Wibble ▔▔▔▔▔▔ } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_constant_from_qualified_reference.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\nimport mod\n\npub fn main() {\n let value = mod.wibble\n mod.wibble + value\n}\n" --- -- mod.gleam pub const wibble = 10 ▔▔▔▔▔▔ fn wobble() { wibble ▔▔▔▔▔▔ } -- app.gleam import mod pub fn main() { let value = mod.wibble ↑▔▔▔▔▔ mod.wibble + value ▔▔▔▔▔▔ } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_constant_from_unqualified_reference.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\nimport mod.{wibble}\n\npub fn main() {\n let value = mod.wibble\n wibble + value\n}\n" --- -- mod.gleam pub const wibble = 10 ▔▔▔▔▔▔ fn wobble() { wibble ▔▔▔▔▔▔ } -- app.gleam import mod.{wibble} ▔▔▔▔▔▔ pub fn main() { let value = mod.wibble ▔▔▔▔▔▔ wibble + value ↑▔▔▔▔▔ } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_function_from_qualified_reference.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\nimport mod\n\npub fn main() {\n let value = mod.wibble()\n mod.wibble()\n value\n}\n" --- -- mod.gleam pub fn wibble() { ▔▔▔▔▔▔ wibble() ▔▔▔▔▔▔ } -- app.gleam import mod pub fn main() { let value = mod.wibble() ↑▔▔▔▔▔ mod.wibble() ▔▔▔▔▔▔ value } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_function_from_unqualified_reference.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\nimport mod.{wibble}\n\npub fn main() {\n let value = wibble()\n mod.wibble()\n value\n}\n" --- -- mod.gleam pub fn wibble() { ▔▔▔▔▔▔ wibble() ▔▔▔▔▔▔ } -- app.gleam import mod.{wibble} ▔▔▔▔▔▔ pub fn main() { let value = wibble() ↑▔▔▔▔▔ mod.wibble() ▔▔▔▔▔▔ value } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_local_variable.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\npub fn main() {\n let wibble = 10\n let wobble = wibble + 1\n wibble + wobble\n}\n" --- -- app.gleam pub fn main() { let wibble = 10 ▔▔▔▔▔▔ let wobble = wibble + 1 ↑▔▔▔▔▔ wibble + wobble ▔▔▔▔▔▔ } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_local_variable_from_definition.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\npub fn main() {\n let wibble = 10\n let wobble = wibble + 1\n wibble + wobble\n}\n" --- -- app.gleam pub fn main() { let wibble = 10 ↑▔▔▔▔▔ let wobble = wibble + 1 ▔▔▔▔▔▔ wibble + wobble ▔▔▔▔▔▔ } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_local_variable_from_guard.snap ================================================ --- source: language-server/src/tests/reference.rs expression: "\npub fn main() {\n let wibble = True\n let wobble = False\n case wibble {\n True if wobble -> !wibble\n False if !wobble -> wibble\n _ -> wobble\n }\n}\n" --- -- app.gleam pub fn main() { let wibble = True let wobble = False ▔▔▔▔▔▔ case wibble { True if wobble -> !wibble ▔↑▔▔▔▔ False if !wobble -> wibble ▔▔▔▔▔▔ _ -> wobble ▔▔▔▔▔▔ } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_module_select_from_guard.snap ================================================ --- source: language-server/src/tests/reference.rs expression: "\nimport mod\n\npub fn main() {\n let wibble = True\n case wibble {\n True if mod.wibble < 5 -> !wibble\n False if mod.wibble != 10 -> wibble\n _ -> mod.wibble + 1\n }\n}\n" --- -- mod.gleam pub const wibble = 10 ▔▔▔▔▔▔ -- app.gleam import mod pub fn main() { let wibble = True case wibble { True if mod.wibble < 5 -> !wibble ↑▔▔▔▔▔ False if mod.wibble != 10 -> wibble ▔▔▔▔▔▔ _ -> mod.wibble + 1 ▔▔▔▔▔▔ } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_prefix_string_alias_and_suffix_complex_guard.snap ================================================ --- source: language-server/src/tests/reference.rs expression: "\nfn main() {\n case \"1-wibble\" {\n \"1\" as digit <> rest if digit == \"1\" && rest == \"-wibble\" -> #(digit, rest)\n _ -> #(\"\", \"\")\n }\n}\n" --- -- app.gleam fn main() { case "1-wibble" { "1" as digit <> rest if digit == "1" && rest == "-wibble" -> #(digit, rest) ↑▔▔▔▔ ▔▔▔▔▔ ▔▔▔▔▔ _ -> #("", "") } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_prefix_string_alias_in_case.snap ================================================ --- source: language-server/src/tests/reference.rs expression: "\npub fn main() -> String {\n let wibble = \"1-wibble\"\n let digit = case wibble {\n \"1\" as digit <> _rest -> digit\n other -> other\n }\n digit\n}\n" --- -- app.gleam pub fn main() -> String { let wibble = "1-wibble" let digit = case wibble { "1" as digit <> _rest -> digit ↑▔▔▔▔ ▔▔▔▔▔ other -> other } digit } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_prefix_string_alias_in_case_triggered_from_usage.snap ================================================ --- source: language-server/src/tests/reference.rs expression: "\npub fn main() -> String {\n let wibble = \"1-wibble\"\n let digit = case wibble {\n \"1\" as digit <> _rest -> digit\n other -> other\n }\n digit\n}\n" --- -- app.gleam pub fn main() -> String { let wibble = "1-wibble" let digit = case wibble { "1" as digit <> _rest -> digit ▔▔▔▔▔ ↑▔▔▔▔ other -> other } digit } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_prefix_string_alias_in_let_assert.snap ================================================ --- source: language-server/src/tests/reference.rs expression: "\npub fn main() -> String {\n let assert \"1\" as digit <> _rest = \"1-wibble\"\n digit\n}\n" --- -- app.gleam pub fn main() -> String { let assert "1" as digit <> _rest = "1-wibble" ↑▔▔▔▔ digit ▔▔▔▔▔ } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_prefix_string_alias_in_let_assert_triggered_from_usage.snap ================================================ --- source: language-server/src/tests/reference.rs expression: "\npub fn main() -> String {\n let assert \"1\" as digit <> _rest = \"1-wibble\"\n digit\n}\n" --- -- app.gleam pub fn main() -> String { let assert "1" as digit <> _rest = "1-wibble" ▔▔▔▔▔ digit ↑▔▔▔▔ } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_prefix_string_alias_used_in_guard.snap ================================================ --- source: language-server/src/tests/reference.rs expression: "\nfn main() {\n case \"1-wibble\" {\n \"1\" as digit <> _rest if digit == \"1\" -> digit\n _ -> \"\"\n }\n}\n" --- -- app.gleam fn main() { case "1-wibble" { "1" as digit <> _rest if digit == "1" -> digit ↑▔▔▔▔ ▔▔▔▔▔ ▔▔▔▔▔ _ -> "" } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_prefix_string_alias_with_alternative_definitions_in_case.snap ================================================ --- source: language-server/src/tests/reference.rs expression: "\npub fn main() -> String {\n let wibble = \"1-wibble\"\n let digit = case wibble {\n \"1\" as digit <> _rest | \"2\" as digit <> _rest -> digit\n other -> other\n }\n digit\n}\n" --- -- app.gleam pub fn main() -> String { let wibble = "1-wibble" let digit = case wibble { "1" as digit <> _rest | "2" as digit <> _rest -> digit ↑▔▔▔▔ ▔▔▔▔▔ ▔▔▔▔▔ other -> other } digit } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_prefix_string_alias_with_alternative_definitions_triggered_from_second_pattern.snap ================================================ --- source: language-server/src/tests/reference.rs expression: "\npub fn main() -> String {\n let wibble = \"1-wibble\"\n let digit = case wibble {\n \"1\" as digit <> _rest | \"2\" as digit <> _rest -> digit\n other -> other\n }\n digit\n}\n" --- -- app.gleam pub fn main() -> String { let wibble = "1-wibble" let digit = case wibble { "1" as digit <> _rest | "2" as digit <> _rest -> digit ▔▔▔▔▔ ↑▔▔▔▔ ▔▔▔▔▔ other -> other } digit } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_prefix_string_alias_with_alternative_definitions_triggered_from_usage.snap ================================================ --- source: language-server/src/tests/reference.rs expression: "\npub fn main() -> String {\n let wibble = \"1-wibble\"\n let digit = case wibble {\n \"1\" as digit <> _rest | \"2\" as digit <> _rest -> digit\n other -> other\n }\n digit\n}\n" --- -- app.gleam pub fn main() -> String { let wibble = "1-wibble" let digit = case wibble { "1" as digit <> _rest | "2" as digit <> _rest -> digit ▔▔▔▔▔ ▔▔▔▔▔ ↑▔▔▔▔ other -> other } digit } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_prefix_string_suffix_shadowing_outer_variable.snap ================================================ --- source: language-server/src/tests/reference.rs expression: "\nfn main() {\n let rest = \"outer\"\n case \"1-wibble\" {\n \"1\" <> rest -> rest\n _ -> rest\n }\n}\n" --- -- app.gleam fn main() { let rest = "outer" case "1-wibble" { "1" <> rest -> rest ↑▔▔▔ ▔▔▔▔ _ -> rest } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_prefix_string_suffix_used_in_guard.snap ================================================ --- source: language-server/src/tests/reference.rs expression: "\nfn main() {\n case \"1-wibble\" {\n \"1\" <> rest if rest == \"-wibble\" -> rest\n _ -> \"\"\n }\n}\n" --- -- app.gleam fn main() { case "1-wibble" { "1" <> rest if rest == "-wibble" -> rest ↑▔▔▔ ▔▔▔▔ ▔▔▔▔ _ -> "" } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_prefix_string_suffix_variable_in_case.snap ================================================ --- source: language-server/src/tests/reference.rs expression: "\npub fn main() -> String {\n let wibble = \"1-wibble\"\n let rest = case wibble {\n \"1\" <> rest -> rest\n other -> other\n }\n rest\n}\n" --- -- app.gleam pub fn main() -> String { let wibble = "1-wibble" let rest = case wibble { "1" <> rest -> rest ↑▔▔▔ ▔▔▔▔ other -> other } rest } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_prefix_string_suffix_variable_in_case_triggered_from_usage.snap ================================================ --- source: language-server/src/tests/reference.rs expression: "\npub fn main() -> String {\n let wibble = \"1-wibble\"\n let rest = case wibble {\n \"1\" <> rest -> rest\n other -> other\n }\n rest\n}\n" --- -- app.gleam pub fn main() -> String { let wibble = "1-wibble" let rest = case wibble { "1" <> rest -> rest ▔▔▔▔ ↑▔▔▔ other -> other } rest } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_prefix_string_suffix_variable_in_let_assert.snap ================================================ --- source: language-server/src/tests/reference.rs expression: "\npub fn main() -> String {\n let assert \"1\" <> rest = \"1-wibble\"\n rest\n}\n" --- -- app.gleam pub fn main() -> String { let assert "1" <> rest = "1-wibble" ↑▔▔▔ rest ▔▔▔▔ } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_prefix_string_suffix_variable_in_let_assert_triggered_from_usage.snap ================================================ --- source: language-server/src/tests/reference.rs expression: "\npub fn main() -> String {\n let assert \"1\" <> rest = \"1-wibble\"\n rest\n}\n" --- -- app.gleam pub fn main() -> String { let assert "1" <> rest = "1-wibble" ▔▔▔▔ rest ↑▔▔▔ } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_prefix_string_suffix_variable_nested_in_tuple.snap ================================================ --- source: language-server/src/tests/reference.rs expression: "\nfn main() {\n case #(\"1-wibble\", 0) {\n #(\"1\" <> rest, _) -> rest\n _ -> \"\"\n }\n}\n" --- -- app.gleam fn main() { case #("1-wibble", 0) { #("1" <> rest, _) -> rest ↑▔▔▔ ▔▔▔▔ _ -> "" } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_prefix_string_suffix_variable_with_alternative_definition_in_case.snap ================================================ --- source: language-server/src/tests/reference.rs expression: "\npub fn main() -> String {\n let wibble = \"1-wibble\"\n let rest = case wibble {\n \"1\" <> rest | \"2\" <> rest -> rest\n other -> other\n }\n rest\n}\n" --- -- app.gleam pub fn main() -> String { let wibble = "1-wibble" let rest = case wibble { "1" <> rest | "2" <> rest -> rest ↑▔▔▔ ▔▔▔▔ ▔▔▔▔ other -> other } rest } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_prefix_string_suffix_variable_with_alternative_definition_triggered_from_second_pattern.snap ================================================ --- source: language-server/src/tests/reference.rs expression: "\npub fn main() -> String {\n let wibble = \"1-wibble\"\n let rest = case wibble {\n \"1\" <> rest | \"2\" <> rest -> rest\n other -> other\n }\n rest\n}\n" --- -- app.gleam pub fn main() -> String { let wibble = "1-wibble" let rest = case wibble { "1" <> rest | "2" <> rest -> rest ▔▔▔▔ ↑▔▔▔ ▔▔▔▔ other -> other } rest } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_prefix_string_suffix_variable_with_alternative_definition_triggered_from_usage.snap ================================================ --- source: language-server/src/tests/reference.rs expression: "\npub fn main() -> String {\n let wibble = \"1-wibble\"\n let rest = case wibble {\n \"1\" <> rest | \"2\" <> rest -> rest\n other -> other\n }\n rest\n}\n" --- -- app.gleam pub fn main() -> String { let wibble = "1-wibble" let rest = case wibble { "1" <> rest | "2" <> rest -> rest ▔▔▔▔ ▔▔▔▔ ↑▔▔▔ other -> other } rest } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_private_constant.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\nconst wibble = 10\n\npub fn main() {\n let _ = wibble\n wibble + 4\n}\n\nfn wobble() {\n wibble + wobble()\n}\n" --- -- app.gleam const wibble = 10 ↑▔▔▔▔▔ pub fn main() { let _ = wibble ▔▔▔▔▔▔ wibble + 4 ▔▔▔▔▔▔ } fn wobble() { wibble + wobble() ▔▔▔▔▔▔ } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_private_constant_from_reference.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\nconst wibble = 10\n\npub fn main() {\n let _ = wibble\n wibble + 4\n}\n\nfn wobble() {\n wibble + wobble()\n}\n" --- -- app.gleam const wibble = 10 ▔▔▔▔▔▔ pub fn main() { let _ = wibble ↑▔▔▔▔▔ wibble + 4 ▔▔▔▔▔▔ } fn wobble() { wibble + wobble() ▔▔▔▔▔▔ } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_private_function.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\nfn wibble() {\n wibble()\n}\n\npub fn main() {\n let _ = wibble()\n wibble() + 4\n}\n\nfn wobble() {\n wibble() || wobble()\n}\n" --- -- app.gleam fn wibble() { ↑▔▔▔▔▔ wibble() ▔▔▔▔▔▔ } pub fn main() { let _ = wibble() ▔▔▔▔▔▔ wibble() + 4 ▔▔▔▔▔▔ } fn wobble() { wibble() || wobble() ▔▔▔▔▔▔ } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_private_function_from_reference.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\nfn wibble() {\n wibble()\n}\n\npub fn main() {\n let _ = wibble()\n wibble() + 4\n}\n\nfn wobble() {\n wibble() || wobble()\n}\n" --- -- app.gleam fn wibble() { ▔▔▔▔▔▔ wibble() ↑▔▔▔▔▔ } pub fn main() { let _ = wibble() ▔▔▔▔▔▔ wibble() + 4 ▔▔▔▔▔▔ } fn wobble() { wibble() || wobble() ▔▔▔▔▔▔ } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_private_type.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\ntype Wibble { Wibble }\n\nfn main() -> Wibble {\n todo\n}\n\nfn wobble(w: Wibble) {\n todo\n}\n" --- -- app.gleam type Wibble { Wibble } ↑▔▔▔▔▔ fn main() -> Wibble { ▔▔▔▔▔▔ todo } fn wobble(w: Wibble) { ▔▔▔▔▔▔ todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_private_type_from_reference.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\ntype Wibble { Wibble }\n\nfn main() -> Wibble {\n todo\n}\n\nfn wobble(w: Wibble) {\n todo\n}\n" --- -- app.gleam type Wibble { Wibble } ▔▔▔▔▔▔ fn main() -> Wibble { ↑▔▔▔▔▔ todo } fn wobble(w: Wibble) { ▔▔▔▔▔▔ todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_private_type_variant.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\ntype Wibble { Wibble }\n\nfn main() {\n let _ = Wibble\n Wibble\n}\n\nfn wobble() {\n Wibble\n wobble()\n}\n" --- -- app.gleam type Wibble { Wibble } ↑▔▔▔▔▔ fn main() { let _ = Wibble ▔▔▔▔▔▔ Wibble ▔▔▔▔▔▔ } fn wobble() { Wibble ▔▔▔▔▔▔ wobble() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_private_type_variant_from_reference.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\ntype Wibble { Wibble }\n\nfn main() {\n let _ = Wibble\n Wibble\n}\n\nfn wobble() {\n Wibble\n wobble()\n}\n" --- -- app.gleam type Wibble { Wibble } ▔▔▔▔▔▔ fn main() { let _ = Wibble ↑▔▔▔▔▔ Wibble ▔▔▔▔▔▔ } fn wobble() { Wibble ▔▔▔▔▔▔ wobble() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_public_constant.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\npub const wibble = 10\n\npub fn main() {\n wibble\n}\n" --- -- mod.gleam import app.{wibble} ▔▔▔▔▔▔ fn wobble() { app.wibble ▔▔▔▔▔▔ } fn other() { wibble ▔▔▔▔▔▔ } -- app.gleam pub const wibble = 10 ▔▔▔▔▔▔ pub fn main() { wibble ↑▔▔▔▔▔ } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_public_function.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\npub fn wibble() {\n wibble()\n}\n" --- -- mod.gleam import app.{wibble} ▔▔▔▔▔▔ fn wobble() { app.wibble() ▔▔▔▔▔▔ } fn other() { wibble() ▔▔▔▔▔▔ } -- app.gleam pub fn wibble() { ▔▔▔▔▔▔ wibble() ↑▔▔▔▔▔ } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_public_type.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\npub type Wibble { Wibble }\n\npub fn main() -> Wibble {\n todo\n}\n" --- -- mod.gleam import app.{type Wibble} ▔▔▔▔▔▔ fn wobble() -> Wibble { ▔▔▔▔▔▔ todo } fn other(w: app.Wibble) { ▔▔▔▔▔▔ todo } -- app.gleam pub type Wibble { Wibble } ↑▔▔▔▔▔ pub fn main() -> Wibble { ▔▔▔▔▔▔ todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_public_type_variant.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\npub type Wibble { Wibble }\n\npub fn main() {\n Wibble\n}\n" --- -- mod.gleam import app.{Wibble} ▔▔▔▔▔▔ fn wobble() { app.Wibble ▔▔▔▔▔▔ } fn other() { Wibble ▔▔▔▔▔▔ } -- app.gleam pub type Wibble { Wibble } ↑▔▔▔▔▔ pub fn main() { Wibble ▔▔▔▔▔▔ } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_type_from_let_annotation.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\nimport mod.{type Wibble}\n\npub fn main() -> Wibble {\n let _: mod.Wibble = todo\n}\n" --- -- mod.gleam pub type Wibble { Wibble } ▔▔▔▔▔▔ fn wobble() -> Wibble { ▔▔▔▔▔▔ todo } -- app.gleam import mod.{type Wibble} ▔▔▔▔▔▔ pub fn main() -> Wibble { ▔▔▔▔▔▔ let _: mod.Wibble = todo ↑▔▔▔▔▔ } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_type_from_qualified_reference.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\nimport mod\n\npub fn main() -> mod.Wibble {\n let _: mod.Wibble = todo\n}\n" --- -- mod.gleam pub type Wibble { Wibble } ▔▔▔▔▔▔ fn wobble() -> Wibble { ▔▔▔▔▔▔ todo } -- app.gleam import mod pub fn main() -> mod.Wibble { ↑▔▔▔▔▔ let _: mod.Wibble = todo ▔▔▔▔▔▔ } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_type_from_unqualified_reference.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\nimport mod.{type Wibble}\n\npub fn main() -> Wibble {\n let _: mod.Wibble = todo\n}\n" --- -- mod.gleam pub type Wibble { Wibble } ▔▔▔▔▔▔ fn wobble() -> Wibble { ▔▔▔▔▔▔ todo } -- app.gleam import mod.{type Wibble} ▔▔▔▔▔▔ pub fn main() -> Wibble { ↑▔▔▔▔▔ let _: mod.Wibble = todo ▔▔▔▔▔▔ } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_type_variant_from_qualified_reference.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\nimport mod\n\npub fn main() {\n let value = mod.Wibble\n mod.Wibble\n value\n}\n" --- -- mod.gleam pub type Wibble { Wibble } ▔▔▔▔▔▔ fn wobble() { Wibble ▔▔▔▔▔▔ } -- app.gleam import mod pub fn main() { let value = mod.Wibble ↑▔▔▔▔▔ mod.Wibble ▔▔▔▔▔▔ value } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__reference__references_for_type_variant_from_unqualified_reference.snap ================================================ --- source: compiler-core/src/language_server/tests/reference.rs expression: "\nimport mod.{Wibble}\n\npub fn main() {\n let value = mod.Wibble\n Wibble\n}\n" --- -- mod.gleam pub type Wibble { Wibble } ▔▔▔▔▔▔ fn wobble() { Wibble ▔▔▔▔▔▔ } -- app.gleam import mod.{Wibble} ▔▔▔▔▔▔ pub fn main() { let value = mod.Wibble ▔▔▔▔▔▔ Wibble ↑▔▔▔▔▔ } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__alias_imported_module_from_guard.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nimport mod\n\npub fn main() {\n let wibble = True\n case wibble {\n True if mod.wibble < 5 -> !wibble\n False if mod.wibble != 10 -> wibble\n _ -> mod.wibble + 1\n }\n}\n" --- ----- BEFORE RENAME -- mod.gleam pub const wibble = 10 -- app.gleam import mod pub fn main() { let wibble = True case wibble { True if mod.wibble < 5 -> !wibble ↑▔▔ False if mod.wibble != 10 -> wibble _ -> mod.wibble + 1 } } ----- AFTER RENAME -- mod.gleam pub const wibble = 10 -- app.gleam import mod as module pub fn main() { let wibble = True case wibble { True if module.wibble < 5 -> !wibble False if module.wibble != 10 -> wibble _ -> module.wibble + 1 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__no_rename_constant_with_invalid_name.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\nconst value = 10\n" --- Error response message: Ten is not a valid name ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__no_rename_function_with_invalid_name.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn main() {\n let wibble = 10\n wibble\n}\n" --- Error response message: Not_AValid_Name is not a valid name ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__no_rename_invalid_name.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn main() {\n let wibble = 10\n wibble\n}\n" --- Error response message: Not_AValid_Name is not a valid name ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__no_rename_type_variant_with_invalid_name.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub type Wibble {\n Constructor(Int)\n}\n" --- Error response message: name_in_snake_case is not a valid name ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__no_rename_type_with_invalid_name.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\ntype Wibble { Wobble }\n" --- Error response message: a_type_name is not a valid name ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__reanem_module_from_import_with_unqualified_values.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "import option . { Some, None }" --- ----- BEFORE RENAME -- option.gleam pub type Option(a) { Some(a) None } -- app.gleam import option . { Some, None } ↑▔▔▔▔▔ ----- AFTER RENAME -- option.gleam pub type Option(a) { Some(a) None } -- app.gleam import option . { Some, None } as opt ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_aliased_constant.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub const something = 10\n\npub fn main() {\n something + { 4 * something }\n}\n" --- ----- BEFORE RENAME -- mod.gleam import app.{something as some_constant} fn wibble() { some_constant } -- app.gleam pub const something = 10 ↑▔▔▔▔▔▔▔▔ pub fn main() { something + { 4 * something } } ----- AFTER RENAME -- mod.gleam import app.{ten as some_constant} fn wibble() { some_constant } -- app.gleam pub const ten = 10 pub fn main() { ten + { 4 * ten } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_aliased_function.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn something() {\n something()\n}\n\nfn something_else() {\n something()\n}\n" --- ----- BEFORE RENAME -- mod.gleam import app.{something as something_else} fn wibble() { something_else() } -- app.gleam pub fn something() { ↑▔▔▔▔▔▔▔▔ something() } fn something_else() { something() } ----- AFTER RENAME -- mod.gleam import app.{some_function as something_else} fn wibble() { something_else() } -- app.gleam pub fn some_function() { some_function() } fn something_else() { some_function() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_aliased_type.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub type Wibble { Constructor }\n\npub fn main(w: Wibble) -> Wibble { todo }\n" --- ----- BEFORE RENAME -- mod.gleam import app.{type Wibble as Wobble} fn wibble() -> Wobble { todo } -- app.gleam pub type Wibble { Constructor } ↑▔▔▔▔▔ pub fn main(w: Wibble) -> Wibble { todo } ----- AFTER RENAME -- mod.gleam import app.{type SomeType as Wobble} fn wibble() -> Wobble { todo } -- app.gleam pub type SomeType { Constructor } pub fn main(w: SomeType) -> SomeType { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_aliased_type_variant.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub type Wibble {\n Constructor(Int)\n}\n\npub fn main() {\n Constructor(42)\n}\n" --- ----- BEFORE RENAME -- mod.gleam import app.{Constructor as ValueConstructor} fn wibble() { ValueConstructor(172) } -- app.gleam pub type Wibble { Constructor(Int) ↑▔▔▔▔▔▔▔▔▔▔ } pub fn main() { Constructor(42) } ----- AFTER RENAME -- mod.gleam import app.{MakeAWibble as ValueConstructor} fn wibble() { ValueConstructor(172) } -- app.gleam pub type Wibble { MakeAWibble(Int) } pub fn main() { MakeAWibble(42) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_aliased_value.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub type Wibble { Wibble }\n\npub fn main() {\n Wibble\n}\n" --- ----- BEFORE RENAME -- mod.gleam import app.{Wibble as Wobble} fn wobble() { Wobble } -- app.gleam pub type Wibble { Wibble } ↑▔▔▔▔▔ pub fn main() { Wibble } ----- AFTER RENAME -- mod.gleam import app.{Wubble as Wobble} fn wobble() { Wobble } -- app.gleam pub type Wibble { Wubble } pub fn main() { Wubble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_alternative_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn main(x) {\n case x {\n #(wibble, [wobble]) | #(wobble, [wibble, _]) | #(_, [wibble, wobble, ..]) ->\n wibble + wobble\n _ -> 0\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn main(x) { case x { #(wibble, [wobble]) | #(wobble, [wibble, _]) | #(_, [wibble, wobble, ..]) -> ↑▔▔▔▔▔ wibble + wobble _ -> 0 } } ----- AFTER RENAME -- app.gleam pub fn main(x) { case x { #(new_name, [wobble]) | #(wobble, [new_name, _]) | #(_, [new_name, wobble, ..]) -> new_name + wobble _ -> 0 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_alternative_pattern_alias_and_variable_1.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn main(x) {\n case x {\n [] as list | [_, ..list] -> list\n _ -> []\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn main(x) { case x { [] as list | [_, ..list] -> list ↑▔▔▔ _ -> [] } } ----- AFTER RENAME -- app.gleam pub fn main(x) { case x { [] as new_name | [_, ..new_name] -> new_name _ -> [] } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_alternative_pattern_alias_and_variable_2.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn main(x) {\n case x {\n [] as list | [_, ..list] -> list\n _ -> []\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn main(x) { case x { [] as list | [_, ..list] -> list ↑▔▔▔ _ -> [] } } ----- AFTER RENAME -- app.gleam pub fn main(x) { case x { [] as new_name | [_, ..new_name] -> new_name _ -> [] } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_alternative_pattern_alias_and_variable_3.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn main(x) {\n case x {\n [_, ..list] | [] as list -> list\n _ -> []\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn main(x) { case x { [_, ..list] | [] as list -> list ↑▔▔▔ _ -> [] } } ----- AFTER RENAME -- app.gleam pub fn main(x) { case x { [_, ..new_name] | [] as new_name -> new_name _ -> [] } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_alternative_pattern_alias_and_variable_4.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn main(x) {\n case x {\n [_, ..list] | [] as list -> list\n _ -> []\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn main(x) { case x { [_, ..list] | [] as list -> list ↑▔▔▔ _ -> [] } } ----- AFTER RENAME -- app.gleam pub fn main(x) { case x { [_, ..new_name] | [] as new_name -> new_name _ -> [] } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_alternative_pattern_aliases.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn main(x) {\n case x {\n [] as list | [_] as list -> list\n _ -> []\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn main(x) { case x { [] as list | [_] as list -> list ↑▔▔▔ _ -> [] } } ----- AFTER RENAME -- app.gleam pub fn main(x) { case x { [] as new_name | [_] as new_name -> new_name _ -> [] } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_alternative_pattern_aliases_from_alternative.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn main(x) {\n case x {\n [] as list | [_] as list -> list\n _ -> []\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn main(x) { case x { [] as list | [_] as list -> list ↑▔▔▔ _ -> [] } } ----- AFTER RENAME -- app.gleam pub fn main(x) { case x { [] as new_name | [_] as new_name -> new_name _ -> [] } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_alternative_pattern_aliases_from_usage.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn main(x) {\n case x {\n [] as list | [_] as list -> list\n _ -> []\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn main(x) { case x { [] as list | [_] as list -> list ↑▔▔▔ _ -> [] } } ----- AFTER RENAME -- app.gleam pub fn main(x) { case x { [] as new_name | [_] as new_name -> new_name _ -> [] } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_alternative_pattern_from_usage.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn main(x) {\n case x {\n #(wibble, [wobble]) | #(wobble, [wibble, _]) | #(_, [wibble, wobble, ..]) ->\n wibble + wobble\n _ -> 0\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn main(x) { case x { #(wibble, [wobble]) | #(wobble, [wibble, _]) | #(_, [wibble, wobble, ..]) -> wibble + wobble ↑▔▔▔▔▔ _ -> 0 } } ----- AFTER RENAME -- app.gleam pub fn main(x) { case x { #(new_name, [wobble]) | #(wobble, [new_name, _]) | #(_, [new_name, wobble, ..]) -> new_name + wobble _ -> 0 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_constant_from_definition.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub const something = 10\n\npub fn main() {\n something + { 4 * something }\n}\n" --- ----- BEFORE RENAME -- mod.gleam import app fn wibble() { app.something } -- app.gleam pub const something = 10 ↑▔▔▔▔▔▔▔▔ pub fn main() { something + { 4 * something } } ----- AFTER RENAME -- mod.gleam import app fn wibble() { app.ten } -- app.gleam pub const ten = 10 pub fn main() { ten + { 4 * ten } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_constant_from_qualified_reference.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\nimport mod\n\npub fn main() {\n mod.something\n}\n" --- ----- BEFORE RENAME -- mod.gleam pub const something = 10 fn wibble() { something } -- app.gleam import mod pub fn main() { mod.something ↑▔▔▔▔▔▔▔▔ } ----- AFTER RENAME -- mod.gleam pub const ten = 10 fn wibble() { ten } -- app.gleam import mod pub fn main() { mod.ten } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_constant_from_reference.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub const something = 10\n\npub fn main() {\n something + { 4 * something }\n}\n" --- ----- BEFORE RENAME -- mod.gleam import app fn wibble() { app.something } -- app.gleam pub const something = 10 pub fn main() { something + { 4 * something } ↑▔▔▔▔▔▔▔▔ } ----- AFTER RENAME -- mod.gleam import app fn wibble() { app.ten } -- app.gleam pub const ten = 10 pub fn main() { ten + { 4 * ten } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_constant_from_unqualified_reference.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\nimport mod.{something}\n\npub fn main() {\n something + mod.something\n}\n" --- ----- BEFORE RENAME -- mod.gleam pub const something = 10 fn wibble() { something } -- app.gleam import mod.{something} pub fn main() { something + mod.something ↑▔▔▔▔▔▔▔▔ } ----- AFTER RENAME -- mod.gleam pub const something = 10 fn wibble() { something } -- app.gleam import mod.{something as ten} pub fn main() { ten + mod.something } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_constant_shadowed_by_field_access.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub const something = 10\n" --- ----- BEFORE RENAME -- mod.gleam import app type App { App(something: Int) } pub fn main() { let app = App(10) app.something } -- app.gleam pub const something = 10 ↑▔▔▔▔▔▔▔▔ ----- AFTER RENAME -- mod.gleam import app type App { App(something: Int) } pub fn main() { let app = App(10) app.something } -- app.gleam pub const constant = 10 ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_constant_shadowing_module.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\nimport gleam/list\n\nconst list = []\n\npub fn main() {\n list.map(todo, todo)\n}\n " --- ----- BEFORE RENAME -- app.gleam import gleam/list const list = [] ↑▔▔▔ pub fn main() { list.map(todo, todo) } ----- AFTER RENAME -- app.gleam import gleam/list const empty_list = [] pub fn main() { list.map(todo, todo) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_custom_type_variant_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub type Type {\n X\n Y\n}\n\npub fn main(t) {\n case t {\n X -> 0\n Y -> 0\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam pub type Type { X ↑ Y } pub fn main(t) { case t { X -> 0 Y -> 0 } } ----- AFTER RENAME -- app.gleam pub type Type { Renamed Y } pub fn main(t) { case t { Renamed -> 0 Y -> 0 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_external_function.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn main() { wibble() }\n\n@external(erlang, \"a\", \"a\")\nfn wibble() -> Nil\n" --- ----- BEFORE RENAME -- app.gleam pub fn main() { wibble() } @external(erlang, "a", "a") fn wibble() -> Nil ↑▔▔▔▔▔ ----- AFTER RENAME -- app.gleam pub fn main() { new_name() } @external(erlang, "a", "a") fn new_name() -> Nil ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_external_javascript_function_with_pure_gleam_fallback.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn main() { wibble() }\n\n@external(javascript, \"a\", \"a\")\nfn wibble() -> Nil {\n Nil\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn main() { wibble() } @external(javascript, "a", "a") fn wibble() -> Nil { ↑▔▔▔▔▔ Nil } ----- AFTER RENAME -- app.gleam pub fn main() { new_name() } @external(javascript, "a", "a") fn new_name() -> Nil { Nil } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_function_from_definition.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn something() {\n something()\n}\n\nfn something_else() {\n something()\n}\n" --- ----- BEFORE RENAME -- mod.gleam import app fn wibble() { app.something() } -- app.gleam pub fn something() { ↑▔▔▔▔▔▔▔▔ something() } fn something_else() { something() } ----- AFTER RENAME -- mod.gleam import app fn wibble() { app.some_function() } -- app.gleam pub fn some_function() { some_function() } fn something_else() { some_function() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_function_from_qualified_reference.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\nimport mod\n\npub fn main() {\n mod.wibble()\n}\n" --- ----- BEFORE RENAME -- mod.gleam pub fn wibble() { wibble() } -- app.gleam import mod pub fn main() { mod.wibble() ↑▔▔▔▔▔ } ----- AFTER RENAME -- mod.gleam pub fn some_function() { some_function() } -- app.gleam import mod pub fn main() { mod.some_function() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_function_from_reference.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn something() {\n something()\n}\n\nfn something_else() {\n something()\n}\n" --- ----- BEFORE RENAME -- mod.gleam import app fn wibble() { app.something() } -- app.gleam pub fn something() { something() ↑▔▔▔▔▔▔▔▔ } fn something_else() { something() } ----- AFTER RENAME -- mod.gleam import app fn wibble() { app.some_function() } -- app.gleam pub fn some_function() { some_function() } fn something_else() { some_function() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_function_from_unqualified_reference.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\nimport mod.{wibble}\n\npub fn main() {\n wibble()\n mod.wibble()\n}\n" --- ----- BEFORE RENAME -- mod.gleam pub fn wibble() { wibble() } -- app.gleam import mod.{wibble} pub fn main() { wibble() ↑▔▔▔▔▔ mod.wibble() } ----- AFTER RENAME -- mod.gleam pub fn wibble() { wibble() } -- app.gleam import mod.{wibble as some_function} pub fn main() { some_function() mod.wibble() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_function_shadowed_by_field_access.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn something() {\n todo\n}\n" --- ----- BEFORE RENAME -- mod.gleam import app type App { App(something: Int) } pub fn main() { let app = App(10) app.something } -- app.gleam pub fn something() { ↑▔▔▔▔▔▔▔▔ todo } ----- AFTER RENAME -- mod.gleam import app type App { App(something: Int) } pub fn main() { let app = App(10) app.something } -- app.gleam pub fn function() { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_function_shadowing_module.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\nimport gleam/list\n\npub fn list() {\n []\n}\n\npub fn main() {\n list.map(todo, todo)\n}\n " --- ----- BEFORE RENAME -- app.gleam import gleam/list pub fn list() { ↑▔▔▔ [] } pub fn main() { list.map(todo, todo) } ----- AFTER RENAME -- app.gleam import gleam/list pub fn empty_list() { [] } pub fn main() { list.map(todo, todo) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_imported_custom_type_variant_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub type Type {\n X\n Y\n}\n" --- ----- BEFORE RENAME -- other.gleam import app pub fn main(t) { case t { app.X -> 0 app.Y -> 0 } } -- app.gleam pub type Type { X ↑ Y } ----- AFTER RENAME -- other.gleam import app pub fn main(t) { case t { app.Renamed -> 0 app.Y -> 0 } } -- app.gleam pub type Type { Renamed Y } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_imported_unqualified_custom_type_variant_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub type Type {\n X\n Y\n}\n" --- ----- BEFORE RENAME -- other.gleam import app.{X, Y} pub fn main(t) { case t { X -> 0 Y -> 0 } } -- app.gleam pub type Type { X ↑ Y } ----- AFTER RENAME -- other.gleam import app.{Renamed, Y} pub fn main(t) { case t { Renamed -> 0 Y -> 0 } } -- app.gleam pub type Type { Renamed Y } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_local_variable.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn main() {\n let wibble = 10\n wibble\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn main() { let wibble = 10 wibble ↑▔▔▔▔▔ } ----- AFTER RENAME -- app.gleam pub fn main() { let wobble = 10 wobble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_local_variable_argument.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn add(first_number: Int, x: Int) -> Int {\n x + first_number\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn add(first_number: Int, x: Int) -> Int { x + first_number ↑ } ----- AFTER RENAME -- app.gleam pub fn add(first_number: Int, second_number: Int) -> Int { second_number + first_number } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_local_variable_argument_from_definition.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn wibble(wibble: Float) {\n wibble /. 0.3\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn wibble(wibble: Float) { ↑▔▔▔▔▔ wibble /. 0.3 } ----- AFTER RENAME -- app.gleam pub fn wibble(wobble: Float) { wobble /. 0.3 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_local_variable_assignment_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn main() {\n let assert Error(12 as something) = Error(12)\n something\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn main() { let assert Error(12 as something) = Error(12) something ↑▔▔▔▔▔▔▔▔ } ----- AFTER RENAME -- app.gleam pub fn main() { let assert Error(12 as the_error) = Error(12) the_error } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_local_variable_from_bit_array_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn starts_with(bits: BitArray, prefix: BitArray) -> Bool {\n let prefix_size = bit_size(prefix)\n\n case bits {\n <> if pref == prefix -> True\n _ -> False\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn starts_with(bits: BitArray, prefix: BitArray) -> Bool { let prefix_size = bit_size(prefix) case bits { <> if pref == prefix -> True ↑▔▔▔▔▔▔▔▔▔▔ _ -> False } } ----- AFTER RENAME -- app.gleam pub fn starts_with(bits: BitArray, prefix: BitArray) -> Bool { let size_of_prefix = bit_size(prefix) case bits { <> if pref == prefix -> True _ -> False } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_local_variable_from_definition.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn main() {\n let wibble = 10\n let wobble = wibble + 1\n wobble - wibble\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn main() { let wibble = 10 ↑▔▔▔▔▔ let wobble = wibble + 1 wobble - wibble } ----- AFTER RENAME -- app.gleam pub fn main() { let some_value = 10 let wobble = some_value + 1 wobble - some_value } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_local_variable_from_definition_assignment_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn main() {\n let assert Error(12 as something) = Error(12)\n something\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn main() { let assert Error(12 as something) = Error(12) ↑▔▔▔▔▔▔▔▔ something } ----- AFTER RENAME -- app.gleam pub fn main() { let assert Error(12 as the_error) = Error(12) the_error } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_local_variable_from_definition_nested_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn main() {\n let assert Ok([_, wibble, ..]) = Error(12)\n wibble\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn main() { let assert Ok([_, wibble, ..]) = Error(12) ↑▔▔▔▔▔ wibble } ----- AFTER RENAME -- app.gleam pub fn main() { let assert Ok([_, second_element, ..]) = Error(12) second_element } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_local_variable_from_guard.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\npub fn main() {\n let wibble = True\n let wobble = False\n case wibble {\n True if wobble -> !wibble\n False if !wobble -> wibble\n _ -> wobble\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn main() { let wibble = True let wobble = False case wibble { True if wobble -> !wibble ↑▔▔▔▔▔ False if !wobble -> wibble _ -> wobble } } ----- AFTER RENAME -- app.gleam pub fn main() { let wibble = True let something_else = False case wibble { True if something_else -> !wibble False if !something_else -> wibble _ -> something_else } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_local_variable_from_label_shorthand.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\ntype Wibble {\n Wibble(wibble: Int)\n}\n\npub fn main() {\n let wibble = todo\n Wibble(wibble:)\n}\n" --- ----- BEFORE RENAME -- app.gleam type Wibble { Wibble(wibble: Int) } pub fn main() { let wibble = todo Wibble(wibble:) ↑▔▔▔▔▔ } ----- AFTER RENAME -- app.gleam type Wibble { Wibble(wibble: Int) } pub fn main() { let wobble = todo Wibble(wibble: wobble) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_local_variable_guard_clause.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn main() {\n let wibble = True\n case Nil {\n Nil if wibble -> todo\n _ -> panic\n }\n wibble || False\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn main() { let wibble = True case Nil { Nil if wibble -> todo _ -> panic } wibble || False ↑▔▔▔▔▔ } ----- AFTER RENAME -- app.gleam pub fn main() { let wobble = True case Nil { Nil if wobble -> todo _ -> panic } wobble || False } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_local_variable_in_bit_array_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn starts_with(bits: BitArray, prefix: BitArray) -> Bool {\n let prefix_size = bit_size(prefix)\n\n case bits {\n <> if pref == prefix -> True\n _ -> False\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn starts_with(bits: BitArray, prefix: BitArray) -> Bool { let prefix_size = bit_size(prefix) ↑▔▔▔▔▔▔▔▔▔▔ case bits { <> if pref == prefix -> True _ -> False } } ----- AFTER RENAME -- app.gleam pub fn starts_with(bits: BitArray, prefix: BitArray) -> Bool { let size_of_prefix = bit_size(prefix) case bits { <> if pref == prefix -> True _ -> False } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_local_variable_label_shorthand.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\ntype Wibble {\n Wibble(wibble: Int)\n}\n\npub fn main() {\n let Wibble(wibble:) = todo\n wibble + 1\n}\n" --- ----- BEFORE RENAME -- app.gleam type Wibble { Wibble(wibble: Int) } pub fn main() { let Wibble(wibble:) = todo wibble + 1 ↑▔▔▔▔▔ } ----- AFTER RENAME -- app.gleam type Wibble { Wibble(wibble: Int) } pub fn main() { let Wibble(wibble: wobble) = todo wobble + 1 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_local_variable_label_shorthand_from_definition.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\ntype Wibble {\n Wibble(wibble: Int)\n}\n\npub fn main() {\n let Wibble(wibble:) = todo\n wibble + 1\n}\n" --- ----- BEFORE RENAME -- app.gleam type Wibble { Wibble(wibble: Int) } pub fn main() { let Wibble(wibble:) = todo ↑▔▔▔▔▔ wibble + 1 } ----- AFTER RENAME -- app.gleam type Wibble { Wibble(wibble: Int) } pub fn main() { let Wibble(wibble: wobble) = todo wobble + 1 } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_local_variable_record_access.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\ntype Wibble {\n Wibble(wibble: Int)\n}\n\npub fn main() {\n let wibble = Wibble(wibble: 1)\n wibble.wibble\n}\n" --- ----- BEFORE RENAME -- app.gleam type Wibble { Wibble(wibble: Int) } pub fn main() { let wibble = Wibble(wibble: 1) wibble.wibble ↑▔▔▔▔▔ } ----- AFTER RENAME -- app.gleam type Wibble { Wibble(wibble: Int) } pub fn main() { let wobble = Wibble(wibble: 1) wobble.wibble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_local_variable_with_label_shorthand.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub type Wibble {\n Wibble(first: Int, second: Int)\n}\n\npub fn main() {\n let second = 2\n Wibble(first: 1, second:)\n}\n" --- ----- BEFORE RENAME -- app.gleam pub type Wibble { Wibble(first: Int, second: Int) } pub fn main() { let second = 2 ↑▔▔▔▔▔ Wibble(first: 1, second:) } ----- AFTER RENAME -- app.gleam pub type Wibble { Wibble(first: Int, second: Int) } pub fn main() { let something = 2 Wibble(first: 1, second: something) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_module_access_in_clause_guard.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub const something = 10\n" --- ----- BEFORE RENAME -- wibble.gleam import app pub fn main() { case app.something { thing if thing == app.something -> True _ -> False } } -- app.gleam pub const something = 10 ↑▔▔▔▔▔▔▔▔ ----- AFTER RENAME -- wibble.gleam import app pub fn main() { case app.new_name { thing if thing == app.new_name -> True _ -> False } } -- app.gleam pub const new_name = 10 ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_module_from_alias_use.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nimport maths as m\n\npub fn main() {\n echo m .pi\n}\n" --- ----- BEFORE RENAME -- maths.gleam pub const pi = 3.14 -- app.gleam import maths as m pub fn main() { echo m .pi ↑ } ----- AFTER RENAME -- maths.gleam pub const pi = 3.14 -- app.gleam import maths as mth pub fn main() { echo mth .pi } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_module_from_constant_in_clause_guard.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nimport maths\n\npub fn count_pi(list) {\n case list {\n [number, ..rest] if number == maths . pi -> 1 + count_pi(rest)\n [_, ..rest] -> count_pi(rest)\n [] -> 0\n }\n}\n" --- ----- BEFORE RENAME -- maths.gleam pub const pi = 3.14 -- app.gleam import maths pub fn count_pi(list) { case list { [number, ..rest] if number == maths . pi -> 1 + count_pi(rest) ↑▔▔▔▔ [_, ..rest] -> count_pi(rest) [] -> 0 } } ----- AFTER RENAME -- maths.gleam pub const pi = 3.14 -- app.gleam import maths as m pub fn count_pi(list) { case list { [number, ..rest] if number == m . pi -> 1 + count_pi(rest) [_, ..rest] -> count_pi(rest) [] -> 0 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_module_from_constant_in_const.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nimport maths\n\nconst x = maths . pi\n" --- ----- BEFORE RENAME -- maths.gleam pub const pi = 3.14 -- app.gleam import maths const x = maths . pi ↑▔▔▔▔ ----- AFTER RENAME -- maths.gleam pub const pi = 3.14 -- app.gleam import maths as m const x = m . pi ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_module_from_constant_in_expression.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nimport maths\n\npub fn main() {\n echo maths . pi\n}\n" --- ----- BEFORE RENAME -- maths.gleam pub const pi = 3.14 -- app.gleam import maths pub fn main() { echo maths . pi ↑▔▔▔▔ } ----- AFTER RENAME -- maths.gleam pub const pi = 3.14 -- app.gleam import maths as m pub fn main() { echo m . pi } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_module_from_function_call.snap ================================================ --- source: language-server/src/tests/rename.rs assertion_line: 2130 expression: "\nimport option\n\npub fn main() {\n option.is_some(option.Some(1))\n}\n" snapshot_kind: text --- ----- BEFORE RENAME -- option.gleam pub type Option(a) { Some(a) None } pub fn is_some(option: Option(a)) -> Bool { case option { Some(_) -> True None -> False } } -- app.gleam import option pub fn main() { option.is_some(option.Some(1)) ↑▔▔▔▔▔ } ----- AFTER RENAME -- option.gleam pub type Option(a) { Some(a) None } pub fn is_some(option: Option(a)) -> Bool { case option { Some(_) -> True None -> False } } -- app.gleam import option as opt pub fn main() { opt.is_some(opt.Some(1)) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_module_from_import.snap ================================================ --- source: language-server/src/tests/rename.rs expression: import option --- ----- BEFORE RENAME -- option.gleam pub type Option(a) { Some(a) None } -- app.gleam import option ↑▔▔▔▔▔ ----- AFTER RENAME -- option.gleam pub type Option(a) { Some(a) None } -- app.gleam import option as opt ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_module_from_import_namespaced.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "import std / option " --- ----- BEFORE RENAME -- std/option.gleam pub type Option(a) { Some(a) None } -- app.gleam import std / option ↑▔▔▔▔▔ ----- AFTER RENAME -- std/option.gleam pub type Option(a) { Some(a) None } -- app.gleam import std / option as opt ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_module_from_import_namespaced_with_alias.snap ================================================ --- source: language-server/src/tests/rename.rs expression: import std / option as opt --- ----- BEFORE RENAME -- std/option.gleam pub type Option(a) { Some(a) None } -- app.gleam import std / option as opt ↑▔▔ ----- AFTER RENAME -- std/option.gleam pub type Option(a) { Some(a) None } -- app.gleam import std / option as o ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_module_from_import_namespaced_with_unqualified_value_and_alias.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "import std / option . { Some, None } as opt" --- ----- BEFORE RENAME -- std/option.gleam pub type Option(a) { Some(a) None } -- app.gleam import std / option . { Some, None } as opt ↑▔▔ ----- AFTER RENAME -- std/option.gleam pub type Option(a) { Some(a) None } -- app.gleam import std / option . { Some, None } as o ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_module_from_import_namespaced_with_unqualified_values.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "import std / option . { Some, None }" --- ----- BEFORE RENAME -- std/option.gleam pub type Option(a) { Some(a) None } -- app.gleam import std / option . { Some, None } ↑▔▔▔▔▔ ----- AFTER RENAME -- std/option.gleam pub type Option(a) { Some(a) None } -- app.gleam import std / option . { Some, None } as opt ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_module_from_import_with_alias.snap ================================================ --- source: language-server/src/tests/rename.rs expression: import option as opt --- ----- BEFORE RENAME -- option.gleam pub type Option(a) { Some(a) None } -- app.gleam import option as opt ↑▔▔ ----- AFTER RENAME -- option.gleam pub type Option(a) { Some(a) None } -- app.gleam import option as o ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_module_from_import_with_alias_to_original_name.snap ================================================ --- source: language-server/src/tests/rename.rs expression: import option as opt --- ----- BEFORE RENAME -- option.gleam pub type Option(a) { Some(a) None } -- app.gleam import option as opt ↑▔▔ ----- AFTER RENAME -- option.gleam pub type Option(a) { Some(a) None } -- app.gleam import option ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_module_from_import_with_unqualified_value_and_alias.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "import option . {Some, None} as opt" --- ----- BEFORE RENAME -- option.gleam pub type Option(a) { Some(a) None } -- app.gleam import option . {Some, None} as opt ↑▔▔ ----- AFTER RENAME -- option.gleam pub type Option(a) { Some(a) None } -- app.gleam import option . {Some, None} as o ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_module_from_type_in_annotation.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nimport option\n\nconst x: option.Option(Int) = option.Some(1)\n" --- ----- BEFORE RENAME -- option.gleam pub type Option(a) { Some(a) None } -- app.gleam import option const x: option.Option(Int) = option.Some(1) ↑▔▔▔▔▔ ----- AFTER RENAME -- option.gleam pub type Option(a) { Some(a) None } -- app.gleam import option as opt const x: opt.Option(Int) = opt.Some(1) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_module_from_type_in_custom_type.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nimport option\n\ntype Value(a) {\n Value(option.Option(a))\n}\n" --- ----- BEFORE RENAME -- option.gleam pub type Option(a) { Some(a) None } -- app.gleam import option type Value(a) { Value(option.Option(a)) ↑▔▔▔▔▔ } ----- AFTER RENAME -- option.gleam pub type Option(a) { Some(a) None } -- app.gleam import option as opt type Value(a) { Value(opt.Option(a)) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_module_from_type_in_type_alias.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nimport option\n\ntype Option(a) =\n option.Option(a)\n" --- ----- BEFORE RENAME -- option.gleam pub type Option(a) { Some(a) None } -- app.gleam import option type Option(a) = option.Option(a) ↑▔▔▔▔▔ ----- AFTER RENAME -- option.gleam pub type Option(a) { Some(a) None } -- app.gleam import option as opt type Option(a) = opt.Option(a) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_module_from_variant_in_clause_guard.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nimport option\n\npub fn count_none(list) {\n case list {\n [option, ..rest] if option == option . None -> 1 + count_none(rest)\n [_, ..rest] -> count_none(rest)\n [] -> 0\n }\n}\n" --- ----- BEFORE RENAME -- option.gleam pub type Option(a) { Some(a) None } -- app.gleam import option pub fn count_none(list) { case list { [option, ..rest] if option == option . None -> 1 + count_none(rest) ↑▔▔▔▔▔ [_, ..rest] -> count_none(rest) [] -> 0 } } ----- AFTER RENAME -- option.gleam pub type Option(a) { Some(a) None } -- app.gleam import option as opt pub fn count_none(list) { case list { [option, ..rest] if option == opt . None -> 1 + count_none(rest) [_, ..rest] -> count_none(rest) [] -> 0 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_module_from_variant_in_const.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nimport option\n\nconst x = option .None\n" --- ----- BEFORE RENAME -- option.gleam pub type Option(a) { Some(a) None } -- app.gleam import option const x = option .None ↑▔▔▔▔▔ ----- AFTER RENAME -- option.gleam pub type Option(a) { Some(a) None } -- app.gleam import option as opt const x = opt .None ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_module_from_variant_in_expression.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nimport option\n\npub fn main() {\n echo option . None\n}\n" --- ----- BEFORE RENAME -- option.gleam pub type Option(a) { Some(a) None } -- app.gleam import option pub fn main() { echo option . None ↑▔▔▔▔▔ } ----- AFTER RENAME -- option.gleam pub type Option(a) { Some(a) None } -- app.gleam import option as opt pub fn main() { echo opt . None } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_module_from_variant_in_pattern.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nimport option\n\npub fn is_some(option) {\n case option {\n option. Some(_) -> True\n option .None -> False\n }\n}\n" --- ----- BEFORE RENAME -- option.gleam pub type Option(a) { Some(a) None } -- app.gleam import option pub fn is_some(option) { case option { option. Some(_) -> True ↑▔▔▔▔▔ option .None -> False } } ----- AFTER RENAME -- option.gleam pub type Option(a) { Some(a) None } -- app.gleam import option as opt pub fn is_some(option) { case option { opt. Some(_) -> True opt .None -> False } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_module_select_from_guard.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nimport mod\n\npub fn main() {\n let wibble = True\n case wibble {\n True if mod.wibble < 5 -> !wibble\n False if mod.wibble != 10 -> wibble\n _ -> mod.wibble + 1\n }\n}\n" --- ----- BEFORE RENAME -- mod.gleam pub const wibble = 10 -- app.gleam import mod pub fn main() { let wibble = True case wibble { True if mod.wibble < 5 -> !wibble ↑▔▔▔▔▔ False if mod.wibble != 10 -> wibble _ -> mod.wibble + 1 } } ----- AFTER RENAME -- mod.gleam pub const ten = 10 -- app.gleam import mod pub fn main() { let wibble = True case wibble { True if mod.ten < 5 -> !wibble False if mod.ten != 10 -> wibble _ -> mod.ten + 1 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_nested_aliased_pattern.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\npub fn go(x) {\n case x {\n [[nested, ..] as wibble, ..] -> todo\n _ -> todo\n }\n}\n " --- ----- BEFORE RENAME -- app.gleam pub fn go(x) { case x { [[nested, ..] as wibble, ..] -> todo ↑▔▔▔▔▔ _ -> todo } } ----- AFTER RENAME -- app.gleam pub fn go(x) { case x { [[new_name, ..] as wibble, ..] -> todo _ -> todo } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_prefix_string_alias_and_suffix_complex_guard.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nfn main() {\n case \"1-wibble\" {\n \"1\" as digit <> rest if digit == \"1\" && rest == \"-wibble\" -> #(digit, rest)\n _ -> #(\"\", \"\")\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam fn main() { case "1-wibble" { "1" as digit <> rest if digit == "1" && rest == "-wibble" -> #(digit, rest) ↑▔▔▔▔ _ -> #("", "") } } ----- AFTER RENAME -- app.gleam fn main() { case "1-wibble" { "1" as new_name <> rest if new_name == "1" && rest == "-wibble" -> #(new_name, rest) _ -> #("", "") } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_prefix_string_alias_in_case.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nfn main() -> String {\n let wibble = \"1-wibble\"\n case wibble {\n \"1\" as digit <> rest -> digit <> rest\n other -> other\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam fn main() -> String { let wibble = "1-wibble" case wibble { "1" as digit <> rest -> digit <> rest ↑▔▔▔▔ other -> other } } ----- AFTER RENAME -- app.gleam fn main() -> String { let wibble = "1-wibble" case wibble { "1" as new_name <> rest -> new_name <> rest other -> other } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_prefix_string_alias_in_case_triggered_from_usage.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nfn main() -> String {\n let wibble = \"1-wibble\"\n case wibble {\n \"1\" as digit <> rest -> digit <> rest\n other -> other\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam fn main() -> String { let wibble = "1-wibble" case wibble { "1" as digit <> rest -> digit <> rest ↑▔▔▔▔ other -> other } } ----- AFTER RENAME -- app.gleam fn main() -> String { let wibble = "1-wibble" case wibble { "1" as new_name <> rest -> new_name <> rest other -> other } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_prefix_string_alias_in_let_assert.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nfn main() -> String {\n let assert \"1\" as digit <> rest = \"1-wibble\"\n digit\n}\n" --- ----- BEFORE RENAME -- app.gleam fn main() -> String { let assert "1" as digit <> rest = "1-wibble" ↑▔▔▔▔ digit } ----- AFTER RENAME -- app.gleam fn main() -> String { let assert "1" as new_name <> rest = "1-wibble" new_name } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_prefix_string_alias_in_let_assert_triggered_from_usage.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nfn main() -> String {\n let assert \"1\" as digit <> rest = \"1-wibble\"\n digit\n}\n" --- ----- BEFORE RENAME -- app.gleam fn main() -> String { let assert "1" as digit <> rest = "1-wibble" digit ↑▔▔▔▔ } ----- AFTER RENAME -- app.gleam fn main() -> String { let assert "1" as new_name <> rest = "1-wibble" new_name } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_prefix_string_alias_used_in_guard.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nfn main() {\n case \"1-wibble\" {\n \"1\" as digit <> _rest if digit == \"1\" -> digit\n _ -> \"\"\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam fn main() { case "1-wibble" { "1" as digit <> _rest if digit == "1" -> digit ↑▔▔▔▔ _ -> "" } } ----- AFTER RENAME -- app.gleam fn main() { case "1-wibble" { "1" as new_name <> _rest if new_name == "1" -> new_name _ -> "" } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_prefix_string_alias_with_alternative_definitions_in_case.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nfn main() -> String {\n let wibble = \"1-wibble\"\n case wibble {\n \"1\" as digit <> rest | \"2\" as digit <> rest -> digit <> rest\n other -> other\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam fn main() -> String { let wibble = "1-wibble" case wibble { "1" as digit <> rest | "2" as digit <> rest -> digit <> rest ↑▔▔▔▔ other -> other } } ----- AFTER RENAME -- app.gleam fn main() -> String { let wibble = "1-wibble" case wibble { "1" as new_name <> rest | "2" as new_name <> rest -> new_name <> rest other -> other } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_prefix_string_alias_with_alternative_definitions_triggered_from_second_pattern.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nfn main() -> String {\n let wibble = \"1-wibble\"\n case wibble {\n \"1\" as digit <> rest | \"2\" as digit <> rest -> digit <> rest\n other -> other\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam fn main() -> String { let wibble = "1-wibble" case wibble { "1" as digit <> rest | "2" as digit <> rest -> digit <> rest ↑▔▔▔▔ other -> other } } ----- AFTER RENAME -- app.gleam fn main() -> String { let wibble = "1-wibble" case wibble { "1" as new_name <> rest | "2" as new_name <> rest -> new_name <> rest other -> other } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_prefix_string_suffix_shadowing_outer_variable.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nfn main() {\n let rest = \"outer\"\n case \"1-wibble\" {\n \"1\" <> rest -> rest\n _ -> rest\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam fn main() { let rest = "outer" case "1-wibble" { "1" <> rest -> rest ↑▔▔▔ _ -> rest } } ----- AFTER RENAME -- app.gleam fn main() { let rest = "outer" case "1-wibble" { "1" <> new_name -> new_name _ -> rest } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_prefix_string_suffix_used_in_guard.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nfn main() {\n case \"1-wibble\" {\n \"1\" <> rest if rest == \"-wibble\" -> rest\n _ -> \"\"\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam fn main() { case "1-wibble" { "1" <> rest if rest == "-wibble" -> rest ↑▔▔▔ _ -> "" } } ----- AFTER RENAME -- app.gleam fn main() { case "1-wibble" { "1" <> new_name if new_name == "-wibble" -> new_name _ -> "" } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_prefix_string_suffix_variable_in_case.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nfn main() -> String {\n let wibble = \"1-wibble\"\n case wibble {\n \"1\" <> rest -> rest\n other -> other\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam fn main() -> String { let wibble = "1-wibble" case wibble { "1" <> rest -> rest ↑▔▔▔ other -> other } } ----- AFTER RENAME -- app.gleam fn main() -> String { let wibble = "1-wibble" case wibble { "1" <> new_name -> new_name other -> other } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_prefix_string_suffix_variable_in_case_triggered_from_usage.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nfn main() -> String {\n let wibble = \"1-wibble\"\n case wibble {\n \"1\" <> rest -> rest\n other -> other\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam fn main() -> String { let wibble = "1-wibble" case wibble { "1" <> rest -> rest ↑▔▔▔ other -> other } } ----- AFTER RENAME -- app.gleam fn main() -> String { let wibble = "1-wibble" case wibble { "1" <> new_name -> new_name other -> other } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_prefix_string_suffix_variable_in_let_assert.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nfn main() -> String {\n let assert \"1\" <> rest = \"1-wibble\"\n rest\n}\n" --- ----- BEFORE RENAME -- app.gleam fn main() -> String { let assert "1" <> rest = "1-wibble" ↑▔▔▔ rest } ----- AFTER RENAME -- app.gleam fn main() -> String { let assert "1" <> new_name = "1-wibble" new_name } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_prefix_string_suffix_variable_in_let_assert_triggered_from_usage.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nfn main() -> String {\n let assert \"1\" <> rest = \"1-wibble\"\n rest\n}\n" --- ----- BEFORE RENAME -- app.gleam fn main() -> String { let assert "1" <> rest = "1-wibble" rest ↑▔▔▔ } ----- AFTER RENAME -- app.gleam fn main() -> String { let assert "1" <> new_name = "1-wibble" new_name } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_prefix_string_suffix_variable_nested_in_tuple.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nfn main() {\n case #(\"1-wibble\", 0) {\n #(\"1\" <> rest, _) -> rest\n _ -> \"\"\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam fn main() { case #("1-wibble", 0) { #("1" <> rest, _) -> rest ↑▔▔▔ _ -> "" } } ----- AFTER RENAME -- app.gleam fn main() { case #("1-wibble", 0) { #("1" <> new_name, _) -> new_name _ -> "" } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_prefix_string_suffix_variable_with_alternative_definition_in_case.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nfn main() -> String {\n let wibble = \"1-wibble\"\n case wibble {\n \"1\" <> rest | \"2\" <> rest -> rest\n other -> other\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam fn main() -> String { let wibble = "1-wibble" case wibble { "1" <> rest | "2" <> rest -> rest ↑▔▔▔ other -> other } } ----- AFTER RENAME -- app.gleam fn main() -> String { let wibble = "1-wibble" case wibble { "1" <> new_name | "2" <> new_name -> new_name other -> other } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_prefix_string_suffix_variable_with_alternative_definition_triggered_from_second_pattern.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nfn main() -> String {\n let wibble = \"1-wibble\"\n case wibble {\n \"1\" <> rest | \"2\" <> rest -> rest\n other -> other\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam fn main() -> String { let wibble = "1-wibble" case wibble { "1" <> rest | "2" <> rest -> rest ↑▔▔▔ other -> other } } ----- AFTER RENAME -- app.gleam fn main() -> String { let wibble = "1-wibble" case wibble { "1" <> new_name | "2" <> new_name -> new_name other -> other } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_prelude_type.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn main() -> Result(Int, Nil) {\n Ok(10)\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn main() -> Result(Int, Nil) { ↑▔▔▔▔▔ Ok(10) } ----- AFTER RENAME -- app.gleam import gleam.{type Result as SuccessOrFailure} pub fn main() -> SuccessOrFailure(Int, Nil) { Ok(10) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_prelude_type_with_prelude_value_imported_with_trailing_comma.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\nimport gleam.{Error,}\n\npub fn main() -> Result(Int, Nil) {\n Error(10)\n}\n" --- ----- BEFORE RENAME -- app.gleam import gleam.{Error,} pub fn main() -> Result(Int, Nil) { ↑▔▔▔▔▔ Error(10) } ----- AFTER RENAME -- app.gleam import gleam.{Error, type Result as OkOrError} pub fn main() -> OkOrError(Int, Nil) { Error(10) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_prelude_value.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn main() {\n Ok(10)\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn main() { Ok(10) ↑▔ } ----- AFTER RENAME -- app.gleam import gleam.{Ok as Success} pub fn main() { Success(10) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_prelude_value_with_other_module_imported.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\nimport something\n\npub fn main() {\n Ok(10)\n}\n" --- ----- BEFORE RENAME -- something.gleam pub type Something -- app.gleam import something pub fn main() { Ok(10) ↑▔ } ----- AFTER RENAME -- something.gleam pub type Something -- app.gleam import gleam.{Ok as Success} import something pub fn main() { Success(10) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_prelude_value_with_other_prelude_value_imported.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\nimport gleam.{Error}\n\npub fn main() {\n Ok(Error(10))\n}\n" --- ----- BEFORE RENAME -- app.gleam import gleam.{Error} pub fn main() { Ok(Error(10)) ↑▔ } ----- AFTER RENAME -- app.gleam import gleam.{Error, Ok as Success} pub fn main() { Success(Error(10)) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_prelude_value_with_prelude_already_imported.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\nimport gleam\n\npub fn main() {\n Ok(gleam.Error(10))\n}\n" --- ----- BEFORE RENAME -- app.gleam import gleam pub fn main() { Ok(gleam.Error(10)) ↑▔ } ----- AFTER RENAME -- app.gleam import gleam.{Ok as Success} pub fn main() { Success(gleam.Error(10)) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_prelude_value_with_prelude_import_with_empty_braces.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\nimport gleam.{}\n\npub fn main() {\n Ok(gleam.Error(10))\n}\n" --- ----- BEFORE RENAME -- app.gleam import gleam.{} pub fn main() { Ok(gleam.Error(10)) ↑▔ } ----- AFTER RENAME -- app.gleam import gleam.{Ok as Success} pub fn main() { Success(gleam.Error(10)) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_shadowed_local_variable.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn main() {\n let wibble = 10\n let wibble = wibble / 2\n wibble\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn main() { let wibble = 10 let wibble = wibble / 2 ↑▔▔▔▔▔ wibble } ----- AFTER RENAME -- app.gleam pub fn main() { let wobble = 10 let wibble = wobble / 2 wibble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_shadowing_local_variable.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn main() {\n let wibble = 10\n let wibble = wibble / 2\n wibble\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn main() { let wibble = 10 let wibble = wibble / 2 wibble ↑▔▔▔▔▔ } ----- AFTER RENAME -- app.gleam pub fn main() { let wibble = 10 let wobble = wibble / 2 wobble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_type_from_definition.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub type Wibble { Constructor }\n\npub fn main(w: Wibble) -> Wibble { todo }\n" --- ----- BEFORE RENAME -- mod.gleam import app fn wibble() -> app.Wibble { todo } -- app.gleam pub type Wibble { Constructor } ↑▔▔▔▔▔ pub fn main(w: Wibble) -> Wibble { todo } ----- AFTER RENAME -- mod.gleam import app fn wibble() -> app.SomeType { todo } -- app.gleam pub type SomeType { Constructor } pub fn main(w: SomeType) -> SomeType { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_type_from_qualified_reference.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\nimport mod\n\npub fn main(w: mod.Wibble) -> mod.Wibble { todo }\n" --- ----- BEFORE RENAME -- mod.gleam pub type Wibble { Constructor } fn wibble(w: Wibble) -> Wibble { todo } -- app.gleam import mod pub fn main(w: mod.Wibble) -> mod.Wibble { todo } ↑▔▔▔▔▔ ----- AFTER RENAME -- mod.gleam pub type SomeType { Constructor } fn wibble(w: SomeType) -> SomeType { todo } -- app.gleam import mod pub fn main(w: mod.SomeType) -> mod.SomeType { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_type_from_reference.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub type Wibble { Constructor }\n\npub fn main(w: Wibble) -> Wibble { todo }\n" --- ----- BEFORE RENAME -- mod.gleam import app fn wibble() -> app.Wibble { todo } -- app.gleam pub type Wibble { Constructor } pub fn main(w: Wibble) -> Wibble { todo } ↑▔▔▔▔▔ ----- AFTER RENAME -- mod.gleam import app fn wibble() -> app.SomeType { todo } -- app.gleam pub type SomeType { Constructor } pub fn main(w: SomeType) -> SomeType { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_type_from_unqualified_reference.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\nimport mod.{type Wibble}\n\npub fn main(w: Wibble) -> mod.Wibble { todo }\n" --- ----- BEFORE RENAME -- mod.gleam pub type Wibble { Constructor } fn wibble(w: Wibble) -> Wibble { todo } -- app.gleam import mod.{type Wibble} pub fn main(w: Wibble) -> mod.Wibble { todo } ↑▔▔▔▔▔ ----- AFTER RENAME -- mod.gleam pub type Wibble { Constructor } fn wibble(w: Wibble) -> Wibble { todo } -- app.gleam import mod.{type Wibble as SomeType} pub fn main(w: SomeType) -> mod.Wibble { todo } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_type_from_variant_constructor_argument.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\nimport mod\n\npub type Wobble {\n Wobble(w: mod.Wibble)\n}\n" --- ----- BEFORE RENAME -- mod.gleam pub type Wibble { Wibble } pub fn main() { let wibble = Wibble } -- app.gleam import mod pub type Wobble { Wobble(w: mod.Wibble) ↑▔▔▔▔▔ } ----- AFTER RENAME -- mod.gleam pub type SomeType { Wibble } pub fn main() { let wibble = Wibble } -- app.gleam import mod pub type Wobble { Wobble(w: mod.SomeType) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_type_referenced_in_variant_constructor_argument.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub type Wibble {\n Wibble\n}\n\npub fn main() {\n let wibble = Wibble\n}\n" --- ----- BEFORE RENAME -- mod.gleam import app pub type Wobble { Wobble(w: app.Wibble) } -- app.gleam pub type Wibble { ↑▔▔▔▔▔ Wibble } pub fn main() { let wibble = Wibble } ----- AFTER RENAME -- mod.gleam import app pub type Wobble { Wobble(w: app.SomeType) } -- app.gleam pub type SomeType { Wibble } pub fn main() { let wibble = Wibble } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_type_variant_from_definition.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub type Wibble {\n Constructor(Int)\n}\n\npub fn main() {\n Constructor(10)\n}\n" --- ----- BEFORE RENAME -- mod.gleam import app fn wibble() { app.Constructor(4) } -- app.gleam pub type Wibble { Constructor(Int) ↑▔▔▔▔▔▔▔▔▔▔ } pub fn main() { Constructor(10) } ----- AFTER RENAME -- mod.gleam import app fn wibble() { app.Wibble(4) } -- app.gleam pub type Wibble { Wibble(Int) } pub fn main() { Wibble(10) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_type_variant_from_pattern.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub type Type {\n X\n Y\n}\n\npub fn main(t) {\n case t {\n X -> 0\n Y -> 0\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam pub type Type { X Y } pub fn main(t) { case t { X -> 0 ↑ Y -> 0 } } ----- AFTER RENAME -- app.gleam pub type Type { Renamed Y } pub fn main(t) { case t { Renamed -> 0 Y -> 0 } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_type_variant_from_qualified_reference.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\nimport mod\n\npub fn main() {\n mod.Constructor\n}\n" --- ----- BEFORE RENAME -- mod.gleam pub type Wibble { Constructor(Int) } fn wibble() { Constructor(42) } -- app.gleam import mod pub fn main() { mod.Constructor ↑▔▔▔▔▔▔▔▔▔▔ } ----- AFTER RENAME -- mod.gleam pub type Wibble { Variant(Int) } fn wibble() { Variant(42) } -- app.gleam import mod pub fn main() { mod.Variant } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_type_variant_from_reference.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub type Wibble {\n Constructor(Int)\n}\n\npub fn main() {\n Constructor(10)\n}\n" --- ----- BEFORE RENAME -- mod.gleam import app fn wibble() { app.Constructor(4) } -- app.gleam pub type Wibble { Constructor(Int) } pub fn main() { Constructor(10) ↑▔▔▔▔▔▔▔▔▔▔ } ----- AFTER RENAME -- mod.gleam import app fn wibble() { app.Wibble(4) } -- app.gleam pub type Wibble { Wibble(Int) } pub fn main() { Wibble(10) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_type_variant_from_unqualified_reference.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\nimport mod.{Constructor}\n\npub fn main() {\n #(Constructor(75), mod.Constructor(57))\n}\n" --- ----- BEFORE RENAME -- mod.gleam pub type Wibble { Constructor(Int) } fn wibble() { Constructor(81) } -- app.gleam import mod.{Constructor} pub fn main() { #(Constructor(75), mod.Constructor(57)) ↑▔▔▔▔▔▔▔▔▔▔ } ----- AFTER RENAME -- mod.gleam pub type Wibble { Constructor(Int) } fn wibble() { Constructor(81) } -- app.gleam import mod.{Constructor as Number} pub fn main() { #(Number(75), mod.Constructor(57)) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_type_variant_pattern_with_arguments.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub type Wibble {\n Wibble(Int)\n Wobble(Float)\n}\n\nfn wibble() {\n case Wibble(10) {\n Wibble(20) -> todo\n Wibble(_) -> panic\n }\n}\n" --- ----- BEFORE RENAME -- app.gleam pub type Wibble { Wibble(Int) Wobble(Float) } fn wibble() { case Wibble(10) { ↑▔▔▔▔▔ Wibble(20) -> todo Wibble(_) -> panic } } ----- AFTER RENAME -- app.gleam pub type Wibble { Variant(Int) Wobble(Float) } fn wibble() { case Variant(10) { Variant(20) -> todo Variant(_) -> panic } } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_value_in_aliased_module.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\nimport mod as the_module\n\npub fn main() {\n the_module.wibble()\n}\n" --- ----- BEFORE RENAME -- mod.gleam pub fn wibble() { wibble() } -- app.gleam import mod as the_module pub fn main() { the_module.wibble() ↑▔▔▔▔▔ } ----- AFTER RENAME -- mod.gleam pub fn some_function() { some_function() } -- app.gleam import mod as the_module pub fn main() { the_module.some_function() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_value_in_nested_module.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\nimport sub/mod\n\npub fn main() {\n mod.wibble()\n}\n" --- ----- BEFORE RENAME -- sub/mod.gleam pub fn wibble() { wibble() } -- app.gleam import sub/mod pub fn main() { mod.wibble() ↑▔▔▔▔▔ } ----- AFTER RENAME -- sub/mod.gleam pub fn some_function() { some_function() } -- app.gleam import sub/mod pub fn main() { mod.some_function() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_variable_used_in_record_update.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\ntype Wibble {\n Wibble(a: Int, b: Int, c: Int)\n}\n\nfn wibble(wibble: Wibble) {\n Wibble(..wibble, c: 1)\n}\n" --- ----- BEFORE RENAME -- app.gleam type Wibble { Wibble(a: Int, b: Int, c: Int) } fn wibble(wibble: Wibble) { ↑▔▔▔▔▔ Wibble(..wibble, c: 1) } ----- AFTER RENAME -- app.gleam type Wibble { Wibble(a: Int, b: Int, c: Int) } fn wibble(value: Wibble) { Wibble(..value, c: 1) } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_variable_with_alternative_pattern_with_same_name.snap ================================================ --- source: compiler-core/src/language_server/tests/rename.rs expression: "\npub fn main(x) {\n let some_var = 10\n\n case x {\n #(some_var, []) | #(_, [some_var]) ->\n some_var\n _ -> 0\n }\n\n some_var\n}\n" --- ----- BEFORE RENAME -- app.gleam pub fn main(x) { let some_var = 10 ↑▔▔▔▔▔▔▔ case x { #(some_var, []) | #(_, [some_var]) -> some_var _ -> 0 } some_var } ----- AFTER RENAME -- app.gleam pub fn main(x) { let new_name = 10 case x { #(some_var, []) | #(_, [some_var]) -> some_var _ -> 0 } new_name } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__rename__rename_works_when_error_is_present.snap ================================================ --- source: language-server/src/tests/rename.rs expression: "\nfn wibble() {\n \"test string\"\n}\n\npub fn main() {\n 1 + \"1\"\n echo wibble()\n}\n " --- ----- BEFORE RENAME -- app.gleam fn wibble() { ↑▔▔▔▔▔ "test string" } pub fn main() { 1 + "1" echo wibble() } ----- AFTER RENAME -- app.gleam fn wobble() { "test string" } pub fn main() { 1 + "1" echo wobble() } ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__signature_help__help_for_aliased_qualified_call.snap ================================================ --- source: compiler-core/src/language_server/tests/signature_help.rs expression: "\nimport example as wibble\npub fn main() {\n wibble.example_fn()\n}\n" --- import example as wibble pub fn main() { wibble.example_fn() ↑ } ----- Signature help ----- wibble.example_fn(Int, String) -> Nil ▔▔▔ No documentation ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__signature_help__help_for_aliased_unqualified_call.snap ================================================ --- source: compiler-core/src/language_server/tests/signature_help.rs expression: "\nimport example.{example_fn as wibble}\npub fn main() {\n wibble()\n}\n" --- import example.{example_fn as wibble} pub fn main() { wibble() ↑ } ----- Signature help ----- wibble(Int, String) -> Nil ▔▔▔ No documentation ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__signature_help__help_for_calling_local_variable_first_arg.snap ================================================ --- source: compiler-core/src/language_server/tests/signature_help.rs expression: "\npub fn main() {\n let wibble = fn(a: Int, b: String) { 1.0 }\n wibble()\n}\n" --- pub fn main() { let wibble = fn(a: Int, b: String) { 1.0 } wibble() ↑ } ----- Signature help ----- wibble(Int, String) -> Float ▔▔▔ Documentation: MarkupContent( MarkupContent { kind: Markdown, value: "A locally defined variable.", }, ) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__signature_help__help_for_calling_local_variable_last_arg.snap ================================================ --- source: compiler-core/src/language_server/tests/signature_help.rs expression: "\npub fn main() {\n let wibble = fn(a: Int, b: String) { 1.0 }\n wibble(1,)\n}\n" --- pub fn main() { let wibble = fn(a: Int, b: String) { 1.0 } wibble(1,) ↑ } ----- Signature help ----- wibble(Int, String) -> Float ▔▔▔▔▔▔ Documentation: MarkupContent( MarkupContent { kind: Markdown, value: "A locally defined variable.", }, ) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__signature_help__help_for_calling_local_variable_referencing_constant_referencing_function.snap ================================================ --- source: compiler-core/src/language_server/tests/signature_help.rs expression: "\npub fn wibble(a: Int, b: String) { 1.0 }\nconst wobble = wibble\n\npub fn main() {\n let woo = wobble\n woo()\n}\n" --- pub fn wibble(a: Int, b: String) { 1.0 } const wobble = wibble pub fn main() { let woo = wobble woo() ↑ } ----- Signature help ----- woo(Int, String) -> Float ▔▔▔ Documentation: MarkupContent( MarkupContent { kind: Markdown, value: "A locally defined variable.", }, ) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__signature_help__help_for_calling_local_variable_with_module_function.snap ================================================ --- source: compiler-core/src/language_server/tests/signature_help.rs expression: "\npub fn wibble(a: Int, b: String) { 1.0 }\n\npub fn main() {\n let wobble = fn(a: Int, b: String) { 1.0 }\n wobble(1,)\n}\n" --- pub fn wibble(a: Int, b: String) { 1.0 } pub fn main() { let wobble = fn(a: Int, b: String) { 1.0 } wobble(1,) ↑ } ----- Signature help ----- wobble(Int, String) -> Float ▔▔▔▔▔▔ Documentation: MarkupContent( MarkupContent { kind: Markdown, value: "A locally defined variable.", }, ) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__signature_help__help_for_calling_module_constant_referencing_function.snap ================================================ --- source: compiler-core/src/language_server/tests/signature_help.rs expression: "\npub fn wibble(a: Int, b: String) { 1.0 }\nconst wobble = wibble\n\npub fn main() {\n wobble()\n}\n" --- pub fn wibble(a: Int, b: String) { 1.0 } const wobble = wibble pub fn main() { wobble() ↑ } ----- Signature help ----- wobble(Int, String) -> Float ▔▔▔ No documentation ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__signature_help__help_for_calling_module_function.snap ================================================ --- source: compiler-core/src/language_server/tests/signature_help.rs expression: "\npub fn wibble(a: Int, b: String) { 1.0 }\n\npub fn main() {\n wibble()\n}\n" --- pub fn wibble(a: Int, b: String) { 1.0 } pub fn main() { wibble() ↑ } ----- Signature help ----- wibble(Int, String) -> Float ▔▔▔ No documentation ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__signature_help__help_for_piped_function_starts_from_second_argument.snap ================================================ --- source: compiler-core/src/language_server/tests/signature_help.rs expression: "\npub fn wibble(a a: Int, b b: Int, c c: String) { 1.0 }\n\npub fn main() {\n 1 |> wibble()\n}\n " --- pub fn wibble(a a: Int, b b: Int, c c: String) { 1.0 } pub fn main() { 1 |> wibble() ↑ } ----- Signature help ----- wibble(a: Int, b: Int, c: String) -> Float ▔▔▔▔▔▔ No documentation ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__signature_help__help_for_piped_imported_function_starts_from_second_argument.snap ================================================ --- source: compiler-core/src/language_server/tests/signature_help.rs expression: "\nimport example\npub fn main() {\n 1 |> example.example_fn()\n}\n " --- import example pub fn main() { 1 |> example.example_fn() ↑ } ----- Signature help ----- example.example_fn(Int, String) -> Nil ▔▔▔▔▔▔ No documentation ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__signature_help__help_for_qualified_call.snap ================================================ --- source: compiler-core/src/language_server/tests/signature_help.rs expression: "\nimport example\npub fn main() {\n example.example_fn()\n}\n" --- import example pub fn main() { example.example_fn() ↑ } ----- Signature help ----- example.example_fn(Int, String) -> Nil ▔▔▔ No documentation ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__signature_help__help_for_unqualified_call.snap ================================================ --- source: compiler-core/src/language_server/tests/signature_help.rs expression: "\nimport example.{example_fn}\npub fn main() {\n example_fn()\n}\n" --- import example.{example_fn} pub fn main() { example_fn() ↑ } ----- Signature help ----- example_fn(Int, String) -> Nil ▔▔▔ No documentation ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__signature_help__help_for_use_function_call_starts_from_first_argument.snap ================================================ --- source: compiler-core/src/language_server/tests/signature_help.rs expression: "\npub fn wibble(a: Int, b: Int, c: fn() -> Int) { 1.0 }\n\npub fn main() {\n use <- wibble()\n}\n " --- pub fn wibble(a: Int, b: Int, c: fn() -> Int) { 1.0 } pub fn main() { use <- wibble() ↑ } ----- Signature help ----- wibble(Int, Int, fn() -> Int) -> Float ▔▔▔ No documentation ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__signature_help__help_for_use_function_call_uses_concrete_types_when_late_ubound.snap ================================================ --- source: language-server/src/tests/signature_help.rs expression: "\nfn identity(x: a) -> a { x }\n\nfn main() {\n let a = Ok(10)\n identity( a)\n}\n" snapshot_kind: text --- fn identity(x: a) -> a { x } fn main() { let a = Ok(10) identity( a) ↑ } ----- Signature help ----- identity(Result(Int, b)) -> Result(Int, b) No documentation ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__signature_help__help_for_use_function_call_uses_concrete_types_when_possible_or_generic_names_when_unbound.snap ================================================ --- source: language-server/src/tests/signature_help.rs expression: "\npub fn wibble(x: something, y: fn() -> something, z: anything) { Nil }\npub fn main() {\n wibble(1, )\n}\n" snapshot_kind: text --- pub fn wibble(x: something, y: fn() -> something, z: anything) { Nil } pub fn main() { wibble(1, ) ↑ } ----- Signature help ----- wibble(Int, fn() -> Int, anything) -> Nil ▔▔▔▔▔▔▔▔▔▔▔ No documentation ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__signature_help__help_for_use_function_call_uses_generic_names_when_missing_all_arguments.snap ================================================ --- source: language-server/src/tests/signature_help.rs expression: "\npub fn wibble(x: something, y: fn() -> something, z: anything) { Nil }\npub fn main() {\n wibble( )\n}\n" snapshot_kind: text --- pub fn wibble(x: something, y: fn() -> something, z: anything) { Nil } pub fn main() { wibble( ) ↑ } ----- Signature help ----- wibble(something, fn() -> something, anything) -> Nil ▔▔▔▔▔▔▔▔▔ No documentation ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__signature_help__help_for_use_function_call_uses_precise_types_when_missing_some_arguments.snap ================================================ --- source: compiler-core/src/language_server/tests/signature_help.rs expression: "\npub fn guard(a: Bool, b: a, c: fn() -> a) { 1.0 }\n\npub fn main() {\n use <- guard(True,)\n}\n " --- pub fn guard(a: Bool, b: a, c: fn() -> a) { 1.0 } pub fn main() { use <- guard(True,) ↑ } ----- Signature help ----- guard(Bool, a, fn() -> a) -> Float ▔ No documentation ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__signature_help__help_for_use_function_shows_next_unlabelled_argument.snap ================================================ --- source: compiler-core/src/language_server/tests/signature_help.rs expression: "\npub fn guard(a a: Bool, b b: a, c c: fn() -> a) { 1.0 }\n\npub fn main() {\n use <- guard(b: 1,)\n}\n " --- pub fn guard(a a: Bool, b b: a, c c: fn() -> a) { 1.0 } pub fn main() { use <- guard(b: 1,) ↑ } ----- Signature help ----- guard(a: Bool, b: a, c: fn() -> a) -> Float ▔▔▔▔▔▔▔ No documentation ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__signature_help__help_shows_documentation_for_imported_function.snap ================================================ --- source: compiler-core/src/language_server/tests/signature_help.rs expression: "\nimport example\npub fn main() {\n example.example_fn()\n}\n" --- import example pub fn main() { example.example_fn() ↑ } ----- Signature help ----- example.example_fn(Int, String) -> Nil ▔▔▔ Documentation: MarkupContent( MarkupContent { kind: Markdown, value: " Some doc!\n", }, ) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__signature_help__help_shows_documentation_for_local_function.snap ================================================ --- source: compiler-core/src/language_server/tests/signature_help.rs expression: "\n/// Some doc!\npub fn wibble(a: Int, b: String) { 1.0 }\n\npub fn main() {\n wibble()\n}\n" --- /// Some doc! pub fn wibble(a: Int, b: String) { 1.0 } pub fn main() { wibble() ↑ } ----- Signature help ----- wibble(Int, String) -> Float ▔▔▔ Documentation: MarkupContent( MarkupContent { kind: Markdown, value: " Some doc!\n", }, ) ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__signature_help__help_shows_first_missing_labelled_argument_if_out_of_order.snap ================================================ --- source: compiler-core/src/language_server/tests/signature_help.rs expression: "\npub fn wibble(a a: Int, b b: Int, c c: String) { 1.0 }\n\npub fn main() {\n wibble(c: \"c\",)\n}\n " --- pub fn wibble(a a: Int, b b: Int, c c: String) { 1.0 } pub fn main() { wibble(c: "c",) ↑ } ----- Signature help ----- wibble(a: Int, b: Int, c: String) -> Float ▔▔▔▔▔▔ No documentation ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__signature_help__help_shows_labelled_argument_after_all_unlabelled.snap ================================================ --- source: compiler-core/src/language_server/tests/signature_help.rs expression: "\npub fn wibble(a: Int, b b: Int, c c: String) { 1.0 }\n\npub fn main() {\n wibble(1,)\n}\n " --- pub fn wibble(a: Int, b b: Int, c c: String) { 1.0 } pub fn main() { wibble(1,) ↑ } ----- Signature help ----- wibble(Int, b: Int, c: String) -> Float ▔▔▔▔▔▔ No documentation ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__signature_help__help_shows_labels.snap ================================================ --- source: compiler-core/src/language_server/tests/signature_help.rs expression: "\npub fn wibble(a: Int, b b: Int, c c: String) { 1.0 }\n\npub fn main() {\n wibble()\n}\n " --- pub fn wibble(a: Int, b b: Int, c c: String) { 1.0 } pub fn main() { wibble() ↑ } ----- Signature help ----- wibble(Int, b: Int, c: String) -> Float ▔▔▔ No documentation ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__signature_help__help_still_shows_up_even_if_an_argument_has_the_wrong_type.snap ================================================ --- source: compiler-core/src/language_server/tests/signature_help.rs expression: "\npub fn wibble(a: Int, b: String) { 1.0 }\n\npub fn main() {\n wibble(\"wrong\",)\n}\n" --- pub fn wibble(a: Int, b: String) { 1.0 } pub fn main() { wibble("wrong",) ↑ } ----- Signature help ----- wibble(Int, String) -> Float ▔▔▔▔▔▔ No documentation ================================================ FILE: language-server/src/tests/snapshots/gleam_language_server__tests__signature_help__help_with_labelled_constructor.snap ================================================ --- source: compiler-core/src/language_server/tests/signature_help.rs expression: "\npub type Pokemon {\n Pokemon(name: String, types: List(String), moves: List(String))\n}\n\npub fn main() {\n Pokemon(name: \"Jirachi\",)\n}\n " --- pub type Pokemon { Pokemon(name: String, types: List(String), moves: List(String)) } pub fn main() { Pokemon(name: "Jirachi",) ↑ } ----- Signature help ----- Pokemon(name: String, types: List(String), moves: List(String)) -> Pokemon ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ No documentation ================================================ FILE: language-server/src/tests.rs ================================================ mod action; mod compilation; mod completion; mod definition; mod document_symbols; mod folding_range; mod hover; mod reference; mod rename; mod router; mod signature_help; use std::{ collections::{HashMap, HashSet}, sync::{Arc, Mutex}, time::SystemTime, }; use ecow::EcoString; use hexpm::version::{Range, Version}; use camino::{Utf8Path, Utf8PathBuf}; use itertools::Itertools; use lsp_types::{Position, TextDocumentIdentifier, TextDocumentPositionParams, Url}; use gleam_core::{ Result, config::PackageConfig, io::{ BeamCompiler, Command, CommandExecutor, FileSystemReader, FileSystemWriter, ReadDir, WrappedReader, memory::InMemoryFileSystem, }, line_numbers::LineNumbers, manifest::{Base16Checksum, Manifest, ManifestPackage, ManifestPackageSource}, paths::ProjectPaths, requirement::Requirement, }; use super::{ DownloadDependencies, LockGuard, Locker, MakeLocker, engine::LanguageServerEngine, files::FileSystemProxy, progress::ProgressReporter, }; pub const LSP_TEST_ROOT_PACKAGE_NAME: &str = "app"; #[derive(Debug, Clone, PartialEq, Eq)] enum Action { CompilationStarted, CompilationFinished, DependencyDownloadingStarted, DependencyDownloadingFinished, DownloadDependencies, LockBuild, UnlockBuild, } #[derive(Debug, Clone)] struct LanguageServerTestIO { io: InMemoryFileSystem, paths: ProjectPaths, actions: Arc>>, manifest: Manifest, } fn default_manifest_package() -> ManifestPackage { ManifestPackage { name: Default::default(), build_tools: Default::default(), otp_app: Default::default(), requirements: Default::default(), version: Version::new(1, 0, 0), source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![]), }, } } impl LanguageServerTestIO { fn new() -> Self { Self { io: Default::default(), actions: Default::default(), paths: ProjectPaths::at_filesystem_root(), manifest: Manifest { requirements: HashMap::new(), packages: vec![], }, } } /// Panics if there are other references to the actions. pub fn into_actions(self) -> Vec { Arc::try_unwrap(self.actions).unwrap().into_inner().unwrap() } pub fn src_module(&self, name: &str, code: &str) -> Utf8PathBuf { let src_dir = self.paths.src_directory(); let path = src_dir.join(name).with_extension("gleam"); self.module(&path, code); path } pub fn test_module(&self, name: &str, code: &str) -> Utf8PathBuf { let test_dir = self.paths.test_directory(); let path = test_dir.join(name).with_extension("gleam"); self.module(&path, code); path } pub fn dev_module(&self, name: &str, code: &str) -> Utf8PathBuf { let dev_directory = self.paths.dev_directory(); let path = dev_directory.join(name).with_extension("gleam"); self.module(&path, code); path } pub fn path_dep_module(&self, dep: &str, name: &str, code: &str) -> Utf8PathBuf { let dep_dir = self.paths.root().join(dep).join("src"); let path = dep_dir.join(name).with_extension("gleam"); self.module(&path, code); path } pub fn hex_dep_module(&self, dep: &str, name: &str, code: &str) -> Utf8PathBuf { let dep_dir = self.paths.build_packages_package(dep).join("src"); let path = dep_dir.join(name).with_extension("gleam"); self.module(&path, code); path } pub fn add_hex_package(&mut self, name: &str) { self.manifest.packages.push(ManifestPackage { name: name.into(), source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![]), }, build_tools: vec!["gleam".into()], ..default_manifest_package() }); } fn module(&self, path: &Utf8Path, code: &str) { self.io.write(path, code).unwrap(); self.io .try_set_modification_time(path, SystemTime::now()) .unwrap(); } fn record(&self, action: Action) { self.actions.lock().unwrap().push(action); } } impl FileSystemReader for LanguageServerTestIO { fn read_dir(&self, path: &Utf8Path) -> Result { self.io.read_dir(path) } fn read(&self, path: &Utf8Path) -> Result { self.io.read(path) } fn read_bytes(&self, path: &Utf8Path) -> Result> { self.io.read_bytes(path) } fn reader(&self, path: &Utf8Path) -> Result { self.io.reader(path) } fn is_file(&self, path: &Utf8Path) -> bool { self.io.is_file(path) } fn is_directory(&self, path: &Utf8Path) -> bool { self.io.is_directory(path) } fn modification_time(&self, path: &Utf8Path) -> Result { self.io.modification_time(path) } fn canonicalise(&self, path: &Utf8Path) -> Result { self.io.canonicalise(path) } } impl FileSystemWriter for LanguageServerTestIO { fn mkdir(&self, path: &Utf8Path) -> Result<()> { self.io.mkdir(path) } fn delete_directory(&self, path: &Utf8Path) -> Result<()> { self.io.delete_directory(path) } fn copy(&self, from: &Utf8Path, to: &Utf8Path) -> Result<()> { self.io.copy(from, to) } fn copy_dir(&self, from: &Utf8Path, to: &Utf8Path) -> Result<()> { self.io.copy_dir(from, to) } fn hardlink(&self, from: &Utf8Path, to: &Utf8Path) -> Result<()> { self.io.hardlink(from, to) } fn symlink_dir(&self, from: &Utf8Path, to: &Utf8Path) -> Result<()> { self.io.symlink_dir(from, to) } fn delete_file(&self, path: &Utf8Path) -> Result<()> { self.io.delete_file(path) } fn write(&self, path: &Utf8Path, content: &str) -> Result<(), gleam_core::Error> { self.io.write(path, content) } fn write_bytes(&self, path: &Utf8Path, content: &[u8]) -> Result<(), gleam_core::Error> { self.io.write_bytes(path, content) } fn exists(&self, path: &Utf8Path) -> bool { self.io.exists(path) } } impl DownloadDependencies for LanguageServerTestIO { fn download_dependencies(&self, _paths: &ProjectPaths) -> Result { self.record(Action::DownloadDependencies); Ok(self.manifest.clone()) } } impl CommandExecutor for LanguageServerTestIO { fn exec(&self, command: Command) -> Result { let Command { program, args, env, cwd, stdio, } = command; panic!("exec({program:?}, {args:?}, {env:?}, {cwd:?}, {stdio:?}) is not implemented") } } impl BeamCompiler for LanguageServerTestIO { fn compile_beam( &self, out: &Utf8Path, lib: &Utf8Path, modules: &HashSet, stdio: gleam_core::io::Stdio, ) -> Result> { panic!("compile_beam({out:?}, {lib:?}, {modules:?}, {stdio:?}) is not implemented") } } impl MakeLocker for LanguageServerTestIO { fn make_locker( &self, _paths: &ProjectPaths, _target: gleam_core::build::Target, ) -> Result> { Ok(Box::new(TestLocker { actions: self.actions.clone(), })) } } #[derive(Debug, Clone)] struct TestLocker { actions: Arc>>, } impl TestLocker { fn record(&self, action: Action) { self.actions.lock().unwrap().push(action); } } impl Locker for TestLocker { fn lock_for_build(&self) -> Result { self.record(Action::LockBuild); Ok(LockGuard(Box::new(Guard(self.actions.clone())))) } } struct Guard(Arc>>); impl Drop for Guard { fn drop(&mut self) { self.0.lock().unwrap().push(Action::UnlockBuild); } } impl ProgressReporter for LanguageServerTestIO { fn compilation_started(&self) { self.record(Action::CompilationStarted); } fn compilation_finished(&self) { self.record(Action::CompilationFinished); } fn dependency_downloading_started(&self) { self.record(Action::DependencyDownloadingStarted); } fn dependency_downloading_finished(&self) { self.record(Action::DependencyDownloadingFinished); } } fn add_package_from_manifest( engine: &mut LanguageServerEngine, toml_path: Utf8PathBuf, package: ManifestPackage, ) { let compiler = &mut engine.compiler.project_compiler; _ = compiler.config.dependencies.insert( package.name.clone(), match package.source { ManifestPackageSource::Hex { .. } => Requirement::Hex { version: Range::new("1.0.0".into()).unwrap(), }, ManifestPackageSource::Local { ref path } => Requirement::Path { path: path.into() }, ManifestPackageSource::Git { ref repo, ref commit, } => Requirement::Git { git: repo.clone(), ref_: commit.clone(), }, }, ); write_toml_from_manifest(engine, toml_path, package); } fn add_dev_package_from_manifest( engine: &mut LanguageServerEngine, toml_path: Utf8PathBuf, package: ManifestPackage, ) { let compiler = &mut engine.compiler.project_compiler; _ = compiler.config.dev_dependencies.insert( package.name.clone(), match package.source { ManifestPackageSource::Hex { .. } => Requirement::Hex { version: Range::new("1.0.0".into()).unwrap(), }, ManifestPackageSource::Local { ref path } => Requirement::Path { path: path.into() }, ManifestPackageSource::Git { ref repo, ref commit, } => Requirement::Git { git: repo.clone(), ref_: commit.clone(), }, }, ); write_toml_from_manifest(engine, toml_path, package); } fn write_toml_from_manifest( engine: &mut LanguageServerEngine, toml_path: Utf8PathBuf, package: ManifestPackage, ) { let compiler = &mut engine.compiler.project_compiler; let toml = format!( r#"name = "{}" version = "{}""#, &package.name, &package.version ); _ = compiler.packages.insert(package.name.to_string(), package); compiler.io.write(toml_path.as_path(), &toml).unwrap(); } fn add_path_dep(engine: &mut LanguageServerEngine, name: &str) { let path = engine.paths.root().join(name); add_package_from_manifest( engine, path.join("gleam.toml"), ManifestPackage { name: name.into(), version: Version::new(1, 0, 0), build_tools: vec!["gleam".into()], otp_app: None, requirements: vec![], source: ManifestPackageSource::Local { path: path.clone() }, }, ) } fn setup_engine( io: &LanguageServerTestIO, ) -> LanguageServerEngine { let mut config = PackageConfig::default(); config.name = LSP_TEST_ROOT_PACKAGE_NAME.into(); LanguageServerEngine::new( config, io.clone(), FileSystemProxy::new(io.clone()), io.paths.clone(), ) .unwrap() } struct TestProject<'a> { src: &'a str, root_package_modules: Vec<(&'a str, &'a str)>, dependency_modules: Vec<(&'a str, &'a str)>, test_modules: Vec<(&'a str, &'a str)>, dev_modules: Vec<(&'a str, &'a str)>, hex_modules: Vec<(&'a str, &'a str)>, dev_hex_modules: Vec<(&'a str, &'a str)>, indirect_hex_modules: Vec<(&'a str, &'a str)>, package_modules: HashMap<&'a str, Vec<(&'a str, &'a str)>>, } impl<'a> TestProject<'a> { pub fn for_source(src: &'a str) -> Self { TestProject { src, root_package_modules: vec![], dependency_modules: vec![], test_modules: vec![], dev_modules: vec![], hex_modules: vec![], dev_hex_modules: vec![], indirect_hex_modules: vec![], package_modules: HashMap::new(), } } pub fn module_name_from_url(&self, url: &Url) -> Option { Some( url.path_segments()? .skip_while(|segment| *segment != "src") .skip(1) .join("/") .trim_end_matches(".gleam") .into(), ) } pub fn src_from_module_url(&self, url: &Url) -> Option<&str> { let module_name: EcoString = self.module_name_from_url(url)?.into(); if module_name == "app" { return Some(self.src); } let find_module = |modules: &Vec<(&'a str, &'a str)>| { modules .iter() .find(|(name, _)| *name == module_name) .map(|(_, src)| *src) }; find_module(&self.root_package_modules) .or_else(|| find_module(&self.dependency_modules)) .or_else(|| find_module(&self.test_modules)) .or_else(|| find_module(&self.hex_modules)) .or_else(|| find_module(&self.dev_hex_modules)) .or_else(|| find_module(&self.indirect_hex_modules)) } pub fn add_module(mut self, name: &'a str, src: &'a str) -> Self { self.root_package_modules.push((name, src)); self } pub fn add_dep_module(mut self, name: &'a str, src: &'a str) -> Self { self.dependency_modules.push((name, src)); self } pub fn add_test_module(mut self, name: &'a str, src: &'a str) -> Self { self.test_modules.push((name, src)); self } pub fn add_dev_module(mut self, name: &'a str, src: &'a str) -> Self { self.dev_modules.push((name, src)); self } pub fn add_hex_module(mut self, name: &'a str, src: &'a str) -> Self { self.hex_modules.push((name, src)); self } pub fn add_dev_hex_module(mut self, name: &'a str, src: &'a str) -> Self { self.dev_hex_modules.push((name, src)); self } pub fn add_indirect_hex_module(mut self, name: &'a str, src: &'a str) -> Self { self.indirect_hex_modules.push((name, src)); self } pub fn add_package_module(mut self, package: &'a str, name: &'a str, src: &'a str) -> Self { self.package_modules .entry(package) .or_default() .push((name, src)); self } pub fn build_engine( &self, io: &mut LanguageServerTestIO, ) -> LanguageServerEngine { io.add_hex_package("hex"); self.hex_modules.iter().for_each(|(name, code)| { _ = io.hex_dep_module("hex", name, code); }); self.dev_hex_modules.iter().for_each(|(name, code)| { _ = io.hex_dep_module("dev_hex", name, code); }); self.indirect_hex_modules.iter().for_each(|(name, code)| { _ = io.hex_dep_module("indirect_hex", name, code); }); for (package, modules) in self.package_modules.iter() { io.add_hex_package(package); for (module, code) in modules { _ = io.hex_dep_module(package, module, code); } } let mut engine = setup_engine(io); // Add an external dependency and all its modules add_path_dep(&mut engine, "dep"); self.dependency_modules.iter().for_each(|(name, code)| { let _ = io.path_dep_module("dep", name, code); }); // Add all the modules belonging to the root package self.root_package_modules.iter().for_each(|(name, code)| { let _ = io.src_module(name, code); }); // Add all the test modules self.test_modules.iter().for_each(|(name, code)| { let _ = io.test_module(name, code); }); // Add all the dev modules self.dev_modules.iter().for_each(|(name, code)| { let _ = io.dev_module(name, code); }); for package in &io.manifest.packages { let toml_path = engine.paths.build_packages_package_config(&package.name); add_package_from_manifest(&mut engine, toml_path, package.clone()); } // Add an indirect dependency manifest let toml_path = engine.paths.build_packages_package_config("indirect_hex"); write_toml_from_manifest( &mut engine, toml_path, ManifestPackage { name: "indirect_hex".into(), source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![]), }, build_tools: vec!["gleam".into()], ..default_manifest_package() }, ); // Add a dev dependency let toml_path = engine.paths.build_packages_package_config("dev_hex"); add_dev_package_from_manifest( &mut engine, toml_path, ManifestPackage { name: "dev_hex".into(), source: ManifestPackageSource::Hex { outer_checksum: Base16Checksum(vec![]), }, build_tools: vec!["gleam".into()], ..default_manifest_package() }, ); engine } pub fn build_path(&self, position: Position) -> TextDocumentPositionParams { let path = Utf8PathBuf::from(if cfg!(target_family = "windows") { r"\\?\C:\src\app.gleam" } else { "/src/app.gleam" }); let url = Url::from_file_path(path).unwrap(); TextDocumentPositionParams::new(TextDocumentIdentifier::new(url), position) } pub fn build_test_path( &self, position: Position, test_name: &str, ) -> TextDocumentPositionParams { let path = Utf8PathBuf::from(if cfg!(target_family = "windows") { format!(r"\\?\C:\test\{test_name}.gleam") } else { format!("/test/{test_name}.gleam") }); let url = Url::from_file_path(path).unwrap(); TextDocumentPositionParams::new(TextDocumentIdentifier::new(url), position) } pub fn build_dev_path( &self, position: Position, test_name: &str, ) -> TextDocumentPositionParams { let path = Utf8PathBuf::from(if cfg!(target_family = "windows") { format!(r"\\?\C:\dev\{test_name}.gleam") } else { format!("/dev/{test_name}.gleam") }); let url = Url::from_file_path(path).unwrap(); TextDocumentPositionParams::new(TextDocumentIdentifier::new(url), position) } pub fn positioned_with_io( &self, position: Position, ) -> ( LanguageServerEngine, TextDocumentPositionParams, ) { let mut io = LanguageServerTestIO::new(); let mut engine = self.build_engine(&mut io); // Add the final module we're going to be positioning the cursor in. _ = io.src_module("app", self.src); let _response = engine.compile_please(); let param = self.build_path(position); (engine, param) } pub fn positioned_with_io_in_test( &self, position: Position, test_name: &str, ) -> ( LanguageServerEngine, TextDocumentPositionParams, ) { let mut io = LanguageServerTestIO::new(); let mut engine = self.build_engine(&mut io); // Add the final module we're going to be positioning the cursor in. _ = io.src_module("app", self.src); let response = engine.compile_please(); assert!(response.result.is_ok()); let param = self.build_test_path(position, test_name); (engine, param) } pub fn positioned_with_io_in_dev( &self, position: Position, test_name: &str, ) -> ( LanguageServerEngine, TextDocumentPositionParams, ) { let mut io = LanguageServerTestIO::new(); let mut engine = self.build_engine(&mut io); // Add the final module we're going to be positioning the cursor in. _ = io.src_module("app", self.src); let response = engine.compile_please(); assert!(response.result.is_ok()); let param = self.build_dev_path(position, test_name); (engine, param) } pub fn at( &self, position: Position, executor: impl FnOnce( &mut LanguageServerEngine, TextDocumentPositionParams, EcoString, ) -> T, ) -> T { let (mut engine, params) = self.positioned_with_io(position); executor(&mut engine, params, self.src.into()) } } #[derive(Clone)] pub struct PositionFinder { value: EcoString, offset: usize, nth_occurrence: usize, } pub struct RangeSelector { from: PositionFinder, to: PositionFinder, } impl RangeSelector { pub fn find_range(&self, src: &str) -> lsp_types::Range { lsp_types::Range { start: self.from.find_position(src), end: self.to.find_position(src), } } } impl PositionFinder { pub fn with_char_offset(self, offset: usize) -> Self { Self { value: self.value, offset, nth_occurrence: self.nth_occurrence, } } pub fn under_char(self, char: char) -> Self { Self { offset: self.value.find(char).unwrap_or(0), value: self.value, nth_occurrence: self.nth_occurrence, } } pub fn under_last_char(self) -> Self { let len = self.value.len(); self.with_char_offset(len - 1) } pub fn nth_occurrence(self, nth_occurrence: usize) -> Self { Self { value: self.value, offset: self.offset, nth_occurrence, } } pub fn for_value(value: &str) -> Self { Self { value: value.into(), offset: 0, nth_occurrence: 1, } } pub fn find_position(&self, src: &str) -> Position { let PositionFinder { value, offset, nth_occurrence, } = self; let byte_index = src .match_indices(value.as_str()) .nth(nth_occurrence - 1) .expect("no match for position") .0; byte_index_to_position(src, byte_index + offset) } pub fn select_until(self, end: PositionFinder) -> RangeSelector { RangeSelector { from: self, to: end, } } pub fn to_selection(self) -> RangeSelector { RangeSelector { from: self.clone(), to: self, } } } pub fn find_position_of(value: &str) -> PositionFinder { PositionFinder::for_value(value) } fn byte_index_to_position(src: &str, byte_index: usize) -> Position { let mut line = 0; let mut col = 0; for (i, char) in src.bytes().enumerate() { if i == byte_index { break; } if char == b'\n' { line += 1; col = 0; } else { col += 1; } } Position::new(line, col) } /// This function replicates how the text editor applies TextEdit. /// pub fn apply_code_edit(src: &str, mut change: Vec) -> String { let mut result = src.to_string(); let line_numbers = LineNumbers::new(src); let mut offset = 0; change.sort_by_key(|edit| (edit.range.start.line, edit.range.start.character)); for edit in change { let start = line_numbers.byte_index(edit.range.start) as i32 - offset; let end = line_numbers.byte_index(edit.range.end) as i32 - offset; let range = (start as usize)..(end as usize); offset += end - start; offset -= edit.new_text.len() as i32; result.replace_range(range, &edit.new_text); } result } ================================================ FILE: test/assert/.gitignore ================================================ build ================================================ FILE: test/assert/Makefile ================================================ .PHONY: test-all test-all: @echo test/assert @./run_tests.sh ================================================ FILE: test/assert/gleam.toml ================================================ name = "project" version = "1.0.0" description = "A Gleam project" [dependencies] gleam_stdlib = ">= 0.58.0 and < 2.0.0" ================================================ FILE: test/assert/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "gleam_stdlib", version = "0.58.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "091F2D2C4A3A4E2047986C47E2C2C9D728A4E068ABB31FDA17B0D347E6248467" }, ] [requirements] gleam_stdlib = { version = ">= 0.58.0 and < 2.0.0" } ================================================ FILE: test/assert/run_tests.sh ================================================ #/usr/bin/env sh set -eu should_succeed() { echo echo Running: "$@" EXIT_CODE=0 cargo run -- $@ > /dev/null 2>&1 || EXIT_CODE=$? if [ $EXIT_CODE -ne 0 ] then echo ERROR: Command should have succeeded exit 1 else echo Test Passed '(command run successfully)' fi } should_fail() { echo echo Running: "$@" EXIT_CODE=0 cargo run -- $@ > /dev/null 2>&1 || EXIT_CODE=$? if [ $EXIT_CODE -eq 0 ] then echo ERROR: Command should have failed exit 1 else echo Test Passed '(command errored as expected)' fi } all_targets() { $@ --target erlang $@ --target javascript --runtime nodejs $@ --target javascript --runtime deno $@ --target javascript --runtime bun } # Ensure the project builds correctly should_succeed build --target erlang should_succeed build --target javascript all_targets should_succeed run --module passing # Since a single failing `assert` will exit immediately, we must test each # failing case individually as separate modules. all_targets should_fail run --module failing1 all_targets should_fail run --module failing2 all_targets should_fail run --module failing3 all_targets should_fail run --module failing4 ================================================ FILE: test/assert/src/failing1.gleam ================================================ pub fn main() { let x = True assert !x } ================================================ FILE: test/assert/src/failing2.gleam ================================================ import gleam/int pub fn main() { assert int.is_even(47) } ================================================ FILE: test/assert/src/failing3.gleam ================================================ import gleam/bool import gleam/result pub fn main() { assert bool.negate(False) && result.is_ok(Error(81)) } ================================================ FILE: test/assert/src/failing4.gleam ================================================ import gleam/int pub fn main() { assert int.add(4, 5) > int.absolute_value(-11) } ================================================ FILE: test/assert/src/passing.gleam ================================================ import gleam/bool import gleam/int import gleam/result /// All these assertions should succeed pub fn main() { let x = True assert x assert case x && False { True -> False False -> True } assert result.is_ok(Ok(10)) assert result.is_error(Error("Hello")) assert int.add(5, 6) == 11 assert int.add(1, 4) < 9 assert int.add(8, 12) >= 20 assert bool.negate(False) && result.is_ok(Ok(42)) assert int.is_even(3) || int.is_even(4) // This should short-circuit so we don't panic here assert True || panic } ================================================ FILE: test/compile_package0/.gitignore ================================================ out build ================================================ FILE: test/compile_package0/Makefile ================================================ .PHONY: build build: # Remove any previously compiled code rm -rf out cargo run -- compile-package --out out --target erlang --lib . --package . erl -pa out/ebin -noshell -eval "erlang:display(two:main()),erlang:display(three:test_()),halt()" ================================================ FILE: test/compile_package0/gleam.toml ================================================ name = "package" version = "1.0.0" ================================================ FILE: test/compile_package0/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ ] [requirements] ================================================ FILE: test/compile_package0/src/one.gleam ================================================ pub fn hello() -> String { "Hello" } ================================================ FILE: test/compile_package0/src/two.gleam ================================================ import one pub fn main() -> String { one.hello() } ================================================ FILE: test/compile_package0/test/three.gleam ================================================ import one import two pub fn test_() -> Bool { one.hello() == two.main() } ================================================ FILE: test/compile_package1/.gitignore ================================================ out1 out2 ================================================ FILE: test/compile_package1/Makefile ================================================ .PHONY: build build: rm -rf out1 out2 cargo run -- compile-package --package app1 --target erlang --out out2 --lib . cargo run -- compile-package --package app2 --target erlang --out out2 --lib . erl -pa out1/ebin out2/ebin -noshell -eval "erlang:display(two:main()),halt()" ================================================ FILE: test/compile_package1/app1/gleam.toml ================================================ name = "app1" version = "1.0.0" ================================================ FILE: test/compile_package1/app1/src/one/nested.gleam ================================================ //// This module is used to test resolution of nested modules pub fn id(a) { a } ================================================ FILE: test/compile_package1/app1/src/one.gleam ================================================ pub fn hello() -> String { "Hello" } ================================================ FILE: test/compile_package1/app2/gleam.toml ================================================ name = "app2" version = "1.0.0" ================================================ FILE: test/compile_package1/app2/src/two.gleam ================================================ import one import one/nested pub fn main() { one.hello() |> nested.id } ================================================ FILE: test/erlang_shipment_no_dev_deps/.gitignore ================================================ *.beam *.ez /build erl_crash.dump ================================================ FILE: test/erlang_shipment_no_dev_deps/gleam.toml ================================================ name = "shipment_test" version = "0.1.0" [dependencies] gleam_stdlib = "~> 0.34" # hpack_erl has otp_app = "hpack" (different from package name) # This tests that packages with different OTP app names are included correctly hpack_erl = "~> 0.3" [dev_dependencies] gleeunit = "~> 1.0" ================================================ FILE: test/erlang_shipment_no_dev_deps/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "gleam_stdlib", version = "0.68.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "F7FAEBD8EF260664E86A46C8DBA23508D1D11BB3BCC6EE1B89B3BC3E5C83FF1E" }, { name = "gleeunit", version = "1.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "DA9553CE58B67924B3C631F96FE3370C49EB6D6DC6B384EC4862CC4AAA718F3C" }, { name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" }, ] [requirements] gleam_stdlib = { version = "~> 0.34" } gleeunit = { version = "~> 1.0" } hpack_erl = { version = "~> 0.3" } ================================================ FILE: test/erlang_shipment_no_dev_deps/src/shipment_test.gleam ================================================ import gleam/io pub fn main() { io.println("Hello from shipment test!") } ================================================ FILE: test/erlang_shipment_no_dev_deps/test.sh ================================================ #!/bin/sh set -eu GLEAM_COMMAND=${GLEAM_COMMAND:-"cargo run --quiet --"} g() { echo "Running: $GLEAM_COMMAND $@" $GLEAM_COMMAND "$@" } echo Resetting the build directory to get to a known state rm -fr build echo Building erlang shipment g export erlang-shipment echo Checking that gleam_stdlib IS in the shipment if [ ! -d "build/erlang-shipment/gleam_stdlib" ]; then echo "ERROR: gleam_stdlib should be in the shipment but was not found" exit 1 fi echo "gleam_stdlib found in shipment" echo Checking that hpack IS in the shipment echo "(hpack_erl package has otp_app=hpack, so directory is named hpack)" if [ ! -d "build/erlang-shipment/hpack" ]; then echo "ERROR: hpack (from hpack_erl package) should be in the shipment but was not found" exit 1 fi echo "hpack found in shipment" echo Checking that gleeunit is NOT in the shipment if [ -d "build/erlang-shipment/gleeunit" ]; then echo "ERROR: gleeunit is a dev dependency and should NOT be in the shipment" exit 1 fi echo "gleeunit correctly excluded from shipment" echo Checking that the root package IS in the shipment if [ ! -d "build/erlang-shipment/shipment_test" ]; then echo "ERROR: shipment_test (root package) should be in the shipment but was not found" exit 1 fi echo "shipment_test found in shipment" echo echo Success! echo ================================================ FILE: test/errors/type_unify_int_string/.gitignore ================================================ build ================================================ FILE: test/errors/type_unify_int_string/gleam.toml ================================================ name = "type_unify_int_string" # [docs] # links = [ # { title = 'GitHub', href = 'https://github.com/username/project_name' } # ] ================================================ FILE: test/errors/type_unify_int_string/src/type_unify_int_string.gleam ================================================ pub fn hello_world() { let x: Int = "Eh?" x } ================================================ FILE: test/external_only_erlang/.gitignore ================================================ *.beam *.ez /build erl_crash.dump ================================================ FILE: test/external_only_erlang/README.md ================================================ # external_only_erlang A project that can only be run on Erlang due to an external function. ================================================ FILE: test/external_only_erlang/gleam.toml ================================================ name = "external_only_erlang" version = "1.0.0" target = "erlang" [dependencies] hello_joe = "~> 1.0" [dev_dependencies] ================================================ FILE: test/external_only_erlang/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "gleam_stdlib", version = "0.34.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "1FB8454D2991E9B4C0C804544D8A9AD0F6184725E20D63C3155F0AEB4230B016" }, { name = "hello_joe", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "hello_joe", source = "hex", outer_checksum = "CC896BC24A45528DE6C59A705171E2DD9F1EA4C3C031428E4A533539EF461519" }, ] [requirements] hello_joe = { version = "~> 1.0" } ================================================ FILE: test/external_only_erlang/src/external_only_erlang.gleam ================================================ // This function is only implemented for Erlang, so if we try and call it from // JavaScript, or build this package for JavaScript, then the compiler will // (should) emit an error. @external(erlang, "external_only_erlang_ffi", "main") pub fn main() -> Nil ================================================ FILE: test/external_only_erlang/src/external_only_erlang_ffi.erl ================================================ -module(external_only_erlang_ffi). -export([main/0]). main() -> io:format("Hello!\n", []). ================================================ FILE: test/external_only_erlang/test.sh ================================================ #!/bin/sh set -eu GLEAM_COMMAND=${GLEAM_COMMAND:-"cargo run --quiet --"} g() { echo "Running: $GLEAM_COMMAND $@" $GLEAM_COMMAND "$@" } echo Resetting the build directory to get to a known state rm -fr build echo This should succeed regardless of target as it is a dependency module g run --module=hello_joe g run --module=hello_joe --target=erlang g run --module=hello_joe --target=javascript echo Building and running for Erlang should succeed g build --target=erlang g run --target=erlang echo Building for JavaScript should fail, even if previously a JavaScript dependency was built if g build --target=javascript; then echo "Expected build to fail" exit 1 fi echo Running for JavaScript should fail, even if previously a JavaScript dependency was built if g run --target=javascript; then echo "Expected run to fail" exit 1 fi echo Running erlang shipment should succeed g export erlang-shipment grep "external_only_erlang_ffi" "build/erlang-shipment/external_only_erlang/ebin/external_only_erlang.app" echo echo Success! 💖 echo ================================================ FILE: test/external_only_javascript/.gitignore ================================================ *.beam *.ez /build erl_crash.dump ================================================ FILE: test/external_only_javascript/README.md ================================================ # external_only_javascript A project that can only be run on JavaScript due to an external function. ================================================ FILE: test/external_only_javascript/gleam.toml ================================================ name = "external_only_javascript" version = "1.0.0" target = "javascript" [dependencies] hello_joe = "~> 1.0" [dev_dependencies] ================================================ FILE: test/external_only_javascript/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "gleam_stdlib", version = "0.34.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "1FB8454D2991E9B4C0C804544D8A9AD0F6184725E20D63C3155F0AEB4230B016" }, { name = "hello_joe", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "hello_joe", source = "hex", outer_checksum = "CC896BC24A45528DE6C59A705171E2DD9F1EA4C3C031428E4A533539EF461519" }, ] [requirements] hello_joe = { version = "~> 1.0" } ================================================ FILE: test/external_only_javascript/src/external_only_javascript.gleam ================================================ // This function is only implemented for JavaScript, so if we try and call it // from Erlang, or build this package for Erlang, then the compiler will // (should) emit an error. @external(javascript, "./external_only_javascript_ffi.mjs", "main") pub fn main() -> Nil ================================================ FILE: test/external_only_javascript/src/external_only_javascript_ffi.mjs ================================================ export function main() { console.log("Hello"); } ================================================ FILE: test/external_only_javascript/test.sh ================================================ #!/bin/sh set -eu GLEAM_COMMAND=${GLEAM_COMMAND:-"cargo run --quiet --"} g() { echo "Running: $GLEAM_COMMAND $@" $GLEAM_COMMAND "$@" } echo Resetting the build directory to get to a known state rm -fr build echo This should succeed regardless of target as it is a dependency module g run --module=hello_joe g run --module=hello_joe --target=erlang g run --module=hello_joe --target=javascript echo Building and running for JavaScript should succeed g build --target=javascript g run --target=javascript echo Building for Erlang should fail, even if previously a Erlang dependency was built if g build --target=erlang; then echo "Expected build to fail" exit 1 fi echo Running for Erlang should fail, even if previously a Erlang dependency was built if g run --target=erlang; then echo "Expected run to fail" exit 1 fi echo echo Success! 💖 echo ================================================ FILE: test/hello_world/.gitignore ================================================ .rebar3 _* .eunit *.o *.beam *.plt *.swp *.swo .erlang.cookie ebin log erl_crash.dump .rebar logs _build build .idea *.iml rebar3.crashdump doc gen ================================================ FILE: test/hello_world/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS Copyright 2018, Louis Pilfold . Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: test/hello_world/README.md ================================================ hello_world ===== An OTP library Build ----- $ rebar3 compile ================================================ FILE: test/hello_world/gleam.toml ================================================ name = "hello_world" tool = "other" ================================================ FILE: test/hello_world/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ ] [requirements] ================================================ FILE: test/hello_world/rebar.config ================================================ {erl_opts, [debug_info]}. {src_dirs, ["src", "gen/src"]}. {profiles, [ {test, [ {src_dirs, ["src", "test", "gen/src", "gen/test"]} ]} ]}. {deps, [ ]}. ================================================ FILE: test/hello_world/src/hello_world.app.src ================================================ {application, hello_world, [{description, "An OTP library"}, {vsn, "0.1.0"}, {registered, []}, {applications, [kernel, stdlib ]}, {env,[]}, {modules, []}, {maintainers, []}, {licenses, ["Apache 2.0"]}, {links, []} ]}. ================================================ FILE: test/hello_world/src/hello_world.gleam ================================================ ================================================ FILE: test/hello_world/src/nest/bird!.gleam ================================================ ================================================ FILE: test/hello_world/src/nest/bird.gleam ================================================ ================================================ FILE: test/hello_world/src/nest/bird.js ================================================ ================================================ FILE: test/hello_world/src/other.gleam ================================================ pub fn add(a, b) { a + b } pub fn main() { 1 |> add() } ================================================ FILE: test/hello_world/test/hello_test.gleam ================================================ import hello_world fn run(x, y) { x + y } ================================================ FILE: test/hextarball/.gitignore ================================================ build ================================================ FILE: test/hextarball/Makefile ================================================ # TODO: migrate to Rust shell commands, possibly ./compiler-cli/src/fs/tests.rs test: # remove old tarball && create one && make will fail when it wasn't cargo run clean && cargo run export hex-tarball && make build/hextarball-0.1.0.tar ================================================ FILE: test/hextarball/gleam.toml ================================================ name = "hextarball" version = "0.1.0" description = "Test project to construct a hex tarball" licences = ["Apache-2.0"] ================================================ FILE: test/hextarball/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ ] [requirements] ================================================ FILE: test/hextarball/src/hextarball.gleam ================================================ pub fn something() { "Just a thing to export" } ================================================ FILE: test/javascript_prelude/.gitignore ================================================ prelude.*js ================================================ FILE: test/javascript_prelude/Makefile ================================================ .PHONY: test test: @echo test/javascript_prelude @cp ../../compiler-core/templates/prelude.mjs prelude.mjs @node main.mjs @rm prelude.mjs ================================================ FILE: test/javascript_prelude/main.mjs ================================================ import { BitArray, BitArray$BitArray, BitArray$BitArray$data, BitArray$isBitArray, CustomType, Error, List, Ok, UtfCodepoint, codepointBits, divideFloat, divideInt, isEqual, stringBits, toBitArray, toList, sizedInt, sizedFloat, bitArraySlice, bitArraySliceToInt, bitArraySliceToFloat, } from "./prelude.mjs"; let failures = 0; let passes = 0; function pass() { process.stdout.write(`\u001b[${32}m.\u001b[${0}m`); passes++; } function fail(message) { console.log(""); console.assert(false, message); failures++; } function inspect(a) { if (typeof a === "object" && a !== null && typeof a.inspect === "function") { return a.inspect(); } else { return JSON.stringify(a); } } function assertEqual(a, b) { if (isEqual(a, b)) { pass(); } else { fail(`\n\t${inspect(a)}\n\t!=\n\t${inspect(b)}`); } } function assertNotEqual(a, b) { if (isEqual(a, b)) { fail(`\n\t${inspect(a)}\n\t==\n\t${inspect(b)}`); } else { pass(); } } function assertThrows(msg, callable) { try { callable(); fail(msg); } catch (error) { pass(); } } function assert(msg, value) { if (value) { pass(); } else { fail(msg); } } class ExampleRecordImpl extends CustomType { constructor(first, detail, boop) { super(); this[0] = first; this.detail = detail; this.boop = boop; } } let fmt = new Intl.DateTimeFormat("en-GB", { timeStyle: "medium" }); console.log(`Running tests at ${fmt.format(new Date())}\n`); // Equality of Gleam values assertEqual(true, true); assertEqual(false, false); assertEqual(undefined, undefined); assertNotEqual(true, false); assertNotEqual(false, true); assertNotEqual(undefined, false); assertNotEqual(undefined, true); assertNotEqual(true, undefined); assertNotEqual(false, undefined); assertEqual(1, 1); assertNotEqual(1, 2); assertEqual(1.1, 1.1); assertNotEqual(2.1, 1.1); assertEqual(-1, -1); assertNotEqual(-1, 1); assertEqual(-1.1, -1.1); assertNotEqual(-1.1, 1.1); assertEqual("", ""); assertEqual("123", "123"); assertEqual("👽", "👽"); assertNotEqual("👽", "👾"); assertEqual(new Ok(1), new Ok(1)); assertEqual(new Ok(2), new Ok(2)); assertEqual(new Ok(new Ok(2)), new Ok(new Ok(2))); assertNotEqual(new Ok(1), new Ok(2)); assertNotEqual(new Ok(new Ok(2)), new Ok(new Ok(3))); assertEqual(new Error(1), new Error(1)); assertEqual(new Error(2), new Error(2)); assertEqual(new Error(new Error(2)), new Error(new Error(2))); assertNotEqual(new Error(2), new Error(3)); assertNotEqual(new Error(new Error(2)), new Error(new Error(3))); assertEqual( new ExampleRecordImpl(undefined, 1, new Ok(2.1)), new ExampleRecordImpl(undefined, 1, new Ok(2.1)), ); assertNotEqual( new ExampleRecordImpl(undefined, 1, new Ok("2.1")), new ExampleRecordImpl(undefined, 1, new Ok(2.1)), ); assertEqual(List.fromArray([]), List.fromArray([])); assertEqual( List.fromArray([1, 2, new Ok(1)]), List.fromArray([1, 2, new Ok(1)]), ); assertNotEqual( List.fromArray([1, 2, new Ok(1)]), List.fromArray([1, 2, new Ok(2)]), ); assertNotEqual(List.fromArray([1, 2]), List.fromArray([1, 2, new Ok(2)])); assertNotEqual(List.fromArray([1]), List.fromArray([])); assertNotEqual(List.fromArray([]), List.fromArray([1])); assertEqual(new UtfCodepoint(128013), new UtfCodepoint(128013)); assertNotEqual(new UtfCodepoint(128013), new UtfCodepoint(128014)); // new BitArray() assertEqual( new BitArray(new Uint8Array([1, 2, 3])), new BitArray(new Uint8Array([1, 2, 3]), 24), ); assertThrows("`new BitArray()` throws with an ArrayBuffer", () => { new BitArray(new ArrayBuffer(8)); }); assertThrows( "`new BitArray()` throws with a raw array", () => new BitArray([1, 2]), ); assertThrows( "`new BitArray()` throws with invalid bit size", () => new BitArray(new Uint8Array([2]), -1), ); assertThrows( "`new BitArray()` throws with too many bytes for the bit size", () => new BitArray(new Uint8Array([1, 2]), 7), ); assertThrows( "`new BitArray()` throws with too few bytes for the bit size", () => new BitArray(new Uint8Array([1, 2]), 17), ); assertThrows( "`new BitArray()` throws with an invalid bit offset", () => new BitArray(new Uint8Array([1]), 0, -1), ); assertThrows( "`new BitArray()` throws with an invalid bit offset", () => new BitArray(new Uint8Array([1]), 0, 8), ); // toBitArray() assertEqual( new BitArray(new Uint8Array([1, 2])), toBitArray([new BitArray(new Uint8Array([1, 2]))]), ); assertEqual(new BitArray(new Uint8Array([])), toBitArray([])); const testValues = [ { input: 0, u8: 0 }, { input: 1, u8: 1 }, { input: 127, u8: 127 }, { input: 128, u8: 128 }, { input: 129, u8: 129 }, { input: 255, u8: 255 }, { input: 256, u8: 0 }, { input: 257, u8: 1 }, { input: 2000, u8: 208 }, { input: 0, u8: 0 }, { input: -1, u8: 255 }, { input: -127, u8: 129 }, { input: -128, u8: 128 }, { input: -129, u8: 127 }, { input: -255, u8: 1 }, { input: -256, u8: 0 }, { input: -257, u8: 255 }, { input: -2000, u8: 48 }, ]; for (const { input, u8 } of testValues) { assertEqual(new BitArray(new Uint8Array([u8])), toBitArray([input])); } assertEqual(new BitArray(new Uint8Array([])), toBitArray([new Uint8Array([])])); assertEqual( new BitArray(new Uint8Array([1, 2, 4, 8])), toBitArray([new Uint8Array([1, 2, 4, 8])]), ); assertEqual( new BitArray(new Uint8Array(testValues.map((t) => t.u8))), toBitArray(testValues.map((t) => t.input)), ); assertEqual( new BitArray( new Uint8Array([1, 2, 4, 8, ...testValues.map((t) => t.u8), 80, 90, 100]), ), toBitArray([ new Uint8Array([]), new Uint8Array([1, 2, 4, 8]), ...testValues.map((t) => t.input), new Uint8Array([80, 90]), new Uint8Array([]), new Uint8Array([100]), ]), ); assertEqual( new BitArray(new Uint8Array([97, 98, 99])), toBitArray([stringBits("abc")]), ); assertEqual( new BitArray(new Uint8Array([97])), toBitArray([codepointBits(new UtfCodepoint(97))]), ); assertEqual( new BitArray(new Uint8Array([240, 159, 144, 141])), toBitArray([codepointBits(new UtfCodepoint(128013))]), ); assertEqual( new BitArray(new Uint8Array([240, 159, 144, 141, 0xfe]), 39), toBitArray([ new BitArray(new Uint8Array([240, 159, 144])), new BitArray(new Uint8Array([141])), new BitArray(new Uint8Array([0xfe]), 7), ]), ); assertEqual( new BitArray( new Uint8Array([240, 159, 144, 0xa9, 0b11101010, 0xf7, 0x39, 0xae]), 64, ), toBitArray([ new BitArray(new Uint8Array([240, 159, 144])), new BitArray(new Uint8Array([0b10110101]), 4, 3), new BitArray(new Uint8Array([0x9f]), 7), new BitArray(new Uint8Array([0b00010100]), 4, 2), new BitArray(new Uint8Array([0]), 1), new BitArray(new Uint8Array([0xaf, 0x73, 0x9a, 0xee]), 24, 4), ]), ); assertEqual( new BitArray( new Uint8Array([ 129, 145, 57, 255, 255, 255, 255, 255, 191, 157, 243, 182, 246, 62, 104, 49, 62, 31, 110, 200, 120, 13, 88, ]), 181, ), toBitArray([ sizedInt(2, 2, false), sizedInt(0, 2, false), sizedInt(200, 11, true), sizedInt(-100, 49, false), sizedFloat(-1.234, 32, true), sizedFloat(-8.2e40, 64, false), sizedInt(0xf, 5, false), new Uint8Array([1]), 0xab, ]), ); // BitArray.equals() assertEqual(new BitArray(new Uint8Array([])), new BitArray(new Uint8Array([]))); assertEqual( new BitArray(new Uint8Array([1, 2, 3])), new BitArray(new Uint8Array([1, 2, 3])), ); assertNotEqual( new BitArray(new Uint8Array([1, 2])), new BitArray(new Uint8Array([1, 2, 3])), ); assertNotEqual( new BitArray(new Uint8Array([1, 0xf0]), 12), new BitArray(new Uint8Array([2, 0xf0]), 12), ); assertNotEqual( new BitArray(new Uint8Array([1, 0xf0]), 12), new BitArray(new Uint8Array([1, 0xf0]), 13), ); assertNotEqual( new BitArray(new Uint8Array([0x12, 0x30]), 12), new BitArray(new Uint8Array([0x12, 0x4f]), 12), ); assertEqual( new BitArray(new Uint8Array([0x12, 0x30]), 12), new BitArray(new Uint8Array([0x12, 0x3f]), 12), ); assertEqual( new BitArray(new Uint8Array([0b10110110, 0b01101001]), 8, 3), new BitArray(new Uint8Array([0b10110011]), 8), ); assertEqual( new BitArray(new Uint8Array([0b10110110, 0b01101001, 0b10011010]), 17, 5), new BitArray(new Uint8Array([0b11001101, 0b00110011, 0b01000001]), 17), ); assertEqual( new BitArray(new Uint8Array([0b11001101, 0b00110011]), 14, 1), new BitArray(new Uint8Array([0b11100110, 0b10011001]), 14, 2), ); assertEqual( new BitArray(new Uint8Array([0b10110110]), 4, 2), new BitArray(new Uint8Array([0b10111011]), 4, 3), ); assertNotEqual( new BitArray(new Uint8Array([0b10110110, 0b10110110]), 9, 2), new BitArray(new Uint8Array([0b10110111, 0b10100110]), 9, 2), ); assertNotEqual( new BitArray(new Uint8Array([0b10110110, 0b10110110]), 9, 2), new BitArray(new Uint8Array([0b10110110, 0b10000110]), 9, 2), ); // toList assertEqual(toList([]), List.fromArray([])); assertEqual(toList([1, 2, 3]), List.fromArray([1, 2, 3])); assertEqual(toList([1, 2], toList([3, 4])), List.fromArray([1, 2, 3, 4])); assertEqual(toList([1, 2, 3], toList([4, 5])), List.fromArray([1, 2, 3, 4, 5])); // Equality of JavaScript values assertEqual([], []); assertEqual([1, 2], [1, 2]); assertEqual([new Ok([1, 2])], [new Ok([1, 2])]); assertNotEqual([], [[]]); assertNotEqual([], [1, []]); assertNotEqual([1, []], []); assertEqual({}, {}); assertEqual({ a: 1 }, { a: 1 }); assertEqual({ a: 1, b: 2 }, { b: 2, a: 1 }); assertEqual({ a: new Ok(1) }, { a: new Ok(1) }); assertNotEqual({ a: new Ok(2) }, { a: new Ok(1) }); assertEqual(new Date(0), new Date(0)); assertNotEqual(new Date(1), new Date(0)); assertEqual(new Uint8Array([1, 2]), new Uint8Array([1, 2])); assertEqual(new Uint16Array([1, 2]), new Uint16Array([1, 2])); assertEqual(new Uint32Array([1, 2]), new Uint32Array([1, 2])); assertNotEqual(new Uint8Array([1, 3]), new Uint8Array([1, 2])); assertNotEqual(new Uint16Array([1, 3]), new Uint16Array([1, 2])); assertNotEqual(new Uint32Array([1, 3]), new Uint32Array([1, 2])); // Promises are not equal unless they have reference equality let promise = Promise.resolve(1); assertEqual(promise, promise); assertNotEqual(Promise.resolve(1), Promise.resolve(1)); // Functions are not equal unless they have reference equality let fun = () => 1; assertEqual(fun, fun); assertNotEqual( () => 1, () => 1, ); // Maps are compared structurally let map = new Map([["a", 1]]); assertEqual(map, map); assertEqual(new Map([["a", 1]]), new Map([["a", 1]])); assertNotEqual( new Map([ ["a", 1], ["b", 2], ]), new Map([["a", 1]]), ); assertNotEqual( new Map([["a", 1]]), new Map([ ["a", 1], ["b", 2], ]), ); assertNotEqual(new Map([["a", 1]]), new Map([["b", 1]])); assertEqual( new Map([["a", new Map([["a", []]])]]), new Map([["a", new Map([["a", []]])]]), ); assertNotEqual(new Map([]), new Map([["b", 1]])); // Sets are compared structurally let set = new Set(["a", 1]); assertEqual(set, set); assertEqual(new Set(["a", 1]), new Set(["a", 1])); assertNotEqual(new Set(["a", 1]), new Set(["b", 1])); assertNotEqual(new Set(["a", 1, "b"]), new Set(["a", 1])); assertNotEqual(new Set(["a", 1]), new Set(["a", 1, "b"])); assertNotEqual( new Set(["a", new Map([["a", []]])]), new Set(["a", new Map([["a", []]])]), ); // WeakMaps are not equal unless they have reference equality let weak_map = new WeakMap([[map, 1]]); assertEqual(weak_map, weak_map); assertNotEqual(new WeakMap([[map, 1]]), new WeakMap([[map, 1]])); // WeakSets are not equal unless they have reference equality let weak_set = new WeakSet([map, set]); assertEqual(weak_set, weak_set); assertNotEqual(new WeakSet([map, set]), new WeakSet([map, set])); // RegExp are compared structurally let re = new RegExp("test", "g"); let re_literal = /test/g; assertEqual(re, re); assertEqual(re_literal, re_literal); assertEqual(re, re_literal); assertNotEqual(re, new RegExp("test", "i")); assertNotEqual(re, new RegExp("test")); assertNotEqual(re_literal, new RegExp("test", "i")); assertNotEqual(re_literal, /test/); assertNotEqual(re_literal, new RegExp("test", "i")); assertNotEqual(re, /test/i); class ExampleA { constructor(x) { this.x = x; } } class ExampleB { constructor(x) { this.x = x; } } assertEqual(new ExampleA(1), new ExampleA(1)); assertEqual(new ExampleB(1), new ExampleB(1)); assertNotEqual(new ExampleA(1), new ExampleA(2)); assertNotEqual(new ExampleA(1), new ExampleB(1)); // Custom .equals() method class NoCustomEquals { constructor(id, notImportant) { this.id = id; this.notImportant = notImportant; } } class HasCustomEqualsThatThrows { constructor(id, notImportant) { this.id = id; this.notImportant = notImportant; } equals() { throw "not today"; } } class HasCustomEquals { constructor(id, notImportant) { this.id = id; this.notImportant = notImportant; } equals(o) { return this.id === o.id; } } function testCustomEquals(o) { this.id === o.id; } const hasEqualsField = { id: 1, notImportant: 2, equals: testCustomEquals, }; const hasEqualsField2 = { id: 1, notImportant: 3, equals: testCustomEquals, }; // no custom equals, use structural equality assertEqual(new NoCustomEquals(1, 1), new NoCustomEquals(1, 1)); assertNotEqual(new NoCustomEquals(1, 1), new NoCustomEquals(1, 2)); // custom equals throws, fallback to structural equality assertEqual( new HasCustomEqualsThatThrows(1, 1), new HasCustomEqualsThatThrows(1, 1), ); assertNotEqual( new HasCustomEqualsThatThrows(1, 1), new HasCustomEqualsThatThrows(1, 2), ); // custom equals works, use it assertEqual(new HasCustomEquals(1, 1), new HasCustomEquals(1, 1)); assertEqual(new HasCustomEquals(1, 1), new HasCustomEquals(1, 2)); assertNotEqual(new HasCustomEquals(1, 1), new HasCustomEquals(2, 1)); // custom equals defined on object instead of prototype, don't use it assertEqual(hasEqualsField, { ...hasEqualsField }); assertNotEqual(hasEqualsField, hasEqualsField2); // Objects assertEqual({ a: 1 }, { a: 1 }); assertEqual({ a: 1, b: 2 }, { b: 2, a: 1 }); assertEqual({ a: {} }, { a: {} }); assertEqual({ a: 1, b: { c: 3 } }, { a: 1, b: { c: 3 } }); assertNotEqual({ a: 1 }, {}); assertNotEqual({ a: 1, b: 2 }, { b: 2 }); assertNotEqual({}, { a: 1 }); assertNotEqual({ b: 2 }, { a: 1, b: 2 }); // BitArray assertEqual(new BitArray(new Uint8Array([1, 2, 3])).byteAt(0), 1); assertEqual(new BitArray(new Uint8Array([1, 2, 3])).byteAt(2), 3); // bitArraySlice assertThrows("`bitArraySlice()` throws if start is less than zero", () => bitArraySlice(new BitArray(new Uint8Array([1])), -1, 8), ); assertThrows( "`bitArraySlice()` throws if start is greater than the bit size", () => bitArraySlice(new BitArray(new Uint8Array([1])), 9, 8), ); assertThrows("`bitArraySlice()` throws if end is before start", () => bitArraySlice(new BitArray(new Uint8Array([1])), 4, 2), ); assertThrows( "`bitArraySlice()` throws if end is greater than the bit size", () => bitArraySlice(new Uint8Array([1]), 0, 10), ); assertEqual( bitArraySlice(new BitArray(new Uint8Array([1, 2, 3])), 0, 0), new BitArray(new Uint8Array([])), ); assertEqual( bitArraySlice(new BitArray(new Uint8Array([0xbe, 0xff])), 0, 2), new BitArray(new Uint8Array([0xbe]), 2), ); assertEqual( bitArraySlice(new BitArray(new Uint8Array([0x12, 0b10101101, 0xff])), 10, 14), new BitArray(new Uint8Array([0b10110100]), 4), ); assertEqual( bitArraySlice(new BitArray(new Uint8Array([1, 2, 3])), 8, 24), new BitArray(new Uint8Array([2, 3])), ); assertEqual( bitArraySlice(new BitArray(new Uint8Array([1, 2, 3, 4, 5])), 8, 32), new BitArray(new Uint8Array([2, 3, 4])), ); assertEqual( bitArraySlice(new BitArray(new Uint8Array([1, 0xfe, 0xa1])), 8, 19), new BitArray(new Uint8Array([0xfe, 0xa1]), 11), ); assertEqual( bitArraySlice( new BitArray(new Uint8Array([17, 79, 190, 151, 98, 222, 101])), 19, 51, ), new BitArray(new Uint8Array([244, 187, 22, 243]), 32), ); assertEqual( bitArraySlice(new BitArray(new Uint8Array([0, 0, 0, 0, 0xff, 0xe0])), 37, 41), new BitArray(new Uint8Array([0b11111100]), 4), ); assertEqual( bitArraySliceToFloat( bitArraySlice( new BitArray(new Uint8Array([0xaa, 0xbb, 0xff, 0, 0, 0])), 8, 48, ), 8, 40, false, ), 3.5733110840282835e-43, ); assertEqual( bitArraySlice( bitArraySlice(new BitArray(new Uint8Array([1, 2, 3, 4, 5])), 8, 32), 8, ), new BitArray(new Uint8Array([3, 4])), ); assertEqual( bitArraySlice( new BitArray(new Uint8Array([0b00000001, 0b00000010, 0b00000011]), 20, 2), 4, 10, ), new BitArray(new Uint8Array([0b00000001, 0b00000010]), 6, 6), ); assertEqual( bitArraySlice(new BitArray(new Uint8Array([1])), 1), new BitArray(new Uint8Array([0b00000010]), 7), ); // sizedFloat() assertEqual(sizedFloat(0.0, 16, true), new Uint8Array([0x00, 0x00])); assertEqual(sizedFloat(1.0, 16, true), new Uint8Array([0x3c, 0x00])); assertEqual(sizedFloat(-1.0, 16, false), new Uint8Array([0x00, 0xbc])); assertEqual(sizedFloat(1.234375, 16, true), new Uint8Array([0x3c, 0xf0])); assertEqual(sizedFloat(-65_504.0, 16, false), new Uint8Array([0xff, 0xfb])); assertEqual( sizedFloat(-0.00001519918441772461, 16, true), new Uint8Array([0x80, 0xff]), ); assertEqual(sizedFloat(Infinity, 16, true), new Uint8Array([0x7c, 0x00])); assertEqual(sizedFloat(-Infinity, 16, false), new Uint8Array([0x00, 0xfc])); assertEqual(sizedFloat(NaN, 16, true), new Uint8Array([0x7e, 0x00])); assertEqual(sizedFloat(1_000_000.0, 16, true), new Uint8Array([0x7c, 0x00])); assertEqual(sizedFloat(-1_000_000.0, 16, true), new Uint8Array([0xfc, 0x00])); assertEqual(sizedFloat(16.25, 32, true), new Uint8Array([65, 130, 0, 0])); assertEqual(sizedFloat(-16.25, 32, false), new Uint8Array([0, 0, 130, 193])); assertEqual( sizedFloat(1000.5, 64, true), new Uint8Array([64, 143, 68, 0, 0, 0, 0, 0]), ); assertEqual( sizedFloat(-1000.5, 64, false), new Uint8Array([0, 0, 0, 0, 0, 68, 143, 192]), ); // sizedInt() assertEqual(sizedInt(100, -8, true), new Uint8Array([])); assertEqual(sizedInt(100, 0, true), new Uint8Array([])); assertEqual(sizedInt(100, 8, true), new Uint8Array([100])); assertEqual(sizedInt(-10, 8, true), new Uint8Array([246])); assertEqual(sizedInt(1, 1, true), new BitArray(new Uint8Array([0x80]), 1)); assertEqual(sizedInt(2, 2, true), new BitArray(new Uint8Array([0x80]), 2)); assertEqual(sizedInt(15, 4, true), new BitArray(new Uint8Array([0xf0]), 4)); assertEqual(sizedInt(100, 7, true), new BitArray(new Uint8Array([200]), 7)); assertEqual(sizedInt(-44, 7, true), new BitArray(new Uint8Array([168]), 7)); assertEqual(sizedInt(-1, 6, true), new BitArray(new Uint8Array([252]), 6)); assertEqual(sizedInt(-1231, 3, true), new BitArray(new Uint8Array([32]), 3)); assertEqual(sizedInt(1231, 5, true), new BitArray(new Uint8Array([120]), 5)); assertEqual( sizedInt(773, 10, true), new BitArray(new Uint8Array([193, 64]), 10), ); assertEqual( sizedInt(-276, 10, false), new BitArray(new Uint8Array([236, 128]), 10), ); assertEqual(sizedInt(80000, 16, true), new Uint8Array([56, 128])); assertEqual(sizedInt(-80000, 16, true), new Uint8Array([199, 128])); assertEqual(sizedInt(1, 24, true), new Uint8Array([0, 0, 1])); assertEqual( sizedInt(-100, 31, false), new BitArray(new Uint8Array([156, 255, 255, 254]), 31), ); assertEqual(sizedInt(0, 32, true), new Uint8Array([0, 0, 0, 0])); assertEqual(sizedInt(-1, 32, true), new Uint8Array([255, 255, 255, 255])); assertEqual( sizedInt(-10, 33, true), new BitArray(new Uint8Array([255, 255, 255, 251, 0]), 33), ); assertEqual( sizedInt(-489_391_639_457_909_760, 56, true), new Uint8Array([53, 84, 229, 150, 16, 180, 0]), ); assertEqual( sizedInt(-1, 64, true), new Uint8Array([255, 255, 255, 255, 255, 255, 255, 255]), ); assertEqual( sizedInt(Number.MAX_SAFE_INTEGER, 64, true), new Uint8Array([0, 31, 255, 255, 255, 255, 255, 255]), ); assertEqual( sizedInt(Number.MIN_SAFE_INTEGER, 64, true), new Uint8Array([255, 224, 0, 0, 0, 0, 0, 1]), ); assertEqual( sizedInt(Number.MAX_SAFE_INTEGER, 77, true), new BitArray( new Uint8Array([0, 0, 0, 255, 255, 255, 255, 255, 255, 248]), 77, ), ); assertEqual( sizedInt(Number.MIN_SAFE_INTEGER, 75, false), new BitArray(new Uint8Array([1, 0, 0, 0, 0, 0, 224, 255, 255, 224]), 75), ); assertEqual( sizedInt(Number(9444732965739289353650176n), 75, true), new BitArray(new Uint8Array([255, 255, 255, 255, 255, 248, 0, 0, 0, 0]), 75), ); // bitArraySliceToFloat() assertEqual( bitArraySliceToFloat(toBitArray([63, 240, 0, 0, 0, 0, 0, 0]), 0, 64, true), 1.0, ); assertEqual( bitArraySliceToFloat(toBitArray([0, 0, 0, 0, 0, 0, 240, 63]), 0, 64, false), 1.0, ); assertEqual( bitArraySliceToFloat(toBitArray([0xff, 0xc9, 0x74, 0x24, 0x00]), 8, 40, true), -1000000.0, ); assertEqual( bitArraySliceToFloat(toBitArray([0x00, 0x24, 0x74, 0xc9]), 0, 32, false), -1000000.0, ); assertEqual( bitArraySliceToFloat( new BitArray(new Uint8Array([112, 152, 127, 244, 0, 7, 192]), 50, 0), 11, 43, true, ), -511.25, ); assertEqual( bitArraySliceToFloat( new BitArray( new Uint8Array([8, 0, 0, 0, 1, 129, 39, 103, 129, 254]), 79, 0, ), 7, 71, false, ), -5011.75, ); assertEqual( bitArraySliceToFloat( new BitArray(new Uint8Array([212, 152, 127, 244, 0, 7, 192]), 50, 3), 11, 43, true, ), 1.0714967429001753e-19, ); assertEqual( bitArraySliceToFloat(new BitArray(new Uint8Array([0x00, 0x00])), 0, 16, true), 0.0, ); assertEqual( bitArraySliceToFloat(new BitArray(new Uint8Array([0x3c, 0x00])), 0, 16, true), 1.0, ); assertEqual( bitArraySliceToFloat( new BitArray(new Uint8Array([0x00, 0xbc])), 0, 16, false, ), -1.0, ); assertEqual( bitArraySliceToFloat( new BitArray(new Uint8Array([0xf0, 0x3c])), 0, 16, false, ), 1.234375, ); assertEqual( bitArraySliceToFloat(new BitArray(new Uint8Array([0xfb, 0xff])), 0, 16, true), -65_504.0, ); assertEqual( bitArraySliceToFloat( new BitArray(new Uint8Array([0xff, 0x80])), 0, 16, false, ), -0.00001519918441772461, ); assertEqual( bitArraySliceToFloat(new BitArray(new Uint8Array([0x7c, 0x00])), 0, 16, true), Infinity, ); assertEqual( bitArraySliceToFloat( new BitArray(new Uint8Array([0x00, 0xfc])), 0, 16, false, ), -Infinity, ); assertEqual( isNaN( bitArraySliceToFloat( new BitArray(new Uint8Array([0x7e, 0x00])), 0, 16, true, ), ), true, ); // bitArraySliceToInt() assertEqual( bitArraySliceToInt(toBitArray([0b10011110]), 0, 4, true, false), 0b1001, ); assertEqual( bitArraySliceToInt( new BitArray(new Uint8Array([0b11001110]), 7, 1), 0, 4, true, false, ), 0b1001, ); assertEqual( bitArraySliceToInt(toBitArray([0b10011110]), 4, 8, true, false), 0b1110, ); assertEqual( bitArraySliceToInt(toBitArray([0b10011110]), 1, 6, true, true), 0b00111, ); assertEqual(bitArraySliceToInt(toBitArray([0b10011010]), 3, 8, true, true), -6); assertEqual( bitArraySliceToInt(toBitArray([0b10011010]), 0, 7, true, true), -51, ); assertEqual( bitArraySliceToInt( new BitArray(new Uint8Array([0b11001100, 0b00100000]), 11), 1, 11, false, false, ), 408, ); assertEqual( bitArraySliceToInt(toBitArray([0xb6, 0xe3]), 0, 12, true, false), 0xb6e, ); assertEqual( bitArraySliceToInt(toBitArray([0xb6, 0xe3]), 0, 12, false, false), 0xeb6, ); assertEqual( bitArraySliceToInt(toBitArray([0xff, 0xb6, 0xe3]), 8, 20, true, false), 0xb6e, ); assertEqual( bitArraySliceToInt(toBitArray([0xff, 0xb6, 0xe3]), 20, 24, true, false), 0x03, ); assertEqual( bitArraySliceToInt(toBitArray([0xff, 0xb6, 0xe3]), 8, 20, false, false), 0xeb6, ); assertEqual( bitArraySliceToInt(toBitArray([0xa5, 0x6c, 0xaa]), 5, 18, true, false), 5554, ); assertEqual( bitArraySliceToInt(toBitArray([0xa5, 0x6c, 0xaa]), 5, 18, false, true), -3411, ); assertEqual(bitArraySliceToInt(toBitArray([1, 2, 3]), 0, 8, true, false), 1); assertEqual( bitArraySliceToInt(toBitArray([160, 2, 3]), 0, 8, false, true), -96, ); assertEqual(bitArraySliceToInt(toBitArray([1, 2, 3]), 0, 16, true, false), 258); assertEqual( bitArraySliceToInt(toBitArray([1, 2, 3]), 0, 16, false, false), 513, ); assertEqual( bitArraySliceToInt(toBitArray([1, 160, 3]), 0, 16, false, true), -24575, ); assertEqual( bitArraySliceToInt(toBitArray([160, 2, 3]), 0, 16, true, false), 40962, ); assertEqual( bitArraySliceToInt(toBitArray([3, 160, 2]), 8, 24, true, true), -24574, ); assertEqual( bitArraySliceToInt( new BitArray(new Uint8Array([146, 192, 70, 25, 128]), 33), 1, 24, true, true, ), 1_228_870, ); assertEqual( bitArraySliceToInt(toBitArray([255, 255, 255, 255]), 0, 32, false, true), -1, ); assertEqual( bitArraySliceToInt( new BitArray(new Uint8Array([217, 150, 209, 191, 0]), 33), 1, 33, true, false, ), 3_006_112_638, ); assertEqual( bitArraySliceToInt( new BitArray(new Uint8Array([146, 192, 70, 25, 128]), 33), 1, 33, true, true, ), 629_181_491, ); assertEqual( bitArraySliceToInt( new BitArray(new Uint8Array([251, 24, 47, 227, 128]), 33), 1, 33, false, false, ), 3_344_904_438, ); assertEqual( bitArraySliceToInt( new BitArray(new Uint8Array([240, 102, 91, 101, 128]), 33), 0, 33, false, false, ), 5_995_456_240, ); assertEqual( bitArraySliceToInt( toBitArray([231, 255, 255, 255, 254, 123]), 0, 40, true, true, ), -103_079_215_106, ); assertEqual( bitArraySliceToInt( toBitArray([0, 231, 255, 255, 253, 123, 17]), 1, 55, true, false, ), 127_543_348_739_464, ); assertEqual( bitArraySliceToInt( toBitArray([142, 231, 255, 255, 253, 123, 17, 139]), 8, 62, false, true, ), -8425025061257241, ); assertEqual( bitArraySliceToInt( toBitArray([142, 231, 255, 255, 253, 123, 17, 139]), 7, 62, false, true, ), -8293899692933261, ); assertEqual( bitArraySliceToInt( toBitArray([142, 231, 255, 255, 253, 123, 17]), 8, 48, true, true, ), -103_079_215_749, ); assertEqual( bitArraySliceToInt( toBitArray([255, 255, 255, 255, 255, 255, 255]), 0, 56, true, true, ), -1, ); assertEqual( bitArraySliceToInt( toBitArray([0x00, 0xaa, 255, 255, 255, 255, 255]), 0, 56, true, false, ), 0xaaffffffffff, ); assertEqual( bitArraySliceToInt( toBitArray([255, 255, 255, 255, 255, 0xaa, 0x00]), 0, 56, false, false, ), 0xaaffffffffff, ); assertEqual( bitArraySliceToInt( toBitArray([255, 255, 255, 255, 255, 255, 255]), 0, 56, true, false, ), Number(0xfffffffffffffen), ); assertEqual( bitArraySliceToInt(toBitArray([0xfe, 0x3f]), 4, 12, true, false), 0xe3, ); assertEqual(bitArraySliceToInt(toBitArray([253, 94]), 3, 11, true, true), -22); assertEqual( bitArraySliceToInt(toBitArray([233, 164]), 3, 15, true, false), 1234, ); assertEqual( bitArraySliceToInt(toBitArray([250, 72]), 3, 15, false, false), 1234, ); assertEqual( bitArraySliceToInt(toBitArray([250, 72, 223, 189]), 7, 29, true, false), 596983, ); assertEqual( bitArraySliceToInt( toBitArray([250, 72, 223, 189, 41, 97, 165, 0, 0, 0, 0, 177]), 14, 85, false, true, ), 70821197049655, ); assertEqual( bitArraySliceToInt( toBitArray([250, 72, 223, 189, 41, 97, 165, 0, 0, 0, 0, 177]), 14, 85, true, true, ), Number(515_906_807_693_217_628_160n), ); // Result.isOk assertEqual(new Ok(1).isOk(), true); assertEqual(new Error(1).isOk(), false); // List.atLeastLength assertEqual(List.fromArray([]).atLeastLength(0), true); assertEqual(List.fromArray([]).atLeastLength(1), false); assertEqual(List.fromArray([]).atLeastLength(-1), true); assertEqual(List.fromArray([1]).atLeastLength(0), true); assertEqual(List.fromArray([1]).atLeastLength(1), true); assertEqual(List.fromArray([1]).atLeastLength(2), false); assertEqual(List.fromArray([1]).atLeastLength(-1), true); // List.hasLength assertEqual(toList([]).hasLength(0), true); assertEqual(toList([]).hasLength(1), false); assertEqual(toList([]).hasLength(-1), false); assertEqual(toList([1]).hasLength(0), false); assertEqual(toList([1]).hasLength(1), true); assertEqual(toList([1]).hasLength(2), false); assertEqual(toList([1, 1]).hasLength(1), false); assertEqual(toList([1, 1]).hasLength(2), true); assertEqual(toList([1, 1]).hasLength(3), false); // List iterable interface assertEqual([...toList([])], []); assertEqual([...toList([1, 2, 3])], [1, 2, 3]); // BitArray.byteSize assertEqual(new BitArray(new Uint8Array([])).byteSize, 0); assertEqual(new BitArray(new Uint8Array([1, 2])).byteSize, 2); assertEqual(new BitArray(new Uint8Array([1, 2, 3, 4])).byteSize, 4); assertEqual(new BitArray(new Uint8Array([1, 2, 3, 4], 28)).byteSize, 4); // BitArray assert("numbers are not bit arrays", !BitArray$isBitArray(123)); assert("strings are not bit arrays", !BitArray$isBitArray("!")); assert("results are not bit arrays", new Ok("!")); assert( "Bit arrays are bit arrays", BitArray$isBitArray(BitArray$BitArray(new Uint8Array([]))), ); // Byte aligned bit array { const bitArray = BitArray$BitArray(new Uint8Array([1, 2, 3])); assertEqual(bitArray.bitSize, 3 * 8); assertEqual(bitArray.byteSize, 3); assertEqual(bitArray.bitOffset, 0); assertEqual(bitArray.rawBuffer, new Uint8Array([1, 2, 3])); assertEqual( BitArray$BitArray$data(bitArray), new DataView(new Uint8Array([1, 2, 3]).buffer), ); } // Non-byte aligned bit array { const bitArray = BitArray$BitArray(new Uint8Array([1, 2, 3]), 23, 1); assertEqual(bitArray.bitSize, 23); assertEqual(bitArray.byteSize, 3); assertEqual(bitArray.bitOffset, 1); assertEqual(bitArray.rawBuffer, new Uint8Array([1, 2, 3])); assertThrows("BitArray$BitArray$data of un-aligned bit array", () => { BitArray$BitArray$data(bitArray); }); } assertEqual( BitArray$BitArray(new Uint8Array([])), new BitArray(new Uint8Array([])), ); // // Division // assertEqual(divideInt(1, 0), 0); assertEqual(divideInt(1, 1), 1); assertEqual(divideInt(1, 2), 0); assertEqual(divideInt(3, 2), 1); assertEqual(divideInt(11, 3), 3); assertEqual(divideInt(-1, 0), 0); assertEqual(divideInt(-1, 1), -1); assertEqual(divideInt(-1, 2), -0); assertEqual(divideInt(-3, 2), -1); assertEqual(divideInt(-11, 3), -3); assertEqual(divideInt(1, -1), -1); assertEqual(divideInt(1, -2), 0); assertEqual(divideInt(3, -2), -1); assertEqual(divideInt(11, -3), -3); assertEqual(divideInt(-1, -1), 1); assertEqual(divideInt(-1, -2), 0); assertEqual(divideInt(-3, -2), 1); assertEqual(divideInt(-11, -3), 3); assertEqual(divideFloat(1.5, 0.0), 0.0); assertEqual(divideFloat(1.5, 2.0), 0.75); assertEqual(divideFloat(1.5, 2.5), 0.6); assertEqual(divideFloat(-1.5, 0.0), -0.0); assertEqual(divideFloat(-1.5, 2.0), -0.75); assertEqual(divideFloat(-1.5, 2.5), -0.6); assertEqual(divideFloat(1.5, -0.0), -0.0); assertEqual(divideFloat(1.5, -2.0), -0.75); assertEqual(divideFloat(1.5, -2.5), -0.6); assertEqual(divideFloat(-1.5, -0.0), 0.0); assertEqual(divideFloat(-1.5, -2.0), 0.75); assertEqual(divideFloat(-1.5, -2.5), 0.6); // Record updates assertEqual(new Ok(1).withFields({ 0: 2 }), new Ok(2)); assertEqual(new Error(1).withFields({ 0: 2 }), new Error(2)); assertEqual( new ExampleRecordImpl(1, 2, 3).withFields({}), new ExampleRecordImpl(1, 2, 3), ); assertEqual( new ExampleRecordImpl(1, 2, 3).withFields({ boop: 6, 0: 40 }), new ExampleRecordImpl(40, 2, 6), ); assertEqual( new ExampleRecordImpl(1, 2, 3).withFields({ boop: 4, detail: 5, 0: 6 }), new ExampleRecordImpl(6, 5, 4), ); // // Summary // console.log(` ${passes + failures} tests ${passes} passes ${failures} failures `); if (failures) process.exit(1); ================================================ FILE: test/language/.gitignore ================================================ build ================================================ FILE: test/language/Makefile ================================================ .PHONY: build build: clean erlang nodejs deno .PHONY: clean clean: rm -rf build .PHONY: erlang erlang: @echo test/language on Erlang cargo run --quiet -- test --target erlang .PHONY: nodejs nodejs: @echo test/language on JavaScript with Node cargo run --quiet -- test --target javascript --runtime nodejs .PHONY: deno deno: @echo test/language on JavaScript with Deno cargo run --quiet -- test --target javascript --runtime deno .PHONY: bun bun: @echo test/language on JavaScript with Bun cargo run --quiet -- test --target javascript --runtime bun ================================================ FILE: test/language/gleam.toml ================================================ name = "language" version = "1.0.0" [dev_dependencies] gleeunit = ">= 1.9.0 and < 2.0.0" [javascript.deno] allow_read = [ "./build", "gleam.toml", "test" ] ================================================ FILE: test/language/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "gleam_stdlib", version = "0.68.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "F7FAEBD8EF260664E86A46C8DBA23508D1D11BB3BCC6EE1B89B3BC3E5C83FF1E" }, { name = "gleeunit", version = "1.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "DA9553CE58B67924B3C631F96FE3370C49EB6D6DC6B384EC4862CC4AAA718F3C" }, ] [requirements] gleeunit = { version = ">= 1.9.0 and < 2.0.0" } ================================================ FILE: test/language/src/language.gleam ================================================ pub fn main() { Nil } ================================================ FILE: test/language/test/ffi.gleam ================================================ pub type Dynamic @external(erlang, "ffi_erlang", "print") @external(javascript, "./ffi_javascript.mjs", "print") pub fn print(a: String) -> Nil @external(erlang, "ffi_erlang", "append") @external(javascript, "./ffi_javascript.mjs", "append") pub fn append(a: String, b: String) -> String @external(erlang, "ffi_erlang", "to_string") @external(javascript, "./ffi_javascript.mjs", "toString") pub fn to_string(a: anything) -> String @external(erlang, "ffi_erlang", "file_exists") @external(javascript, "./ffi_javascript.mjs", "fileExists") pub fn file_exists(a: String) -> Bool @external(erlang, "ffi_erlang", "halt") @external(javascript, "./ffi_javascript.mjs", "halt") pub fn halt(a: Int) -> Nil @external(erlang, "ffi_erlang", "to_dynamic") @external(javascript, "./ffi_javascript.mjs", "toDynamic") pub fn to_dynamic(a: x) -> Dynamic @external(erlang, "ffi_erlang", "to_codepoint") @external(javascript, "./ffi_javascript.mjs", "toCodepoint") pub fn utf_codepoint(a: Int) -> UtfCodepoint @external(erlang, "ffi_erlang", "read_uint16_from_bit_array") @external(javascript, "./ffi_javascript.mjs", "readUint16FromBitArray") pub fn read_uint16_from_bit_array(a: BitArray) -> Int ================================================ FILE: test/language/test/ffi_erlang.erl ================================================ -module(ffi_erlang). -export([ to_string/1, append/2, print/1, file_exists/1, halt/1, to_dynamic/1, to_codepoint/1, read_uint16_from_bit_array/1 ]). append(A, B) -> <>. print(S) -> io:format("~s", [S]), nil. to_string(Term) -> List = io_lib:format("~p", [Term]), iolist_to_binary(List). file_exists(Path) -> filelib:is_regular(Path). halt(Code) -> erlang:halt(Code). to_dynamic(X) -> X. to_codepoint(X) -> X. read_uint16_from_bit_array(BitArray) -> <> = BitArray, Value. ================================================ FILE: test/language/test/ffi_javascript.mjs ================================================ import { UtfCodepoint, BitArray$BitArray$data } from "./gleam.mjs"; let fs; if (!globalThis.Deno) { fs = await import("fs"); } export function append(a, b) { return a + b; } export function print(string) { if (globalThis.Deno) { globalThis.Deno.stdout.writeSync(new TextEncoder().encode(string)); } else { process.stdout.write(string); } return string; } export function toString(a) { let sanitise = (_k, v) => (typeof v === "bigint" ? `${v}n` : v); try { return JSON.stringify(a, sanitise); } catch (_error) { return "//"; } } export function fileExists(path) { if (globalThis.Deno) { try { Deno.statSync(path); return true; } catch { return false; } } else { return fs.existsSync(path); } } export function halt(code) { if (globalThis.Deno) { Deno.exit(code); } else { process.exit(code); } } export function toDynamic(a) { return a; } export function toCodepoint(x) { return new UtfCodepoint(x); } export function readUint16FromBitArray(bitArray) { return BitArray$BitArray$data(bitArray).getUint16(0); } ================================================ FILE: test/language/test/ffi_typescript.ts ================================================ export function append(a: string, b: string) { return a + b; } ================================================ FILE: test/language/test/importable.gleam ================================================ pub type NoFields { NoFields } /// This function has argument names that are not valid in Erlang or JavaScript pub fn bad_argument_names(in, class, receive) { #(in, class, receive) } /// This custom type has label names that are not valid in Erlang or JavaScript pub type BadLabelNames { BadLabelNames(in: String, class: String, receive: String) } pub const ints_in_bit_array = <<1, 2, 3>> pub const string_in_bit_array = <<"Gleam":utf8>> pub const data = << 0x1, 2, 2:size(16), 0x4:size(32), "Gleam":utf8, 4.2:float, <<<<1, 2, 3>>:bits, "Gleam":utf8, 1024>>:bits, >> pub fn get_bit_array() { << 0x1, 2, 2:size(16), 0x4:size(32), "Gleam":utf8, 4.2:float, <<<<1, 2, 3>>:bits, "Gleam":utf8, 1024>>:bits, >> } pub const language = "gleam" pub type Movie { Movie(title: String) } pub const war_games = Movie("WarGames") ================================================ FILE: test/language/test/language/alternative_pattern.gleam ================================================ fn make_int_zero() { 0 } pub fn numbers_test() { let int = 4 let int_2 = case int { 1 | 2 | 3 | 4 -> 0 _ -> 1 } assert 0 == int_2 } pub fn lists_test() { let ints = [1, 2] let int = case ints { [0] | [1, 2] -> 0 _ -> 1 } assert 0 == int } pub fn assignment_test() { let ints = [1, 2] let int = case ints { [x] | [_, x] -> x _ -> 0 } assert 2 == int } pub fn multiple_assignment_test() { let ints = [1, 2, 3] let value = case ints { [x, y] | [x, y, 3] -> #(x, y) _ -> #(0, 0) } assert #(1, 2) == value } pub fn guard_test() { let int_two = make_int_zero() + 2 let ints = [1, 2] let int = case ints { [x] | [_, x] if x == int_two -> x _ -> 0 } assert 2 == int } pub fn guard_left_hand_side_test() { let int_one = make_int_zero() + 1 let ints = [1] let int = case ints { [x] | [_, x] if x == int_one -> x _ -> 0 } assert 1 == int } ================================================ FILE: test/language/test/language/anonymous_function_test.gleam ================================================ // https://github.com/gleam-lang/gleam/issues/1637 pub fn anonymous_function_test() { let f = fn(x) { let x = x x } assert 1 == f(1) } pub fn immutable_scope_test() { let x = 1 let f = fn() { x } let x = 2 assert f() == 1 assert x == 2 } type FnBox { FnBox(f: fn(Int) -> Int) } pub fn call_record_access_test() { let b = FnBox(f: fn(x) { x }) assert b.f(5) == 5 } pub fn call_tuple_access_test() { let t = #(fn(x) { x }) assert t.0(5) == 5 } ================================================ FILE: test/language/test/language/assertion_test.gleam ================================================ pub fn let_assert_ok_discard_test() { let result = { let x = ok_one() let assert Ok(_) = x } assert Ok(1) == result } pub fn let_assert_ok_bind_test() { let x = ok_one() let assert Ok(x) = x assert 1 == x } fn ok_one() -> Result(Int, a) { Ok(1) } ================================================ FILE: test/language/test/language/bit_array_dynamic_size_test.gleam ================================================ pub fn dynamic_size_1_test() { let size = 8 let assert <> = <<42>> assert value == 42 } pub fn dynamic_size_2_test() { let size = 3 let other_size = 5 let third_size = 8 let assert << value:size(size), second:size(other_size), last:size(third_size), >> = <<5:3, 8:5, 128>> assert #(value, second, last) == #(5, 8, 128) } pub fn dynamic_size_3_test() { let size = 2 let assert <> = <<1, 2, 3, 4>> assert first_bytes == <<1, 2>> } pub fn dynamic_size_4_test() { let size = 6 let assert <> = <<0b10010100, 6, 2938>> assert bits == <<0b100101:6>> } pub fn dynamic_size_5_test() { let size = 7 let assert <<123:size(size)>> = <<123:7>> } pub fn dynamic_size_6_test() { let size = 4 let size = size + 2 let assert <> = <<61:6>> assert value == 61 } ================================================ FILE: test/language/test/language/bit_array_match_test.gleam ================================================ import importable pub fn match_1_test() { let assert <<1, x>> = <<1, 2>> assert x == 2 } pub fn match_2_test() { let assert <> = <<1>> assert a == 1 } pub fn match_3_test() { let assert <> = <<1, 2, 3>> assert #(a, b) == #(258, 3) } pub fn match_4_test() { let assert <> = << 255, 255, 255, 255, 255, 216, 240, >> assert #(a, b) == #(-1, -10_000) } pub fn match_5_test() { let assert <> = << 255, 255, 255, 255, 240, 216, 255, >> assert #(a, b) == #(65_535, -655_294_465) } pub fn match_6_test() { let assert <> = <<255, 255, 255, 255, 255, 255, 255, 255>> assert a == -1 } pub fn match_7_test() { let assert <> = <<0x00, 0xaa, 255, 255, 255, 255, 255>> assert a == 0xaaffffffffff } pub fn match_8_test() { let assert <> = <<255, 255, 255, 255, 255, 0xaa, 0x00>> assert a == 0xaaffffffffff } pub fn match_9_test() { let assert <> = <<63, 240, 0, 0, 0, 0, 0, 0, 1>> assert #(a, b) == #(1.0, 1) } pub fn match_10_test() { let assert <> = <<1.23:float>> assert a == 1.23 } pub fn match_11_test() { let assert <> = <<63, 176, 0, 0>> assert a == 1.375 } pub fn match_12_test() { let assert <> = <<61, 10, 215, 163, 112, 61, 18, 64>> assert a == 4.56 } pub fn match_13_test() { let assert <> = <<0, 0>> assert a == 0.0 } pub fn match_14_test() { let assert <> = <<0x3C, 0xF0>> assert a == 1.234375 } pub fn match_15_test() { let assert <> = <<0xFF, 0xFB>> assert a == -65_504.0 } pub fn match_16_test() { let assert <<_, rest:bytes>> = <<1>> assert rest == <<>> } pub fn match_17_test() { let assert <<_, rest:bytes>> = <<1, 2, 3>> assert rest == <<2, 3>> } pub fn match_18_test() { let assert <> = <<1, 2, 3>> assert x == <<1, 2>> } pub fn match_19_test() { assert << 0x1, 2, 2:size(16), 0x4:size(32), "Gleam":utf8, 4.2:float, <<<<1, 2, 3>>:bits, "Gleam":utf8, 1024>>:bits, >> == importable.get_bit_array() } pub fn match_20_test() { assert << 0x1, 2, 2:size(16), 0x4:size(32), "Gleam":utf8, 4.2:float, <<<<1, 2, 3>>:bits, "Gleam":utf8, 1024>>:bits, >> == importable.data } pub fn match_21_test() { assert <<71, 108, 101, 97, 109>> == <<"Gleam":utf8>> } pub fn match_22_test() { let assert <> = <<231, 255, 255, 255, 254, 123>> assert i == -103_079_215_106 } pub fn match_23_test() { let assert <<_, i:40-signed, _:bits>> = <<142, 231, 255, 255, 253, 123, 17>> assert i == -103_079_215_749 } // https://github.com/gleam-lang/gleam/issues/4712 pub fn multiple_variable_segments_test() { let assert <> = <<2, 3:2, 7:3>> assert a + b + c == 12 } ================================================ FILE: test/language/test/language/bit_array_test.gleam ================================================ import ffi import gleam/bit_array pub fn utf8_emoji_equal_test() { let left = <<"Gleam":utf8, "👍":utf8>> let right = <<"Gleam":utf8, "👍":utf8>> assert left == right } pub fn utf8_emoji_not_equal_test() { let left = <<"Gleam":utf8, "👍":utf8>> let right = <<"👍":utf8>> assert left != right } pub fn utf8_ascii_test() { let left = <<"abc":utf8>> let right = <<97, 98, 99>> assert left == right } pub fn utf8_unicode_escape_test() { let left = <<"😀":utf8>> let right = <<"\u{1F600}":utf8>> assert left == right } pub fn nested_bit_array_test() { let left = <<<<1>>:bits, 2>> let right = <<1, 2>> assert left == right } pub fn int_segment_test() { let left = <<1>> let right = <<1:int>> assert left == right } pub fn int_16_test() { let left = <<80_000:16>> let right = <<56, 128>> assert left == right } pub fn negative_int_16_test() { let left = <<-80_000:16>> let right = <<199, 128>> assert left == right } pub fn negative_int_64_test() { let left = <<-1:64>> let right = <<255, 255, 255, 255, 255, 255, 255, 255>> assert left == right } pub fn negative_int_56_test() { let left = <<-489_391_639_457_909_760:56>> let right = <<53, 84, 229, 150, 16, 180, 0>> assert left == right } pub fn float_16_zero_test() { let left = <<0, 0>> let right = <<0.0:float-16>> assert left == right } pub fn float_16_little_negative_test() { let left = <<0x00, 0xbc>> let right = <<-1.0:float-16-little>> assert left == right } pub fn float_16_little_test() { let left = <<0xf0, 0x3c>> let right = <<1.234375:float-16-little>> assert left == right } pub fn float_16_negative_large_test() { let left = <<0xfb, 0xff>> let right = <<-65_504.0:float-16>> assert left == right } pub fn float_64_test() { let left = <<63, 240, 0, 0, 0, 0, 0, 0>> let right = <<1.0:float>> assert left == right } pub fn float_32_test() { let left = <<63, 128, 0, 0>> let right = <<1.0:float-32>> assert left == right } pub fn float_64_little_test() { let left = <<0, 0, 0, 0, 0, 0, 240, 63>> let right = <<1.0:float-64-little>> assert left == right } pub fn float_64_big_test() { let left = <<63, 240, 0, 0, 0, 0, 0, 0>> let right = <<1.0:float-64-big>> assert left == right } pub fn pattern_match_utf8_test() { let result = case <<0x20, "😀👍":utf8, 0x20>> { <<" ":utf8, "😀👍":utf8, 0x20>> -> True _ -> False } assert result == True } pub fn pattern_match_bytes_sliced_test() { let assert <<_, b:bytes-3, _>> = <<1, 2, 3, 4, 5>> let assert <<_, rest:bytes>> = b assert rest == <<3, 4>> } pub fn matching_zero_length_segment_test() { let size = 0 let data = <<>> let result = case data { <<_:bytes-size(size), _:bytes>> -> "ok" _ -> "this cause should not be reached" } assert result == "ok" } pub fn float_16_erlang_test() { let left = <<60, 0>> let right = <<1.0:float-16>> assert left == right } // https://github.com/gleam-lang/gleam/issues/3375 pub fn assignment_int_pattern_test() { let assert <<10 as a, _>> = <<10, 20>> assert a == 10 } pub fn assignment_float_pattern_test() { let assert <<3.14 as pi:float>> = <<3.14>> assert pi == 3.14 } pub fn assignment_string_pattern_test() { let assert <<"Hello" as h:utf8, ", world!">> = <<"Hello, world!">> assert h == "Hello" } @target(erlang) pub fn pattern_match_utf16_codepoint_little_test() { let assert <> = <<"🌍":utf16-little>> assert codepoint == ffi.utf_codepoint(127_757) } @target(erlang) pub fn pattern_match_utf32_codepoint_little_test() { let assert <> = <<"🌍":utf32-little>> assert codepoint == ffi.utf_codepoint(127_757) } pub fn unicode_overflow_test() { // In erlang, literally creating binaries can cause entries to overflow. // For example `<<"🌵">> == <<"5">>` evaluates to true. // This checks that we are not doing that. // See: https://github.com/gleam-lang/gleam/issues/457 let string = "5" assert "🌵" != string } pub fn sliced_bit_array_data_view_offset_test() { let data = <<0xAA, 0xBB, 0xCC, 0xDD>> let assert Ok(sliced) = bit_array.slice(data, 1, 3) assert sliced == <<0xBB, 0xCC, 0xDD>> let value = ffi.read_uint16_from_bit_array(sliced) assert value == 48_076 } pub fn sliced_bit_array_data_view_byte_length_test() { let data = <<0xAA, 0xBB, 0xCC, 0xDD>> let assert Ok(sliced) = bit_array.slice(data, 1, 2) assert sliced == <<0xBB, 0xCC>> } ================================================ FILE: test/language/test/language/block_test.gleam ================================================ // https://github.com/gleam-lang/gleam/issues/1991 pub fn block_scoping_test() { let x = 1 let _ = { let x = 2 x } assert x == 1 } ================================================ FILE: test/language/test/language/bool_negation_test.gleam ================================================ pub fn negation_true_test() { assert !True == False } pub fn negation_false_test() { assert !False == True } // This would crash if the right hand side evaluated pub fn negation_true_and_panic_test() { let bool = !True bool && panic } // This would crash if the right hand side evaluated pub fn negation_false_or_panic_test() { let bool = !False bool || panic } ================================================ FILE: test/language/test/language/build_files_test.gleam ================================================ import ffi @target(javascript) pub fn typescript_file_included_test() { let path = "./build/dev/javascript/language/ffi_typescript.ts" assert ffi.file_exists(path) } @target(erlang) pub fn typescript_file_included_test() { let path = "./build/dev/erlang/language/_gleam_artefacts/ffi_typescript.ts" assert ffi.file_exists(path) } ================================================ FILE: test/language/test/language/clause_guard_test.gleam ================================================ import importable // Constructor functions are used rather than literals to stop the Erlang // compiler being clever and complaining about the guards always having the // same result fn true() { True } fn false() { False } fn make_int_zero() { 0 } fn make_float_zero() { 0.0 } fn make_pair(a, b) { #(a, b) } fn make_ok(value) { Ok(value) } fn make_error(reason) { Error(reason) } pub fn var_true_test() { let true_ = true() assert 0 == case Nil { _ if true_ -> 0 _ -> 1 } } pub fn var_false_test() { let false_ = false() assert 1 == case Nil { _ if false_ -> 0 _ -> 1 } } pub fn int_equals_match_test() { let int_zero = make_int_zero() assert 0 == case Nil { _ if int_zero == int_zero -> 0 _ -> 1 } } pub fn int_equals_nomatch_test() { let int_zero = make_int_zero() let int_one = make_int_zero() + 1 assert 1 == case Nil { _ if int_zero == int_one -> 0 _ -> 1 } } pub fn int_not_equals_match_test() { let int_zero = make_int_zero() let int_one = make_int_zero() + 1 assert 0 == case Nil { _ if int_zero != int_one -> 0 _ -> 1 } } pub fn int_not_equals_nomatch_test() { let int_zero = make_int_zero() assert 1 == case Nil { _ if int_zero != int_zero -> 0 _ -> 1 } } pub fn record_equals_match_test() { let ok = make_ok(1) assert 0 == case Nil { _ if ok == ok -> 0 _ -> 1 } } pub fn record_equals_nomatch_test() { let ok = make_ok(1) let error = make_error(1) assert 1 == case Nil { _ if ok == error -> 0 _ -> 1 } } pub fn record_not_equals_match_test() { let ok = make_ok(1) let error = make_error(1) assert 0 == case Nil { _ if ok != error -> 0 _ -> 1 } } pub fn record_not_equals_nomatch_test() { let error = make_error(1) assert 1 == case Nil { _ if error != error -> 0 _ -> 1 } } pub fn and_true_true_test() { let true_ = true() assert 0 == case Nil { _ if true_ && true_ -> 0 _ -> 1 } } pub fn and_true_false_test() { let true_ = true() let false_ = false() assert 1 == case Nil { _ if true_ && false_ -> 0 _ -> 1 } } pub fn and_false_true_test() { let true_ = true() let false_ = false() assert 1 == case Nil { _ if false_ && true_ -> 0 _ -> 1 } } pub fn and_false_false_test() { let false_ = false() assert 1 == case Nil { _ if false_ && false_ -> 0 _ -> 1 } } pub fn or_true_true_test() { let true_ = true() assert 0 == case Nil { _ if true_ || true_ -> 0 _ -> 1 } } pub fn or_true_false_test() { let true_ = true() let false_ = false() assert 0 == case Nil { _ if true_ || false_ -> 0 _ -> 1 } } pub fn or_false_true_test() { let true_ = true() let false_ = false() assert 0 == case Nil { _ if false_ || true_ -> 0 _ -> 1 } } pub fn or_false_false_test() { let false_ = false() assert 1 == case Nil { _ if false_ || false_ -> 0 _ -> 1 } } pub fn float_gt_1_test() { let float_zero = make_float_zero() let float_one = make_float_zero() +. 1.0 assert 0 == case Nil { _ if float_one >. float_zero -> 0 _ -> 1 } } pub fn float_gt_2_test() { let float_zero = make_float_zero() assert 1 == case Nil { _ if float_zero >. float_zero -> 0 _ -> 1 } } pub fn float_gte_1_test() { let float_zero = make_float_zero() let float_one = make_float_zero() +. 1.0 assert 0 == case Nil { _ if float_one >=. float_zero -> 0 _ -> 1 } } pub fn float_gte_2_test() { let float_zero = make_float_zero() assert 0 == case Nil { _ if float_zero >=. float_zero -> 0 _ -> 1 } } pub fn float_gte_3_test() { let float_zero = make_float_zero() let float_one = make_float_zero() +. 1.0 assert 1 == case Nil { _ if float_zero >=. float_one -> 0 _ -> 1 } } pub fn float_lt_1_test() { let float_zero = make_float_zero() let float_one = make_float_zero() +. 1.0 assert 0 == case Nil { _ if float_zero <. float_one -> 0 _ -> 1 } } pub fn float_lt_2_test() { let float_zero = make_float_zero() assert 1 == case Nil { _ if float_zero <. float_zero -> 0 _ -> 1 } } pub fn float_lte_1_test() { let float_zero = make_float_zero() let float_one = make_float_zero() +. 1.0 assert 0 == case Nil { _ if float_zero <=. float_one -> 0 _ -> 1 } } pub fn float_lte_2_test() { let float_zero = make_float_zero() assert 0 == case Nil { _ if float_zero <=. float_zero -> 0 _ -> 1 } } pub fn float_lte_3_test() { let float_zero = make_float_zero() let float_one = make_float_zero() +. 1.0 assert 1 == case Nil { _ if float_one <=. float_zero -> 0 _ -> 1 } } pub fn int_gt_1_test() { let int_zero = make_int_zero() let int_one = make_int_zero() + 1 assert 0 == case Nil { _ if int_one > int_zero -> 0 _ -> 1 } } pub fn int_gt_2_test() { let int_zero = make_int_zero() assert 1 == case Nil { _ if int_zero > int_zero -> 0 _ -> 1 } } pub fn int_gte_1_test() { let int_zero = make_int_zero() let int_one = make_int_zero() + 1 assert 0 == case Nil { _ if int_one >= int_zero -> 0 _ -> 1 } } pub fn int_gte_2_test() { let int_zero = make_int_zero() assert 0 == case Nil { _ if int_zero >= int_zero -> 0 _ -> 1 } } pub fn int_gte_3_test() { let int_zero = make_int_zero() let int_one = make_int_zero() + 1 assert 1 == case Nil { _ if int_zero >= int_one -> 0 _ -> 1 } } pub fn int_lt_1_test() { let int_zero = make_int_zero() let int_one = make_int_zero() + 1 assert 0 == case Nil { _ if int_zero < int_one -> 0 _ -> 1 } } pub fn int_lt_2_test() { let int_zero = make_int_zero() assert 1 == case Nil { _ if int_zero < int_zero -> 0 _ -> 1 } } pub fn int_lte_1_test() { let int_zero = make_int_zero() let int_one = make_int_zero() + 1 assert 0 == case Nil { _ if int_zero <= int_one -> 0 _ -> 1 } } pub fn int_lte_2_test() { let int_zero = make_int_zero() assert 0 == case Nil { _ if int_zero <= int_zero -> 0 _ -> 1 } } pub fn int_lte_3_test() { let int_zero = make_int_zero() let int_one = make_int_zero() + 1 assert 1 == case Nil { _ if int_one <= int_zero -> 0 _ -> 1 } } pub fn arithmetic_1_test() { assert 0 == case Nil { _ if 1 + 1 == 2 -> 0 _ -> 1 } } pub fn arithmetic_2_test() { assert 0 == case Nil { _ if 47 % 5 == 2 -> 0 _ -> 1 } } pub fn arithmetic_3_test() { assert 0 == case Nil { _ if 3 * 5 == 15 -> 0 _ -> 1 } } pub fn arithmetic_4_test() { assert 0 == case Nil { _ if 3 * 5 + 1 == 16 -> 0 _ -> 1 } } pub fn arithmetic_5_test() { assert 0 == case Nil { _ if 1 + 3 * 5 == 16 -> 0 _ -> 1 } } pub fn arithmetic_6_test() { assert 0 == case Nil { _ if 1 - 15 / 5 == -2 -> 0 _ -> 1 } } pub fn arithmetic_7_test() { assert 0 == case Nil { _ if 15 / 5 - 1 == 2 -> 0 _ -> 1 } } pub fn tuple_access_1_test() { let tuple_true_false = make_pair(True, False) assert 0 == case Nil { _ if tuple_true_false.0 -> 0 _ -> 1 } } pub fn tuple_access_2_test() { let tuple_true_false = make_pair(True, False) assert 1 == case Nil { _ if tuple_true_false.1 -> 0 _ -> 1 } } pub fn const_1_test() { let int_zero = make_int_zero() assert 0 == case Nil { _ if int_zero == 0 -> 0 _ -> 1 } } pub fn const_2_test() { let int_zero = make_int_zero() assert 1 == case Nil { _ if int_zero == 1 -> 0 _ -> 1 } } pub fn const_ok_test() { let ok = make_ok(1) assert 0 == case Nil { _ if ok == Ok(1) -> 0 _ -> 1 } } pub fn const_error_test() { let ok = make_ok(1) assert 1 == case Nil { _ if ok == Error(1) -> 0 _ -> 1 } } pub fn tuple_with_pattern_var_test() { let x = True let int = case x { a if #(a) == #(True) -> 0 _ -> 1 } assert 0 == int } pub fn module_access_string_const_matches_test() { let string = "gleam" assert True == case string { lang if lang == importable.language -> True _ -> False } } pub fn module_access_string_const_nomatch_test() { let string = "python" assert False == case string { lang if lang == importable.language -> True _ -> False } } pub fn module_access_custom_type_const_matches_test() { let string = "WarGames" assert True == case string { movie if movie == importable.war_games.title -> True _ -> False } } pub fn module_access_custom_type_const_nomatch_test() { let string = "Gattaca" assert False == case string { movie if movie == importable.war_games.title -> True _ -> False } } // https://github.com/gleam-lang/gleam/issues/5283 pub fn case_with_guard_does_not_pollute_outer_scope_test() { let a = case 1337 { n if n == 1347 -> 1 _ -> 2 } let b = case 1337 { n -> 2 } assert a == b } ================================================ FILE: test/language/test/language/constant_test.gleam ================================================ const const_int = 5 const const_float = 1.0 const const_string = "Gleam" const const_nil = Nil const const_ok = Ok(1) const const_list_empty = [] const const_list_1 = [1] const const_list_2 = [1, 2] pub fn int_test() { assert const_int == 5 } pub fn float_test() { assert const_float == 1.0 } pub fn string_test() { assert const_string == "Gleam" } pub fn nil_test() { assert const_nil == Nil } pub fn ok_test() { assert const_ok == Ok(1) } pub fn list_empty_test() { assert const_list_empty == [] } pub fn list_1_test() { assert const_list_1 == [1] } pub fn list_2_test() { assert const_list_2 == [1, 2] } ================================================ FILE: test/language/test/language/directly_matching_case_subject_test.gleam ================================================ // github.com/gleam-lang/gleam/issues/5265 pub fn directly_matching_case_subject_test() { let result = { let x = "ABC" case True { True -> { let x = 79 0 } False -> { let x = True 0 } } x } assert result == "ABC" } ================================================ FILE: test/language/test/language/equality_test.gleam ================================================ pub fn list_eq_1_test() { let left = [] let right = [] assert left == right } pub fn list_eq_2_test() { let left = [] let right = [0] assert left != right } pub fn list_eq_3_test() { let left = [0] let right = [] assert left != right } pub fn list_eq_4_test() { let left = [0] let right = [0] assert left == right } pub fn list_neq_1_test() { let left = [] let right = [] assert left == right } pub fn list_neq_2_test() { let left = [] let right = [0] assert left != right } pub fn list_neq_3_test() { let left = [0] let right = [] assert left != right } pub fn list_neq_4_test() { let left = [0] let right = [0] assert left == right } pub fn int_eq_1_test() { let left = 0 let right = 0 assert left == right } pub fn int_neq_1_test() { let left = 0 let right = 0 assert left == right } pub fn int_eq_2_test() { let left = 1 let right = 0 assert left != right } pub fn int_neq_2_test() { let left = 1 let right = 0 assert left != right } pub fn int_eq_3_test() { let left = 1 let right = 1 assert left == right } pub fn int_neq_3_test() { let left = 1 let right = 1 assert left == right } pub fn bit_array_eq_1_test() { let left = <<>> let right = <<>> assert left == right } pub fn bit_array_neq_1_test() { let left = <<>> let right = <<>> assert left == right } pub fn bit_array_eq_2_test() { let left = <<1, 2>> let right = <<1, 2>> assert left == right } pub fn bit_array_neq_2_test() { let left = <<1, 2>> let right = <<1, 2>> assert left == right } pub fn bit_array_eq_3_test() { let left = <<1, 2>> let right = <<2>> assert left != right } pub fn bit_array_neq_3_test() { let left = <<1, 2>> let right = <<2>> assert left != right } pub fn record_ok_eq_1_test() { let left = Ok(1) let right = Ok(1) assert left == right } pub fn record_ok_neq_1_test() { let left = Ok(1) let right = Ok(1) assert left == right } pub fn record_ok_eq_2_test() { let left = Ok(2) let right = Ok(1) assert left != right } pub fn record_ok_neq_2_test() { let left = Ok(2) let right = Ok(1) assert left != right } pub fn record_error_eq_1_test() { let left = Error(1) let right = Error(1) assert left == right } pub fn record_error_neq_1_test() { let left = Error(1) let right = Error(1) assert left == right } pub fn record_error_eq_2_test() { let left = Error(2) let right = Error(1) assert left != right } pub fn record_error_neq_2_test() { let left = Error(2) let right = Error(1) assert left != right } ================================================ FILE: test/language/test/language/float_test.gleam ================================================ pub fn addition_1_test() { assert 0.0 +. 0.0 == 0.0 } pub fn addition_2_test() { assert 1.0 +. 1.0 == 2.0 } pub fn addition_3_test() { assert 5.0 +. 1.0 == 6.0 } pub fn addition_4_test() { assert 1.0 +. 3.0 == 4.0 } pub fn addition_5_test() { assert 1.0 +. -3.0 == -2.0 } pub fn subtraction_1_test() { assert 0.0 -. 0.0 == 0.0 } pub fn subtraction_2_test() { assert 1.0 -. 1.0 == 0.0 } pub fn subtraction_3_test() { assert 5.0 -. 1.0 == 4.0 } pub fn subtraction_4_test() { assert 1.0 -. 3.0 == -2.0 } pub fn subtraction_5_test() { assert 1.0 -. -3.0 == 4.0 } pub fn subtraction_6_test() { assert 0.5 -. 0.0 == 0.5 } pub fn subtraction_7_test() { assert 1.0 -. 4.5 == -3.5 } pub fn multiplication_1_test() { assert 0.0 *. 0.0 == 0.0 } pub fn multiplication_2_test() { assert 1.0 *. 1.0 == 1.0 } pub fn multiplication_3_test() { assert 2.0 *. 2.0 == 4.0 } pub fn multiplication_4_test() { assert 2.0 *. 4.0 == 8.0 } pub fn multiplication_5_test() { assert -2.0 *. 4.0 == -8.0 } pub fn multiplication_6_test() { assert 2.0 *. -4.0 == -8.0 } pub fn precedence_1_test() { assert 2.0 *. 2.0 +. 3.0 == 7.0 } pub fn precedence_2_test() { assert 2.0 +. 2.0 *. 3.0 == 8.0 } pub fn precedence_3_test() { assert 2.0 *. { 2.0 +. 3.0 } == 10.0 } pub fn precedence_4_test() { assert { 2.0 +. 2.0 } *. 3.0 == 12.0 } pub fn scientific_notation_addition_1_test() { assert 0.0e0 +. 0.0 == 0.0 } pub fn scientific_notation_addition_2_test() { assert 0.0 +. 0.0e0 == 0.0 } pub fn scientific_notation_addition_3_test() { assert 0.0e-0 +. 0.0 == 0.0 } pub fn scientific_notation_addition_4_test() { assert 0.0 +. 0.0e-0 == 0.0 } pub fn scientific_notation_addition_5_test() { assert 1.0e3 +. 1.0 == 1001.0 } pub fn scientific_notation_addition_6_test() { assert 5.0 +. 1.0e3 == 1005.0 } pub fn scientific_notation_addition_7_test() { assert 1.0e5 +. -3.0e5 == -200_000.0 } pub fn scientific_notation_addition_8_test() { assert 1.0e50 +. -1.0e50 == 0.0 } pub fn scientific_notation_addition_9_test() { assert 1.0e-3 +. 1.0 == 1.001 } pub fn scientific_notation_addition_10_test() { assert 5.0 +. 1.0e-3 == 5.001 } pub fn scientific_notation_addition_11_test() { assert 10.0e-2 +. -3.0e-2 == 0.07 } pub fn scientific_notation_subtraction_1_test() { assert 0.0e0 -. 0.0 == 0.0 } pub fn scientific_notation_subtraction_2_test() { assert 0.0 -. 0.0e0 == 0.0 } pub fn scientific_notation_subtraction_3_test() { assert 0.0e-0 -. 0.0 == 0.0 } pub fn scientific_notation_subtraction_4_test() { assert 0.0 -. 0.0e-0 == 0.0 } pub fn scientific_notation_subtraction_5_test() { assert 1.0e3 -. 1.0 == 999.0 } pub fn scientific_notation_subtraction_6_test() { assert 5.0 -. 1.0e3 == -995.0 } pub fn scientific_notation_subtraction_7_test() { assert 1.0e5 -. 1.0e5 == 0.0 } pub fn scientific_notation_subtraction_8_test() { assert 1.0e-3 -. 1.0 == -0.999 } pub fn scientific_notation_subtraction_9_test() { assert 5.0 -. 1.0e-3 == 4.999 } pub fn scientific_notation_subtraction_10_test() { assert 10.0e-2 -. -3.0e-2 == 0.13 } pub fn scientific_notation_multiplication_1_test() { assert 0.0e0 *. 0.0 == 0.0 } pub fn scientific_notation_multiplication_2_test() { assert 0.0 *. 0.0e0 == 0.0 } pub fn scientific_notation_multiplication_3_test() { assert 0.0e-0 *. 0.0 == 0.0 } pub fn scientific_notation_multiplication_4_test() { assert 0.0 *. 0.0e-0 == 0.0 } pub fn scientific_notation_multiplication_5_test() { assert 2.0e1 *. 2.0e1 == 400.0 } pub fn scientific_notation_multiplication_6_test() { assert 1.0e-5 *. 1.0e5 == 1.0 } pub fn scientific_notation_multiplication_7_test() { assert 2.0e5 *. 2.0e-5 == 4.0 } pub fn scientific_notation_multiplication_8_test() { assert 2.0e5 *. 4.0e-4 == 80.0 } pub fn scientific_notation_multiplication_9_test() { assert 2.0e-5 *. 2.0e5 == 4.0 } pub fn scientific_notation_multiplication_10_test() { assert 2.0e5 *. 4.0e-5 == 8.0 } pub fn scientific_notation_multiplication_11_test() { assert -2.0e-5 *. 2.0e5 == -4.0 } pub fn scientific_notation_multiplication_12_test() { assert -2.0e5 *. -4.0e-5 == 8.0 } pub fn divide_by_2_test() { let left = 2.0 let right = 2.0 assert left /. right == 1.0 } pub fn divide_by_0_test() { let left = 2.0 let right = 0.0 assert left /. right == 0.0 } ================================================ FILE: test/language/test/language/imported_custom_type_test.gleam ================================================ import importable.{NoFields} pub fn no_fields_qualified_and_unqualified_test() { assert importable.NoFields == NoFields } pub fn no_fields_assert_assignment_test() { let result = { let assert importable.NoFields = importable.NoFields } assert result == importable.NoFields } pub fn no_fields_unqualified_assert_assignment_test() { let result = { let assert NoFields = importable.NoFields } assert result == importable.NoFields } pub fn no_fields_let_assignment_test() { let result = { let importable.NoFields = importable.NoFields } assert result == importable.NoFields } pub fn no_fields_unqualified_let_assignment_test() { let result = { let NoFields = importable.NoFields } assert result == importable.NoFields } ================================================ FILE: test/language/test/language/importing_test.gleam ================================================ import mod_with_numbers_0123456789 import shadowed_module.{ShadowPerson} pub fn mod_with_numbers_test() { assert mod_with_numbers_0123456789.hello() == "world" } pub fn shadowed_module_test() { let shadowed_module = ShadowPerson(18) let shadowed_module = shadowed_module.celebrate_birthday(shadowed_module) assert shadowed_module.age == 19 } ================================================ FILE: test/language/test/language/int_negation_test.gleam ================================================ pub fn negation_1_test() { let a = 3 let b = -a assert -3 == b } pub fn negation_2_test() { let a = 3 let b = -{ -a } assert 3 == b } pub fn negation_3_test() { let a = 3 let b = -{ -{ -a } } assert -3 == b } pub fn negation_4_test() { let a = 3 let b = -a let c = a - -b assert 0 == c } pub fn negation_5_test() { let a = 3 let b = -a let c = a - -{ -{ -{ -{ -b } } } } assert 0 == c } pub fn negation_6_test() { let a = 3 let b = -a let c = -a - -b assert -6 == c } pub fn negation_7_test() { let abs = fn(value) { case value { value if value > 0 -> value _ -> -value } } assert -6 == -abs(-6) } ================================================ FILE: test/language/test/language/int_test.gleam ================================================ pub fn addition_1_test() { assert 0 + 0 == 0 } pub fn addition_2_test() { assert 1 + 1 == 2 } pub fn addition_3_test() { assert 5 + 1 == 6 } pub fn addition_4_test() { assert 1 + 3 == 4 } pub fn addition_5_test() { assert 1 + -3 == -2 } pub fn subtraction_1_test() { assert 0 - 0 == 0 } pub fn subtraction_2_test() { assert 1 - 1 == 0 } pub fn subtraction_3_test() { assert 5 - 1 == 4 } pub fn subtraction_4_test() { assert 1 - 3 == -2 } pub fn subtraction_5_test() { assert 1 - -3 == 4 } pub fn multiplication_1_test() { assert 0 * 0 == 0 } pub fn multiplication_2_test() { assert 1 * 1 == 1 } pub fn multiplication_3_test() { assert 2 * 2 == 4 } pub fn multiplication_4_test() { assert 2 * 4 == 8 } pub fn multiplication_5_test() { assert -2 * 4 == -8 } pub fn multiplication_6_test() { assert 2 * -4 == -8 } pub fn precedence_1_test() { assert 2 * 2 + 3 == 7 } pub fn precedence_2_test() { assert 2 + 2 * 3 == 8 } pub fn precedence_3_test() { assert 2 * { 2 + 3 } == 10 } pub fn precedence_4_test() { assert { 2 + 2 } * 3 == 12 } pub fn hex_int_test() { let int = 15 assert 0xF == int } pub fn octal_int_test() { let int = 15 assert 0o17 == int } pub fn binary_int_test() { let int = 15 assert 0b00001111 == int } pub fn lex_1_test() { assert 1 - 1 == 0 } pub fn lex_2_test() { let a = 1 assert a - 1 == 0 } pub fn lex_3_test() { assert 1 - 1 == 0 } pub fn division_1_test() { assert 1 / 1 == 1 } pub fn division_2_test() { assert 1 / 0 == 0 } pub fn division_3_test() { assert 3 / 2 == 1 } pub fn division_4_test() { assert 3 / 0 == 0 } ================================================ FILE: test/language/test/language/list_prepend_test.gleam ================================================ pub fn prepend_1_test() { let left = [1, ..[]] let right = [1] assert left == right } pub fn prepend_2_test() { let left = [1, 2, ..[]] let right = [1, 2] assert left == right } pub fn prepend_3_test() { let left = [1, 2, ..[3]] let right = [1, 2, 3] assert left == right } pub fn prepend_4_test() { let left = [1, 2, ..[3, 4]] let right = [1, 2, 3, 4] assert left == right } ================================================ FILE: test/language/test/language/mixed_arg_match_test.gleam ================================================ type Cat { Cat(String, cuteness: Int) } type NestedCat { NestedCat(Cat, String, cuteness: Int) } pub fn matching_second_labelled_arg_as_first_test() { let Cat(cuteness: y, ..) = Cat("fluffy", 10) assert y == 10 } pub fn matching_both_args_on_position_test() { let Cat(x, y) = Cat("fluffy", 10) assert #(x, y) == #("fluffy", 10) } pub fn matching_second_labelled_arg_as_second_test() { let Cat(x, cuteness: y) = Cat("fluffy", 10) assert #(x, y) == #("fluffy", 10) } pub fn nested_custom_types_test() { let NestedCat(Cat(x, cuteness: y), cuteness: y2, ..) = NestedCat(Cat("fluffy", 10), "gleamy", 100) assert #(x, y, y2) == #("fluffy", 10, 100) } ================================================ FILE: test/language/test/language/multiple_case_subject_test.gleam ================================================ pub fn wildcard_test() { let x = true() let y = false() let result = case x, y { _, _ -> 0 } assert result == 0 } pub fn no_match_test() { let x = true() let y = false() let result = case x, y { False, True -> 1 _, _ -> 0 } assert result == 0 } pub fn match_test() { let x = true() let y = false() let result = case x, y { False, True -> 1 _, _ -> 0 } assert result == 0 } pub fn alternative_test() { let x = true() let y = false() let result = case x, y { False, True | True, False -> 1 _, _ -> 0 } assert result == 1 } pub fn guard_test() { let x = true() let y = true() let result = case x, y { x, y if x == y -> 1 _, _ -> 0 } assert result == 1 } fn true() -> Bool { True } fn false() -> Bool { False } ================================================ FILE: test/language/test/language/non_utf8_string_bit_array_test.gleam ================================================ import ffi pub fn utf16_pattern_match_test() { let result = { let assert <<"Hello, world":utf16>> = <<"Hello, world":utf16>> } assert result == <<"Hello, world":utf16>> } pub fn utf32_pattern_match_test() { let result = { let assert <<"Hello, world":utf32>> = <<"Hello, world":utf32>> } assert result == <<"Hello, world":utf32>> } pub fn utf16_bytes_test() { let left = <<"Hello, 🌍!":utf16>> let right = << 0, 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 44, 0, 32, 216, 60, 223, 13, 0, 33, >> assert left == right } pub fn utf32_bytes_test() { let left = <<"Hello, 🌍!":utf32>> let right = << 0, 0, 0, 72, 0, 0, 0, 101, 0, 0, 0, 108, 0, 0, 0, 108, 0, 0, 0, 111, 0, 0, 0, 44, 0, 0, 0, 32, 0, 1, 243, 13, 0, 0, 0, 33, >> assert left == right } pub fn utf16_pattern_matching_bytes_test() { let result = { let assert <<"Hello, 🌍!":utf16>> = << 0, 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 44, 0, 32, 216, 60, 223, 13, 0, 33, >> } assert result == <<"Hello, 🌍!":utf16>> } pub fn utf32_pattern_matching_bytes_test() { let result = { let assert <<"Hello, 🌍!":utf32>> = << 0, 0, 0, 72, 0, 0, 0, 101, 0, 0, 0, 108, 0, 0, 0, 108, 0, 0, 0, 111, 0, 0, 0, 44, 0, 0, 0, 32, 0, 1, 243, 13, 0, 0, 0, 33, >> } assert result == <<"Hello, 🌍!":utf32>> } pub fn utf16_bytes_little_endian_test() { let left = <<"Hello, 🌍!":utf16-little>> let right = << 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 44, 0, 32, 0, 60, 216, 13, 223, 33, 0, >> assert left == right } pub fn utf32_bytes_little_endian_test() { let left = <<"Hello, 🌍!":utf32-little>> let right = << 72, 0, 0, 0, 101, 0, 0, 0, 108, 0, 0, 0, 108, 0, 0, 0, 111, 0, 0, 0, 44, 0, 0, 0, 32, 0, 0, 0, 13, 243, 1, 0, 33, 0, 0, 0, >> assert left == right } pub fn utf16_pattern_matching_little_endian_test() { let result = { let assert <<"Hello, 🌍!":utf16-little>> = << 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 44, 0, 32, 0, 60, 216, 13, 223, 33, 0, >> } assert result == <<"Hello, 🌍!":utf16-little>> } pub fn utf32_pattern_matching_little_endian_test() { let result = { let assert <<"Hello, 🌍!":utf32-little>> = << 72, 0, 0, 0, 101, 0, 0, 0, 108, 0, 0, 0, 108, 0, 0, 0, 111, 0, 0, 0, 44, 0, 0, 0, 32, 0, 0, 0, 13, 243, 1, 0, 33, 0, 0, 0, >> } assert result == <<"Hello, 🌍!":utf32-little>> } pub fn utf16_codepoint_test() { // 🌍 let codepoint = ffi.utf_codepoint(127_757) let result = <> assert result == <<216, 60, 223, 13>> } pub fn utf16_codepoint_little_endian_test() { // 🌍 let codepoint = ffi.utf_codepoint(127_757) let result = <> assert result == <<60, 216, 13, 223>> } pub fn utf32_codepoint_test() { // 🌍 let codepoint = ffi.utf_codepoint(127_757) let result = <> assert result == <<0, 1, 243, 13>> } pub fn utf32_codepoint_little_endian_test() { // 🌍 let codepoint = ffi.utf_codepoint(127_757) let result = <> assert result == <<13, 243, 1, 0>> } ================================================ FILE: test/language/test/language/pipe_test.gleam ================================================ fn identity(x: a) -> a { x } fn pair(x: a, y: b) -> #(a, b) { #(x, y) } fn triplet(x x: a, y y: b, z z: c) -> #(a, b, c) { #(x, y, z) } pub fn pipe_last_test() { let result = 100 |> identity assert 100 == result } pub fn pipe_into_anon_test() { let result = 100 |> fn(x) { x } assert 100 == result } pub fn pipe_into_capture_test() { let result = 1 |> pair(2, _) assert #(2, 1) == result } pub fn pipe_first_test() { let result = 1 |> pair(2) assert #(1, 2) == result } pub fn pipe_middle_with_label_requires_false_capture_test() { let result = 2 |> triplet(z: 3, x: 1) assert #(1, 2, 3) == result } pub fn pipe_last_with_label_requires_false_capture_test() { let result = 3 |> triplet(y: 2, x: 1) assert #(1, 2, 3) == result } ================================================ FILE: test/language/test/language/precedence_test.gleam ================================================ pub fn precedence_1_test() { assert 7 == 1 + 2 * 3 } pub fn precedence_2_test() { assert 5 == 3 * 1 + 2 } pub fn precedence_3_test() { assert 9 == { 1 + 2 } * 3 } pub fn precedence_4_test() { assert 9 == 3 * { 1 + 2 } } pub fn precedence_5_test() { assert 11 == 1 + 2 * 3 + 4 } pub fn precedence_6_test() { assert 26 == 2 * 3 + 4 * 5 } pub fn precedence_7_test() { assert 4 == 2 * { 3 + 1 } / 2 } pub fn precedence_8_test() { assert -17 == 5 + 3 / 3 * 2 - 6 * 4 } pub fn precedence_9_test() { assert -31 == -5 + -3 / -3 * -2 - -6 * -4 } pub fn precedence_10_test() { let a = 5 let b = 3 let c = 3 let d = 2 let e = 6 let f = 4 assert -17 == a + b / c * d - e * f } pub fn precedence_11_test() { let a = 5 let b = 3 let c = 3 let d = 2 let e = 6 let f = 4 assert -31 == -a + -b / -c * -d - -e * -f } ================================================ FILE: test/language/test/language/prelude_test.gleam ================================================ import gleam pub fn gleam_ok_test() { let left = Ok(1) let right = gleam.Ok(1) assert left == right } pub fn gleam_error_test() { let left = Error(1) let right = gleam.Error(1) assert left == right } pub fn gleam_nil_test() { let left = Nil let right = gleam.Nil assert left == right } ================================================ FILE: test/language/test/language/record_access_test.gleam ================================================ type Person { Person(name: String, age: Int, country: String) } pub fn record_access_name_test() { let person = Person(name: "Quinn", age: 27, country: "Canada") assert person.name == "Quinn" } pub fn record_access_age_test() { let person = Person(name: "Quinn", age: 27, country: "Canada") assert person.age == 27 } // https://github.com/gleam-lang/gleam/issues/1093 pub fn contextual_info_for_access_test() { let person = Person(name: "Quinn", age: 27, country: "Canada") let apply = fn(a, f) { f(a) } assert apply(person, fn(x) { x.name }) == "Quinn" } ================================================ FILE: test/language/test/language/record_update_test.gleam ================================================ import record_update type Person { Person(name: String, age: Int, country: String) } type MixedRecord { MixedRecord(Int, Float, labelled_1: Int, labelled_2: String) } fn id(x) { x } pub fn unqualified_record_update_test() { let past = Person("Quinn", 27, "Canada") let present = Person(..past, country: "USA", age: past.age + 1) assert present == Person("Quinn", 28, "USA") } pub fn qualified_record_update_test() { let module_box = record_update.Box("a", 5) let updated = record_update.Box(..module_box, value: 6) assert updated == record_update.Box("a", 6) } // https://github.com/gleam-lang/gleam/issues/1379 pub fn pipe_in_record_update_test() { let module_box = record_update.Box("a", 5) let updated = record_update.Box( ..module_box, value: 6 |> id, ) assert updated == record_update.Box("a", 6) } pub fn unlabelled_field_in_record_update_test() { let record = MixedRecord(1, 3.14, labelled_1: 3982, labelled_2: "Something") let updated = MixedRecord(..record, labelled_1: 12) assert updated == MixedRecord(1, 3.14, 12, "Something") } ================================================ FILE: test/language/test/language/remainder_test.gleam ================================================ pub fn remainder_1_test() { assert 1 % 1 == 0 } pub fn remainder_2_test() { assert 1 % 0 == 0 } pub fn remainder_3_test() { assert 3 % 2 == 1 } pub fn remainder_4_test() { assert 3 % 0 == 0 } pub fn remainder_5_test() { assert 3 % -2 == 1 } pub fn remainder_6_test() { assert 3 % -0 == 0 } pub fn remainder_7_test() { assert -13 % 3 == -1 } pub fn remainder_8_test() { assert 13 % -3 == 1 } pub fn remainder_9_test() { assert -13 % -3 == -1 } ================================================ FILE: test/language/test/language/sized_bit_array_test.gleam ================================================ pub fn size_8_literal_test() { let left = <<257:size(8)>> let right = <<1>> assert left == right } pub fn size_8_variable_test() { let i = 257 let left = <> let right = <<1>> assert left == right } pub fn size_16_literal_test() { let left = <<257:size(16)>> let right = <<1, 1>> assert left == right } pub fn size_16_variable_test() { let i = 257 let left = <> let right = <<1, 1>> assert left == right } pub fn size_24_literal_test() { let left = <<257:size(24)>> let right = <<0, 1, 1>> assert left == right } pub fn size_24_variable_test() { let i = 257 let left = <> let right = <<0, 1, 1>> assert left == right } pub fn size_40_literal_test() { let left = <<4_294_967_297:size(40)>> let right = <<1, 0, 0, 0, 1>> assert left == right } pub fn size_40_variable_test() { let i = 4_294_967_297 let left = <> let right = <<1, 0, 0, 0, 1>> assert left == right } pub fn size_24_little_literal_test() { let left = <<100_000:24-little>> let right = <<160, 134, 1>> assert left == right } pub fn size_24_little_variable_test() { let i = 100_000 let left = <> let right = <<160, 134, 1>> assert left == right } pub fn size_32_big_negative_literal_test() { let left = <<-1:32-big>> let right = <<255, 255, 255, 255>> assert left == right } pub fn size_32_big_negative_variable_test() { let i = -1 let left = <> let right = <<255, 255, 255, 255>> assert left == right } pub fn size_32_little_large_literal_test() { let left = <<100_000_000_000:32-little>> let right = <<0, 232, 118, 72>> assert left == right } pub fn size_32_little_large_variable_test() { let i = 100_000_000_000 let left = <> let right = <<0, 232, 118, 72>> assert left == right } pub fn negative_size_literal_test() { let left = <<>> let right = <<256:size(-1)>> assert left == right } pub fn negative_size_variable_test() { let i = 256 let left = <> let right = <<>> assert left == right } // JS Number.MAX_SAFE_INTEGER pub fn size_64_max_safe_int_literal_test() { let left = <<9_007_199_254_740_991:size(64)>> let right = <<0, 31, 255, 255, 255, 255, 255, 255>> assert left == right } pub fn size_64_max_safe_int_variable_test() { let i = 9_007_199_254_740_991 let left = <> let right = <<0, 31, 255, 255, 255, 255, 255, 255>> assert left == right } pub fn size_unit_test() { let i = 9_007_199_254_740_991 let left = <> let right = <<0, 31, 255, 255, 255, 255, 255, 255>> assert left == right } pub fn dynamic_size_unit_test() { let size = 5 let left = <<405:size(size)-unit(2)>> let right = <<101, 1:2>> assert left == right } pub fn pattern_bits_size_expression_test() { let assert <> = <<4, 1, 2, 3, 4:4>> assert payload == <<1, 2, 3, 4:4>> } pub fn pattern_bytes_size_expression_test() { let assert <> = <<32, 1, 2, 3, 4, 5, 6>> assert payload == <<1, 2, 3, 4, 5, 6>> } pub fn pattern_bits_size_with_variable_test() { let additional = 5 let assert <> = << 8, 1, 2, 3, 4, 5, 6, >> assert payload == <<1, 2, 3, 4, 5, 6>> } pub fn pattern_bits_size_block_expression_test() { let assert <> = <<3, 1, 2, 3, 4>> assert payload == <<1, 2, 3, 4>> } pub fn pattern_negative_size_test() { let result = case <<1, 2, 3, 4>> { <> -> 1 _ -> 2 } assert result == 2 } ================================================ FILE: test/language/test/language/string_pattern_matching_test.gleam ================================================ pub fn case_string_prefix_match_test() { let string = "12345" let string_2 = case string { "0" <> rest -> rest "123" <> rest -> rest _ -> "" } assert "45" == string_2 } pub fn match_emoji_test() { let string = "🫥 is neutral dotted" let string_2 = case string { "🫥" <> rest -> rest _ -> panic } assert " is neutral dotted" == string_2 } pub fn match_theta_test() { let string = "Θ wibble wobble" let string_2 = case string { "Θ" <> rest -> rest _ -> panic } assert " wibble wobble" == string_2 } pub fn match_flag_emoji_test() { let string = "🇺🇸 is a cluster" let string_2 = case string { "🇺🇸" <> rest -> rest _ -> panic } assert " is a cluster" == string_2 } pub fn match_backslash_test() { let string = "\" is a backslash" let string_2 = case string { "\"" <> rest -> rest _ -> panic } assert " is a backslash" == string_2 } pub fn match_newline_test() { let string = "\n is a newline" let string_2 = case string { "\n" <> rest -> rest _ -> panic } assert " is a newline" == string_2 } pub fn match_escaped_newline_test() { let string = "\\n is a newline that escaped" let string_2 = case string { "\\n" <> rest -> rest _ -> panic } assert " is a newline that escaped" == string_2 } ================================================ FILE: test/language/test/language/string_test.gleam ================================================ pub fn empty_strings_equal_test() { assert "" == "" } pub fn newlines_equal_test() { assert " " == "\n" } pub fn let_assert_string_prefix_test() { let assert "ab" <> rest = "abcdef" assert "cdef" == rest } ================================================ FILE: test/language/test/language/tail_call_test.gleam ================================================ fn count_down(from i) { case i { 0 -> Nil _ -> count_down(i - 1) } } fn tail_recursive_accumulate_down(x, y) { case x { 0 -> y _ -> tail_recursive_accumulate_down(x - 1, [x, ..y]) } } fn function_shadowed_by_own_argument(function_shadowed_by_own_argument) { function_shadowed_by_own_argument() } pub fn ten_million_recursions_doesnt_overflow_the_stack_test() { assert Nil == count_down(from: 10_000_000) } // https://github.com/gleam-lang/gleam/issues/1214 // https://github.com/gleam-lang/gleam/issues/1380 pub fn arguments_correctly_reassigned_test() { assert [1, 2, 3] == tail_recursive_accumulate_down(3, []) } // https://github.com/gleam-lang/gleam/issues/2400 pub fn function_shadowed_by_own_argument_test() { assert 1 == function_shadowed_by_own_argument(fn() { 1 }) } ================================================ FILE: test/language/test/language/tuple_access_test.gleam ================================================ type ContainsTuple { ContainsTuple(data: #(Int, #(Int, Person))) } type Person { Person(name: String, age: Int, country: String) } // https://github.com/gleam-lang/gleam/issues/1980 pub fn access_regular_tuple_item_test() { let tup = #(3, 4, 5) let x = tup.0 let y = tup.1 let z = tup.2 assert #(z, y, x) == #(5, 4, 3) } pub fn access_nested_tuple_item_test() { let tup = #(#(4, 5), #(6, 7)) assert #(tup.0.1, tup.1.1, tup.1.0, tup.0.0) == #(5, 7, 6, 4) } pub fn access_deeply_nested_tuple_item_test() { let tup = #(#(5, #(6, 7, #(8)))) assert tup.0.1.2.0 == 8 } pub fn access_nested_struct_in_a_tuple_item_test() { let tup = #(Person("Quinn", 27, "Canada"), Person("Nikita", 99, "Internet")) assert { tup.0 }.name == "Quinn" } pub fn access_nested_tuple_in_a_struct_test() { let person = Person("Nikita", 99, "Internet") let container = ContainsTuple(#(5, #(6, person))) assert { container.data.1.1 }.name == "Nikita" } pub fn access_tuple_then_struct_then_tuple_test() { let person = Person("Nikita", 99, "Internet") let container = ContainsTuple(#(5, #(6, person))) let tup = #(container) assert { tup.0 }.data.0 == 5 } ================================================ FILE: test/language/test/language/unaligned_bit_array_expression_test.gleam ================================================ pub fn unaligned_1_test() { let left = <<0xFF:6, 0:2>> let right = <<0xFC>> assert left == right } pub fn unaligned_2_test() { let left = <<0xFF:6, 0:3, 0x75:9>> let right = <<252, 29, 1:2>> assert left == right } pub fn unaligned_3_test() { let left = <<-1:55, 44:11-little, 0x75:9-big>> let right = <<255, 255, 255, 255, 255, 255, 254, 88, 14, 5:3>> assert left == right } pub fn unaligned_4_test() { let left = <<0:1, 2:2, 2:3, 1:1>> let right = <<0b0100101:7>> assert left == right } pub fn unaligned_5_test() { let left = <<-100:6, -10:32-little, -10:32-big, -100:48-big, -100:48-little>> let right = << 115, 219, 255, 255, 255, 255, 255, 255, 219, 255, 255, 255, 255, 254, 114, 115, 255, 255, 255, 255, 63:6, >> assert left == right } pub fn unaligned_6_test() { let left = <<2:3, 2.9283123:float-little, -1.375e5:32-float-big>> let right = <<91, 153, 120, 255, 229, 205, 160, 232, 25, 0, 200, 224, 0:3>> assert left == right } pub fn unaligned_7_test() { let left = << 7:6, <<1:3>>:bits, <<1, 2, 3>>:bits, 1:1, <<-1124.789e4:float-little>>:bits, >> let right = <<28, 128, 129, 1, 192, 0, 0, 16, 8, 157, 25, 112, 1:2>> assert left == right } pub fn unaligned_8_test() { let left = <<9_444_732_965_739_289_353_650_176:75>> let right = <<255, 255, 255, 255, 255, 248, 0, 0, 0, 0:size(3)>> assert left == right } pub fn unaligned_9_test() { let left = <<0b100101:6>> let right = <<<<0b10010100>>:bits-6>> assert left == right } pub fn unaligned_10_test() { let left = <<0xE7>> let right = <<<<0xEC>>:bits-4, 7:4>> assert left == right } pub fn unaligned_11_test() { let three = 3 let two = 2 let left = <<0b11001:5>> let right = <<<<0b11011100>>:bits-size(three), 1:size(two)>> assert left == right } ================================================ FILE: test/language/test/language/unaligned_bit_array_pattern_test.gleam ================================================ pub fn unaligned_1_test() { let assert <> = <<0xAB, 0b11010101>> assert #(a, b, c, d, e) == #(0xA, 0xB, 0b110, 0b1010, 0b1) } pub fn unaligned_2_test() { let assert <> = <<0xB6, 0xE3>> assert #(a, b) == #(0xB6E, 0x03) } pub fn unaligned_3_test() { let assert <> = <<0xB6, 0xE3>> assert #(a, b) == #(0x0B, 0x6E3) } pub fn unaligned_4_test() { let assert <> = <<0xB6, 0xE3>> assert #(a, b) == #(0xEB6, 0x03) } pub fn unaligned_5_test() { let assert <> = <<0xB6, 0xE3>> assert #(a, b) == #(0x0B, 0x36E) } pub fn unaligned_6_test() { let assert <> = <<0xB6, 0xE3>> assert #(a, b) == #(22, 1763) } pub fn unaligned_7_test() { let assert <<_:8, a:17>> = <<0xFF, 0xB6, 0xE3, 1:1>> assert a == 93_639 } pub fn unaligned_8_test() { let assert <> = <<0xA6, 0xE3, 6:3>> assert a == 85_447 } pub fn unaligned_9_test() { let assert <> = <<0xA6, 0xE3, 6:3>> assert a == 85_447 } pub fn unaligned_10_test() { let assert <> = <<0b10011010>> assert a == -51 } pub fn unaligned_11_test() { let assert <<_:5, a:13-big, _:bits>> = <<0xA5, 0x6C, 0xAA>> assert a == 5554 } pub fn unaligned_12_test() { let assert <<_:5, a:13-little-signed, _:bits>> = <<0xA5, 0x6C, 0xAA>> assert a == -3411 } pub fn unaligned_13_test() { let assert <<_:3, a:8-big-signed, _:bits>> = <<253, 94>> assert a == -22 } pub fn unaligned_14_test() { let assert <<_:3, a:12-big-unsigned, _:bits>> = <<233, 164>> assert a == 1234 } pub fn unaligned_15_test() { let assert <<_:3, a:12-little, _:bits>> = <<250, 72>> assert a == 1234 } pub fn unaligned_16_test() { let assert <<_:7, a:22, _:bits>> = <<250, 72, 223, 189>> assert a == 596_983 } pub fn unaligned_17_test() { let assert <<_:1, a:23, _:bits>> = <<146, 192, 70, 25, 1:1>> assert a == 1_228_870 } pub fn unaligned_18_test() { let assert <<_:1, a:32>> = <<217, 150, 209, 191, 0:1>> assert a == 3_006_112_638 } pub fn unaligned_19_test() { let assert <<_:1, a:32-signed>> = <<146, 192, 70, 25, 1:1>> assert a == 629_181_491 } pub fn unaligned_20_test() { let assert <<_:1, a:32-little-unsigned>> = <<251, 24, 47, 227, 1:1>> assert a == 3_344_904_438 } pub fn unaligned_21_test() { let assert <> = <<240, 102, 91, 101, 1:1>> assert a == 5_995_456_240 } pub fn unaligned_22_test() { let assert <> = <<231, 255, 255, 255, 254, 123>> assert a == -103_079_215_106 } pub fn unaligned_23_test() { let assert <<_:1, a:54-big-unsigned, _:bits>> = << 0, 231, 255, 255, 253, 123, 17, >> assert a == 127_543_348_739_464 } pub fn unaligned_24_test() { let assert <<_:8, a:54-little-signed, _:bits>> = << 142, 231, 255, 255, 253, 123, 17, 139, >> assert a == -8_425_025_061_257_241 } pub fn unaligned_25_test() { let assert <<_:7, a:55-little-signed, _:bits>> = << 142, 231, 255, 255, 253, 123, 17, 139, >> assert a == -8_293_899_692_933_261 } pub fn unaligned_26_test() { let assert <<_:8, a:40-big-signed, _:8>> = << 142, 231, 255, 255, 253, 123, 17, >> assert a == -103_079_215_749 } pub fn unaligned_27_test() { let assert <<_:14, a:71-little-signed, _:bits>> = << 250, 72, 223, 189, 41, 97, 165, 0, 0, 0, 0, 177, >> assert a == 70_821_197_049_655 } pub fn unaligned_28_test() { let assert <<_:14, a:71-big-signed, _:bits>> = << 250, 72, 223, 189, 41, 97, 165, 0, 0, 0, 0, 177, >> assert a == 515_906_807_693_217_628_160 } pub fn unaligned_29_test() { let assert <<_:11, a:float-32, _:bits>> = <<112, 152, 127, 244, 0, 7, 0:2>> assert a == -511.25 } pub fn unaligned_30_test() { let assert <<_:7, a:float-little, _:bits>> = << 8, 0, 0, 0, 1, 129, 39, 103, 129, 127:7, >> assert a == -5011.75 } pub fn unaligned_31_test() { let assert <> = <<0b11001011, 0b01:2>> assert #(a, b) == #(<<0b1100101:7>>, <<0b101:3>>) } pub fn unaligned_32_test() { let assert <> = << 0x47, 0x9A, 0x25, 0x0C, 0xDA, 0xF1, 0xEE, 0x31, >> assert #(a, b, c, d) == #(<<71, 154>>, <<37, 1:5>>, <<155, 2:3>>, <<0xF1, 0xEE, 0x31>>) } pub fn unaligned_33_test() { let assert <> = << 0b110:3, 0x12, 0xAB, 0x95, 0xFE, >> assert #(a, b, c) == #(<<0b110:3>>, <<0x12, 0xAB>>, <<0x95, 0xFE>>) } pub fn unaligned_34_test() { let result = case <<0x12, 0xAB, 0x95, 0xFE>> { <<0x34, _:5, _:bytes>> -> True _ -> False } assert result == False } ================================================ FILE: test/language/test/language_test.gleam ================================================ import gleeunit pub fn main() { gleeunit.main() } ================================================ FILE: test/language/test/mod_with_numbers_0123456789.gleam ================================================ pub fn hello() { "world" } ================================================ FILE: test/language/test/port.gleam ================================================ pub type Port ================================================ FILE: test/language/test/record_update.gleam ================================================ pub type Box(a) { Box(tag: String, value: a) } ================================================ FILE: test/language/test/shadowed_module.gleam ================================================ pub type ShadowPerson { ShadowPerson(age: Int) } pub fn celebrate_birthday(person: ShadowPerson) -> ShadowPerson { ShadowPerson(age: person.age + 1) } ================================================ FILE: test/multi_namespace/.gitignore ================================================ *.beam *.ez build ================================================ FILE: test/multi_namespace/README.md ================================================ # Multi namespace project should not be published! ================================================ FILE: test/multi_namespace/gleam.toml ================================================ name = "multi_namespace" version = "1.0.0" description = "Test project for multi namespace" licences = ["Apache-2.0"] ================================================ FILE: test/multi_namespace/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ ] [requirements] ================================================ FILE: test/multi_namespace/src/multi_namespace.gleam ================================================ pub fn main() { "Hello from multi_namespace!" } ================================================ FILE: test/multi_namespace/src/second.gleam ================================================ pub fn main() { "Hello from second!" } ================================================ FILE: test/multi_namespace/test.sh ================================================ #!/bin/sh set -eu GLEAM_COMMAND=${GLEAM_COMMAND:-"cargo run --quiet --"} g() { echo "Running: $GLEAM_COMMAND $@" $GLEAM_COMMAND "$@" } echo Resetting the build directory to get to a known state rm -fr build echo Running publish should not publish anything output=$(yes "n" | g publish) if echo "$output" | grep -q "Your package defines multiple top-level modules"; then echo "Publish was correctly prevented with warning" else echo "Expected publish to be aborted" exit 1 fi echo echo Success! 💖 echo ================================================ FILE: test/multi_namespace_not_top_level/.gitignore ================================================ *.beam *.ez build manifest.toml ================================================ FILE: test/multi_namespace_not_top_level/README.md ================================================ # Multi namespace project should not be published! ================================================ FILE: test/multi_namespace_not_top_level/gleam.toml ================================================ name = "multi_namespace" version = "1.0.0" description = "Test project for multi namespace" licences = ["Apache-2.0"] ================================================ FILE: test/multi_namespace_not_top_level/src/module1/sub.gleam ================================================ pub fn main() { "Hello from multi_namespace!" } ================================================ FILE: test/multi_namespace_not_top_level/src/module2/sub.gleam ================================================ pub fn main() { "Hello from second!" } ================================================ FILE: test/multi_namespace_not_top_level/test.sh ================================================ #!/bin/sh # https://github.com/gleam-lang/gleam/pull/4445 set -eu GLEAM_COMMAND=${GLEAM_COMMAND:-"cargo run --quiet --"} g() { echo "Running: $GLEAM_COMMAND $@" $GLEAM_COMMAND "$@" } echo Resetting the build directory to get to a known state rm -fr build echo Running publish should not print the warning output=$(yes "n" | g publish) if echo "$output" | grep -q "Your package defines multiple top-level modules"; then echo "Expected warning to be printed" exit 1 fi echo echo Success! 💖 echo ================================================ FILE: test/package.jsonc ================================================ // An idea for how generated docs could contain information on modules within // the package in a machine readable format. { "publishing-gleam-version": "0.20.0", "name": "my_package", "modules": { "my_module": { "documentation": "...", "types": { // pub type MyType1 { One Two(String) } "MyType1": { "documentation": "...", "parameters": 0, "constructors": ["One", "Two"] }, // pub type MyType2 "MyType2": { "documentation": "...", "parameters": 0, "constructors": [] }, // pub type MyType3(a) { One(a, Int) } "MyType3": { "documentation": "...", "parameters": 1, "constructors": ["One"] }, // pub type MyType4(a, b) "MyType4": { "documentation": "...", "parameters": 2, "constructors": [] }, // pub opaque type MyType5(a, b) { One Two } "MyType5": { "documentation": "...", "parameters": 2, "constructors": [] } }, "values": { // pub const nil_constant = Nil "nil_constant": { "documentation": "...", "type": { "kind": "Nil" } }, // pub const tuple_constant = #(1, "2", 3.0) "tuple_constant": { "documentation": "...", "type": { "kind": "Tuple", "elements": [ { "kind": "Int" }, { "kind": "String" }, { "kind": "Float" } ] } }, // pub const ok_constant = Ok(1) "ok_constant": { "documentation": "...", "type": { "kind": "Result", "ok": { "kind": "Int" }, "error": { "kind": "Variable", "id": 0 } } }, // pub const error_constant = Error(Nil) "error_constant": { "documentation": "...", "type": { "kind": "Result", "ok": { "kind": "Nil" }, "error": { "kind": "Variable", "id": 0 } } }, // pub const empty_list_constant = [] "empty_list_constant": { "documentation": "...", "type": { "kind": "List", "elements": { "kind": "Variable", "id": 0 } } }, // pub const int_list_constant = [1, 2, 3] "int_list_constant": { "documentation": "...", "type": { "kind": "List", "elements": { "kind": "Int" } } }, // pub type Wibble { Wobble(String) } // pub const custom_type_constant1 = Wobble("Hi") "custom_type_constant1": { "documentation": "...", "type": { "kind": "Named", "package": "my_package", "module": "my_module", "parameters": [] } }, // pub type Blip(a) { Blap(a) } // pub const custom_type_constant2 = Blap(1) "custom_type_constant2": { "documentation": "...", "type": { "kind": "Named", "package": "my_package", "module": "my_module", "parameters": [ { "kind": "Int" } ] } }, // import some/module // pub const imported_custom_type: module.Something = module.SomethingConstructor "imported_custom_type": { "documentation": "...", "type": { "kind": "Named", "package": "other_package", "module": "some/module", "parameters": [] } }, // pub fn main() -> Nil { ... } "main": { "documentation": "...", "type": { "kind": "Fn", "parameters": [], "return": { "kind": "Nil" } } }, // pub fn multiply(x: Int, x: Int) -> Int { ... } "multiply": { "documentation": "...", "type": { "kind": "Fn", "parameters": [ { "kind": "Int" }, { "kind": "Int" } ], "return": { "kind": "Int" } } } } } } } ================================================ FILE: test/project_deno/.gitignore ================================================ *.beam *.ez build ================================================ FILE: test/project_deno/README.md ================================================ # Project A test Gleam project using the Deno runtime. It covers these features: - Deno runtime specific configuration, including - `location` - used to configure [`--location`](https://docs.deno.com/runtime/manual/runtime/location_api) flag which sets the [`location`](https://developer.mozilla.org/en-US/docs/Web/API/Window/location) global from the web. ## Quick start ```sh gleam run gleam test ``` ================================================ FILE: test/project_deno/gleam.toml ================================================ name = "project" version = "0.1.0" target = "javascript" [javascript] typescript_declarations = true runtime = "deno" [javascript.deno] location = "http://localhost:8080" [dependencies] gleam_stdlib = "~> 0.18" gleam_erlang = "~> 0.23" [dev_dependencies] ================================================ FILE: test/project_deno/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "gleam_erlang", version = "0.24.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "26BDB52E61889F56A291CB34167315780EE4AA20961917314446542C90D1C1A0" }, { name = "gleam_stdlib", version = "0.34.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "1FB8454D2991E9B4C0C804544D8A9AD0F6184725E20D63C3155F0AEB4230B016" }, ] [requirements] gleam_erlang = { version = "~> 0.23" } gleam_stdlib = { version = "~> 0.18" } ================================================ FILE: test/project_deno/src/project.gleam ================================================ pub type Location { Location( href: String, origin: String, protocol: String, host: String, hostname: String, port: String, pathname: String, search: String, hash: String, ) } pub fn main() { location() } @external(javascript, "./project_ffi.mjs", "location") fn location() -> Location ================================================ FILE: test/project_deno/src/project_ffi.mjs ================================================ export function location() { return globalThis.location; } ================================================ FILE: test/project_deno/test/project_test.gleam ================================================ import project pub fn main() { let location = project.main() let assert "http://localhost:8080/" = location.href let assert "http://localhost:8080" = location.origin let assert "http:" = location.protocol let assert "localhost:8080" = location.host let assert "localhost" = location.hostname let assert "8080" = location.port let assert "/" = location.pathname let assert "" = location.search let assert "" = location.hash } ================================================ FILE: test/project_erlang/.gitignore ================================================ *.beam *.ez build ================================================ FILE: test/project_erlang/README.md ================================================ # Project A test Gleam project. It covers these features: - Downloading packages - Specified in config.dependencies - Specified in config.dev_dependencies - Importing packages - Specified in config.dependencies - Specified in config.dev_dependencies - Compilation of Gleam code - in src - in test - Compilation of locally defined Erlang modules - in src - in test - Importing of Erlang header files - in src - in test - Importing Gleam src code into test ## Quick start ```sh gleam run gleam test ``` ================================================ FILE: test/project_erlang/gleam.toml ================================================ name = "project" version = "0.1.0" [dependencies] # These are Gleam deps gleam_stdlib = "~> 0.18" gleam_erlang = "~> 0.5" # This is a rebar3 dep that uses files in ./priv certifi = "~> 2.8" # This is a rebar3 dep that uses files in ./ebin cowboy = "~> 2.9" # This is a mix dep that uses files in ./priv countries = "~> 1.6" # This is both a mix and a rebar3 dep! # We want to default to using rebar3 as that is the build tool that is more # likely to be installed. ssl_verify_fun = "~> 1.1" # This is a rebar3 dep that calls make to compile C into a .so file that is # loaded at runtime from ./priv # TODO: replace this with a package with a nif that compiles super fast. Perhaps # just a hello world. bcrypt = "~> 1.1" # This is a rebar3 dep that output files in $REBAR_BARE_COMPILER_OUTPUT_DIR/priv # and requires absolute paths ezstd = "~> 1.1" # This is a rebar3 dep where the application name (hpack, used by the BEAM) # doesn't match the package name (hpack_erl, used by Hex). hpack_erl = "~> 0.1" gleam_javascript = "~> 0.7" [dev_dependencies] gleeunit = "~> 1.0" ================================================ FILE: test/project_erlang/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "bcrypt", version = "1.2.1", build_tools = ["rebar3"], requirements = ["poolboy"], otp_app = "bcrypt", source = "hex", outer_checksum = "DC4973B57C0F76C86D5275C8056E12878BB5526876481EC799D015A7B19515B6" }, { name = "certifi", version = "2.12.0", build_tools = ["rebar3"], requirements = [], otp_app = "certifi", source = "hex", outer_checksum = "EE68D85DF22E554040CDB4BE100F33873AC6051387BAF6A8F6CE82272340FF1C" }, { name = "countries", version = "1.6.0", build_tools = ["mix"], requirements = ["yamerl"], otp_app = "countries", source = "hex", outer_checksum = "A1E4D0FDD2A799F16A95AE2E842EDEAABD9AC7639624AC5E139C54DA7A6BCCB0" }, { name = "cowboy", version = "2.10.0", build_tools = ["make", "rebar3"], requirements = ["cowlib", "ranch"], otp_app = "cowboy", source = "hex", outer_checksum = "3AFDCCB7183CC6F143CB14D3CF51FA00E53DB9EC80CDCD525482F5E99BC41D6B" }, { name = "cowlib", version = "2.12.1", build_tools = ["make", "rebar3"], requirements = [], otp_app = "cowlib", source = "hex", outer_checksum = "163B73F6367A7341B33C794C4E88E7DBFE6498AC42DCD69EF44C5BC5507C8DB0" }, { name = "ezstd", version = "1.1.0", build_tools = ["rebar3"], requirements = [], otp_app = "ezstd", source = "hex", outer_checksum = "28CFA0ED6CC3922095AD5BA0F23392A1664273358B17184BAA909868361184E7" }, { name = "gleam_erlang", version = "0.24.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "26BDB52E61889F56A291CB34167315780EE4AA20961917314446542C90D1C1A0" }, { name = "gleam_javascript", version = "0.7.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_javascript", source = "hex", outer_checksum = "EEA30D1ABF62B06FC378764D598DF041303CFA33A6586BFF4C4BFEFFA83DBDBE" }, { name = "gleam_stdlib", version = "0.34.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "1FB8454D2991E9B4C0C804544D8A9AD0F6184725E20D63C3155F0AEB4230B016" }, { name = "gleeunit", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D364C87AFEB26BDB4FB8A5ABDE67D635DC9FA52D6AB68416044C35B096C6882D" }, { name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" }, { name = "poolboy", version = "1.5.2", build_tools = ["rebar3"], requirements = [], otp_app = "poolboy", source = "hex", outer_checksum = "DAD79704CE5440F3D5A3681C8590B9DC25D1A561E8F5A9C995281012860901E3" }, { name = "ranch", version = "1.8.0", build_tools = ["make", "rebar3"], requirements = [], otp_app = "ranch", source = "hex", outer_checksum = "49FBCFD3682FAB1F5D109351B61257676DA1A2FDBE295904176D5E521A2DDFE5" }, { name = "ssl_verify_fun", version = "1.1.7", build_tools = ["mix", "rebar3", "make"], requirements = [], otp_app = "ssl_verify_fun", source = "hex", outer_checksum = "FE4C190E8F37401D30167C8C405EDA19469F34577987C76DDE613E838BBC67F8" }, { name = "yamerl", version = "0.10.0", build_tools = ["rebar3"], requirements = [], otp_app = "yamerl", source = "hex", outer_checksum = "346ADB2963F1051DC837A2364E4ACF6EB7D80097C0F53CBDC3046EC8EC4B4E6E" }, ] [requirements] bcrypt = { version = "~> 1.1" } certifi = { version = "~> 2.8" } countries = { version = "~> 1.6" } cowboy = { version = "~> 2.9" } ezstd = { version = "~> 1.1" } gleam_erlang = { version = "~> 0.5" } gleam_javascript = { version = "~> 0.7" } gleam_stdlib = { version = "~> 0.18" } gleeunit = { version = "~> 1.0" } hpack_erl = { version = "~> 0.1" } ssl_verify_fun = { version = "~> 1.1" } ================================================ FILE: test/project_erlang/priv/hello.txt ================================================ Hello, Joe! ================================================ FILE: test/project_erlang/src/elixir_file.ex ================================================ defmodule ElixirFile do def main() do "Hello, from the Elixir module!" end end defmodule ElixirFileAgain do def main() do "Hello, from another Elixir module!" end end ================================================ FILE: test/project_erlang/src/erlang_file.erl ================================================ -module(erlang_file). -export([main/0]). -include("erlang_header.hrl"). main() -> String = header_function(), <<"Hello, from the Erlang module!\n"/utf8, String/binary>>. ================================================ FILE: test/project_erlang/src/erlang_header.hrl ================================================ header_function() -> <<"Hello, from the Erlang header!">>. ================================================ FILE: test/project_erlang/src/project.gleam ================================================ import gleam/io pub fn main() { io.println("Hello, from Gleam compiled to Erlang!") io.println(erlang_function()) io.println(elixir_function()) io.println(another_elixir_function()) } @external(erlang, "erlang_file", "main") fn erlang_function() -> String @external(erlang, "Elixir.ElixirFile", "main") fn elixir_function() -> String @external(erlang, "Elixir.ElixirFileAgain", "main") fn another_elixir_function() -> String ================================================ FILE: test/project_erlang/test/elixir_test_file.ex ================================================ defmodule ElixirTestFile do def main() do "Hello, from the Elixir test module!" end end ================================================ FILE: test/project_erlang/test/erlang_test_file.erl ================================================ -module(erlang_test_file). -export([main/0]). -include("erlang_test_header.hrl"). main() -> String = header_function(), <<"Hello, from the Erlang test module!\n"/utf8, String/binary>>. ================================================ FILE: test/project_erlang/test/erlang_test_header.hrl ================================================ header_function() -> <<"Hello, from the Erlang test header!">>. ================================================ FILE: test/project_erlang/test/project_test.gleam ================================================ import gleam/io import gleam/dynamic.{type Dynamic} import project import gleeunit import gleam/erlang/atom.{from_string as atom_from_string} pub fn main() { project.main() io.println("Hello, from the Gleam test module!") io.println(erlang_function()) io.println(elixir_function()) gleeunit.main() } pub fn rebar3_dep_function_test() { io.println("Testing calling a rebar3 library (that uses headers)") rebar3_dep_function() } pub fn mix_dep_function_test() { io.println("Testing calling a mix library") mix_dep_function() } @external(erlang, "erlang_test_file", "main") fn erlang_function() -> String @external(erlang, "certifi", "cacertfile") fn rebar3_dep_function() -> Dynamic @external(erlang, "Elixir.ElixirTestFile", "main") fn elixir_function() -> String @external(erlang, "Elixir.Countries", "all") fn mix_dep_function() -> Dynamic // Testing for this bug in metadata encoding. // https://github.com/gleam-lang/gleam/commit/c8f3bd0ddbf61c27ea35f37297058ecca7515f6c pub fn name_test() { let assert True = atom.from_string("ok") == atom_from_string("ok") } ================================================ FILE: test/project_erlang_windows/.gitignore ================================================ *.beam *.ez build erl_crash.dump ================================================ FILE: test/project_erlang_windows/gleam.toml ================================================ name = "project" version = "0.1.0" [dependencies] # These are Gleam deps gleam_stdlib = "~> 0.32" gleam_erlang = "~> 0.23" # This is a rebar3 dep that uses files in ./priv certifi = "~> 2.12" # This is a rebar3 dep that uses files in ./ebin cowboy = "~> 2.10" # This is a mix dep that uses files in ./priv countries = "~> 1.6" [dev_dependencies] gleeunit = "~> 1.0" ================================================ FILE: test/project_erlang_windows/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "certifi", version = "2.12.0", build_tools = ["rebar3"], requirements = [], otp_app = "certifi", source = "hex", outer_checksum = "EE68D85DF22E554040CDB4BE100F33873AC6051387BAF6A8F6CE82272340FF1C" }, { name = "countries", version = "1.6.0", build_tools = ["mix"], requirements = ["yamerl"], otp_app = "countries", source = "hex", outer_checksum = "A1E4D0FDD2A799F16A95AE2E842EDEAABD9AC7639624AC5E139C54DA7A6BCCB0" }, { name = "cowboy", version = "2.10.0", build_tools = ["make", "rebar3"], requirements = ["ranch", "cowlib"], otp_app = "cowboy", source = "hex", outer_checksum = "3AFDCCB7183CC6F143CB14D3CF51FA00E53DB9EC80CDCD525482F5E99BC41D6B" }, { name = "cowlib", version = "2.12.1", build_tools = ["make", "rebar3"], requirements = [], otp_app = "cowlib", source = "hex", outer_checksum = "163B73F6367A7341B33C794C4E88E7DBFE6498AC42DCD69EF44C5BC5507C8DB0" }, { name = "gleam_erlang", version = "0.24.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "26BDB52E61889F56A291CB34167315780EE4AA20961917314446542C90D1C1A0" }, { name = "gleam_stdlib", version = "0.34.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "1FB8454D2991E9B4C0C804544D8A9AD0F6184725E20D63C3155F0AEB4230B016" }, { name = "gleeunit", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D364C87AFEB26BDB4FB8A5ABDE67D635DC9FA52D6AB68416044C35B096C6882D" }, { name = "ranch", version = "1.8.0", build_tools = ["make", "rebar3"], requirements = [], otp_app = "ranch", source = "hex", outer_checksum = "49FBCFD3682FAB1F5D109351B61257676DA1A2FDBE295904176D5E521A2DDFE5" }, { name = "yamerl", version = "0.10.0", build_tools = ["rebar3"], requirements = [], otp_app = "yamerl", source = "hex", outer_checksum = "346ADB2963F1051DC837A2364E4ACF6EB7D80097C0F53CBDC3046EC8EC4B4E6E" }, ] [requirements] certifi = { version = "~> 2.12" } countries = { version = "~> 1.6" } cowboy = { version = "~> 2.10" } gleam_erlang = { version = "~> 0.23" } gleam_stdlib = { version = "~> 0.32" } gleeunit = { version = "~> 1.0" } ================================================ FILE: test/project_erlang_windows/priv/hello.txt ================================================ Hello, Joe! ================================================ FILE: test/project_erlang_windows/src/elixir_file.ex ================================================ defmodule ElixirFile do def main() do "Hello, from the Elixir module!" end end defmodule ElixirFileAgain do def main() do "Hello, from another Elixir module!" end end ================================================ FILE: test/project_erlang_windows/src/erlang_file.erl ================================================ -module(erlang_file). -export([main/0]). -include("erlang_header.hrl"). main() -> String = header_function(), <<"Hello, from the Erlang module!\n"/utf8, String/binary>>. ================================================ FILE: test/project_erlang_windows/src/erlang_header.hrl ================================================ header_function() -> <<"Hello, from the Erlang header!">>. ================================================ FILE: test/project_erlang_windows/src/project.gleam ================================================ import gleam/io pub fn main() { io.println("Hello, from Gleam compiled to Erlang!") io.println(erlang_function()) io.println(elixir_function()) io.println(another_elixir_function()) } @external(erlang, "erlang_file", "main") fn erlang_function() -> String @external(erlang, "Elixir.ElixirFile", "main") fn elixir_function() -> String @external(erlang, "Elixir.ElixirFileAgain", "main") fn another_elixir_function() -> String ================================================ FILE: test/project_erlang_windows/test/elixir_test_file.ex ================================================ defmodule ElixirTestFile do def main() do "Hello, from the Elixir test module!" end end ================================================ FILE: test/project_erlang_windows/test/erlang_test_file.erl ================================================ -module(erlang_test_file). -export([main/0]). -include("erlang_test_header.hrl"). main() -> String = header_function(), <<"Hello, from the Erlang test module!\n"/utf8, String/binary>>. ================================================ FILE: test/project_erlang_windows/test/erlang_test_header.hrl ================================================ header_function() -> <<"Hello, from the Erlang test header!">>. ================================================ FILE: test/project_erlang_windows/test/project_test.gleam ================================================ import gleam/io import gleam/dynamic.{type Dynamic} import project import gleeunit import gleam/erlang/atom.{from_string as atom_from_string} pub fn main() { project.main() io.println("Hello, from the Gleam test module!") io.println(erlang_function()) io.println(elixir_function()) gleeunit.main() } pub fn rebar3_dep_function_test() { io.println("Testing calling a rebar3 library (that uses headers)") rebar3_dep_function() } pub fn mix_dep_function_test() { io.println("Testing calling a mix library") mix_dep_function() } @external(erlang, "erlang_test_file", "main") fn erlang_function() -> String @external(erlang, "certifi", "cacertfile") fn rebar3_dep_function() -> Dynamic @external(erlang, "Elixir.ElixirTestFile", "main") fn elixir_function() -> String @external(erlang, "Elixir.Countries", "all") fn mix_dep_function() -> Dynamic // Testing for this bug in metadata encoding. // https://github.com/gleam-lang/gleam/commit/c8f3bd0ddbf61c27ea35f37297058ecca7515f6c pub fn name_test() { let assert True = atom.from_string("ok") == atom_from_string("ok") } ================================================ FILE: test/project_git_deps/.gitignore ================================================ build ================================================ FILE: test/project_git_deps/gleam.toml ================================================ name = "git_deps" version = "0.1.0" description = "Test project to test git dependencies" licences = ["Apache-2.0"] [dependencies] gleam_stdlib = { git = "https://github.com/gleam-lang/stdlib.git", ref = "957b83b" } ================================================ FILE: test/project_git_deps/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "gleam_stdlib", version = "0.54.0", build_tools = ["gleam"], requirements = [], source = "git", repo = "https://github.com/gleam-lang/stdlib.git", commit = "957b83bbb6103aa0d96c148ce7409243681cf1ab" }, ] [requirements] gleam_stdlib = { git = "https://github.com/gleam-lang/stdlib.git", ref = "957b83b" } ================================================ FILE: test/project_git_deps/src/git_deps.gleam ================================================ import gleam/io pub fn main() { // gleam/io is provided via a git dependency io.println("Hello, world!") } ================================================ FILE: test/project_javascript/.gitignore ================================================ *.beam *.ez build ================================================ FILE: test/project_javascript/README.md ================================================ # Project A test Gleam project. It covers these features: - Downloading packages - Specified in config.dependencies - Importing packages - Specified in config.dependencies - Compilation of Gleam code - in src - in test - Importing Gleam src code into test These features are not tested yet - Downloading packages - Specified in config.dev_dependencies - Importing packages - Specified in config.dev_dependencies - Compilation of locally defined JavaScript modules - in src - in test - Importing Gleam src code into test ## Quick start ```sh gleam run gleam test ``` ================================================ FILE: test/project_javascript/gleam.toml ================================================ name = "project" version = "0.1.0" target = "javascript" [javascript] typescript_declarations = true runtime = "node" [dependencies] gleam_stdlib = "~> 0.18" gleam_erlang = "~> 0.23" [dev_dependencies] ================================================ FILE: test/project_javascript/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "gleam_erlang", version = "0.24.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "26BDB52E61889F56A291CB34167315780EE4AA20961917314446542C90D1C1A0" }, { name = "gleam_stdlib", version = "0.34.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "1FB8454D2991E9B4C0C804544D8A9AD0F6184725E20D63C3155F0AEB4230B016" }, ] [requirements] gleam_erlang = { version = "~> 0.23" } gleam_stdlib = { version = "~> 0.18" } ================================================ FILE: test/project_javascript/src/project.gleam ================================================ pub fn main() { println("Hello, from project_javascript!") } @external(erlang, "erlang", "display") @external(javascript, "./project_ffi.mjs", "log") fn println(a: String) -> Nil ================================================ FILE: test/project_javascript/src/project_ffi.mjs ================================================ export function log(x) { console.log(x); } ================================================ FILE: test/project_javascript/test/project_test.gleam ================================================ import project pub fn main() { project.main() } ================================================ FILE: test/project_path_deps/.gitignore ================================================ *.beam *.ez build erl_crash.dump ================================================ FILE: test/project_path_deps/README.md ================================================ This directory contains four projects used in CI test `test/project_path_deps`. The dependency graph of these projects is as follows: ``` _-> project_b -_ / v project_a project_d \ ^ '-> project_c -' ``` ================================================ FILE: test/project_path_deps/project_a/gleam.toml ================================================ name = "project_a" version = "0.1.0" [dependencies] project_b = { path = "../project_b" } project_c = { path = "../project_c" } [dev_dependencies] gleeunit = "~> 1.0" ================================================ FILE: test/project_path_deps/project_a/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "gleam_stdlib", version = "0.34.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "1FB8454D2991E9B4C0C804544D8A9AD0F6184725E20D63C3155F0AEB4230B016" }, { name = "gleeunit", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D3682ED8C5F9CAE1C928F2506DE91625588CC752495988CBE0F5653A42A6F334" }, { name = "project_b", version = "0.1.0", build_tools = ["gleam"], requirements = ["project_d"], source = "local", path = "../project_b" }, { name = "project_c", version = "0.1.0", build_tools = ["gleam"], requirements = ["project_b"], source = "local", path = "../project_c" }, { name = "project_d", version = "1.0.0", build_tools = ["gleam"], requirements = [], source = "local", path = "../project_d" }, ] [requirements] gleeunit = { version = "~> 1.0" } project_b = { path = "../project_b" } project_c = { path = "../project_c" } ================================================ FILE: test/project_path_deps/project_a/src/project_a.gleam ================================================ import project_b import project_c pub type TypeA { VariantB(project_b.TypeB) VariantC(project_c.TypeC) } pub fn main() { let _ = VariantB(project_b.new("thing", "name")) let _ = VariantC(project_c.new("thing")) } ================================================ FILE: test/project_path_deps/project_a/test/project_a_test.gleam ================================================ import gleeunit import gleeunit/should pub fn main() { gleeunit.main() } // gleeunit test functions end in `_test` pub fn hello_world_test() { 1 |> should.equal(1) } ================================================ FILE: test/project_path_deps/project_b/gleam.toml ================================================ name = "project_b" version = "0.1.0" description = "A Gleam project" [dependencies] project_d = { path = "../project_d" } ================================================ FILE: test/project_path_deps/project_b/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "project_d", version = "1.0.0", build_tools = ["gleam"], requirements = [], source = "local", path = "../project_d" }, ] [requirements] project_d = { path = "../project_d" } ================================================ FILE: test/project_path_deps/project_b/src/project_b.gleam ================================================ import project_d pub type TypeB { ConstructorB(contained: project_d.TypeD, name: String) } pub fn new(contained, name) { ConstructorB(project_d.ConstructorD(contained), name) } ================================================ FILE: test/project_path_deps/project_c/gleam.toml ================================================ name = "project_c" version = "0.1.0" [dependencies] project_b = { path = "../project_b" } ================================================ FILE: test/project_path_deps/project_c/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "project_b", version = "0.1.0", build_tools = ["gleam"], requirements = ["project_d"], source = "local", path = "../project_b" }, { name = "project_d", version = "1.0.0", build_tools = ["gleam"], requirements = [], source = "local", path = "../project_d" }, ] [requirements] project_b = { path = "../project_b" } ================================================ FILE: test/project_path_deps/project_c/src/project_c.gleam ================================================ import project_d pub type TypeC { ConstructorC(project_d.TypeD) } pub fn new(str) { ConstructorC(project_d.ConstructorD(str)) } ================================================ FILE: test/project_path_deps/project_d/gleam.toml ================================================ name = "project_d" version = "1.0.0" [dev_dependencies] # This dependency is not used by any of the other packages in this project, and # it is is only used by this package in dev mode, so it will not be in the other # packages at all. # The test file for this package imports this dep, so if there is a bug in the # build tool and it attempts to compile the test deps for a path dep it will # fail. gleam_bitwise = "~> 1.2" ================================================ FILE: test/project_path_deps/project_d/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "gleam_bitwise", version = "1.3.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_bitwise", source = "hex", outer_checksum = "B36E1D3188D7F594C7FD4F43D0D2CE17561DE896202017548578B16FE1FE9EFC" }, ] [requirements] gleam_bitwise = { version = "~> 1.2" } ================================================ FILE: test/project_path_deps/project_d/src/project_d.gleam ================================================ pub type TypeD { ConstructorD(String) } ================================================ FILE: test/project_path_deps/project_d/test/project_d_test.gleam ================================================ import gleam/bitwise pub fn main() { bitwise.and(1, 2) } ================================================ FILE: test/publishing_default_main/.gitignore ================================================ *.beam *.ez build ================================================ FILE: test/publishing_default_main/gleam.toml ================================================ name = "default_main" version = "1.0.0" description = "Test project for default main" licences = ["Apache-2.0"] [dependencies] gleam_stdlib = ">= 0.44.0 and < 2.0.0" ================================================ FILE: test/publishing_default_main/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "gleam_stdlib", version = "0.62.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "0080706D3A5A9A36C40C68481D1D231D243AF602E6D2A2BE67BA8F8F4DFF45EC" }, ] [requirements] gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } ================================================ FILE: test/publishing_default_main/src/default_main/one.gleam ================================================ pub fn one() { "hello from one!" } ================================================ FILE: test/publishing_default_main/src/default_main/two.gleam ================================================ pub fn two() { "hello from two!" } ================================================ FILE: test/publishing_default_main/src/default_main.gleam ================================================ import gleam/io pub fn main() -> Nil { io.println("Hello from default_main!") } ================================================ FILE: test/publishing_default_main/test.sh ================================================ #!/bin/sh set -eu GLEAM_COMMAND=${GLEAM_COMMAND:-"cargo run --quiet --"} g() { echo "Running: $GLEAM_COMMAND $@" $GLEAM_COMMAND "$@" } echo Resetting the build directory to get to a known state rm -fr build echo Running publish should not publish anything if yes "n" | g publish; then echo "Expected publish to fail, but it succeeded" exit 1 fi echo echo Success! 💖 echo ================================================ FILE: test/publishing_default_readme/.gitignore ================================================ *.beam *.ez build ================================================ FILE: test/publishing_default_readme/README.md ================================================ # default_readme [![Package Version](https://img.shields.io/hexpm/v/default_readme)](https://hex.pm/packages/default_readme) [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/default_readme/) ```sh gleam add default_readme@1 ``` ```gleam import default_readme pub fn main() -> Nil { // TODO: An example of the project in use } ``` Further documentation can be found at . ## Development ```sh gleam run # Run the project gleam test # Run the tests ``` ================================================ FILE: test/publishing_default_readme/gleam.toml ================================================ name = "default_readme" version = "1.0.0" description = "Test project for default readme" licences = ["Apache-2.0"] [dependencies] gleam_stdlib = ">= 0.44.0 and < 2.0.0" ================================================ FILE: test/publishing_default_readme/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "gleam_stdlib", version = "0.62.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "0080706D3A5A9A36C40C68481D1D231D243AF602E6D2A2BE67BA8F8F4DFF45EC" }, ] [requirements] gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } ================================================ FILE: test/publishing_default_readme/src/default_readme.gleam ================================================ import gleam/io /// This tests that a project with a default readme doesn't get published. /// pub fn main() -> Nil { greeting() first_line() second_line() } fn greeting() { io.println("Hello from default_readme!") } fn first_line() { io.println("Here we have some additional code so that this is not mistaken") } fn second_line() { io.println("for a default main project, that would be rejected as well!") } ================================================ FILE: test/publishing_default_readme/test.sh ================================================ #!/bin/sh set -eu GLEAM_COMMAND=${GLEAM_COMMAND:-"cargo run --quiet --"} g() { echo "Running: $GLEAM_COMMAND $@" $GLEAM_COMMAND "$@" } echo Resetting the build directory to get to a known state rm -fr build echo Running publish should not publish anything if yes "n" | g publish; then echo "Expected publish to fail, but it succeeded" exit 1 fi echo echo Success! 💖 echo ================================================ FILE: test/publishing_empty_readme/.gitignore ================================================ *.beam *.ez build ================================================ FILE: test/publishing_empty_readme/README.md ================================================ ================================================ FILE: test/publishing_empty_readme/gleam.toml ================================================ name = "empty_readme" version = "1.0.0" description = "Test project for empty readme" licences = ["Apache-2.0"] [dependencies] gleam_stdlib = ">= 0.44.0 and < 2.0.0" ================================================ FILE: test/publishing_empty_readme/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "gleam_stdlib", version = "0.62.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "0080706D3A5A9A36C40C68481D1D231D243AF602E6D2A2BE67BA8F8F4DFF45EC" }, ] [requirements] gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } ================================================ FILE: test/publishing_empty_readme/src/empty_readme.gleam ================================================ import gleam/io /// This tests that a project with an empty readme doesn't get published. /// pub fn main() -> Nil { greeting() first_line() second_line() } fn greeting() { io.println("Hello from empty_readme!") } fn first_line() { io.println("Here we have some additional code so that this is not mistaken") } fn second_line() { io.println("for a default main project, that would be rejected as well!") } ================================================ FILE: test/publishing_empty_readme/test.sh ================================================ #!/bin/sh set -eu GLEAM_COMMAND=${GLEAM_COMMAND:-"cargo run --quiet --"} g() { echo "Running: $GLEAM_COMMAND $@" $GLEAM_COMMAND "$@" } echo Resetting the build directory to get to a known state rm -fr build echo Running publish should not publish anything if yes "n" | g publish; then echo "Expected publish to fail, but it succeeded" exit 1 fi echo echo Success! 💖 echo ================================================ FILE: test/publishing_no_readme/.gitignore ================================================ *.beam *.ez build ================================================ FILE: test/publishing_no_readme/gleam.toml ================================================ name = "no_readme" version = "1.0.0" description = "Test project for no readme" licences = ["Apache-2.0"] [dependencies] gleam_stdlib = ">= 0.44.0 and < 2.0.0" ================================================ FILE: test/publishing_no_readme/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "gleam_stdlib", version = "0.62.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "0080706D3A5A9A36C40C68481D1D231D243AF602E6D2A2BE67BA8F8F4DFF45EC" }, ] [requirements] gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } ================================================ FILE: test/publishing_no_readme/src/no_readme.gleam ================================================ import gleam/io /// This tests that a project with no readme doesn't get published. /// pub fn main() -> Nil { greeting() first_line() second_line() } fn greeting() { io.println("Hello from no_readme!") } fn first_line() { io.println("Here we have some additional code so that this is not mistaken") } fn second_line() { io.println("for a default main project, that would be rejected as well!") } ================================================ FILE: test/publishing_no_readme/test.sh ================================================ #!/bin/sh set -eu GLEAM_COMMAND=${GLEAM_COMMAND:-"cargo run --quiet --"} g() { echo "Running: $GLEAM_COMMAND $@" $GLEAM_COMMAND "$@" } echo Resetting the build directory to get to a known state rm -fr build echo Running publish should not publish anything if yes "n" | g publish; then echo "Expected publish to fail, but it succeeded" exit 1 fi echo echo Success! 💖 echo ================================================ FILE: test/root_package_not_compiled_when_running_dep/.gitignore ================================================ *.beam *.ez /build erl_crash.dump ================================================ FILE: test/root_package_not_compiled_when_running_dep/README.md ================================================ # root_package_not_compiled_when_running_dep This package is used to check that the compiler can compile and run a dependency even if the root package has compilation errors. So one can do `gleam run -m ` even if their own code doesn't compile. ================================================ FILE: test/root_package_not_compiled_when_running_dep/gleam.toml ================================================ name = "root_package_not_compiled_when_running_dep" version = "1.0.0" # Fill out these fields if you intend to generate HTML documentation or publish # your project to the Hex package manager. # # description = "" # licences = ["Apache-2.0"] # repository = { type = "github", user = "", repo = "" } # links = [{ title = "Website", href = "" }] # # For a full reference of all the available options, you can have a look at # https://gleam.run/writing-gleam/gleam-toml/. [dependencies] gleam_stdlib = ">= 0.34.0 and < 2.0.0" hello_joe = ">= 1.0.0 and < 2.0.0" [dev_dependencies] gleeunit = ">= 1.0.0 and < 2.0.0" ================================================ FILE: test/root_package_not_compiled_when_running_dep/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "gleam_stdlib", version = "0.40.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "86606B75A600BBD05E539EB59FABC6E307EEEA7B1E5865AFB6D980A93BCB2181" }, { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" }, { name = "hello_joe", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "hello_joe", source = "hex", outer_checksum = "CC896BC24A45528DE6C59A705171E2DD9F1EA4C3C031428E4A533539EF461519" }, ] [requirements] gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" } gleeunit = { version = ">= 1.0.0 and < 2.0.0" } hello_joe = { version = ">= 1.0.0 and < 2.0.0" } ================================================ FILE: test/root_package_not_compiled_when_running_dep/src/root_package_not_compiled_when_running_dep.gleam ================================================ pub fn main() { compilation_error } ================================================ FILE: test/root_package_not_compiled_when_running_dep/test.sh ================================================ #!/bin/sh set -eu GLEAM_COMMAND=${GLEAM_COMMAND:-"cargo run --quiet --"} g() { echo "Running: $GLEAM_COMMAND $@" $GLEAM_COMMAND "$@" } echo Resetting the build directory to get to a known state rm -fr build echo This should succeed regardless of root package compilation errors as it is a dependency module g run --module=hello_joe g run --module=hello_joe --target=erlang g run --module=hello_joe --target=javascript echo Running for Erlang should fail, even if previously a Erlang dependency was built if g run --target=erlang; then echo "Expected run to fail" exit 1 fi echo Running for JavaScript should fail, even if previously a JavaScript dependency was built if g run --target=javascript; then echo "Expected run to fail" exit 1 fi echo echo Success! 💖 echo ================================================ FILE: test/running_modules/.gitignore ================================================ build ================================================ FILE: test/running_modules/Makefile ================================================ .PHONY: test-all test-all: @echo test/running_modules @./run_tests.sh ================================================ FILE: test/running_modules/README.md ================================================ # Running Modules Tests running modules with the `gleam run -m` command on all targets and runtimes. The `test` directory is required for gleeunit to run in the running a dependency module tests. ================================================ FILE: test/running_modules/dev/module_dev.gleam ================================================ import gleam/io pub fn main() { io.println("This is the default dev module") } ================================================ FILE: test/running_modules/dev/module_in_dev.gleam ================================================ import gleam/io pub fn main() { io.println("Hello from dev/") } ================================================ FILE: test/running_modules/dev/nested/module_in_dev.gleam ================================================ import gleam/io pub fn main() { io.println("Hello from dev/nested!") } ================================================ FILE: test/running_modules/dev/wrong_dev_arity.gleam ================================================ import gleam/io pub fn main(something) { io.println(something) } ================================================ FILE: test/running_modules/gleam.toml ================================================ name = "module" version = "0.1.0" description = "A Gleam project" [dependencies] gleam_stdlib = ">= 0.58.0 and < 2.0.0" gleeunit = ">= 1.0.0 and < 2.0.0" [javascript.deno] allow_read = true ================================================ FILE: test/running_modules/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "gleam_stdlib", version = "0.58.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "091F2D2C4A3A4E2047986C47E2C2C9D728A4E068ABB31FDA17B0D347E6248467" }, { name = "gleeunit", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "0E6C83834BA65EDCAAF4FE4FB94AC697D9262D83E6F58A750D63C9F6C8A9D9FF" }, ] [requirements] gleam_stdlib = { version = ">= 0.58.0 and < 2.0.0" } gleeunit = { version = ">= 1.0.0 and < 2.0.0" } ================================================ FILE: test/running_modules/run_tests.sh ================================================ #/usr/bin/env sh set -eu should_succeed() { echo echo Running: "$@" cargo run -- $@ > /dev/null 2>&1 if [ $? -ne 0 ] then echo ERROR: Command should have succeeded exit 1 else echo Test Passed '(command run successfully)' fi } should_fail() { echo echo Running: "$@" EXIT_CODE=0 cargo run -- $@ > /dev/null 2>&1 || EXIT_CODE=$? if [ $EXIT_CODE -eq 0 ] then echo ERROR: Command should have failed exit 1 else echo Test Passed '(command errored as expected)' fi } # No module should_succeed run --target erlang should_succeed run --target javascript --runtime nodejs should_succeed run --target javascript --runtime deno # Module from same package should_succeed run --module module --target erlang should_succeed run --module module --target javascript --runtime nodejs should_succeed run --module module --target javascript --runtime deno # Nested module from same package should_succeed run --module module/sub_module --target erlang should_succeed run --module module/sub_module --target javascript --runtime nodejs should_succeed run --module module/sub_module --target javascript --runtime deno # Dependency package should_succeed run --module gleeunit --target erlang should_succeed run --module gleeunit --target javascript --runtime nodejs should_succeed run --module gleeunit --target javascript --runtime deno # Unknown module should_fail run --module doesnt_exist # Unknown module should only belong as a package or in src/ and test/ should_fail run --module src/doesnt_exist # No main function should_fail run --module module/no_main_function # Main function with wrong arity should_fail run --module module/wrong_arity should_fail run --module wrong_test_arity should_fail run --module wrong_dev_arity # Test modules should_succeed test --target erlang should_succeed test --target javascript --runtime nodejs should_succeed test --target javascript --runtime deno should_succeed run --module module_in_test --target erlang should_succeed run --module module_in_test --target javascript --runtime nodejs should_succeed run --module module_in_test --target javascript --runtime deno # Nested module in test should_succeed run --module nested/module_in_test --target erlang should_succeed run --module nested/module_in_test --target javascript --runtime nodejs should_succeed run --module nested/module_in_test --target javascript --runtime deno # Dev modules should_succeed dev --target erlang should_succeed dev --target javascript --runtime nodejs should_succeed dev --target javascript --runtime deno should_succeed run --module module_in_dev --target erlang should_succeed run --module module_in_dev --target javascript --runtime nodejs should_succeed run --module module_in_dev --target javascript --runtime deno # Nested module in dev should_succeed run --module nested/module_in_dev --target erlang should_succeed run --module nested/module_in_dev --target javascript --runtime nodejs should_succeed run --module nested/module_in_dev --target javascript --runtime deno ================================================ FILE: test/running_modules/src/module/no_main_function.gleam ================================================ ================================================ FILE: test/running_modules/src/module/sub_module.gleam ================================================ import gleam/io pub fn main() { io.println("sub module") } ================================================ FILE: test/running_modules/src/module/wrong_arity.gleam ================================================ pub fn main(arg: Int) -> Nil { Nil } ================================================ FILE: test/running_modules/src/module.gleam ================================================ import gleam/io pub fn main() { io.println("top level module") } ================================================ FILE: test/running_modules/test/module_in_test.gleam ================================================ import gleam/io pub fn main() { io.println("Hello from test/") } ================================================ FILE: test/running_modules/test/module_test.gleam ================================================ import gleam/io pub fn main() { io.println("Default test module") } ================================================ FILE: test/running_modules/test/nested/module_in_test.gleam ================================================ import gleam/io pub fn main() { io.println("Hello from test/nested!") } ================================================ FILE: test/running_modules/test/wrong_test_arity.gleam ================================================ import gleam/io pub fn main(something) { io.println(something) } ================================================ FILE: test/subdir_ffi/.gitignore ================================================ *.beam *.ez build erl_crash.dump ================================================ FILE: test/subdir_ffi/Makefile ================================================ .PHONY: build build: clean erlang nodejs deno bun .PHONY: clean clean: rm -rf build .PHONY: erlang erlang: @echo test/subdir_ffi on Erlang cargo run --quiet -- test --target erlang .PHONY: nodejs nodejs: @echo test/subdir_ffi on JavaScript with Node cargo run --quiet -- test --target javascript --runtime nodejs .PHONY: deno deno: @echo test/subdir_ffi on JavaScript with Deno cargo run --quiet -- test --target javascript --runtime deno .PHONY: bun bun: @echo test/subdir_ffi on JavaScript with Bun cargo run --quiet -- test --target javascript --runtime bun ================================================ FILE: test/subdir_ffi/README.md ================================================ # subdir_ffi This package is used to check if the compiler properly supports FFI files (such as `.mjs`, `.erl` and `.hrl`) in subdirectories. Test across all targets and runtimes by running `make`. ================================================ FILE: test/subdir_ffi/gleam.toml ================================================ name = "subdir_ffi" version = "1.0.0" # Fill out these fields if you intend to generate HTML documentation or publish # your project to the Hex package manager. # # description = "" # licences = ["Apache-2.0"] # repository = { type = "github", user = "", repo = "" } # links = [{ title = "Website", href = "" }] # # For a full reference of all the available options, you can have a look at # https://gleam.run/writing-gleam/gleam-toml/. [dependencies] gleam_stdlib = ">= 0.34.0 and < 2.0.0" [dev_dependencies] gleeunit = ">= 1.0.0 and < 2.0.0" ================================================ FILE: test/subdir_ffi/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "gleam_stdlib", version = "0.40.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "86606B75A600BBD05E539EB59FABC6E307EEEA7B1E5865AFB6D980A93BCB2181" }, { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" }, ] [requirements] gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" } gleeunit = { version = ">= 1.0.0 and < 2.0.0" } ================================================ FILE: test/subdir_ffi/src/headers/submodule_ffi_header.hrl ================================================ header_function() -> <<"Hello, from the nested Erlang header!">>. ================================================ FILE: test/subdir_ffi/src/nested/submodule.gleam ================================================ pub fn submodule_main() { parent_println(message()) parent_println(elixir_message()) } @external(erlang, "project_ffi", "log") @external(javascript, "../project_ffi.mjs", "log") fn parent_println(a: String) -> Nil @external(erlang, "submodule_ffi", "main") @external(javascript, "./submodule_ffi.mjs", "main") fn message() -> String @external(erlang, "Elixir.ElixirFile", "main") @external(javascript, "./submodule_ffi.mjs", "main") fn elixir_message() -> String ================================================ FILE: test/subdir_ffi/src/nested/submodule_ffi.erl ================================================ -module(submodule_ffi). -export([main/0, main2/0]). -include("../headers/submodule_ffi_header.hrl"). main() -> String = header_function(), <<"Hello, from the nested Erlang module!\n"/utf8, String/binary>>. main2() -> String = header_function(), <<"Hello again, from the nested Erlang module!\n"/utf8, String/binary>>. ================================================ FILE: test/subdir_ffi/src/nested/submodule_ffi.ex ================================================ defmodule ElixirFile do def main() do "Hello, from the Elixir module!" end end defmodule ElixirFileAgain do def main() do "Hello, from another Elixir module!" end end ================================================ FILE: test/subdir_ffi/src/nested/submodule_ffi.mjs ================================================ export function main(x) { return "Hello from the nested JavaScript native module!"; } export function main2(x) { return "Hello again from the nested JavaScript native module!"; } ================================================ FILE: test/subdir_ffi/src/project.gleam ================================================ import nested/submodule pub fn main() { println("Hello from subdir_ffi!") submodule.submodule_main() println(subdir_message()) println(subdir_elixir_message()) } @external(erlang, "project_ffi", "log") @external(javascript, "./project_ffi.mjs", "log") fn println(a: String) -> Nil @external(erlang, "submodule_ffi", "main2") @external(javascript, "./nested/submodule_ffi.mjs", "main2") fn subdir_message() -> String @external(erlang, "Elixir.ElixirFileAgain", "main") @external(javascript, "./nested/submodule_ffi.mjs", "main2") fn subdir_elixir_message() -> String ================================================ FILE: test/subdir_ffi/src/project_ffi.erl ================================================ -module(project_ffi). -export([log/1]). log(Message) -> erlang:display(Message). ================================================ FILE: test/subdir_ffi/src/project_ffi.mjs ================================================ export function log(x) { console.log(x); } ================================================ FILE: test/subdir_ffi/test/subdir_ffi_test.gleam ================================================ import project pub fn main() { project.main() } ================================================ FILE: test/unicode_path ⭐/.gitignore ================================================ *.beam *.ez /build erl_crash.dump ================================================ FILE: test/unicode_path ⭐/Makefile ================================================ .PHONY: build build: rm -fr build cargo run --quiet -- build --target erlang ================================================ FILE: test/unicode_path ⭐/gleam.toml ================================================ name = "unicode_path" version = "1.0.0" ================================================ FILE: test/unicode_path ⭐/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ ] [requirements] ================================================ FILE: test/unicode_path ⭐/src/unicode_path.gleam ================================================ pub fn main() { Nil } ================================================ FILE: test-community-packages/.gitignore ================================================ *.beam *.ez /build erl_crash.dump ================================================ FILE: test-community-packages/README.md ================================================ # test_community_packages [![Package Version](https://img.shields.io/hexpm/v/test_community_packages)](https://hex.pm/packages/test_community_packages) [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/test_community_packages/) ```sh gleam add test_community_packages ``` ```gleam import test_community_packages pub fn main() { // TODO: An example of the project in use } ``` Further documentation can be found at . ## Development ```sh gleam run # Run the project gleam test # Run the tests gleam shell # Run an Erlang shell ``` ================================================ FILE: test-community-packages/gleam.toml ================================================ name = "test_community_packages" version = "1.0.0" # Fill out these fields if you intend to generate HTML documentation or publish # your project to the Hex package manager. # # description = "" # licences = ["Apache-2.0"] # repository = { type = "github", user = "username", repo = "project" } # links = [{ title = "Website", href = "https://gleam.run" }] # # For a full reference of all the available options, you can have a look at # https://gleam.run/writing-gleam/gleam-toml/. [dependencies] gleam_stdlib = ">= 0.36.0 and < 1.0.0" argamak = ">= 1.1.0 and < 2.0.0" argv = ">= 1.0.2 and < 2.0.0" aws4_request = ">= 0.1.1 and < 1.0.0" bigi = ">= 2.1.0 and < 3.0.0" birdie = ">= 1.1.0 and < 2.0.0" cgi = ">= 1.0.1 and < 2.0.0" conversation = ">= 1.4.3 and < 2.0.0" edit_distance = ">= 2.0.1 and < 3.0.0" envoy = ">= 1.0.1 and < 2.0.0" exception = ">= 2.0.0 and < 3.0.0" exercism_test_runner = ">= 1.7.0 and < 2.0.0" filepath = ">= 0.2.0 and < 1.0.0" gap = ">= 1.1.3 and < 2.0.0" gen_core_erlang = ">= 0.5.1 and < 1.0.0" gen_gleam = ">= 0.3.1 and < 1.0.0" glance = ">= 0.8.2 and < 1.0.0" glance_printer = ">= 1.1.0 and < 2.0.0" glatus = ">= 1.0.0 and < 2.0.0" gleam_community_ansi = ">= 1.4.0 and < 2.0.0" gleam_community_colour = ">= 1.4.0 and < 2.0.0" gleam_community_maths = ">= 1.1.0 and < 2.0.0" gleam_crypto = ">= 1.3.0 and < 2.0.0" gleam_elli = ">= 2.4.0 and < 3.0.0" gleam_erlang = ">= 0.25.0 and < 1.0.0" gleam_fetch = ">= 0.4.0 and < 1.0.0" gleam_hackney = ">= 1.2.0 and < 2.0.0" gleam_http = ">= 3.6.0 and < 4.0.0" gleam_httpc = ">= 2.2.0 and < 3.0.0" gleam_javascript = ">= 0.8.0 and < 1.0.0" gleam_json = ">= 0.7.0 and < 1.0.0" gleam_otp = ">= 0.10.0 and < 1.0.0" gleam_pgo = ">= 0.6.1 and < 1.0.0" gleam_sendgrid = ">= 0.2.0 and < 1.0.0" gleamy_bench = ">= 0.4.0 and < 1.0.0" glearray = ">= 0.2.1 and < 1.0.0" gleescript = ">= 1.0.0 and < 2.0.0" glen = ">= 2.0.0 and < 3.0.0" glenvy = ">= 0.5.1 and < 1.0.0" glesha = ">= 0.1.3 and < 1.0.0" glexer = ">= 0.7.0 and < 1.0.0" glisten = ">= 2.0.0 and < 3.0.0" globe = ">= 0.1.0 and < 1.0.0" gsv = ">= 1.4.0 and < 2.0.0" htmb = ">= 2.0.0 and < 3.0.0" htmgrrrl = ">= 0.3.0 and < 1.0.0" ids = ">= 0.12.0 and < 1.0.0" iso_8859 = ">= 2.0.0 and < 3.0.0" jot = ">= 0.3.1 and < 1.0.0" justin = ">= 1.0.1 and < 2.0.0" lustre = ">= 3.1.4 and < 4.0.0" lustre_http = ">= 0.5.0 and < 1.0.0" lustre_ui = ">= 0.4.0 and < 1.0.0" lustre_websocket = ">= 0.7.6 and < 1.0.0" marceau = ">= 1.1.0 and < 2.0.0" mineflayer = ">= 0.1.0 and < 1.0.0" mist = ">= 1.0.0 and < 2.0.0" mug = ">= 0.2.0 and < 1.0.0" nakai = ">= 0.9.0 and < 1.0.0" nibble = ">= 1.1.0 and < 2.0.0" outil = ">= 0.5.0 and < 1.0.0" phony = ">= 1.1.6 and < 2.0.0" plinth = ">= 0.1.13 and < 1.0.0" prng = ">= 3.0.2 and < 4.0.0" process_waiter = ">= 1.0.0 and < 2.0.0" puddle = ">= 0.5.0 and < 1.0.0" punycode = ">= 0.1.1 and < 1.0.0" radish = ">= 0.13.0 and < 1.0.0" ranger = ">= 1.2.0 and < 2.0.0" rank = ">= 1.0.0 and < 2.0.0" repeatedly = ">= 2.1.1 and < 3.0.0" simplifile = ">= 1.5.1 and < 2.0.0" snag = ">= 0.3.0 and < 1.0.0" spinner = ">= 1.1.0 and < 2.0.0" testbldr = ">= 1.1.0 and < 2.0.0" tom = ">= 0.3.0 and < 1.0.0" wimp = ">= 1.1.0 and < 2.0.0" wisp = ">= 0.14.0 and < 1.0.0" zeptomail = ">= 1.0.0 and < 2.0.0" jwt = ">= 0.1.11 and < 1.0.0" jasper = ">= 1.2.0 and < 2.0.0" immutable_lru = ">= 1.0.1 and < 2.0.0" xmleam = ">= 1.1.4 and < 2.0.0" pears = ">= 0.3.0 and < 1.0.0" typed_headers = ">= 1.1.2 and < 2.0.0" reactive_signal = ">= 0.0.1 and < 1.0.0" nanoworker = ">= 0.0.2 and < 1.0.0" file_streams = ">= 0.3.1 and < 1.0.0" morse_code_translator = ">= 2.0.0 and < 3.0.0" gleeunit = ">= 1.1.2 and < 2.0.0" ================================================ FILE: test-community-packages/manifest.toml ================================================ # This file was generated by Gleam # You typically do not need to edit this file packages = [ { name = "argamak", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "nx"], otp_app = "argamak", source = "hex", outer_checksum = "4124DD1D004D43D9F9734A3274BE063F2770940C773BDE21AFFC43EB45572C9A" }, { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" }, { name = "aws4_request", version = "0.1.1", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_http", "gleam_stdlib"], otp_app = "aws4_request", source = "hex", outer_checksum = "90B1DB6E2A7F0396CD4713850B14B3A910331B5BA76D051E411D1499AAA2EA9A" }, { name = "backoff", version = "1.1.6", build_tools = ["rebar3"], requirements = [], otp_app = "backoff", source = "hex", outer_checksum = "CF0CFFF8995FB20562F822E5CC47D8CCF664C5ECDC26A684CBE85C225F9D7C39" }, { name = "base64url", version = "0.0.1", build_tools = ["rebar"], requirements = [], otp_app = "base64url", source = "hex", outer_checksum = "FAB09B20E3F5DB886725544CBCF875B8E73EC93363954EB8A1A9ED834AA8C1F9" }, { name = "bigi", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "bigi", source = "hex", outer_checksum = "B6F7CAF319F13F32DB4331A750534912A9AEE1C195DD8E5DA83A42A4AD390274" }, { name = "birdie", version = "1.1.0", build_tools = ["gleam"], requirements = ["argv", "filepath", "gap", "glance", "gleam_community_ansi", "gleam_erlang", "gleam_stdlib", "gleeunit", "justin", "rank", "simplifile", "trie_again"], otp_app = "birdie", source = "hex", outer_checksum = "DE7BEE4C76A52E91E72800531B6E5B41093D06FA90C7F6D9FDE768BCE714FAA7" }, { name = "birl", version = "1.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "ranger"], otp_app = "birl", source = "hex", outer_checksum = "976CFF85D34D50F7775896615A71745FBE0C325E50399787088F941B539A0497" }, { name = "certifi", version = "2.12.0", build_tools = ["rebar3"], requirements = [], otp_app = "certifi", source = "hex", outer_checksum = "EE68D85DF22E554040CDB4BE100F33873AC6051387BAF6A8F6CE82272340FF1C" }, { name = "cgi", version = "1.0.1", build_tools = ["gleam"], requirements = ["envoy", "gleam_http", "gleam_stdlib"], otp_app = "cgi", source = "hex", outer_checksum = "CEAF4AE877A115E1C03810289924B5102073F5FB1B786C5F28BA241BAC632042" }, { name = "complex", version = "0.5.0", build_tools = ["mix"], requirements = [], otp_app = "complex", source = "hex", outer_checksum = "2683BD3C184466CFB94FAD74CBFDDFAA94B860E27AD4CA1BFFE3BFF169D91EF1" }, { name = "conversation", version = "1.4.3", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_javascript", "gleam_stdlib"], otp_app = "conversation", source = "hex", outer_checksum = "908B46F60444442785A495197D482558AD8B849C3714A38FAA1940358CC8CCCD" }, { name = "edit_distance", version = "2.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "edit_distance", source = "hex", outer_checksum = "A1E485C69A70210223E46E63985FA1008B8B2DDA9848B7897469171B29020C05" }, { name = "elli", version = "3.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "elli", source = "hex", outer_checksum = "698B13B33D05661DB9FE7EFCBA41B84825A379CCE86E486CF6AFF9285BE0CCF8" }, { name = "envoy", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "CFAACCCFC47654F7E8B75E614746ED924C65BD08B1DE21101548AC314A8B6A41" }, { name = "exception", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "F5580D584F16A20B7FCDCABF9E9BE9A2C1F6AC4F9176FA6DD0B63E3B20D450AA" }, { name = "exercism_test_runner", version = "1.7.0", build_tools = ["gleam"], requirements = ["argv", "gap", "glance", "gleam_community_ansi", "gleam_erlang", "gleam_json", "gleam_stdlib", "simplifile"], otp_app = "exercism_test_runner", source = "hex", outer_checksum = "2FC1BADB19BEC2AE77BFD2D3A606A014C85412A7B874CAFC4BA8CF04B0B257CD" }, { name = "file_streams", version = "0.3.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "file_streams", source = "hex", outer_checksum = "4064B4ED044460D6BD53A9654AABBC7D38A471D8805A0FF17DF511FE219396B4" }, { name = "filepath", version = "0.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "FC1B1B29438A5BA6C990F8047A011430BEC0C5BA638BFAA62718C4EAEFE00435" }, { name = "gap", version = "1.1.3", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_stdlib"], otp_app = "gap", source = "hex", outer_checksum = "6EF5E3B523FDFBC317E9EA28D5163EE04744A97C007106F90207569789612291" }, { name = "gen_core_erlang", version = "0.5.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gen_core_erlang", source = "hex", outer_checksum = "07B01FF8515B6B8E9CAF7CFD3D0100D713C61E4B8F8A88E586A9D88457C9A127" }, { name = "gen_gleam", version = "0.3.1", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib", "simplifile"], otp_app = "gen_gleam", source = "hex", outer_checksum = "19CD1648B44246BDA137936AFD4A09EFF4DFB7C48FA1D5A2C18EF39CDCFB6CD8" }, { name = "glam", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glam", source = "hex", outer_checksum = "02E0311862B9669C3E8CE73FA5A95D8FA457C6ACB48D95FBE808ABFAE0A1CEB0" }, { name = "glance", version = "0.8.2", build_tools = ["gleam"], requirements = ["gleam_stdlib", "glexer"], otp_app = "glance", source = "hex", outer_checksum = "ACF09457E8B564AD7A0D823DAFDD326F58263C01ACB0D432A9BEFDEDD1DA8E73" }, { name = "glance_printer", version = "1.1.0", build_tools = ["gleam"], requirements = ["glam", "glance", "gleam_stdlib"], otp_app = "glance_printer", source = "hex", outer_checksum = "3140D4DD3F6C9119C60F2BA994F728D04E56014498A9C6C994814003EA115DE7" }, { name = "glatus", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_json", "gleam_stdlib"], otp_app = "glatus", source = "hex", outer_checksum = "8D1C5D7F3AC7FB0616D14C3BD8930F8D3F427EC195086FE24D60745CF5900C7D" }, { name = "gleam_bitwise", version = "1.3.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_bitwise", source = "hex", outer_checksum = "B36E1D3188D7F594C7FD4F43D0D2CE17561DE896202017548578B16FE1FE9EFC" }, { name = "gleam_community_ansi", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "FE79E08BF97009729259B6357EC058315B6FBB916FAD1C2FF9355115FEB0D3A4" }, { name = "gleam_community_colour", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "795964217EBEDB3DA656F5EB8F67D7AD22872EB95182042D3E7AFEF32D3FD2FE" }, { name = "gleam_community_maths", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_community_maths", source = "hex", outer_checksum = "E30C61A75051DAF7CFD77C4FBAA04140FDA0B5D831955E7A74521E5576E2780D" }, { name = "gleam_crypto", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "ADD058DEDE8F0341F1ADE3AAC492A224F15700829D9A3A3F9ADF370F875C51B7" }, { name = "gleam_elli", version = "2.4.0", build_tools = ["gleam"], requirements = ["elli", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib"], otp_app = "gleam_elli", source = "hex", outer_checksum = "433F5AF4ED92C55F3EBA8942610E974254EEF90F484AF26E3D775E33338DE832" }, { name = "gleam_erlang", version = "0.25.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "054D571A7092D2A9727B3E5D183B7507DAB0DA41556EC9133606F09C15497373" }, { name = "gleam_fetch", version = "0.4.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_javascript", "gleam_stdlib"], otp_app = "gleam_fetch", source = "hex", outer_checksum = "7446410A44A1D1328F5BC1FF4FC9CBD1570479EA69349237B3F82E34521CCC10" }, { name = "gleam_hackney", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib", "hackney"], otp_app = "gleam_hackney", source = "hex", outer_checksum = "066B1A55D37DBD61CC72A1C4EDE43C6015B1797FAF3818C16FE476534C7B6505" }, { name = "gleam_http", version = "3.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "8C07DF9DF8CC7F054C650839A51C30A7D3C26482AC241C899C1CEA86B22DBE51" }, { name = "gleam_httpc", version = "2.2.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib"], otp_app = "gleam_httpc", source = "hex", outer_checksum = "CF76C71002DEECF6DC5D9CA83D962728FAE166B57926BE442D827004D3C7DF1B" }, { name = "gleam_javascript", version = "0.8.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_javascript", source = "hex", outer_checksum = "14D5B7E1A70681E0776BF0A0357F575B822167960C844D3D3FA114D3A75F05A8" }, { name = "gleam_json", version = "0.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "CB405BD93A8828BCD870463DE29375E7B2D252D9D124C109E5B618AAC00B86FC" }, { name = "gleam_otp", version = "0.10.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "0B04FE915ACECE539B317F9652CAADBBC0F000184D586AAAF2D94C100945D72B" }, { name = "gleam_pgo", version = "0.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "pgo"], otp_app = "gleam_pgo", source = "hex", outer_checksum = "18A4940471BA798AA1FB85CD6E6D035A7403F66C4A2F19CDD471E0DA450C3633" }, { name = "gleam_sendgrid", version = "0.2.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_json", "gleam_stdlib"], otp_app = "gleam_sendgrid", source = "hex", outer_checksum = "15DA0C569F0B318C42E145D2604C8307D0CA0A03DC6CE947A453B7DDFFC64C17" }, { name = "gleam_stdlib", version = "0.36.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "C0D14D807FEC6F8A08A7C9EF8DFDE6AE5C10E40E21325B2B29365965D82EB3D4" }, { name = "gleamy_bench", version = "0.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleamy_bench", source = "hex", outer_checksum = "B094BD02EAD14BBF24B8A790C1BD4ECAADBBF17B0503DE46EC74DAD9F4930FE9" }, { name = "glearray", version = "0.2.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glearray", source = "hex", outer_checksum = "908154F695D330E06A37FAB2C04119E8F315D643206F8F32B6A6C14A8709FFF4" }, { name = "gleescript", version = "1.0.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_erlang", "gleam_stdlib", "simplifile", "snag", "tom"], otp_app = "gleescript", source = "hex", outer_checksum = "F7C152E206167000420F90983E4D4A076703292AAC4335A9248BA46D380841AC" }, { name = "gleeunit", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "72CDC3D3F719478F26C4E2C5FED3E657AC81EC14A47D2D2DEBB8693CA3220C3B" }, { name = "glen", version = "2.0.0", build_tools = ["gleam"], requirements = ["conversation", "gleam_community_ansi", "gleam_http", "gleam_javascript", "gleam_stdlib", "marceau"], otp_app = "glen", source = "hex", outer_checksum = "63224B9BD0994ABCE4E8D6B3C94520D59A5E70CC807ED61BA9CFA32368CA1DBF" }, { name = "glenvy", version = "0.5.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib", "nibble", "simplifile"], otp_app = "glenvy", source = "hex", outer_checksum = "4D1090DC984DB3184CC5E151F240FBE9ED6EBE302B69DC0B6CC222DD4C74357E" }, { name = "glesha", version = "0.1.3", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glesha", source = "hex", outer_checksum = "8DC5F211670C12870FC36C211B8C0A0FFB07BC83708484174CAE1F9DA4B8AF9F" }, { name = "glexer", version = "0.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glexer", source = "hex", outer_checksum = "4484942A465482A0A100936E1E5F12314DB4B5AC0D87575A7B9E9062090B96BE" }, { name = "glint", version = "0.14.0", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_community_colour", "gleam_stdlib", "snag"], otp_app = "glint", source = "hex", outer_checksum = "21AB16D5A50D4EF34DF935915FDBEE06B2DAEDEE3FCC8584C6E635A866566B38" }, { name = "glisten", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "glisten", source = "hex", outer_checksum = "CF3A9383E9BA4A8CBAF2F7B799716290D02F2AC34E7A77556B49376B662B9314" }, { name = "globe", version = "0.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "globe", source = "hex", outer_checksum = "803B1B983238AF34627AA1135050838388468364D64B1660A7F973F66E9B6C3B" }, { name = "gsv", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gsv", source = "hex", outer_checksum = "4D6B08A60D38AB8D6605822B50A120E2BCDE1F3F2EA9DD3BBBEC3350CFB15D3F" }, { name = "hackney", version = "1.20.1", build_tools = ["rebar3"], requirements = ["certifi", "idna", "metrics", "mimerl", "parse_trans", "ssl_verify_fun", "unicode_util_compat"], otp_app = "hackney", source = "hex", outer_checksum = "FE9094E5F1A2A2C0A7D10918FEE36BFEC0EC2A979994CFF8CFE8058CD9AF38E3" }, { name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" }, { name = "htmb", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "htmb", source = "hex", outer_checksum = "915186FE1759EB8B78CBDC01228D0B7ED74DA2E64B08B4115525E293B036C428" }, { name = "htmerl", version = "0.1.0", build_tools = ["rebar3"], requirements = [], otp_app = "htmerl", source = "hex", outer_checksum = "D932F76EA33C318A79F41A429FDBEAE30820390DDB72BA89F38EEF32FEC36395" }, { name = "htmgrrrl", version = "0.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "htmerl"], otp_app = "htmgrrrl", source = "hex", outer_checksum = "983492567967DAA64776E005B9E70353368B14E6F87D543E01308B48A7A0398F" }, { name = "idna", version = "6.1.1", build_tools = ["rebar3"], requirements = ["unicode_util_compat"], otp_app = "idna", source = "hex", outer_checksum = "92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA" }, { name = "ids", version = "0.12.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "ids", source = "hex", outer_checksum = "C0772022F69E6C91825115F6C7053F1AE2F5567A31650229B7B8749F8F429F3C" }, { name = "immutable_lru", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "immutable_lru", source = "hex", outer_checksum = "D3AA3FDE2AAE9B5CF48D825A75E9D0D7ECAD8085AB4A25B068C7C2E456E81F29" }, { name = "iso_8859", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "iso_8859", source = "hex", outer_checksum = "B296DC9587480C7A7D1E6862F888844956E03A1CD99ECE7713970515C0AA4F6A" }, { name = "jasper", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "pears"], otp_app = "jasper", source = "hex", outer_checksum = "F86B8D44B72A1623940A947605AEC348A87BBC8D0570F3484EC74EF4FE2C0B53" }, { name = "jot", version = "0.3.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "jot", source = "hex", outer_checksum = "574A2DACA106E9B4826C9F3F2D3911844C7826D554C08E404696CC16F85E0392" }, { name = "jsx", version = "2.8.3", build_tools = ["mix", "rebar3"], requirements = [], otp_app = "jsx", source = "hex", outer_checksum = "FC3499FED7A726995AA659143A248534ADC754EBD16CCD437CD93B649A95091F" }, { name = "justin", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "justin", source = "hex", outer_checksum = "7FA0C6DB78640C6DC5FBFD59BF3456009F3F8B485BF6825E97E1EB44E9A1E2CD" }, { name = "jwt", version = "0.1.11", build_tools = ["rebar3"], requirements = ["base64url", "jsx"], otp_app = "jwt", source = "hex", outer_checksum = "B483FBB786D1BD050B3C551D323112D604E5B0F9CF9DC5671ADEEC462C2E36BB" }, { name = "logging", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "82C112ED9B6C30C1772A6FE2613B94B13F62EA35F5869A2630D13948D297BD39" }, { name = "lustre", version = "3.1.4", build_tools = ["gleam"], requirements = ["argv", "gleam_community_ansi", "gleam_stdlib", "glint"], otp_app = "lustre", source = "hex", outer_checksum = "E651E39189F55473837FB7386C06BAED7276B37B5058302CAC880F89C25CB4E9" }, { name = "lustre_http", version = "0.5.0", build_tools = ["gleam"], requirements = ["gleam_fetch", "gleam_http", "gleam_javascript", "gleam_json", "gleam_stdlib", "lustre"], otp_app = "lustre_http", source = "hex", outer_checksum = "7D7D91EB7C59177E7C2ECCF7D0BE530DDFF86264AE1BEF473E6F9F8C6C62CEB0" }, { name = "lustre_ui", version = "0.4.0", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_stdlib", "lustre"], otp_app = "lustre_ui", source = "hex", outer_checksum = "9FE07E26EABDB13F7CB29F90AD8763618040729BF16E5F451A6ED584C52AA093" }, { name = "lustre_websocket", version = "0.7.6", build_tools = ["gleam"], requirements = ["gleam_stdlib", "lustre"], otp_app = "lustre_websocket", source = "hex", outer_checksum = "E9773508D3FF9EF7A36EB86826D89879214B26641F7AEC8B44B2A95579D21BBD" }, { name = "marceau", version = "1.1.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "1AAD727A30BE0F95562C3403BB9B27C823797AD90037714255EEBF617B1CDA81" }, { name = "metrics", version = "1.0.1", build_tools = ["rebar3"], requirements = [], otp_app = "metrics", source = "hex", outer_checksum = "69B09ADDDC4F74A40716AE54D140F93BEB0FB8978D8636EADED0C31B6F099F16" }, { name = "mimerl", version = "1.2.0", build_tools = ["rebar3"], requirements = [], otp_app = "mimerl", source = "hex", outer_checksum = "F278585650AA581986264638EBF698F8BB19DF297F66AD91B18910DFC6E19323" }, { name = "mineflayer", version = "0.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "mineflayer", source = "hex", outer_checksum = "77AFA091107A42AD63D0FE6C417CE9E4984CE33C9225DCE8C802DD625A684262" }, { name = "mist", version = "1.0.0", build_tools = ["gleam"], requirements = ["birl", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "glisten", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "7765E53DCC9ACCACF217B8E0CA3DE7E848C783BFAE5118B75011E81C2C80385C" }, { name = "morse_code_translator", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "morse_code_translator", source = "hex", outer_checksum = "29492E275B5BAAFB7748CD6DA12BA16E6D2CCF5F202C672231F36F61DD63F291" }, { name = "mug", version = "0.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "mug", source = "hex", outer_checksum = "35064CE144C23AD60842F44C989D70DD7A8923B8A74885F06E09B73DD3CC5283" }, { name = "nakai", version = "0.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "nakai", source = "hex", outer_checksum = "F6FFED9EF4B0E14C7A09B2FB87B42D3A93EFE024FD0299C11F041E92321163A6" }, { name = "nanoworker", version = "0.0.2", build_tools = ["gleam"], requirements = ["gleam_javascript", "gleam_stdlib"], otp_app = "nanoworker", source = "hex", outer_checksum = "78508877B313F01596CD2ABE2F3A866DC3C7AADE82DAFBB45396F8ED89EC2A14" }, { name = "nibble", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "nibble", source = "hex", outer_checksum = "2D9045E2CB12421783745113FD74DAC593EE3C2CCB74EA56E981ACEC0D442C20" }, { name = "nx", version = "0.7.1", build_tools = ["mix"], requirements = ["complex", "telemetry"], otp_app = "nx", source = "hex", outer_checksum = "E3DDD6A3F2A9BAC79C67B3933368C25BB5EC814A883FC68ABA8FD8A236751777" }, { name = "opentelemetry_api", version = "1.3.0", build_tools = ["rebar3", "mix"], requirements = ["opentelemetry_semantic_conventions"], otp_app = "opentelemetry_api", source = "hex", outer_checksum = "B9E5FF775FD064FA098DBA3C398490B77649A352B40B0B730A6B7DC0BDD68858" }, { name = "opentelemetry_semantic_conventions", version = "0.2.0", build_tools = ["rebar3", "mix"], requirements = [], otp_app = "opentelemetry_semantic_conventions", source = "hex", outer_checksum = "D61FA1F5639EE8668D74B527E6806E0503EFC55A42DB7B5F39939D84C07D6895" }, { name = "outil", version = "0.5.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "outil", source = "hex", outer_checksum = "ED5DD5E3333AF18782E3A30136879BFFAE86293B0E4598FAEF6DBC389186E29C" }, { name = "parse_trans", version = "3.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "parse_trans", source = "hex", outer_checksum = "620A406CE75DADA827B82E453C19CF06776BE266F5A67CFF34E1EF2CBB60E49A" }, { name = "pears", version = "0.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "pears", source = "hex", outer_checksum = "F823EDF5C7F8606A0B7764C071B6EE8515FC341D6FC69902E81120633DF7E983" }, { name = "pg_types", version = "0.4.0", build_tools = ["rebar3"], requirements = [], otp_app = "pg_types", source = "hex", outer_checksum = "B02EFA785CAECECF9702C681C80A9CA12A39F9161A846CE17B01FB20AEEED7EB" }, { name = "pgo", version = "0.14.0", build_tools = ["rebar3"], requirements = ["backoff", "opentelemetry_api", "pg_types"], otp_app = "pgo", source = "hex", outer_checksum = "71016C22599936E042DC0012EE4589D24C71427D266292F775EBF201D97DF9C9" }, { name = "phony", version = "1.1.6", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "phony", source = "hex", outer_checksum = "64836B3DC384EECB8D117E6AB01A888ABEE65EBB33998B660A7238C41EB7AC66" }, { name = "plinth", version = "0.1.13", build_tools = ["gleam"], requirements = ["gleam_javascript", "gleam_json", "gleam_stdlib"], otp_app = "plinth", source = "hex", outer_checksum = "F4E20F8739D4D704BB69C8336B5E178D346EB69775BF263DC15B2A77B113A63C" }, { name = "prng", version = "3.0.2", build_tools = ["gleam"], requirements = ["gleam_bitwise", "gleam_stdlib"], otp_app = "prng", source = "hex", outer_checksum = "C61B103F9AF5031ADAA35187CCE7130845EF5088D88FD084E5995D4FBEC9D745" }, { name = "process_waiter", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "process_waiter", source = "hex", outer_checksum = "51D27A90BA736449D5447673FF295348D23462F9D9D4BD3E6794F507D65463D2" }, { name = "puddle", version = "0.5.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "puddle", source = "hex", outer_checksum = "1D199F61CAB692DA84CE8153C9351DC21C68C62BCE75782AFAAD5EE780144806" }, { name = "punycode", version = "0.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "punycode", source = "hex", outer_checksum = "BBEC025DE579CDD4F69578290E71DBC50B6CDAC17F8D1A1732EBA7E027526F60" }, { name = "radish", version = "0.13.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib", "mug"], otp_app = "radish", source = "hex", outer_checksum = "AEB090E74EE001D285CBEC474F91F201738363E167353B26D723B94AF65B6C1E" }, { name = "ranger", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "ranger", source = "hex", outer_checksum = "1566C272B1D141B3BBA38B25CB761EF56E312E79EC0E2DFD4D3C19FB0CC1F98C" }, { name = "rank", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "rank", source = "hex", outer_checksum = "5660E361F0E49CBB714CC57CC4C89C63415D8986F05B2DA0C719D5642FAD91C9" }, { name = "reactive_signal", version = "0.0.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "reactive_signal", source = "hex", outer_checksum = "735335DABE0513637641ACC425C2EAEE0EBDB7F1E38E6095F2AF20A3F835518E" }, { name = "repeatedly", version = "2.1.1", build_tools = ["gleam"], requirements = [], otp_app = "repeatedly", source = "hex", outer_checksum = "38808C3EC382B0CD981336D5879C24ECB37FCB9C1D1BD128F7A80B0F74404D79" }, { name = "simplifile", version = "1.5.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "C44DB387524F90DC42142699C78C850003289D32C7C99C7D32873792A299CDF7" }, { name = "snag", version = "0.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "snag", source = "hex", outer_checksum = "54D32E16E33655346AA3E66CBA7E191DE0A8793D2C05284E3EFB90AD2CE92BCC" }, { name = "spinner", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_erlang", "gleam_stdlib", "glearray", "repeatedly"], otp_app = "spinner", source = "hex", outer_checksum = "200BA3D4A04D468898E63C0D316E23F526E02514BC46454091975CB5BAE41E8F" }, { name = "ssl_verify_fun", version = "1.1.7", build_tools = ["mix", "rebar3", "make"], requirements = [], otp_app = "ssl_verify_fun", source = "hex", outer_checksum = "FE4C190E8F37401D30167C8C405EDA19469F34577987C76DDE613E838BBC67F8" }, { name = "telemetry", version = "1.2.1", build_tools = ["rebar3"], requirements = [], otp_app = "telemetry", source = "hex", outer_checksum = "DAD9CE9D8EFFC621708F99EAC538EF1CBE05D6A874DD741DE2E689C47FEAFED5" }, { name = "testbldr", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib", "simplifile"], otp_app = "testbldr", source = "hex", outer_checksum = "D4B4EE780E522E2C41386DA454B748971FC59CA8F710F138466E505FBFF01C86" }, { name = "thoas", version = "0.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "4918D50026C073C4AB1388437132C77A6F6F7C8AC43C60C13758CC0ADCE2134E" }, { name = "tom", version = "0.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "tom", source = "hex", outer_checksum = "0831C73E45405A2153091226BF98FB485ED16376988602CC01A5FD086B82D577" }, { name = "trie_again", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "trie_again", source = "hex", outer_checksum = "5B19176F52B1BD98831B57FDC97BD1F88C8A403D6D8C63471407E78598E27184" }, { name = "typed_headers", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "typed_headers", source = "hex", outer_checksum = "352DE5BE502DF1B33F8B49BEB07A01295222A6A99311F9C3E09C1F071038B07C" }, { name = "unicode_util_compat", version = "0.7.0", build_tools = ["rebar3"], requirements = [], otp_app = "unicode_util_compat", source = "hex", outer_checksum = "25EEE6D67DF61960CF6A794239566599B09E17E668D3700247BC498638152521" }, { name = "wimp", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_json", "gleam_stdlib"], otp_app = "wimp", source = "hex", outer_checksum = "2C4C33992D503F51F2787103FEF345B3D5C097B566146B587CB370AAAF48DD84" }, { name = "wisp", version = "0.14.0", build_tools = ["gleam"], requirements = ["exception", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_json", "gleam_stdlib", "logging", "marceau", "mist", "simplifile"], otp_app = "wisp", source = "hex", outer_checksum = "9F5453AF1F9275E6F8707BC815D6A6A9DF41551921B16FBDBA52883773BAE684" }, { name = "xmleam", version = "1.1.4", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "xmleam", source = "hex", outer_checksum = "A747975444CE3343B6728AB217A1C44888A38060DFA41CA0C0B41D3E06417981" }, { name = "zeptomail", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_json", "gleam_stdlib"], otp_app = "zeptomail", source = "hex", outer_checksum = "D41269BE9F0E4BC52598E54E5AD087B386A44E97F49DA3D9C34CF554A5CB8A71" }, ] [requirements] argamak = { version = ">= 1.1.0 and < 2.0.0" } argv = { version = ">= 1.0.2 and < 2.0.0" } aws4_request = { version = ">= 0.1.1 and < 1.0.0" } bigi = { version = ">= 2.1.0 and < 3.0.0" } birdie = { version = ">= 1.1.0 and < 2.0.0" } cgi = { version = ">= 1.0.1 and < 2.0.0" } conversation = { version = ">= 1.4.3 and < 2.0.0" } edit_distance = { version = ">= 2.0.1 and < 3.0.0" } envoy = { version = ">= 1.0.1 and < 2.0.0" } exception = { version = ">= 2.0.0 and < 3.0.0" } exercism_test_runner = { version = ">= 1.7.0 and < 2.0.0" } file_streams = { version = ">= 0.3.1 and < 1.0.0" } filepath = { version = ">= 0.2.0 and < 1.0.0" } gap = { version = ">= 1.1.3 and < 2.0.0" } gen_core_erlang = { version = ">= 0.5.1 and < 1.0.0" } gen_gleam = { version = ">= 0.3.1 and < 1.0.0" } glance = { version = ">= 0.8.2 and < 1.0.0" } glance_printer = { version = ">= 1.1.0 and < 2.0.0" } glatus = { version = ">= 1.0.0 and < 2.0.0" } gleam_community_ansi = { version = ">= 1.4.0 and < 2.0.0" } gleam_community_colour = { version = ">= 1.4.0 and < 2.0.0" } gleam_community_maths = { version = ">= 1.1.0 and < 2.0.0" } gleam_crypto = { version = ">= 1.3.0 and < 2.0.0" } gleam_elli = { version = ">= 2.4.0 and < 3.0.0" } gleam_erlang = { version = ">= 0.25.0 and < 1.0.0" } gleam_fetch = { version = ">= 0.4.0 and < 1.0.0" } gleam_hackney = { version = ">= 1.2.0 and < 2.0.0" } gleam_http = { version = ">= 3.6.0 and < 4.0.0" } gleam_httpc = { version = ">= 2.2.0 and < 3.0.0" } gleam_javascript = { version = ">= 0.8.0 and < 1.0.0" } gleam_json = { version = ">= 0.7.0 and < 1.0.0" } gleam_otp = { version = ">= 0.10.0 and < 1.0.0" } gleam_pgo = { version = ">= 0.6.1 and < 1.0.0" } gleam_sendgrid = { version = ">= 0.2.0 and < 1.0.0" } gleam_stdlib = { version = ">= 0.36.0 and < 1.0.0" } gleamy_bench = { version = ">= 0.4.0 and < 1.0.0" } glearray = { version = ">= 0.2.1 and < 1.0.0" } gleescript = { version = ">= 1.0.0 and < 2.0.0" } gleeunit = { version = ">= 1.1.2 and < 2.0.0" } glen = { version = ">= 2.0.0 and < 3.0.0" } glenvy = { version = ">= 0.5.1 and < 1.0.0" } glesha = { version = ">= 0.1.3 and < 1.0.0" } glexer = { version = ">= 0.7.0 and < 1.0.0" } glisten = { version = ">= 2.0.0 and < 3.0.0" } globe = { version = ">= 0.1.0 and < 1.0.0" } gsv = { version = ">= 1.4.0 and < 2.0.0" } htmb = { version = ">= 2.0.0 and < 3.0.0" } htmgrrrl = { version = ">= 0.3.0 and < 1.0.0" } ids = { version = ">= 0.12.0 and < 1.0.0" } immutable_lru = { version = ">= 1.0.1 and < 2.0.0" } iso_8859 = { version = ">= 2.0.0 and < 3.0.0" } jasper = { version = ">= 1.2.0 and < 2.0.0" } jot = { version = ">= 0.3.1 and < 1.0.0" } justin = { version = ">= 1.0.1 and < 2.0.0" } jwt = { version = ">= 0.1.11 and < 1.0.0" } lustre = { version = ">= 3.1.4 and < 4.0.0" } lustre_http = { version = ">= 0.5.0 and < 1.0.0" } lustre_ui = { version = ">= 0.4.0 and < 1.0.0" } lustre_websocket = { version = ">= 0.7.6 and < 1.0.0" } marceau = { version = ">= 1.1.0 and < 2.0.0" } mineflayer = { version = ">= 0.1.0 and < 1.0.0" } mist = { version = ">= 1.0.0 and < 2.0.0" } morse_code_translator = { version = ">= 2.0.0 and < 3.0.0" } mug = { version = ">= 0.2.0 and < 1.0.0" } nakai = { version = ">= 0.9.0 and < 1.0.0" } nanoworker = { version = ">= 0.0.2 and < 1.0.0" } nibble = { version = ">= 1.1.0 and < 2.0.0" } outil = { version = ">= 0.5.0 and < 1.0.0" } pears = { version = ">= 0.3.0 and < 1.0.0" } phony = { version = ">= 1.1.6 and < 2.0.0" } plinth = { version = ">= 0.1.13 and < 1.0.0" } prng = { version = ">= 3.0.2 and < 4.0.0" } process_waiter = { version = ">= 1.0.0 and < 2.0.0" } puddle = { version = ">= 0.5.0 and < 1.0.0" } punycode = { version = ">= 0.1.1 and < 1.0.0" } radish = { version = ">= 0.13.0 and < 1.0.0" } ranger = { version = ">= 1.2.0 and < 2.0.0" } rank = { version = ">= 1.0.0 and < 2.0.0" } reactive_signal = { version = ">= 0.0.1 and < 1.0.0" } repeatedly = { version = ">= 2.1.1 and < 3.0.0" } simplifile = { version = ">= 1.5.1 and < 2.0.0" } snag = { version = ">= 0.3.0 and < 1.0.0" } spinner = { version = ">= 1.1.0 and < 2.0.0" } testbldr = { version = ">= 1.1.0 and < 2.0.0" } tom = { version = ">= 0.3.0 and < 1.0.0" } typed_headers = { version = ">= 1.1.2 and < 2.0.0" } wimp = { version = ">= 1.1.0 and < 2.0.0" } wisp = { version = ">= 0.14.0 and < 1.0.0" } xmleam = { version = ">= 1.1.4 and < 2.0.0" } zeptomail = { version = ">= 1.0.0 and < 2.0.0" } ================================================ FILE: test-community-packages/src/test_community_packages.gleam ================================================ import gleam/io pub fn main() { io.println("Hello from test_community_packages!") } ================================================ FILE: test-community-packages/test/test_community_packages_test.gleam ================================================ import gleeunit import gleeunit/should pub fn main() { gleeunit.main() } // gleeunit test functions end in `_test` pub fn hello_world_test() { 1 |> should.equal(1) } ================================================ FILE: test-helpers-rs/Cargo.toml ================================================ [package] name = "test-helpers-rs" version = "0.1.0" edition = "2024" license = "Apache-2.0" [dependencies] gleam-core = { path = "../compiler-core" } walkdir = "2" toml.workspace = true im.workspace = true itertools.workspace = true regex.workspace = true camino.workspace = true [dev-dependencies] insta.workspace = true ================================================ FILE: test-helpers-rs/src/lib.rs ================================================ use camino::{Utf8Path, Utf8PathBuf}; use gleam_core::{ io::{Content, FileSystemWriter, memory::InMemoryFileSystem}, version::COMPILER_VERSION, }; use itertools::Itertools; use regex::Regex; use std::{collections::HashMap, fmt::Write, sync::LazyLock}; #[derive(Debug)] pub struct TestCompileOutput { pub files: HashMap, pub warnings: Vec, } impl TestCompileOutput { pub fn as_overview_text(&self) -> String { let mut buffer = String::new(); for (path, content) in self.files.iter().sorted_by(|a, b| a.0.cmp(b.0)) { let normalised_path = if path.as_str().contains("cases") { path.as_str() .split("cases") .skip(1) .collect::() .as_str() .replace('\\', "/") .split('/') .skip(1) .join("/") } else { path.as_str().replace('\\', "/") }; buffer.push_str("//// "); buffer.push_str(&normalised_path); buffer.push('\n'); let extension = path.extension(); match content { _ if extension == Some("cache") => buffer.push_str("<.cache binary>"), Content::Binary(data) => write!(buffer, "<{} byte binary>", data.len()).unwrap(), Content::Text(_) if normalised_path.ends_with("@@main.erl") => { write!(buffer, "").unwrap() } Content::Text(text) => { let format_path = |caps: ®ex::Captures| { caps.get(1) .expect("file path") .as_str() .replace("\\\\", "/") }; let text = FILE_LINE_REGEX.replace_all(text, |caps: ®ex::Captures| { let path = format_path(caps); let line_number = caps.get(2).expect("line number").as_str(); format!("-file(\"{path}\", {line_number}).") }); let text = FILEPATH_MACRO_REGEX .replace_all(text.to_string().as_str(), |caps: ®ex::Captures| { let path = format_path(caps); format!("-define(FILEPATH, \"{path}\").") }) .replace(COMPILER_VERSION, ""); buffer.push_str(&text) } }; buffer.push('\n'); buffer.push('\n'); } for warning in self.warnings.iter().map(|w| w.to_pretty_string()).sorted() { write!(buffer, "//// Warning\n{}", normalise_diagnostic(&warning)).unwrap(); buffer.push('\n'); buffer.push('\n'); } buffer } } pub fn to_in_memory_filesystem(path: &Utf8Path) -> InMemoryFileSystem { let fs = InMemoryFileSystem::new(); let files = walkdir::WalkDir::new(path) .follow_links(true) .into_iter() .filter_map(Result::ok) .filter(|entry| entry.file_type().is_file()) .map(|entry| entry.into_path()); for fullpath in files { let content = std::fs::read(&fullpath).unwrap(); let path = fullpath.strip_prefix(path).unwrap(); fs.write_bytes(Utf8Path::from_path(path).unwrap(), &content) .unwrap(); } fs } static FILE_LINE_REGEX: LazyLock = LazyLock::new(|| Regex::new(r#"-file\("([^"]+)", (\d+)\)\."#).expect("Invalid regex")); static FILEPATH_MACRO_REGEX: LazyLock = LazyLock::new(|| Regex::new(r#"-define\(FILEPATH, "([^"]+)"\)\."#).expect("Invalid regex")); pub fn normalise_diagnostic(text: &str) -> String { // There is an extra ^ on Windows in some error messages' code // snippets. // I've not managed to determine why this is yet (it is especially // tricky without a Windows computer) so for now we just squash them // in these cross-platform tests. Regex::new(r"\^+") .expect("^ sequence regex") .replace_all(text, "^") .replace('\\', "/") } ================================================ FILE: test-output/Cargo.toml ================================================ [package] name = "test-output" version = "1.15.1" authors = ["Louis Pilfold "] edition = "2024" license = "Apache-2.0" [dependencies] gleam-core = { path = "../compiler-core" } gleam-cli = { path = "../compiler-cli" } test-helpers-rs = { path = "../test-helpers-rs" } camino = { workspace = true, features = ["serde1"] } [dev-dependencies] insta.workspace = true ================================================ FILE: test-output/cases/.gitignore ================================================ **/build **/manifest.toml ================================================ FILE: test-output/cases/echo_bitarray/gleam.toml ================================================ name = "echo_bitarray" version = "1.0.0" ================================================ FILE: test-output/cases/echo_bitarray/src/main.gleam ================================================ pub fn main() { echo <<>> echo <<1, 2, 3>> echo <<1, 2, 3:2>> } ================================================ FILE: test-output/cases/echo_bool/gleam.toml ================================================ name = "echo_bool" version = "1.0.0" ================================================ FILE: test-output/cases/echo_bool/src/main.gleam ================================================ pub fn main() { echo True echo False } ================================================ FILE: test-output/cases/echo_charlist/gleam.toml ================================================ name = "echo_charlist" version = "1.0.0" ================================================ FILE: test-output/cases/echo_charlist/src/main.gleam ================================================ pub fn main() { echo [72, 101, 108, 108, 111, 44, 32, 74, 111, 101, 33] } ================================================ FILE: test-output/cases/echo_circular_reference/gleam.toml ================================================ name = "echo_circular_reference" version = "1.0.0" target = "javascript" ================================================ FILE: test-output/cases/echo_circular_reference/src/main.gleam ================================================ pub fn main() { echo circular_reference() Nil } type Thing @external(javascript, "./main_ffi.mjs", "circular_reference") fn circular_reference() -> Thing ================================================ FILE: test-output/cases/echo_circular_reference/src/main_ffi.mjs ================================================ export function circular_reference() { const x = []; x.push(x); return x; } ================================================ FILE: test-output/cases/echo_custom_type/gleam.toml ================================================ name = "echo_custom_type" version = "1.0.0" ================================================ FILE: test-output/cases/echo_custom_type/src/main.gleam ================================================ pub type Wibble { Wibble(a: Int, b: String) Wobble(a: List(Float)) Woo } pub fn main() { echo Wibble(1, "hello") echo Wobble([1.0, 1.1]) echo Woo } ================================================ FILE: test-output/cases/echo_dict/gleam.toml ================================================ name = "echo_tuple" version = "1.0.0" [dependencies] gleam_stdlib = ">= 0.34.0 and < 2.0.0" [dev_dependencies] ================================================ FILE: test-output/cases/echo_dict/src/main.gleam ================================================ import gleam/dict pub fn main() { echo dict.new() echo dict.from_list([#(1, "hello"), #(2, "world!")]) } ================================================ FILE: test-output/cases/echo_float/gleam.toml ================================================ name = "echo_float" version = "1.0.0" ================================================ FILE: test-output/cases/echo_float/src/main.gleam ================================================ pub fn main() { echo 1.0 echo 2.1 echo 11.11 } ================================================ FILE: test-output/cases/echo_function/gleam.toml ================================================ name = "echo_custom_type" version = "1.0.0" ================================================ FILE: test-output/cases/echo_function/src/main.gleam ================================================ pub fn main() { echo fn(n) { n + 1 } echo private } fn private() { 1 } ================================================ FILE: test-output/cases/echo_importing_module_named_inspect/gleam.toml ================================================ name = "echo_importing_module_named_inspect" version = "1.0.0" ================================================ FILE: test-output/cases/echo_importing_module_named_inspect/src/inspect.gleam ================================================ pub const x = Nil ================================================ FILE: test-output/cases/echo_importing_module_named_inspect/src/main.gleam ================================================ import inspect pub fn main() { echo inspect.x } ================================================ FILE: test-output/cases/echo_int/gleam.toml ================================================ name = "echo_int" version = "1.0.0" ================================================ FILE: test-output/cases/echo_int/src/main.gleam ================================================ pub fn main() { echo 1 echo 2 echo 100_000 } ================================================ FILE: test-output/cases/echo_list/gleam.toml ================================================ name = "echo_list" version = "1.0.0" ================================================ FILE: test-output/cases/echo_list/src/main.gleam ================================================ pub fn main() { echo [] echo [1, 2, 3] } ================================================ FILE: test-output/cases/echo_nil/gleam.toml ================================================ name = "echo_nil" version = "1.0.0" ================================================ FILE: test-output/cases/echo_nil/src/main.gleam ================================================ pub fn main() { echo Nil } ================================================ FILE: test-output/cases/echo_non_record_atom_tag/gleam.toml ================================================ name = "echo_non_record_atom_tag" version = "1.0.0" ================================================ FILE: test-output/cases/echo_non_record_atom_tag/src/main.gleam ================================================ pub fn main() { echo #(to_atom("UP"), 1, 2) echo #(to_atom("down"), 12.34) echo #(to_atom("Both"), "ok") } pub type Atom @external(erlang, "erlang", "binary_to_atom") pub fn to_atom(string: String) -> Atom ================================================ FILE: test-output/cases/echo_singleton/gleam.toml ================================================ name = "echo_circular_reference" version = "1.0.0" target = "javascript" ================================================ FILE: test-output/cases/echo_singleton/src/main.gleam ================================================ import thing pub fn main() { echo #(1, singleton(), singleton()) Nil } @external(javascript, "./main_ffi.mjs", "singleton") fn singleton() -> thing.Thing { thing.Thing } ================================================ FILE: test-output/cases/echo_singleton/src/main_ffi.mjs ================================================ import { Thing } from "./thing.mjs"; const it = new Thing(); export function singleton() { return it; } ================================================ FILE: test-output/cases/echo_singleton/src/thing.gleam ================================================ pub type Thing { Thing } ================================================ FILE: test-output/cases/echo_string/gleam.toml ================================================ name = "echo_string" version = "1.0.0" ================================================ FILE: test-output/cases/echo_string/src/main.gleam ================================================ pub fn main() { echo "hello world" echo "multiline string" echo "string with\t\r\nescaped chars" } ================================================ FILE: test-output/cases/echo_tuple/gleam.toml ================================================ name = "echo_tuple" version = "1.0.0" ================================================ FILE: test-output/cases/echo_tuple/src/main.gleam ================================================ pub fn main() { echo #() echo #(True, 1, "hello") } ================================================ FILE: test-output/cases/echo_with_message/gleam.toml ================================================ name = "echo_bitarray" version = "1.0.0" ================================================ FILE: test-output/cases/echo_with_message/src/main.gleam ================================================ pub fn main() { let message = "message" echo 1 as "message1" 1 |> echo as { message <> "2" } |> fn(n) { n + 1 } |> echo as "message3" } ================================================ FILE: test-output/cases/linked_process_exit/gleam.toml ================================================ name = "linked_process_exit" version = "1.0.0" ================================================ FILE: test-output/cases/linked_process_exit/src/main.gleam ================================================ @external(erlang, "main_ffi", "spawn_and_exit") fn spawn_and_exit() -> Nil pub fn main() { spawn_and_exit() } ================================================ FILE: test-output/cases/linked_process_exit/src/main_ffi.erl ================================================ -module(main_ffi). -export([spawn_and_exit/0]). %% Spawn a linked process that exits with a non-standard reason. %% This tests that the @@main.erl template handles exit reasons %% that aren't {Reason, StackTrace} tuples (issue #4766). spawn_and_exit() -> spawn_link(fun() -> exit({shutdown, <<"test reason">>}) end), %% Wait briefly to ensure the linked process exits and kills us receive after 100 -> nil end. ================================================ FILE: test-output/src/lib.rs ================================================ #[cfg(test)] mod tests; ================================================ FILE: test-output/src/tests/echo.rs ================================================ use std::{io::Read, process::Stdio}; use camino::{Utf8Path, Utf8PathBuf}; use gleam_core::{ build::{Runtime, Target}, io::Command, paths::ProjectPaths, }; use gleam_cli::{ fs, run::{self, Which}, }; fn run_and_produce_pretty_snapshot( target: Option, runtime: Option, project_directory: Utf8PathBuf, ) -> String { let project_root = fs::get_project_root(project_directory).expect("project root"); let paths = ProjectPaths::new(project_root); let output = run_and_capture_output(&paths, "main", target, runtime) // Since the echo output's contains a path we will replace the `\` with a `/` // so that the snapshot doesn't fail on Windows in CI. .replace("src\\", "src/"); let main_module_content = fs::read(paths.src_directory().join("main.gleam")).expect("read main module"); format!( "--- main.gleam ---------------------- {main_module_content} --- gleam run output ---------------- {output} " ) } fn run_and_capture_output( paths: &ProjectPaths, main_module: &str, target: Option, runtime: Option, ) -> String { fs::delete_directory(&paths.build_directory()).expect("delete build directory content"); let Command { program, args, env, cwd: _, stdio: _, } = run::setup( paths, vec![], target, runtime, Some(main_module.into()), Which::Src, true, ) .expect("run setup"); let mut process = std::process::Command::new(&program) .args(args) .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::piped()) .envs(env.iter().map(|pair| (&pair.0, &pair.1))) .current_dir(paths.root()) .spawn() .unwrap_or_else(|e| panic!("Failed to spawn process '{}': {}", &program, &e)); let mut stderr = process.stderr.take().expect("take stderr"); let mut output = String::new(); let _ = stderr.read_to_string(&mut output).expect("read stderr"); let _ = process.wait().expect("run with no errors"); output } macro_rules! assert_echo { ($project_name: expr) => { let snapshot_name = snapshot_name(None, None, $project_name); insta::allow_duplicates! { assert_echo!(&snapshot_name, Some(Target::Erlang), None, $project_name); assert_echo!(&snapshot_name, Some(Target::JavaScript), Some(Runtime::Bun), $project_name); assert_echo!(&snapshot_name, Some(Target::JavaScript), Some(Runtime::Deno), $project_name); assert_echo!(&snapshot_name, Some(Target::JavaScript), Some(Runtime::NodeJs), $project_name); } }; ($target: expr, $project_name: expr) => { let snapshot_name = snapshot_name(Some($target), None, $project_name); match $target { Target::JavaScript => insta::allow_duplicates! { assert_echo!(&snapshot_name, Some($target), Some(Runtime::Bun), $project_name); assert_echo!(&snapshot_name, Some($target), Some(Runtime::Deno), $project_name); assert_echo!(&snapshot_name, Some($target), Some(Runtime::NodeJs), $project_name); }, Target::Erlang => { assert_echo!(&snapshot_name, Some($target), None, $project_name); } } }; ($snapshot_name: expr, $target: expr, $runtime: expr, $project_name: expr) => { let path = fs::canonicalise(&Utf8Path::new("../test-output/cases").join($project_name)) .expect("canonicalise path"); let output = run_and_produce_pretty_snapshot($target, $runtime, path); insta::assert_snapshot!($snapshot_name.to_string(), output); }; } fn snapshot_name(target: Option, runtime: Option, suffix: &str) -> String { let show_target = |target: Target| match target { Target::Erlang => "erlang", Target::JavaScript => "javascript", }; let show_runtime = |runtime: Runtime| match runtime { Runtime::NodeJs => "nodejs", Runtime::Deno => "deno", Runtime::Bun => "bun", }; let prefix = match (target, runtime) { (None, None) => "".into(), (None, Some(runtime)) => format!("{}-", show_runtime(runtime)), (Some(target), None) => format!("{}-", show_target(target)), (Some(target), Some(runtime)) => { format!("{}-{}-", show_target(target), show_runtime(runtime)) } }; format!("{prefix}{suffix}") } #[test] fn echo_bitarray() { assert_echo!(Target::JavaScript, "echo_bitarray"); assert_echo!(Target::Erlang, "echo_bitarray"); } #[test] fn echo_bool() { assert_echo!("echo_bool"); } #[test] fn echo_charlist() { assert_echo!("echo_charlist"); } #[test] fn echo_custom_type() { assert_echo!(Target::Erlang, "echo_custom_type"); assert_echo!(Target::JavaScript, "echo_custom_type"); } #[test] fn echo_dict() { assert_echo!("echo_dict"); } #[test] fn echo_float() { assert_echo!(Target::Erlang, "echo_float"); assert_echo!(Target::JavaScript, "echo_float"); } #[test] fn echo_function() { assert_echo!("echo_function"); } #[test] fn echo_importing_module_named_inspect() { assert_echo!("echo_importing_module_named_inspect"); } #[test] fn echo_int() { assert_echo!("echo_int"); } #[test] fn echo_list() { assert_echo!("echo_list"); } #[test] fn echo_nil() { assert_echo!("echo_nil"); } #[test] fn echo_string() { assert_echo!("echo_string"); } #[test] fn echo_tuple() { assert_echo!("echo_tuple"); } #[test] fn echo_non_record_atom_tag() { assert_echo!(Target::Erlang, "echo_non_record_atom_tag"); } #[test] fn echo_circular_reference() { assert_echo!(Target::JavaScript, "echo_circular_reference"); } #[test] fn echo_singleton() { assert_echo!("echo_singleton"); } #[test] fn echo_with_message() { assert_echo!("echo_with_message"); } #[test] fn linked_process_exit() { assert_echo!(Target::Erlang, "linked_process_exit"); } ================================================ FILE: test-output/src/tests/snapshots/test_output__tests__echo__echo_bool.snap ================================================ --- source: test-output/src/tests/echo.rs expression: output --- --- main.gleam ---------------------- pub fn main() { echo True echo False } --- gleam run output ---------------- src/main.gleam:2 True src/main.gleam:3 False ================================================ FILE: test-output/src/tests/snapshots/test_output__tests__echo__echo_charlist.snap ================================================ --- source: test-output/src/tests/echo.rs assertion_line: 139 expression: output snapshot_kind: text --- --- main.gleam ---------------------- pub fn main() { echo [72, 101, 108, 108, 111, 44, 32, 74, 111, 101, 33] } --- gleam run output ---------------- src/main.gleam:2 charlist.from_string("Hello, Joe!") ================================================ FILE: test-output/src/tests/snapshots/test_output__tests__echo__echo_dict.snap ================================================ --- source: test-output/src/tests/echo.rs expression: output --- --- main.gleam ---------------------- import gleam/dict pub fn main() { echo dict.new() echo dict.from_list([#(1, "hello"), #(2, "world!")]) } --- gleam run output ---------------- src/main.gleam:4 dict.from_list([]) src/main.gleam:5 dict.from_list([#(1, "hello"), #(2, "world!")]) ================================================ FILE: test-output/src/tests/snapshots/test_output__tests__echo__echo_function.snap ================================================ --- source: test-output/src/tests/echo.rs expression: output --- --- main.gleam ---------------------- pub fn main() { echo fn(n) { n + 1 } echo private } fn private() { 1 } --- gleam run output ---------------- src/main.gleam:2 //fn(a) { ... } src/main.gleam:3 //fn() { ... } ================================================ FILE: test-output/src/tests/snapshots/test_output__tests__echo__echo_importing_module_named_inspect.snap ================================================ --- source: test-output/src/tests/echo.rs expression: output --- --- main.gleam ---------------------- import inspect pub fn main() { echo inspect.x } --- gleam run output ---------------- src/main.gleam:4 Nil ================================================ FILE: test-output/src/tests/snapshots/test_output__tests__echo__echo_int.snap ================================================ --- source: test-output/src/tests/echo.rs expression: output --- --- main.gleam ---------------------- pub fn main() { echo 1 echo 2 echo 100_000 } --- gleam run output ---------------- src/main.gleam:2 1 src/main.gleam:3 2 src/main.gleam:4 100000 ================================================ FILE: test-output/src/tests/snapshots/test_output__tests__echo__echo_list.snap ================================================ --- source: test-output/src/tests/echo.rs expression: output --- --- main.gleam ---------------------- pub fn main() { echo [] echo [1, 2, 3] } --- gleam run output ---------------- src/main.gleam:2 [] src/main.gleam:3 [1, 2, 3] ================================================ FILE: test-output/src/tests/snapshots/test_output__tests__echo__echo_nil.snap ================================================ --- source: test-output/src/tests/echo.rs expression: output --- --- main.gleam ---------------------- pub fn main() { echo Nil } --- gleam run output ---------------- src/main.gleam:2 Nil ================================================ FILE: test-output/src/tests/snapshots/test_output__tests__echo__echo_singleton.snap ================================================ --- source: test-output/src/tests/echo.rs assertion_line: 206 expression: output snapshot_kind: text --- --- main.gleam ---------------------- import thing pub fn main() { echo #(1, singleton(), singleton()) Nil } @external(javascript, "./main_ffi.mjs", "singleton") fn singleton() -> thing.Thing { thing.Thing } --- gleam run output ---------------- src/main.gleam:4 #(1, Thing, Thing) ================================================ FILE: test-output/src/tests/snapshots/test_output__tests__echo__echo_string.snap ================================================ --- source: test-output/src/tests/echo.rs expression: output --- --- main.gleam ---------------------- pub fn main() { echo "hello world" echo "multiline string" echo "string with\t\r\nescaped chars" } --- gleam run output ---------------- src/main.gleam:2 "hello world" src/main.gleam:3 "multiline\nstring" src/main.gleam:5 "string with\t\r\nescaped chars" ================================================ FILE: test-output/src/tests/snapshots/test_output__tests__echo__echo_tuple.snap ================================================ --- source: test-output/src/tests/echo.rs expression: output --- --- main.gleam ---------------------- pub fn main() { echo #() echo #(True, 1, "hello") } --- gleam run output ---------------- src/main.gleam:2 #() src/main.gleam:3 #(True, 1, "hello") ================================================ FILE: test-output/src/tests/snapshots/test_output__tests__echo__echo_with_message.snap ================================================ --- source: test-output/src/tests/echo.rs expression: output --- --- main.gleam ---------------------- pub fn main() { let message = "message" echo 1 as "message1" 1 |> echo as { message <> "2" } |> fn(n) { n + 1 } |> echo as "message3" } --- gleam run output ---------------- src/main.gleam:4 message1 1 src/main.gleam:7 message2 1 src/main.gleam:9 message3 2 ================================================ FILE: test-output/src/tests/snapshots/test_output__tests__echo__erlang-echo_bitarray.snap ================================================ --- source: test-output/src/tests/echo.rs expression: output --- --- main.gleam ---------------------- pub fn main() { echo <<>> echo <<1, 2, 3>> echo <<1, 2, 3:2>> } --- gleam run output ---------------- src/main.gleam:2 "" src/main.gleam:3 "\u{0001}\u{0002}\u{0003}" src/main.gleam:4 <<1, 2, 3:size(2)>> ================================================ FILE: test-output/src/tests/snapshots/test_output__tests__echo__erlang-echo_custom_type.snap ================================================ --- source: test-output/src/tests/echo.rs expression: output --- --- main.gleam ---------------------- pub type Wibble { Wibble(a: Int, b: String) Wobble(a: List(Float)) Woo } pub fn main() { echo Wibble(1, "hello") echo Wobble([1.0, 1.1]) echo Woo } --- gleam run output ---------------- src/main.gleam:8 Wibble(1, "hello") src/main.gleam:9 Wobble([1.0, 1.1]) src/main.gleam:10 Woo ================================================ FILE: test-output/src/tests/snapshots/test_output__tests__echo__erlang-echo_float.snap ================================================ --- source: test-output/src/tests/echo.rs expression: output --- --- main.gleam ---------------------- pub fn main() { echo 1.0 echo 2.1 echo 11.11 } --- gleam run output ---------------- src/main.gleam:2 1.0 src/main.gleam:3 2.1 src/main.gleam:4 11.11 ================================================ FILE: test-output/src/tests/snapshots/test_output__tests__echo__erlang-echo_non_record_atom_tag.snap ================================================ --- source: test-output/src/tests/echo.rs expression: output --- --- main.gleam ---------------------- pub fn main() { echo #(to_atom("UP"), 1, 2) echo #(to_atom("down"), 12.34) echo #(to_atom("Both"), "ok") } pub type Atom @external(erlang, "erlang", "binary_to_atom") pub fn to_atom(string: String) -> Atom --- gleam run output ---------------- src/main.gleam:2 #(atom.create("UP"), 1, 2) src/main.gleam:3 Down(12.34) src/main.gleam:4 #(atom.create("Both"), "ok") ================================================ FILE: test-output/src/tests/snapshots/test_output__tests__echo__erlang-linked_process_exit.snap ================================================ --- source: test-output/src/tests/echo.rs expression: output --- --- main.gleam ---------------------- @external(erlang, "main_ffi", "spawn_and_exit") fn spawn_and_exit() -> Nil pub fn main() { spawn_and_exit() } --- gleam run output ---------------- runtime error: Erlang exit An error occurred outside of Gleam. exit reason: {shutdown,<<"test reason">>} ================================================ FILE: test-output/src/tests/snapshots/test_output__tests__echo__javascript-echo_bitarray.snap ================================================ --- source: test-output/src/tests/echo.rs expression: output --- --- main.gleam ---------------------- pub fn main() { echo <<>> echo <<1, 2, 3>> echo <<1, 2, 3:2>> } --- gleam run output ---------------- src/main.gleam:2 <<>> src/main.gleam:3 <<1, 2, 3>> src/main.gleam:4 <<1, 2, 3:size(2)>> ================================================ FILE: test-output/src/tests/snapshots/test_output__tests__echo__javascript-echo_circular_reference.snap ================================================ --- source: test-output/src/tests/echo.rs assertion_line: 201 expression: output snapshot_kind: text --- --- main.gleam ---------------------- pub fn main() { echo circular_reference() Nil } type Thing @external(javascript, "./main_ffi.mjs", "circular_reference") fn circular_reference() -> Thing --- gleam run output ---------------- src/main.gleam:2 #(//js(circular reference)) ================================================ FILE: test-output/src/tests/snapshots/test_output__tests__echo__javascript-echo_custom_type.snap ================================================ --- source: test-output/src/tests/echo.rs expression: output --- --- main.gleam ---------------------- pub type Wibble { Wibble(a: Int, b: String) Wobble(a: List(Float)) Woo } pub fn main() { echo Wibble(1, "hello") echo Wobble([1.0, 1.1]) echo Woo } --- gleam run output ---------------- src/main.gleam:8 Wibble(a: 1, b: "hello") src/main.gleam:9 Wobble(a: [1, 1.1]) src/main.gleam:10 Woo ================================================ FILE: test-output/src/tests/snapshots/test_output__tests__echo__javascript-echo_float.snap ================================================ --- source: test-output/src/tests/echo.rs expression: output --- --- main.gleam ---------------------- pub fn main() { echo 1.0 echo 2.1 echo 11.11 } --- gleam run output ---------------- src/main.gleam:2 1 src/main.gleam:3 2.1 src/main.gleam:4 11.11 ================================================ FILE: test-output/src/tests.rs ================================================ #[cfg(test)] mod echo; ================================================ FILE: test-package-compiler/.gitignore ================================================ cases/*/build cases/*/manifest.toml ================================================ FILE: test-package-compiler/Cargo.toml ================================================ [package] name = "test-package-compiler" version = "1.15.1" authors = ["Louis Pilfold "] edition = "2024" license = "Apache-2.0" [dependencies] gleam-core = { path = "../compiler-core" } test-helpers-rs = { path = "../test-helpers-rs" } toml.workspace = true im.workspace = true itertools.workspace = true regex.workspace = true camino.workspace = true [dev-dependencies] insta.workspace = true ================================================ FILE: test-package-compiler/build.rs ================================================ use std::path::PathBuf; pub fn main() { println!("cargo:rerun-if-changed=cases"); let mut module = "//! This file is generated by build.rs //! Do not edit it directly, instead add new test cases to ./cases " .to_string(); let cases = PathBuf::from("./cases"); let mut names: Vec<_> = std::fs::read_dir(&cases) .unwrap() .map(|entry| entry.unwrap().file_name().into_string().unwrap()) .collect(); names.sort(); for name in names { let path = cases.join(&name); let path = path.to_str().unwrap().replace('\\', "/"); module.push_str(&format!( r#" #[rustfmt::skip] #[test] fn {name}() {{ let output = crate::prepare("{path}"); insta::assert_snapshot!( "{name}", output, "{path}", ); }} "# )); } let out = PathBuf::from("./src/generated_tests.rs"); std::fs::write(out, module).unwrap(); } ================================================ FILE: test-package-compiler/cases/alias_unqualified_import/gleam.toml ================================================ # https://github.com/gleam-lang/gleam/issues/303 name = "importy" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/alias_unqualified_import/src/one.gleam ================================================ // https://github.com/gleam-lang/gleam/issues/303 pub fn id(x) { x } pub type Empty { Empty } ================================================ FILE: test-package-compiler/cases/alias_unqualified_import/src/two.gleam ================================================ // https://github.com/gleam-lang/gleam/issues/303 import one.{Empty as E, id as i} pub fn make() { i(E) } ================================================ FILE: test-package-compiler/cases/dev_importing_test/dev/one.gleam ================================================ // This import will fail because the `two` module is in the `test` directory and // as such isn't permitted to be imported into the `dev` directory. import two ================================================ FILE: test-package-compiler/cases/dev_importing_test/gleam.toml ================================================ name = "importy" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/dev_importing_test/test/two.gleam ================================================ ================================================ FILE: test-package-compiler/cases/duplicate_module/gleam.toml ================================================ name = "duplicate_module_dev" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/duplicate_module/src/main.gleam ================================================ // There is another module with the same name in the `test` directory. pub fn main() { "Hello, Joe!" } ================================================ FILE: test-package-compiler/cases/duplicate_module/test/main.gleam ================================================ // There is another module with the same name in the `src` directory. pub fn main() { "Hello, Joe!" } ================================================ FILE: test-package-compiler/cases/duplicate_module_dev/dev/main.gleam ================================================ // There is another module with the same name in the `src` directory. pub fn main() { "Hello, Joe!" } ================================================ FILE: test-package-compiler/cases/duplicate_module_dev/gleam.toml ================================================ name = "importy" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/duplicate_module_dev/src/main.gleam ================================================ // There is another module with the same name in the `dev` directory. pub fn main() { "Hello, Joe!" } ================================================ FILE: test-package-compiler/cases/duplicate_module_test_dev/dev/main.gleam ================================================ // There is another module with the same name in the `test` directory. pub fn main() { "Hello, Joe!" } ================================================ FILE: test-package-compiler/cases/duplicate_module_test_dev/gleam.toml ================================================ name = "duplicate_module" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/duplicate_module_test_dev/test/main.gleam ================================================ // There is another module with the same name in the `dev` directory. pub fn main() { "Hello, Joe!" } ================================================ FILE: test-package-compiler/cases/empty_module_warning/gleam.toml ================================================ name = "empty_module_warning" version = "1.0.0" description = "Test package with empty modules" licences = ["Apache-2.0"] [dependencies] gleam_stdlib = ">= 0.44.0 and < 2.0.0" ================================================ FILE: test-package-compiler/cases/empty_module_warning/src/empty.gleam ================================================ // This is an empty module ================================================ FILE: test-package-compiler/cases/empty_module_warning/src/internal.gleam ================================================ @internal pub fn private_function() -> Nil { Nil } ================================================ FILE: test-package-compiler/cases/empty_module_warning/src/private.gleam ================================================ fn private_function() -> Nil { Nil } ================================================ FILE: test-package-compiler/cases/empty_module_warning/src/public.gleam ================================================ pub fn main() { "This module has public definitions" } ================================================ FILE: test-package-compiler/cases/erlang_app_generation/gleam.toml ================================================ # This config file has a bunch of properties that will be entered into the # Erlang .app file name = "my_erlang_application" # <- version = "0.1.0" # <- description = "It's very cool" # <- target = "erlang" [erlang] extra_applications = ["inets", "ssl"] # <- application_start_module = "my_erlang_application_sup" # <- [dependencies] # <- gleam_stdlib = "~> 1337.0" gleam_otp = "~> 1337.0" [dev_dependencies] # <- midas = "~> 1337.0" simple_json = "~> 1337.0" ================================================ FILE: test-package-compiler/cases/erlang_app_generation/src/main.gleam ================================================ ================================================ FILE: test-package-compiler/cases/erlang_app_generation_with_argument/gleam.toml ================================================ # This config file has a bunch of properties that will be entered into the # Erlang .app file name = "my_erlang_application" # <- version = "0.1.0" # <- description = "It's very cool" # <- target = "erlang" [erlang] extra_applications = ["inets", "ssl"] # <- application_start_module = "my_erlang_application_sup" # <- application_start_argument = "[1, two]" # <- [dependencies] # <- gleam_stdlib = "~> 1337.0" gleam_otp = "~> 1337.0" [dev_dependencies] # <- midas = "~> 1337.0" simple_json = "~> 1337.0" ================================================ FILE: test-package-compiler/cases/erlang_app_generation_with_argument/src/main.gleam ================================================ ================================================ FILE: test-package-compiler/cases/erlang_bug_752/gleam.toml ================================================ # https://github.com/gleam-lang/gleam/issues/752 name = "importy" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/erlang_bug_752/src/one.gleam ================================================ pub type One(a) { One(a) } ================================================ FILE: test-package-compiler/cases/erlang_bug_752/src/two.gleam ================================================ import one.{type One} pub type Two(b) { Two(thing: One(Int)) } ================================================ FILE: test-package-compiler/cases/erlang_empty/gleam.toml ================================================ name = "hello_joe" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/erlang_empty/src/empty.gleam ================================================ ================================================ FILE: test-package-compiler/cases/erlang_escape_names/gleam.toml ================================================ # https://github.com/gleam-lang/gleam/issues/340 name = "importy" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/erlang_escape_names/src/one.gleam ================================================ // https://github.com/gleam-lang/gleam/issues/340 pub fn receive(x) { x } ================================================ FILE: test-package-compiler/cases/erlang_escape_names/src/two.gleam ================================================ // https://github.com/gleam-lang/gleam/issues/340 import one.{receive} pub fn qualified_call() { one.receive(1) } pub fn qualified_value() { one.receive } pub fn unqualified_call() { receive(1) } pub fn unqualified_value() { receive } ================================================ FILE: test-package-compiler/cases/erlang_import/gleam.toml ================================================ name = "importy" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/erlang_import/src/one.gleam ================================================ import two pub fn unbox(x) { let two.Box(i) = x i } ================================================ FILE: test-package-compiler/cases/erlang_import/src/two.gleam ================================================ pub type Box { Box(Int) } ================================================ FILE: test-package-compiler/cases/erlang_import_shadowing_prelude/gleam.toml ================================================ # https://github.com/gleam-lang/gleam/issues/1495 name = "importy" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/erlang_import_shadowing_prelude/src/one.gleam ================================================ pub type Error { // This constructor shadows the Error constructor in the prelude. Error } ================================================ FILE: test-package-compiler/cases/erlang_import_shadowing_prelude/src/two.gleam ================================================ // This import shadows the Error type in the prelude. import one.{Error} pub fn main() { Error } ================================================ FILE: test-package-compiler/cases/erlang_nested/gleam.toml ================================================ name = "importy" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/erlang_nested/src/one/two.gleam ================================================ pub fn main() { "Hi there" } ================================================ FILE: test-package-compiler/cases/erlang_nested_qualified_constant/gleam.toml ================================================ # https://github.com/gleam-lang/gleam/issues/922#issuecomment-803272624 name = "importy" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/erlang_nested_qualified_constant/src/one/two.gleam ================================================ pub type A { A } ================================================ FILE: test-package-compiler/cases/erlang_nested_qualified_constant/src/two.gleam ================================================ // This module uses imports in a way which previously failed to compile due a // compiler bug. // // https://github.com/gleam-lang/gleam/issues/922#issuecomment-803272624 // import one/two pub const x = two.A ================================================ FILE: test-package-compiler/cases/hello_joe/gleam.toml ================================================ name = "hello_joe" version = "0.1.0" ================================================ FILE: test-package-compiler/cases/hello_joe/src/hello_joe.gleam ================================================ pub fn main() { "Hello, Joe!" } ================================================ FILE: test-package-compiler/cases/import_cycle/gleam.toml ================================================ name = "importy" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/import_cycle/src/one.gleam ================================================ import one ================================================ FILE: test-package-compiler/cases/import_cycle_multi/gleam.toml ================================================ name = "importy" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/import_cycle_multi/src/one.gleam ================================================ import two ================================================ FILE: test-package-compiler/cases/import_cycle_multi/src/three.gleam ================================================ import one ================================================ FILE: test-package-compiler/cases/import_cycle_multi/src/two.gleam ================================================ import three ================================================ FILE: test-package-compiler/cases/import_shadowed_name_warning/gleam.toml ================================================ # This should emit no warnings. # https://github.com/gleam-lang/otp/pull/22 name = "importy" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/import_shadowed_name_warning/src/one.gleam ================================================ // https://github.com/gleam-lang/otp/pull/22 pub type Port ================================================ FILE: test-package-compiler/cases/import_shadowed_name_warning/src/two.gleam ================================================ //// https://github.com/gleam-lang/otp/pull/22 // No warning should be emitted about this imported type. The compiler does not // confuse it for the value constructor defined below. import one.{type Port} type Shadowing { // This value constructor has the same name as the imported type. Port } // Here the type is used. @external(erlang, "wibble", "wobble") pub fn use_type(port: Port) -> Nil ================================================ FILE: test-package-compiler/cases/imported_constants/gleam.toml ================================================ name = "importy" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/imported_constants/src/one.gleam ================================================ pub type A { A } pub type B { B(A, A) } pub type User { User(name: String, score: Int) } ================================================ FILE: test-package-compiler/cases/imported_constants/src/two.gleam ================================================ import one.{A, A as C, B, B as D, User, User as XUser} /// For these statements we use the records in a qualified fashion pub const qualified_const_a = one.A pub fn qualified_fn_a() { qualified_const_a } pub const qualified_const_b = one.B(one.A, one.A) pub fn qualified_fn_b() { qualified_const_b } /// For these statements we use the records in a unqualified fashion pub const unqualified_const_a = A pub fn unqualified_fn_a() { unqualified_const_a } pub const unqualified_const_b = B(A, A) pub fn unqualified_fn_b() { unqualified_const_b } /// For these statements we use the records in a unqualified and also aliased /// fashion pub const aliased_const_a = C pub fn aliased_fn_a() { aliased_const_a } pub const aliased_const_b = D(C, C) pub fn aliased_fn_b() { aliased_const_b } /// For these statements we use the accessors for the record from the other /// module pub fn accessors(user: one.User) { let name = user.name let score = user.score #(name, score) } /// For these statements we use destructure the record pub fn destructure_qualified(user) { let one.User(name: name, score: score) = user #(name, score) } pub fn destructure_unqualified(user) { let User(name: name, score: score) = user #(name, score) } pub fn destructure_aliased(user) { let XUser(name: name, score: score) = user #(name, score) } ================================================ FILE: test-package-compiler/cases/imported_external_fns/gleam.toml ================================================ name = "importy" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/imported_external_fns/src/one.gleam ================================================ @external(erlang, "thing", "new") pub fn thing() -> Nil // https://github.com/gleam-lang/gleam/issues/4507 @external(erlang, "the.thing", "make.new") pub fn escaped_thing() -> Nil ================================================ FILE: test-package-compiler/cases/imported_external_fns/src/three.gleam ================================================ @external(erlang, "thing", "new") pub fn thing() -> Nil // https://github.com/gleam-lang/gleam/issues/4507 @external(erlang, "the.thing", "make.new") pub fn escaped_thing() -> Nil ================================================ FILE: test-package-compiler/cases/imported_external_fns/src/two.gleam ================================================ import one.{ escaped_thing, escaped_thing as xescaped_thing, thing, thing as xthing, } import three as aliased // Assigning a function to constants const const_qualified = one.thing const const_qualified_aliased = aliased.thing const const_unqualified = thing const const_unqualified_aliased = xthing const escaped_const_qualified = one.escaped_thing const escaped_const_qualified_aliased = aliased.escaped_thing const escaped_const_unqualified = escaped_thing const escaped_const_unqualified_aliased = xescaped_thing pub fn the_consts() { let _ = const_qualified let _ = const_qualified_aliased let _ = const_unqualified let _ = const_unqualified_aliased let _ = escaped_const_qualified let _ = escaped_const_qualified_aliased let _ = escaped_const_unqualified let _ = escaped_const_unqualified_aliased const_qualified() const_qualified_aliased() const_unqualified() const_unqualified_aliased() escaped_const_qualified() escaped_const_qualified_aliased() escaped_const_unqualified() escaped_const_unqualified_aliased() } // Referencing in a function pub fn fn_reference_qualified() { one.thing } pub fn fn_reference_qualified_aliased() { aliased.thing } pub fn fn_reference_unqualified() { thing } pub fn fn_reference_unqualified_aliased() { xthing } // Calling the function pub fn fn_call_qualified() { one.thing() } pub fn fn_call_qualified_aliased() { aliased.thing() } pub fn fn_call_unqualified() { thing() } pub fn fn_call_unqualified_aliased() { xthing() } // Referencing a function as an argument pub fn argument_reference_qualified() { x(one.escaped_thing) } pub fn argument_reference_qualified_aliased() { x(aliased.escaped_thing) } pub fn argument_reference_unqualified() { x(escaped_thing) } pub fn argument_reference_unqualified_aliased() { x(xescaped_thing) } fn x(_) { Nil } ================================================ FILE: test-package-compiler/cases/imported_record_constructors/gleam.toml ================================================ name = "importy" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/imported_record_constructors/src/one/one.gleam ================================================ pub type A { A } pub type B { B(A, A) } pub type User { User(name: String, score: Int) } ================================================ FILE: test-package-compiler/cases/imported_record_constructors/src/one/two.gleam ================================================ pub type A { A } pub type B { B(A, A) } pub type User { User(name: String, score: Int) } ================================================ FILE: test-package-compiler/cases/imported_record_constructors/src/two.gleam ================================================ import one/one.{A, A as C, B, B as D, User, User as XUser} import one/two as aliased /// For these statements we use the records in a qualified fashion pub const qualified_const_a = one.A pub fn qualified_fn_a() { qualified_const_a } pub const qualified_const_b = one.B(one.A, one.A) pub fn qualified_fn_b() { qualified_const_b } pub const qualified_aliased_const_a = aliased.A pub fn qualified_aliased_fn_a() { qualified_aliased_const_a } pub const qualified_aliased_const_b = aliased.B(aliased.A, aliased.A) pub fn qualified_aliased_fn_b() { qualified_aliased_const_b } /// For these statements we use the records in a unqualified fashion pub const unqualified_const_a = A pub fn unqualified_fn_a() { unqualified_const_a } pub const unqualified_const_b = B(A, A) pub fn unqualified_fn_b() { unqualified_const_b } /// For these statements we use the records in a unqualified and also aliased /// fashion pub const aliased_const_a = C pub fn aliased_fn_a() { aliased_const_a } pub const aliased_const_b = D(C, C) pub fn aliased_fn_b() { aliased_const_b } /// For these statements we use the accessors for the record from the other /// module pub fn accessors(user: one.User) { let name = user.name let score = user.score #(name, score) } /// For these statements we use destructure the record pub fn destructure_qualified(user) { let one.User(name: name, score: score) = user #(name, score) } pub fn destructure_qualified_aliased(user) { let aliased.User(name: name, score: score) = user #(name, score) } pub fn destructure_unqualified(user) { let User(name: name, score: score) = user #(name, score) } pub fn destructure_aliased(user) { let XUser(name: name, score: score) = user #(name, score) } /// For these statements we use update the record pub fn update_qualified(user) { one.User(..user, name: "wibble") } pub fn update_qualified_aliased(user) { aliased.User(..user, name: "wibble") } pub fn update_unqualified(user) { User(..user, name: "wibble") } pub fn update_aliased(user) { XUser(..user, name: "wibble") } ================================================ FILE: test-package-compiler/cases/javascript_d_ts/gleam.toml ================================================ name = "hello" version = "0.1.0" target = "javascript" [javascript] typescript_declarations = true ================================================ FILE: test-package-compiler/cases/javascript_d_ts/src/hello.gleam ================================================ pub type Wibble { Woo } pub fn wobble() -> Wibble { Woo } ================================================ FILE: test-package-compiler/cases/javascript_empty/gleam.toml ================================================ name = "hello_joe" version = "0.1.0" target = "javascript" ================================================ FILE: test-package-compiler/cases/javascript_empty/src/empty.gleam ================================================ ================================================ FILE: test-package-compiler/cases/javascript_import/gleam.toml ================================================ name = "importy" version = "0.1.0" target = "javascript" [javascript] typescript_declarations = true ================================================ FILE: test-package-compiler/cases/javascript_import/src/one/two.gleam ================================================ pub type A { A } ================================================ FILE: test-package-compiler/cases/javascript_import/src/two.gleam ================================================ import one/two pub const x = two.A ================================================ FILE: test-package-compiler/cases/not_overwriting_erlang_module/gleam.toml ================================================ name = "importy" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/not_overwriting_erlang_module/src/app/code.gleam ================================================ // This module is named "app/code". // // The last segment is the same as a built in Erlang module, but it is in the // "app" namespace, so it doesn't clash with Erlang. This should compile // successfully. ================================================ FILE: test-package-compiler/cases/opaque_type_accessor/gleam.toml ================================================ name = "importy" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/opaque_type_accessor/src/one.gleam ================================================ pub opaque type User { User(name: String, score: Int) } // These operations are permitted in this module as it is the one that defined // the custom type pub fn construct() { User(name: "Alim", score: 10) } pub fn accessors(user: User) { let name = user.name let score = user.score #(name, score) } pub fn destructure(user: User) { let User(name: name, score: score) = user #(name, score) } ================================================ FILE: test-package-compiler/cases/opaque_type_accessor/src/two.gleam ================================================ import one.{type User} // This operation is not permitted because the type is opaque and this module // did not define the custom type. pub fn accessors(user: User) { let name = user.name let score = user.score #(name, score) } ================================================ FILE: test-package-compiler/cases/opaque_type_destructure/gleam.toml ================================================ name = "importy" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/opaque_type_destructure/src/one.gleam ================================================ pub opaque type User { User(name: String, score: Int) } // These operations are permitted in this module as it is the one that defined // the custom type pub fn construct() { User(name: "Alim", score: 10) } pub fn accessors(user: User) { let name = user.name let score = user.score #(name, score) } pub fn destructure(user: User) { let User(name: name, score: score) = user #(name, score) } ================================================ FILE: test-package-compiler/cases/opaque_type_destructure/src/two.gleam ================================================ import one // This operation is not permitted because the type is opaque and this module // did not define the custom type. pub fn destructure(user: one.User) { let one.User(name: name, score: score) = user #(name, score) } ================================================ FILE: test-package-compiler/cases/overwriting_erlang_module/gleam.toml ================================================ name = "importy" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/overwriting_erlang_module/src/code.gleam ================================================ // This module is named "code". // // If you were to compile this and load it into the Erlang virtual machine it // would replace the built-in "code" module. That would be _bad_. You'd get // super cryptic error messages and likely not know what is happening. This did // happen fairly often with new folks, so now the build tool ensures you don't // use one of these Erlang names. // // This test case ensures this is true. ================================================ FILE: test-package-compiler/cases/src_importing_dev/dev/two.gleam ================================================ ================================================ FILE: test-package-compiler/cases/src_importing_dev/gleam.toml ================================================ name = "importy" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/src_importing_dev/src/one.gleam ================================================ // This import will fail because the `two` module is in the `dev` directory and // as such isn't permitted to be imported into the `src` directory. import two ================================================ FILE: test-package-compiler/cases/src_importing_test/gleam.toml ================================================ name = "importy" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/src_importing_test/src/one.gleam ================================================ // This import will fail because the `two` module is in the `test` directory and // as such isn't permitted to be imported into the `src` directory. import two ================================================ FILE: test-package-compiler/cases/src_importing_test/test/two.gleam ================================================ ================================================ FILE: test-package-compiler/cases/unknown_module_field_in_constant/gleam.toml ================================================ name = "importy" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/unknown_module_field_in_constant/src/one.gleam ================================================ pub type A { A } ================================================ FILE: test-package-compiler/cases/unknown_module_field_in_constant/src/two.gleam ================================================ import one pub const it = one.B ================================================ FILE: test-package-compiler/cases/unknown_module_field_in_expression/gleam.toml ================================================ name = "importy" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/unknown_module_field_in_expression/src/one.gleam ================================================ pub type A { A } ================================================ FILE: test-package-compiler/cases/unknown_module_field_in_expression/src/two.gleam ================================================ import one pub fn it() { one.B } ================================================ FILE: test-package-compiler/cases/unknown_module_field_in_import/gleam.toml ================================================ name = "importy" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/unknown_module_field_in_import/src/one.gleam ================================================ pub type A { A } ================================================ FILE: test-package-compiler/cases/unknown_module_field_in_import/src/two.gleam ================================================ import one.{B} ================================================ FILE: test-package-compiler/cases/variable_or_module/gleam.toml ================================================ # Distinguish between a module and a value with the same local name. # https://github.com/gleam-lang/gleam/issues/807 name = "importy" version = "0.1.0" target = "erlang" ================================================ FILE: test-package-compiler/cases/variable_or_module/src/main.gleam ================================================ // https://github.com/gleam-lang/gleam/issues/807 import power.{type Power} pub fn module_function(power: Power) { // Here we are referring to the `power` module's function, not a field on the // `Power` record. power.to_int(power) } pub fn record_field(power: Power) { // Here we are referring to the `Power` record's field. power.value } ================================================ FILE: test-package-compiler/cases/variable_or_module/src/power.gleam ================================================ // https://github.com/gleam-lang/gleam/issues/807 pub type Power { Power(value: Int) } pub fn to_int(p: Power) { p.value * 9000 } ================================================ FILE: test-package-compiler/src/generated_tests.rs ================================================ //! This file is generated by build.rs //! Do not edit it directly, instead add new test cases to ./cases #[rustfmt::skip] #[test] fn alias_unqualified_import() { let output = crate::prepare("./cases/alias_unqualified_import"); insta::assert_snapshot!( "alias_unqualified_import", output, "./cases/alias_unqualified_import", ); } #[rustfmt::skip] #[test] fn dev_importing_test() { let output = crate::prepare("./cases/dev_importing_test"); insta::assert_snapshot!( "dev_importing_test", output, "./cases/dev_importing_test", ); } #[rustfmt::skip] #[test] fn duplicate_module() { let output = crate::prepare("./cases/duplicate_module"); insta::assert_snapshot!( "duplicate_module", output, "./cases/duplicate_module", ); } #[rustfmt::skip] #[test] fn duplicate_module_dev() { let output = crate::prepare("./cases/duplicate_module_dev"); insta::assert_snapshot!( "duplicate_module_dev", output, "./cases/duplicate_module_dev", ); } #[rustfmt::skip] #[test] fn duplicate_module_test_dev() { let output = crate::prepare("./cases/duplicate_module_test_dev"); insta::assert_snapshot!( "duplicate_module_test_dev", output, "./cases/duplicate_module_test_dev", ); } #[rustfmt::skip] #[test] fn empty_module_warning() { let output = crate::prepare("./cases/empty_module_warning"); insta::assert_snapshot!( "empty_module_warning", output, "./cases/empty_module_warning", ); } #[rustfmt::skip] #[test] fn erlang_app_generation() { let output = crate::prepare("./cases/erlang_app_generation"); insta::assert_snapshot!( "erlang_app_generation", output, "./cases/erlang_app_generation", ); } #[rustfmt::skip] #[test] fn erlang_app_generation_with_argument() { let output = crate::prepare("./cases/erlang_app_generation_with_argument"); insta::assert_snapshot!( "erlang_app_generation_with_argument", output, "./cases/erlang_app_generation_with_argument", ); } #[rustfmt::skip] #[test] fn erlang_bug_752() { let output = crate::prepare("./cases/erlang_bug_752"); insta::assert_snapshot!( "erlang_bug_752", output, "./cases/erlang_bug_752", ); } #[rustfmt::skip] #[test] fn erlang_empty() { let output = crate::prepare("./cases/erlang_empty"); insta::assert_snapshot!( "erlang_empty", output, "./cases/erlang_empty", ); } #[rustfmt::skip] #[test] fn erlang_escape_names() { let output = crate::prepare("./cases/erlang_escape_names"); insta::assert_snapshot!( "erlang_escape_names", output, "./cases/erlang_escape_names", ); } #[rustfmt::skip] #[test] fn erlang_import() { let output = crate::prepare("./cases/erlang_import"); insta::assert_snapshot!( "erlang_import", output, "./cases/erlang_import", ); } #[rustfmt::skip] #[test] fn erlang_import_shadowing_prelude() { let output = crate::prepare("./cases/erlang_import_shadowing_prelude"); insta::assert_snapshot!( "erlang_import_shadowing_prelude", output, "./cases/erlang_import_shadowing_prelude", ); } #[rustfmt::skip] #[test] fn erlang_nested() { let output = crate::prepare("./cases/erlang_nested"); insta::assert_snapshot!( "erlang_nested", output, "./cases/erlang_nested", ); } #[rustfmt::skip] #[test] fn erlang_nested_qualified_constant() { let output = crate::prepare("./cases/erlang_nested_qualified_constant"); insta::assert_snapshot!( "erlang_nested_qualified_constant", output, "./cases/erlang_nested_qualified_constant", ); } #[rustfmt::skip] #[test] fn hello_joe() { let output = crate::prepare("./cases/hello_joe"); insta::assert_snapshot!( "hello_joe", output, "./cases/hello_joe", ); } #[rustfmt::skip] #[test] fn import_cycle() { let output = crate::prepare("./cases/import_cycle"); insta::assert_snapshot!( "import_cycle", output, "./cases/import_cycle", ); } #[rustfmt::skip] #[test] fn import_cycle_multi() { let output = crate::prepare("./cases/import_cycle_multi"); insta::assert_snapshot!( "import_cycle_multi", output, "./cases/import_cycle_multi", ); } #[rustfmt::skip] #[test] fn import_shadowed_name_warning() { let output = crate::prepare("./cases/import_shadowed_name_warning"); insta::assert_snapshot!( "import_shadowed_name_warning", output, "./cases/import_shadowed_name_warning", ); } #[rustfmt::skip] #[test] fn imported_constants() { let output = crate::prepare("./cases/imported_constants"); insta::assert_snapshot!( "imported_constants", output, "./cases/imported_constants", ); } #[rustfmt::skip] #[test] fn imported_external_fns() { let output = crate::prepare("./cases/imported_external_fns"); insta::assert_snapshot!( "imported_external_fns", output, "./cases/imported_external_fns", ); } #[rustfmt::skip] #[test] fn imported_record_constructors() { let output = crate::prepare("./cases/imported_record_constructors"); insta::assert_snapshot!( "imported_record_constructors", output, "./cases/imported_record_constructors", ); } #[rustfmt::skip] #[test] fn javascript_d_ts() { let output = crate::prepare("./cases/javascript_d_ts"); insta::assert_snapshot!( "javascript_d_ts", output, "./cases/javascript_d_ts", ); } #[rustfmt::skip] #[test] fn javascript_empty() { let output = crate::prepare("./cases/javascript_empty"); insta::assert_snapshot!( "javascript_empty", output, "./cases/javascript_empty", ); } #[rustfmt::skip] #[test] fn javascript_import() { let output = crate::prepare("./cases/javascript_import"); insta::assert_snapshot!( "javascript_import", output, "./cases/javascript_import", ); } #[rustfmt::skip] #[test] fn not_overwriting_erlang_module() { let output = crate::prepare("./cases/not_overwriting_erlang_module"); insta::assert_snapshot!( "not_overwriting_erlang_module", output, "./cases/not_overwriting_erlang_module", ); } #[rustfmt::skip] #[test] fn opaque_type_accessor() { let output = crate::prepare("./cases/opaque_type_accessor"); insta::assert_snapshot!( "opaque_type_accessor", output, "./cases/opaque_type_accessor", ); } #[rustfmt::skip] #[test] fn opaque_type_destructure() { let output = crate::prepare("./cases/opaque_type_destructure"); insta::assert_snapshot!( "opaque_type_destructure", output, "./cases/opaque_type_destructure", ); } #[rustfmt::skip] #[test] fn overwriting_erlang_module() { let output = crate::prepare("./cases/overwriting_erlang_module"); insta::assert_snapshot!( "overwriting_erlang_module", output, "./cases/overwriting_erlang_module", ); } #[rustfmt::skip] #[test] fn src_importing_dev() { let output = crate::prepare("./cases/src_importing_dev"); insta::assert_snapshot!( "src_importing_dev", output, "./cases/src_importing_dev", ); } #[rustfmt::skip] #[test] fn src_importing_test() { let output = crate::prepare("./cases/src_importing_test"); insta::assert_snapshot!( "src_importing_test", output, "./cases/src_importing_test", ); } #[rustfmt::skip] #[test] fn unknown_module_field_in_constant() { let output = crate::prepare("./cases/unknown_module_field_in_constant"); insta::assert_snapshot!( "unknown_module_field_in_constant", output, "./cases/unknown_module_field_in_constant", ); } #[rustfmt::skip] #[test] fn unknown_module_field_in_expression() { let output = crate::prepare("./cases/unknown_module_field_in_expression"); insta::assert_snapshot!( "unknown_module_field_in_expression", output, "./cases/unknown_module_field_in_expression", ); } #[rustfmt::skip] #[test] fn unknown_module_field_in_import() { let output = crate::prepare("./cases/unknown_module_field_in_import"); insta::assert_snapshot!( "unknown_module_field_in_import", output, "./cases/unknown_module_field_in_import", ); } #[rustfmt::skip] #[test] fn variable_or_module() { let output = crate::prepare("./cases/variable_or_module"); insta::assert_snapshot!( "variable_or_module", output, "./cases/variable_or_module", ); } ================================================ FILE: test-package-compiler/src/lib.rs ================================================ #[cfg(test)] mod generated_tests; use camino::Utf8PathBuf; use gleam_core::{ build::{ ErlangAppCodegenConfiguration, Mode, NullTelemetry, Outcome, StaleTracker, Target, TargetCodegenConfiguration, }, config::PackageConfig, io::{FileSystemReader, FileSystemWriter}, warning::{VectorWarningEmitterIO, WarningEmitter}, }; use std::{ collections::{HashMap, HashSet}, rc::Rc, }; pub fn prepare(path: &str) -> String { let root = Utf8PathBuf::from(path).canonicalize_utf8().unwrap(); let toml = std::fs::read_to_string(root.join("gleam.toml")).unwrap(); let config: PackageConfig = toml::from_str(&toml).unwrap(); let target = match config.target { Target::Erlang => TargetCodegenConfiguration::Erlang { app_file: Some(ErlangAppCodegenConfiguration { include_dev_deps: true, package_name_overrides: HashMap::new(), }), }, Target::JavaScript => TargetCodegenConfiguration::JavaScript { emit_typescript_definitions: config.javascript.typescript_declarations, prelude_location: Utf8PathBuf::from("../prelude.mjs"), }, }; let ids = gleam_core::uid::UniqueIdGenerator::new(); let mut modules = im::HashMap::new(); let warnings = VectorWarningEmitterIO::default(); let warning_emitter = WarningEmitter::new(Rc::new(warnings.clone())); let filesystem = test_helpers_rs::to_in_memory_filesystem(&root); let initial_files = filesystem.files(); let root = Utf8PathBuf::from(""); let out = Utf8PathBuf::from("/out/lib/the_package"); let lib = Utf8PathBuf::from("/out/lib"); let mut compiler = gleam_core::build::PackageCompiler::new( &config, Mode::Dev, &root, &out, &lib, &target, ids, filesystem.clone(), ); compiler.write_entrypoint = false; compiler.write_metadata = true; compiler.compile_beam_bytecode = false; compiler.copy_native_files = false; let result = compiler.compile( &warning_emitter, &mut modules, &mut im::HashMap::new(), &mut StaleTracker::default(), &mut HashSet::new(), &NullTelemetry, ); match result { Outcome::Ok(_) => { for path in initial_files { if filesystem.is_file(&path) { filesystem.delete_file(&path).unwrap(); } } let files = filesystem.into_contents(); let warnings = warnings.take(); test_helpers_rs::TestCompileOutput { files, warnings }.as_overview_text() } Outcome::TotalFailure(error) | Outcome::PartialFailure(_, error) => { test_helpers_rs::normalise_diagnostic(&error.pretty_string()) } } } ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__alias_unqualified_import.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/alias_unqualified_import" --- //// /out/lib/the_package/_gleam_artefacts/one.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/one.cache_meta <85 byte binary> //// /out/lib/the_package/_gleam_artefacts/one.erl -module(one). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/one.gleam"). -export([id/1]). -export_type([empty/0]). -type empty() :: empty. -file("src/one.gleam", 2). -spec id(I) -> I. id(X) -> X. //// /out/lib/the_package/_gleam_artefacts/two.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/two.cache_meta <96 byte binary> //// /out/lib/the_package/_gleam_artefacts/two.erl -module(two). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/two.gleam"). -export([make/0]). -file("src/two.gleam", 4). -spec make() -> one:empty(). make() -> one:id(empty). //// /out/lib/the_package/ebin/importy.app {application, importy, [ {vsn, "0.1.0"}, {applications, []}, {description, ""}, {modules, [one, two]}, {registered, []} ]}. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__dev_importing_test.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/dev_importing_test" --- error: Dev importing test module ┌─ dev/one.gleam:3:1 │ 3 │ import two │ ^ Imported here The development module `one` is importing the test module `two`. Test modules should only contain test-related code, and not general development code. Perhaps move the `two` module to the dev directory. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__duplicate_module.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/duplicate_module" --- error: Duplicate module The module `main` is defined multiple times. It is first defined at src/main.gleam It is defined a second time at test/main.gleam ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__duplicate_module_dev.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/duplicate_module_dev" --- error: Duplicate module The module `main` is defined multiple times. It is first defined at src/main.gleam It is defined a second time at dev/main.gleam ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__duplicate_module_test_dev.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/duplicate_module_test_dev" --- error: Duplicate module The module `main` is defined multiple times. It is first defined at test/main.gleam It is defined a second time at dev/main.gleam ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__empty_module_warning.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/empty_module_warning" --- //// /out/lib/the_package/_gleam_artefacts/empty.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/empty.cache_meta <53 byte binary> //// /out/lib/the_package/_gleam_artefacts/empty.erl -module(empty). //// /out/lib/the_package/_gleam_artefacts/internal.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/internal.cache_meta <69 byte binary> //// /out/lib/the_package/_gleam_artefacts/internal.erl -module(internal). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/internal.gleam"). -export([private_function/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -file("src/internal.gleam", 2). ?DOC(false). -spec private_function() -> nil. private_function() -> nil. //// /out/lib/the_package/_gleam_artefacts/private.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/private.cache_meta <65 byte binary> //// /out/lib/the_package/_gleam_artefacts/private.erl -module(private). //// /out/lib/the_package/_gleam_artefacts/public.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/public.cache_meta <65 byte binary> //// /out/lib/the_package/_gleam_artefacts/public.erl -module(public). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/public.gleam"). -export([main/0]). -file("src/public.gleam", 1). -spec main() -> binary(). main() -> <<"This module has public definitions"/utf8>>. //// /out/lib/the_package/ebin/empty_module_warning.app {application, empty_module_warning, [ {vsn, "1.0.0"}, {applications, [gleam_stdlib]}, {description, "Test package with empty modules"}, {modules, [empty, internal, private, public]}, {registered, []} ]}. //// Warning warning: Empty module Module 'empty' contains no public definitions. Hint: You can safely remove this module. //// Warning warning: Unused private function ┌─ src/private.gleam:1:1 │ 1 │ fn private_function() -> Nil { │ ^ This private function is never used Hint: You can safely remove it. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__erlang_app_generation.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/erlang_app_generation" --- //// /out/lib/the_package/_gleam_artefacts/main.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/main.cache_meta <57 byte binary> //// /out/lib/the_package/_gleam_artefacts/main.erl -module(main). //// /out/lib/the_package/ebin/my_erlang_application.app {application, my_erlang_application, [ {mod, {'my_erlang_application_sup', []}}, {vsn, "0.1.0"}, {applications, [gleam_otp, gleam_stdlib, inets, midas, simple_json, ssl]}, {description, "It's very cool"}, {modules, [main]}, {registered, []} ]}. //// Warning warning: Empty module Module 'main' contains no public definitions. Hint: You can safely remove this module. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__erlang_app_generation_with_argument.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/erlang_app_generation_with_argument" --- //// /out/lib/the_package/_gleam_artefacts/main.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/main.cache_meta <57 byte binary> //// /out/lib/the_package/_gleam_artefacts/main.erl -module(main). //// /out/lib/the_package/ebin/my_erlang_application.app {application, my_erlang_application, [ {mod, {'my_erlang_application_sup', [1, two]}}, {vsn, "0.1.0"}, {applications, [gleam_otp, gleam_stdlib, inets, midas, simple_json, ssl]}, {description, "It's very cool"}, {modules, [main]}, {registered, []} ]}. //// Warning warning: Empty module Module 'main' contains no public definitions. Hint: You can safely remove this module. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__erlang_bug_752.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/erlang_bug_752" --- //// /out/lib/the_package/_gleam_artefacts/one.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/one.cache_meta <65 byte binary> //// /out/lib/the_package/_gleam_artefacts/one.erl -module(one). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/one.gleam"). -export_type([one/1]). -type one(I) :: {one, I}. //// /out/lib/the_package/_gleam_artefacts/two.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/two.cache_meta <92 byte binary> //// /out/lib/the_package/_gleam_artefacts/two.erl -module(two). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/two.gleam"). -export_type([two/1]). -type two(K) :: {two, one:one(integer())} | {gleam_phantom, K}. //// /out/lib/the_package/ebin/importy.app {application, importy, [ {vsn, "0.1.0"}, {applications, []}, {description, ""}, {modules, [one, two]}, {registered, []} ]}. //// /out/lib/the_package/include/two_Two.hrl -record(two, {thing :: one:one(integer())}). ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__erlang_empty.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/erlang_empty" --- //// /out/lib/the_package/_gleam_artefacts/empty.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/empty.cache_meta <57 byte binary> //// /out/lib/the_package/_gleam_artefacts/empty.erl -module(empty). //// /out/lib/the_package/ebin/hello_joe.app {application, hello_joe, [ {vsn, "0.1.0"}, {applications, []}, {description, ""}, {modules, [empty]}, {registered, []} ]}. //// Warning warning: Empty module Module 'empty' contains no public definitions. Hint: You can safely remove this module. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__erlang_escape_names.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/erlang_escape_names" --- //// /out/lib/the_package/_gleam_artefacts/one.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/one.cache_meta <69 byte binary> //// /out/lib/the_package/_gleam_artefacts/one.erl -module(one). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/one.gleam"). -export(['receive'/1]). -file("src/one.gleam", 2). -spec 'receive'(I) -> I. 'receive'(X) -> X. //// /out/lib/the_package/_gleam_artefacts/two.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/two.cache_meta <144 byte binary> //// /out/lib/the_package/_gleam_artefacts/two.erl -module(two). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/two.gleam"). -export([qualified_call/0, qualified_value/0, unqualified_call/0, unqualified_value/0]). -file("src/two.gleam", 4). -spec qualified_call() -> integer(). qualified_call() -> one:'receive'(1). -file("src/two.gleam", 8). -spec qualified_value() -> fun((Q) -> Q). qualified_value() -> fun one:'receive'/1. -file("src/two.gleam", 12). -spec unqualified_call() -> integer(). unqualified_call() -> one:'receive'(1). -file("src/two.gleam", 16). -spec unqualified_value() -> fun((S) -> S). unqualified_value() -> fun one:'receive'/1. //// /out/lib/the_package/ebin/importy.app {application, importy, [ {vsn, "0.1.0"}, {applications, []}, {description, ""}, {modules, [one, two]}, {registered, []} ]}. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__erlang_import.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/erlang_import" --- //// /out/lib/the_package/_gleam_artefacts/one.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/one.cache_meta <96 byte binary> //// /out/lib/the_package/_gleam_artefacts/one.erl -module(one). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/one.gleam"). -export([unbox/1]). -file("src/one.gleam", 3). -spec unbox(two:box()) -> integer(). unbox(X) -> {box, I} = X, I. //// /out/lib/the_package/_gleam_artefacts/two.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/two.cache_meta <65 byte binary> //// /out/lib/the_package/_gleam_artefacts/two.erl -module(two). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/two.gleam"). -export_type([box/0]). -type box() :: {box, integer()}. //// /out/lib/the_package/ebin/importy.app {application, importy, [ {vsn, "0.1.0"}, {applications, []}, {description, ""}, {modules, [one, two]}, {registered, []} ]}. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__erlang_import_shadowing_prelude.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/erlang_import_shadowing_prelude" --- //// /out/lib/the_package/_gleam_artefacts/one.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/one.cache_meta <69 byte binary> //// /out/lib/the_package/_gleam_artefacts/one.erl -module(one). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/one.gleam"). -export_type([error/0]). -type error() :: error. //// /out/lib/the_package/_gleam_artefacts/two.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/two.cache_meta <96 byte binary> //// /out/lib/the_package/_gleam_artefacts/two.erl -module(two). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/two.gleam"). -export([main/0]). -file("src/two.gleam", 4). -spec main() -> one:error(). main() -> error. //// /out/lib/the_package/ebin/importy.app {application, importy, [ {vsn, "0.1.0"}, {applications, []}, {description, ""}, {modules, [one, two]}, {registered, []} ]}. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__erlang_nested.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/erlang_nested" --- //// /out/lib/the_package/_gleam_artefacts/one@two.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/one@two.cache_meta <65 byte binary> //// /out/lib/the_package/_gleam_artefacts/one@two.erl -module(one@two). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/one/two.gleam"). -export([main/0]). -file("src/one/two.gleam", 1). -spec main() -> binary(). main() -> <<"Hi there"/utf8>>. //// /out/lib/the_package/ebin/importy.app {application, importy, [ {vsn, "0.1.0"}, {applications, []}, {description, ""}, {modules, [one@two]}, {registered, []} ]}. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__erlang_nested_qualified_constant.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/erlang_nested_qualified_constant" --- //// /out/lib/the_package/_gleam_artefacts/one@two.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/one@two.cache_meta <65 byte binary> //// /out/lib/the_package/_gleam_artefacts/one@two.erl -module(one@two). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/one/two.gleam"). -export_type([a/0]). -type a() :: a. //// /out/lib/the_package/_gleam_artefacts/two.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/two.cache_meta <108 byte binary> //// /out/lib/the_package/_gleam_artefacts/two.erl -module(two). //// /out/lib/the_package/ebin/importy.app {application, importy, [ {vsn, "0.1.0"}, {applications, []}, {description, ""}, {modules, [one@two, two]}, {registered, []} ]}. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__hello_joe.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/hello_joe" --- //// /out/lib/the_package/_gleam_artefacts/hello_joe.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/hello_joe.cache_meta <65 byte binary> //// /out/lib/the_package/_gleam_artefacts/hello_joe.erl -module(hello_joe). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/hello_joe.gleam"). -export([main/0]). -file("src/hello_joe.gleam", 1). -spec main() -> binary(). main() -> <<"Hello, Joe!"/utf8>>. //// /out/lib/the_package/ebin/hello_joe.app {application, hello_joe, [ {vsn, "0.1.0"}, {applications, []}, {description, ""}, {modules, [hello_joe]}, {registered, []} ]}. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__import_cycle.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/import_cycle" --- error: Import cycle ┌─ src/one.gleam:1:1 │ 1 │ import one │ ^ Imported here The import statements for these modules form a cycle: ┌─────┐ │ one └─────┘ Gleam doesn't support dependency cycles like these, please break the cycle to continue. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__import_cycle_multi.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/import_cycle_multi" --- error: Import cycle ┌─ src/three.gleam:1:1 │ 1 │ import one │ ^ Imported here │ ┌─ src/two.gleam:1:1 │ 1 │ import three │ ------------ Imported here │ ┌─ src/one.gleam:1:1 │ 1 │ import two │ ---------- Imported here The import statements for these modules form a cycle: ┌─────┐ │ three │ ↓ │ two │ ↓ │ one └─────┘ Gleam doesn't support dependency cycles like these, please break the cycle to continue. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__import_shadowed_name_warning.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/import_shadowed_name_warning" --- //// /out/lib/the_package/_gleam_artefacts/one.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/one.cache_meta <65 byte binary> //// /out/lib/the_package/_gleam_artefacts/one.erl -module(one). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/one.gleam"). -export_type([port_/0]). -type port_() :: any(). //// /out/lib/the_package/_gleam_artefacts/two.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/two.cache_meta <128 byte binary> //// /out/lib/the_package/_gleam_artefacts/two.erl -module(two). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/two.gleam"). -export([use_type/1]). -export_type([shadowing/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC(" https://github.com/gleam-lang/otp/pull/22\n"). -type shadowing() :: port. -file("src/two.gleam", 14). -spec use_type(one:port_()) -> nil. use_type(Port) -> wibble:wobble(Port). //// /out/lib/the_package/ebin/importy.app {application, importy, [ {vsn, "0.1.0"}, {applications, []}, {description, ""}, {modules, [one, two]}, {registered, []} ]}. //// Warning warning: Unused private constructor ┌─ src/two.gleam:9:3 │ 9 │ Port │ ^ This private constructor is never used Hint: You can safely remove it. //// Warning warning: Unused private type ┌─ src/two.gleam:7:1 │ 7 │ type Shadowing { │ ^ This private type is never used Hint: You can safely remove it. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__imported_constants.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/imported_constants" --- //// /out/lib/the_package/_gleam_artefacts/one.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/one.cache_meta <97 byte binary> //// /out/lib/the_package/_gleam_artefacts/one.erl -module(one). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/one.gleam"). -export_type([a/0, b/0, user/0]). -type a() :: a. -type b() :: {b, a(), a()}. -type user() :: {user, binary(), integer()}. //// /out/lib/the_package/_gleam_artefacts/two.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/two.cache_meta <332 byte binary> //// /out/lib/the_package/_gleam_artefacts/two.erl -module(two). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/two.gleam"). -export([accessors/1, destructure_qualified/1, destructure_unqualified/1, destructure_aliased/1, qualified_fn_a/0, qualified_fn_b/0, unqualified_fn_a/0, unqualified_fn_b/0, aliased_fn_a/0, aliased_fn_b/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -file("src/two.gleam", 45). ?DOC( " For these statements we use the accessors for the record from the other\n" " module\n" ). -spec accessors(one:user()) -> {binary(), integer()}. accessors(User) -> Name = erlang:element(2, User), Score = erlang:element(3, User), {Name, Score}. -file("src/two.gleam", 52). ?DOC(" For these statements we use destructure the record\n"). -spec destructure_qualified(one:user()) -> {binary(), integer()}. destructure_qualified(User) -> {user, Name, Score} = User, {Name, Score}. -file("src/two.gleam", 57). -spec destructure_unqualified(one:user()) -> {binary(), integer()}. destructure_unqualified(User) -> {user, Name, Score} = User, {Name, Score}. -file("src/two.gleam", 62). -spec destructure_aliased(one:user()) -> {binary(), integer()}. destructure_aliased(User) -> {user, Name, Score} = User, {Name, Score}. -file("src/two.gleam", 6). -spec qualified_fn_a() -> one:a(). qualified_fn_a() -> a. -file("src/two.gleam", 12). -spec qualified_fn_b() -> one:b(). qualified_fn_b() -> {b, a, a}. -file("src/two.gleam", 19). -spec unqualified_fn_a() -> one:a(). unqualified_fn_a() -> a. -file("src/two.gleam", 25). -spec unqualified_fn_b() -> one:b(). unqualified_fn_b() -> {b, a, a}. -file("src/two.gleam", 33). -spec aliased_fn_a() -> one:a(). aliased_fn_a() -> a. -file("src/two.gleam", 39). -spec aliased_fn_b() -> one:b(). aliased_fn_b() -> {b, a, a}. //// /out/lib/the_package/ebin/importy.app {application, importy, [ {vsn, "0.1.0"}, {applications, []}, {description, ""}, {modules, [one, two]}, {registered, []} ]}. //// /out/lib/the_package/include/one_User.hrl -record(user, {name :: binary(), score :: integer()}). ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__imported_external_fns.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/imported_external_fns" --- //// /out/lib/the_package/_gleam_artefacts/one.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/one.cache_meta <77 byte binary> //// /out/lib/the_package/_gleam_artefacts/one.erl -module(one). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/one.gleam"). -export([thing/0, escaped_thing/0]). -file("src/one.gleam", 2). -spec thing() -> nil. thing() -> thing:new(). -file("src/one.gleam", 6). -spec escaped_thing() -> nil. escaped_thing() -> 'the.thing':'make.new'(). //// /out/lib/the_package/_gleam_artefacts/three.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/three.cache_meta <77 byte binary> //// /out/lib/the_package/_gleam_artefacts/three.erl -module(three). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/three.gleam"). -export([thing/0, escaped_thing/0]). -file("src/three.gleam", 2). -spec thing() -> nil. thing() -> thing:new(). -file("src/three.gleam", 6). -spec escaped_thing() -> nil. escaped_thing() -> 'the.thing':'make.new'(). //// /out/lib/the_package/_gleam_artefacts/two.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/two.cache_meta <489 byte binary> //// /out/lib/the_package/_gleam_artefacts/two.erl -module(two). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/two.gleam"). -export([fn_reference_qualified/0, fn_reference_qualified_aliased/0, fn_reference_unqualified/0, fn_reference_unqualified_aliased/0, fn_call_qualified/0, fn_call_qualified_aliased/0, fn_call_unqualified/0, fn_call_unqualified_aliased/0, argument_reference_qualified/0, argument_reference_qualified_aliased/0, argument_reference_unqualified/0, argument_reference_unqualified_aliased/0, the_consts/0]). -file("src/two.gleam", 45). -spec fn_reference_qualified() -> fun(() -> nil). fn_reference_qualified() -> fun thing:new/0. -file("src/two.gleam", 49). -spec fn_reference_qualified_aliased() -> fun(() -> nil). fn_reference_qualified_aliased() -> fun thing:new/0. -file("src/two.gleam", 53). -spec fn_reference_unqualified() -> fun(() -> nil). fn_reference_unqualified() -> fun thing:new/0. -file("src/two.gleam", 57). -spec fn_reference_unqualified_aliased() -> fun(() -> nil). fn_reference_unqualified_aliased() -> fun thing:new/0. -file("src/two.gleam", 63). -spec fn_call_qualified() -> nil. fn_call_qualified() -> thing:new(). -file("src/two.gleam", 67). -spec fn_call_qualified_aliased() -> nil. fn_call_qualified_aliased() -> thing:new(). -file("src/two.gleam", 71). -spec fn_call_unqualified() -> nil. fn_call_unqualified() -> thing:new(). -file("src/two.gleam", 75). -spec fn_call_unqualified_aliased() -> nil. fn_call_unqualified_aliased() -> thing:new(). -file("src/two.gleam", 97). -spec x(any()) -> nil. x(_) -> nil. -file("src/two.gleam", 81). -spec argument_reference_qualified() -> nil. argument_reference_qualified() -> x(fun 'the.thing':'make.new'/0). -file("src/two.gleam", 85). -spec argument_reference_qualified_aliased() -> nil. argument_reference_qualified_aliased() -> x(fun 'the.thing':'make.new'/0). -file("src/two.gleam", 89). -spec argument_reference_unqualified() -> nil. argument_reference_unqualified() -> x(fun 'the.thing':'make.new'/0). -file("src/two.gleam", 93). -spec argument_reference_unqualified_aliased() -> nil. argument_reference_unqualified_aliased() -> x(fun 'the.thing':'make.new'/0). -file("src/two.gleam", 24). -spec the_consts() -> nil. the_consts() -> _ = fun thing:new/0, _ = fun thing:new/0, _ = fun thing:new/0, _ = fun thing:new/0, _ = fun 'the.thing':'make.new'/0, _ = fun 'the.thing':'make.new'/0, _ = fun 'the.thing':'make.new'/0, _ = fun 'the.thing':'make.new'/0, thing:new(), thing:new(), thing:new(), thing:new(), 'the.thing':'make.new'(), 'the.thing':'make.new'(), 'the.thing':'make.new'(), 'the.thing':'make.new'(). //// /out/lib/the_package/ebin/importy.app {application, importy, [ {vsn, "0.1.0"}, {applications, []}, {description, ""}, {modules, [one, three, two]}, {registered, []} ]}. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__imported_record_constructors.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/imported_record_constructors" --- //// /out/lib/the_package/_gleam_artefacts/one@one.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/one@one.cache_meta <97 byte binary> //// /out/lib/the_package/_gleam_artefacts/one@one.erl -module(one@one). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/one/one.gleam"). -export_type([a/0, b/0, user/0]). -type a() :: a. -type b() :: {b, a(), a()}. -type user() :: {user, binary(), integer()}. //// /out/lib/the_package/_gleam_artefacts/one@two.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/one@two.cache_meta <97 byte binary> //// /out/lib/the_package/_gleam_artefacts/one@two.erl -module(one@two). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/one/two.gleam"). -export_type([a/0, b/0, user/0]). -type a() :: a. -type b() :: {b, a(), a()}. -type user() :: {user, binary(), integer()}. //// /out/lib/the_package/_gleam_artefacts/two.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/two.cache_meta <499 byte binary> //// /out/lib/the_package/_gleam_artefacts/two.erl -module(two). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/two.gleam"). -export([accessors/1, destructure_qualified/1, destructure_qualified_aliased/1, destructure_unqualified/1, destructure_aliased/1, update_qualified/1, update_qualified_aliased/1, update_unqualified/1, update_aliased/1, qualified_fn_a/0, qualified_fn_b/0, qualified_aliased_fn_a/0, qualified_aliased_fn_b/0, unqualified_fn_a/0, unqualified_fn_b/0, aliased_fn_a/0, aliased_fn_b/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -file("src/two.gleam", 58). ?DOC( " For these statements we use the accessors for the record from the other\n" " module\n" ). -spec accessors(one@one:user()) -> {binary(), integer()}. accessors(User) -> Name = erlang:element(2, User), Score = erlang:element(3, User), {Name, Score}. -file("src/two.gleam", 65). ?DOC(" For these statements we use destructure the record\n"). -spec destructure_qualified(one@one:user()) -> {binary(), integer()}. destructure_qualified(User) -> {user, Name, Score} = User, {Name, Score}. -file("src/two.gleam", 70). -spec destructure_qualified_aliased(one@two:user()) -> {binary(), integer()}. destructure_qualified_aliased(User) -> {user, Name, Score} = User, {Name, Score}. -file("src/two.gleam", 75). -spec destructure_unqualified(one@one:user()) -> {binary(), integer()}. destructure_unqualified(User) -> {user, Name, Score} = User, {Name, Score}. -file("src/two.gleam", 80). -spec destructure_aliased(one@one:user()) -> {binary(), integer()}. destructure_aliased(User) -> {user, Name, Score} = User, {Name, Score}. -file("src/two.gleam", 86). ?DOC(" For these statements we use update the record\n"). -spec update_qualified(one@one:user()) -> one@one:user(). update_qualified(User) -> {user, <<"wibble"/utf8>>, erlang:element(3, User)}. -file("src/two.gleam", 90). -spec update_qualified_aliased(one@two:user()) -> one@two:user(). update_qualified_aliased(User) -> {user, <<"wibble"/utf8>>, erlang:element(3, User)}. -file("src/two.gleam", 94). -spec update_unqualified(one@one:user()) -> one@one:user(). update_unqualified(User) -> {user, <<"wibble"/utf8>>, erlang:element(3, User)}. -file("src/two.gleam", 98). -spec update_aliased(one@one:user()) -> one@one:user(). update_aliased(User) -> {user, <<"wibble"/utf8>>, erlang:element(3, User)}. -file("src/two.gleam", 7). -spec qualified_fn_a() -> one@one:a(). qualified_fn_a() -> a. -file("src/two.gleam", 13). -spec qualified_fn_b() -> one@one:b(). qualified_fn_b() -> {b, a, a}. -file("src/two.gleam", 19). -spec qualified_aliased_fn_a() -> one@two:a(). qualified_aliased_fn_a() -> a. -file("src/two.gleam", 25). -spec qualified_aliased_fn_b() -> one@two:b(). qualified_aliased_fn_b() -> {b, a, a}. -file("src/two.gleam", 32). -spec unqualified_fn_a() -> one@one:a(). unqualified_fn_a() -> a. -file("src/two.gleam", 38). -spec unqualified_fn_b() -> one@one:b(). unqualified_fn_b() -> {b, a, a}. -file("src/two.gleam", 46). -spec aliased_fn_a() -> one@one:a(). aliased_fn_a() -> a. -file("src/two.gleam", 52). -spec aliased_fn_b() -> one@one:b(). aliased_fn_b() -> {b, a, a}. //// /out/lib/the_package/ebin/importy.app {application, importy, [ {vsn, "0.1.0"}, {applications, []}, {description, ""}, {modules, [one@one, one@two, two]}, {registered, []} ]}. //// /out/lib/the_package/include/one@one_User.hrl -record(user, {name :: binary(), score :: integer()}). //// /out/lib/the_package/include/one@two_User.hrl -record(user, {name :: binary(), score :: integer()}). ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__javascript_d_ts.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/javascript_d_ts" --- //// /out/lib/the_package/_gleam_artefacts/hello.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/hello.cache_meta <81 byte binary> //// /out/lib/the_package/gleam.d.mts export * from "../prelude.mjs"; export type * from "../prelude.d.mts"; //// /out/lib/the_package/gleam.mjs export * from "../prelude.mjs"; //// /out/lib/the_package/hello.d.mts import type * as _ from "./gleam.d.mts"; export class Woo extends _.CustomType {} export function Wibble$Woo(): Wibble$; export function Wibble$isWoo(value: any): value is Wibble$; export type Wibble$ = Woo; export function wobble(): Wibble$; //// /out/lib/the_package/hello.mjs /// import { CustomType as $CustomType } from "./gleam.mjs"; export class Woo extends $CustomType {} export const Wibble$Woo = () => new Woo(); export const Wibble$isWoo = (value) => value instanceof Woo; export function wobble() { return new Woo(); } ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__javascript_empty.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/javascript_empty" --- //// /out/lib/the_package/_gleam_artefacts/empty.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/empty.cache_meta <57 byte binary> //// /out/lib/the_package/empty.mjs export {} //// /out/lib/the_package/gleam.mjs export * from "../prelude.mjs"; //// Warning warning: Empty module Module 'empty' contains no public definitions. Hint: You can safely remove this module. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__javascript_import.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/javascript_import" --- //// /out/lib/the_package/_gleam_artefacts/one@two.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/one@two.cache_meta <65 byte binary> //// /out/lib/the_package/_gleam_artefacts/two.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/two.cache_meta <88 byte binary> //// /out/lib/the_package/gleam.d.mts export * from "../prelude.mjs"; export type * from "../prelude.d.mts"; //// /out/lib/the_package/gleam.mjs export * from "../prelude.mjs"; //// /out/lib/the_package/one/two.d.mts import type * as _ from "../gleam.d.mts"; export class A extends _.CustomType {} export function A$A(): A$; export function A$isA(value: any): value is A$; export type A$ = A; //// /out/lib/the_package/one/two.mjs /// import { CustomType as $CustomType } from "../gleam.mjs"; export class A extends $CustomType {} export const A$A = () => new A(); export const A$isA = (value) => value instanceof A; //// /out/lib/the_package/two.d.mts import type * as $two from "./one/two.d.mts"; export const x: $two.A$; //// /out/lib/the_package/two.mjs /// import * as $two from "./one/two.mjs"; export const x = /* @__PURE__ */ new $two.A(); ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__not_overwriting_erlang_module.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/not_overwriting_erlang_module" --- //// /out/lib/the_package/_gleam_artefacts/app@code.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/app@code.cache_meta <73 byte binary> //// /out/lib/the_package/_gleam_artefacts/app@code.erl -module(app@code). //// /out/lib/the_package/ebin/importy.app {application, importy, [ {vsn, "0.1.0"}, {applications, []}, {description, ""}, {modules, [app@code]}, {registered, []} ]}. //// Warning warning: Empty module Module 'app/code' contains no public definitions. Hint: You can safely remove this module. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__opaque_type_accessor.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/opaque_type_accessor" --- error: Unknown record field ┌─ src/two.gleam:7:19 │ 7 │ let name = user.name │ ^ This field does not exist The value being accessed has this type: User It does not have any fields. error: Unknown record field ┌─ src/two.gleam:8:20 │ 8 │ let score = user.score │ ^ This field does not exist The value being accessed has this type: User It does not have any fields. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__opaque_type_destructure.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/opaque_type_destructure" --- error: Unknown module value ┌─ src/two.gleam:7:7 │ 7 │ let one.User(name: name, score: score) = user │ ^ The module `one` does not have a `User` value. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__overwriting_erlang_module.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/overwriting_erlang_module" --- error: Erlang module name collision The module `src/code.gleam` compiles to an Erlang module named `code`. By default Erlang includes a module with the same name so if we were to compile and load your module it would overwrite the Erlang one, potentially causing confusing errors and crashes. Hint: Rename this module and try again. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__src_importing_dev.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/src_importing_dev" --- error: App importing dev module ┌─ src/one.gleam:3:1 │ 3 │ import two │ ^ Imported here The application module `one` is importing the development module `two`. Development modules are not included in production builds so application modules cannot import them. Perhaps move the `two` module to the src directory. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__src_importing_test.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/src_importing_test" --- error: App importing test module ┌─ src/one.gleam:3:1 │ 3 │ import two │ ^ Imported here The application module `one` is importing the test module `two`. Test modules are not included in production builds so application modules cannot import them. Perhaps move the `two` module to the src directory. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__unknown_module_field_in_constant.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/unknown_module_field_in_constant" --- error: Unknown module value ┌─ src/two.gleam:3:16 │ 3 │ pub const it = one.B │ ^ Did you mean `A`? The module `one` does not have a `B` value. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__unknown_module_field_in_expression.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/unknown_module_field_in_expression" --- error: Unknown module value ┌─ src/two.gleam:4:7 │ 4 │ one.B │ ^ Did you mean `A`? The module `one` does not have a `B` value. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__unknown_module_field_in_import.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/unknown_module_field_in_import" --- error: Unknown module value ┌─ src/two.gleam:1:13 │ 1 │ import one.{B} │ ^ Did you mean `A`? The module `one` does not have a `B` value. ================================================ FILE: test-package-compiler/src/snapshots/test_package_compiler__generated_tests__variable_or_module.snap ================================================ --- source: test-package-compiler/src/generated_tests.rs expression: "./cases/variable_or_module" --- //// /out/lib/the_package/_gleam_artefacts/main.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/main.cache_meta <126 byte binary> //// /out/lib/the_package/_gleam_artefacts/main.erl -module(main). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/main.gleam"). -export([module_function/1, record_field/1]). -file("src/main.gleam", 4). -spec module_function(power:power()) -> integer(). module_function(Power) -> power:to_int(Power). -file("src/main.gleam", 10). -spec record_field(power:power()) -> integer(). record_field(Power) -> erlang:element(2, Power). //// /out/lib/the_package/_gleam_artefacts/power.cache <.cache binary> //// /out/lib/the_package/_gleam_artefacts/power.cache_meta <85 byte binary> //// /out/lib/the_package/_gleam_artefacts/power.erl -module(power). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/power.gleam"). -export([to_int/1]). -export_type([power/0]). -type power() :: {power, integer()}. -file("src/power.gleam", 6). -spec to_int(power()) -> integer(). to_int(P) -> erlang:element(2, P) * 9000. //// /out/lib/the_package/ebin/importy.app {application, importy, [ {vsn, "0.1.0"}, {applications, []}, {description, ""}, {modules, [main, power]}, {registered, []} ]}. //// /out/lib/the_package/include/power_Power.hrl -record(power, {value :: integer()}). ================================================ FILE: test-project-compiler/.gitignore ================================================ cases/*/build cases/*/manifest.toml support/*/build support/*/manifest.toml ================================================ FILE: test-project-compiler/Cargo.toml ================================================ [package] name = "test-project-compiler" version = "1.15.1" authors = ["Louis Pilfold "] edition = "2024" license = "Apache-2.0" [dependencies] gleam-core = { path = "../compiler-core" } test-helpers-rs = { path = "../test-helpers-rs" } toml.workspace = true im.workspace = true itertools.workspace = true regex.workspace = true camino.workspace = true [dev-dependencies] insta.workspace = true ================================================ FILE: test-project-compiler/build.rs ================================================ use std::path::PathBuf; pub fn main() { println!("cargo:rerun-if-changed=cases"); let mut module = "//! This file is generated by build.rs //! Do not edit it directly, instead add new test cases to ./cases use gleam_core::build::Mode; " .to_string(); let cases = PathBuf::from("./cases"); let mut names: Vec<_> = std::fs::read_dir(&cases) .unwrap() .map(|entry| entry.unwrap().file_name().into_string().unwrap()) .collect(); names.sort(); for name in names { let path = cases.join(&name); let path = path.to_str().unwrap().replace('\\', "/"); module.push_str(&testcase(&name, &path, "Dev")); module.push_str(&testcase(&name, &path, "Prod")); module.push_str(&testcase(&name, &path, "Lsp")); } let out = PathBuf::from("./src/generated_tests.rs"); std::fs::write(out, module).unwrap(); } fn testcase(name: &str, path: &str, mode: &str) -> String { format!( r#" #[rustfmt::skip] #[test] fn {name}() {{ let output = crate::prepare("{path}", Mode::{mode}); insta::assert_snapshot!( "{name}", output, "{path}", ); }} "#, name = format!("{name}_{}", mode.to_lowercase()) ) } ================================================ FILE: test-project-compiler/cases/with_dep/gleam.toml ================================================ name = "example" version = "1.0.0" [dependencies] package_a = { path = "../support/package_a" } ================================================ FILE: test-project-compiler/cases/with_dep/src/example.gleam ================================================ pub fn main() { 0 } ================================================ FILE: test-project-compiler/cases/with_dev_dep/gleam.toml ================================================ name = "example" version = "1.0.0" [dev_dependencies] package_a = { path = "../support/package_a" } ================================================ FILE: test-project-compiler/cases/with_dev_dep/src/example.gleam ================================================ pub fn main() { 0 } ================================================ FILE: test-project-compiler/src/generated_tests.rs ================================================ //! This file is generated by build.rs //! Do not edit it directly, instead add new test cases to ./cases use gleam_core::build::Mode; #[rustfmt::skip] #[test] fn with_dep_dev() { let output = crate::prepare("./cases/with_dep", Mode::Dev); insta::assert_snapshot!( "with_dep_dev", output, "./cases/with_dep", ); } #[rustfmt::skip] #[test] fn with_dep_prod() { let output = crate::prepare("./cases/with_dep", Mode::Prod); insta::assert_snapshot!( "with_dep_prod", output, "./cases/with_dep", ); } #[rustfmt::skip] #[test] fn with_dep_lsp() { let output = crate::prepare("./cases/with_dep", Mode::Lsp); insta::assert_snapshot!( "with_dep_lsp", output, "./cases/with_dep", ); } #[rustfmt::skip] #[test] fn with_dev_dep_dev() { let output = crate::prepare("./cases/with_dev_dep", Mode::Dev); insta::assert_snapshot!( "with_dev_dep_dev", output, "./cases/with_dev_dep", ); } #[rustfmt::skip] #[test] fn with_dev_dep_prod() { let output = crate::prepare("./cases/with_dev_dep", Mode::Prod); insta::assert_snapshot!( "with_dev_dep_prod", output, "./cases/with_dev_dep", ); } #[rustfmt::skip] #[test] fn with_dev_dep_lsp() { let output = crate::prepare("./cases/with_dev_dep", Mode::Lsp); insta::assert_snapshot!( "with_dev_dep_lsp", output, "./cases/with_dev_dep", ); } ================================================ FILE: test-project-compiler/src/lib.rs ================================================ #[cfg(test)] mod generated_tests; use camino::Utf8PathBuf; use gleam_core::{ analyse::TargetSupport, build::{Codegen, Compile, Mode, NullTelemetry, Options, ProjectCompiler, Telemetry}, config::PackageConfig, io::{FileSystemReader, FileSystemWriter}, paths::ProjectPaths, warning::VectorWarningEmitterIO, }; use std::rc::Rc; pub fn prepare(path: &str, mode: Mode) -> String { let root = Utf8PathBuf::from(path).canonicalize_utf8().unwrap(); let filesystem = test_helpers_rs::to_in_memory_filesystem(&root); let initial_files = filesystem.files(); let toml = std::fs::read_to_string(root.join("gleam.toml")).unwrap(); let config: PackageConfig = toml::from_str(&toml).unwrap(); let warnings = VectorWarningEmitterIO::default(); let telemetry: &'static dyn Telemetry = &NullTelemetry; let options = Options { mode, target: None, compile: Compile::All, codegen: Codegen::All, warnings_as_errors: false, root_target_support: TargetSupport::Enforced, no_print_progress: true, }; let compiler = ProjectCompiler::new( config, options, vec![], telemetry, Rc::new(warnings.clone()), ProjectPaths::new(root), filesystem.clone(), ); compiler.compile().unwrap(); for path in initial_files { if filesystem.is_file(&path) { filesystem.delete_file(&path).unwrap(); } } let files = filesystem.into_contents(); let warnings = warnings.take(); test_helpers_rs::TestCompileOutput { files, warnings }.as_overview_text() } ================================================ FILE: test-project-compiler/src/snapshots/test_project_compiler__generated_tests__with_dep_dev.snap ================================================ --- source: test-project-compiler/src/generated_tests.rs expression: "./cases/with_dep" --- //// with_dep/build/dev/erlang/example/_gleam_artefacts/example@@main.erl //// with_dep/build/dev/erlang/example/ebin/example.app {application, example, [ {vsn, "1.0.0"}, {applications, [package_a]}, {description, ""}, {modules, []}, {registered, []} ]}. //// with_dep/build/dev/erlang/gleam_version ================================================ FILE: test-project-compiler/src/snapshots/test_project_compiler__generated_tests__with_dep_lsp.snap ================================================ --- source: test-project-compiler/src/generated_tests.rs expression: "./cases/with_dep" --- //// with_dep/build/lsp/erlang/example/_gleam_artefacts/example@@main.erl //// with_dep/build/lsp/erlang/example/ebin/example.app {application, example, [ {vsn, "1.0.0"}, {applications, [package_a]}, {description, ""}, {modules, []}, {registered, []} ]}. //// with_dep/build/lsp/erlang/gleam_version ================================================ FILE: test-project-compiler/src/snapshots/test_project_compiler__generated_tests__with_dep_prod.snap ================================================ --- source: test-project-compiler/src/generated_tests.rs expression: "./cases/with_dep" --- //// with_dep/build/prod/erlang/example/_gleam_artefacts/example@@main.erl //// with_dep/build/prod/erlang/example/ebin/example.app {application, example, [ {vsn, "1.0.0"}, {applications, [package_a]}, {description, ""}, {modules, []}, {registered, []} ]}. //// with_dep/build/prod/erlang/gleam_version ================================================ FILE: test-project-compiler/src/snapshots/test_project_compiler__generated_tests__with_dev_dep_dev.snap ================================================ --- source: test-project-compiler/src/generated_tests.rs expression: "./cases/with_dev_dep" --- //// with_dev_dep/build/dev/erlang/example/_gleam_artefacts/example@@main.erl //// with_dev_dep/build/dev/erlang/example/ebin/example.app {application, example, [ {vsn, "1.0.0"}, {applications, [package_a]}, {description, ""}, {modules, []}, {registered, []} ]}. //// with_dev_dep/build/dev/erlang/gleam_version ================================================ FILE: test-project-compiler/src/snapshots/test_project_compiler__generated_tests__with_dev_dep_lsp.snap ================================================ --- source: test-project-compiler/src/generated_tests.rs expression: "./cases/with_dev_dep" --- //// with_dev_dep/build/lsp/erlang/example/_gleam_artefacts/example@@main.erl //// with_dev_dep/build/lsp/erlang/example/ebin/example.app {application, example, [ {vsn, "1.0.0"}, {applications, [package_a]}, {description, ""}, {modules, []}, {registered, []} ]}. //// with_dev_dep/build/lsp/erlang/gleam_version ================================================ FILE: test-project-compiler/src/snapshots/test_project_compiler__generated_tests__with_dev_dep_prod.snap ================================================ --- source: test-project-compiler/src/generated_tests.rs expression: "./cases/with_dev_dep" --- //// with_dev_dep/build/prod/erlang/example/_gleam_artefacts/example@@main.erl //// with_dev_dep/build/prod/erlang/example/ebin/example.app {application, example, [ {vsn, "1.0.0"}, {applications, []}, {description, ""}, {modules, []}, {registered, []} ]}. //// with_dev_dep/build/prod/erlang/gleam_version ================================================ FILE: test-project-compiler/support/package_a/gleam.toml ================================================ name = "package_a" version = "1.0.0" [dependencies] [dev_dependencies] ================================================ FILE: test-project-compiler/support/package_a/src/package_a.gleam ================================================ pub fn main() { "package_a" }